2020年1月5日日曜日

[Go] mysql threadが増え過ぎてしんだ

以前、PHPで書いたものを勉強がてらにGo言語で書いてるのだが、そこで失敗した件について。
失敗内容は掲題の通り。監視もしてないのでなかなか気付かなかったが、数時間はサーバーが死んでいた。
グラフ見たらすごいことになっていたのがわかる。再起動後も順調にあがるのでロールバックしたのだった。



で、原因は単純なことなのだけれど、rollaback処理が漏れていた。

バグを含んだコードはこんな感じの存在確認してからinsertするような処理だった。
簡易化したので、不要な処理に見える、unique制約使えというツッコミをしたくなるな…

func (d *db) CreateUser(id, name string) error {
 var err error
 tx, err := d.Begin()
 if err != nil {
  return err
 }
 defer func() {
  if err != nil {
   tx.Rollback()
  }
 }()

 var existID string
 err = tx.QueryRow("select id from user where id = ?", id).Scan(&existID)
 if existID != "" {
  return xerrors.New("The id is already exist")
 }
 if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
  return err
 }

 if _, err = tx.Exec("insert user (id, name) values(?, ?)", id, name); err != nil {
  return err
 }

 tx.Commit()
 return nil
}


deferでerrがnilじゃないときはrollbackするようにしていた。
で、存在する場合にreturnすると、rollbackされないバグだった。
errに代入して、返すようにしてバグは解消。

 if existID != "" {
  err = xerrors.New("The id is already exist")
  return err
 }

tx.Rollback()をreturn errの前に書いてたのをdeferに移して起きたバグ。
Goでのerror処理周りになかなか慣れないという言い訳。