N+1問題
Noahです。
ペアプロ等で指摘されたことで、重要だと思ったら1つずつメモを書こうと思い、書く。
続…かないかもしれない
今日はN+1問題について(ここのコードは深夜に書いたメモ程度でなんと言うか、補助的役割である👀)
N+1の問題
まず、1つのクエリで複数のidを取得し、その後繰り返しのクエリで、idのスライスのそれぞれをDBから抜いてくること。 idの数をNとすると、合計N+1のクエリが発生する。
// func Query() (Data, error){ // 内部では、1つの構造体Dataのものを返すようになっている // } // func Query2ByID(id string) (User, error){ // 内部では、idから紐付けられる1つの構造体Userを返すようになっている(Userモデル内でid はUNIQUEとする) // } func f() { // あるデータをDBから抜いてくる // 中に複数のidがあるような構造体 // (e.g.) // type Data struct { // Subs []Sub `db:"-"` // } // type Sub struct { // ID string `db:"user_id"` // } // type User struct { // ID string `db:"id"` // Name string `db:"name"` // } //構造体Dataを一件DBから取得するクエリ ......(1) data, err := Query() if err != nil { log.Fatal(err) } // idsをもとにDBからユーザ情報を抜く for i, sub := range data.Subs { //構造体Userを、Sub.IDから紐付けてDBから抜いてくるクエリ ......(2) user, err := Query2ByID(sub.ID) if err != nil { log.Fatal(err) } // なんか処理する } }
愚直にこんな感じで書いてしまうと、(1), (2) で、合計N+1のクエリを実行してしまうため、パフォーマンスが非常に悪い。
良い方法として、mapを利用した解決策がある
解決策
// func Query() (Data, error){ // 内部では、1つの構造体Dataのものを返すようになっている // } // func Query2ByIDs(id []string) ([]User, error){ // 内部では、idから紐付けられる構造体Userを返すようになっている(Userモデル内でid はUNIQUEとする) // 先程との変更点は、複数のidを受け取り、複数の結果を返すようになっている // } func g() { // あるデータをDBから抜いてくる // 中に複数のidがあるような構造体 // (e.g.) // type Data struct { // Subs []Sub `db:"-"` // } // type Sub struct { // ID string `db:"user_id"` // } // type User struct { // ID string `db:"id"` // Name string `db:"name"` // } //構造体Dataを一件DBから取得するクエリ ......(1) data, err := Query() if err != nil { log.Fatal(err) } // idをKeyとして、Userモデルを引っ張れるmapを作成 userMap := map[string]User{} for i, sub := range data.Subs { userMap[sub.ID] = User{} // keyとvalueを初期化しておく } // idのスライスを作る ids := make([]string, len(data.Subs)) for i, sub := range data.Subs { ids[i] = sub.ID } // idのスライスからそれらに紐付けられている複数のUserを引っ張ってくる ......(2) users, err := Query2ByIDs(ids) if err != nil { log.Fatal(err) } // usersをidをkeyにmapに入れていく for _, u := range users { userMap[u.ID] = u } // 以降、userMapからkeyをIDにするだけでモデルを取得できる }
このようにすると、(1), (2) のクエリが2個で済むようになり、パフォーマンスの向上が図れる。
書いてるときは、パフォーマンス等を全く考慮しないで書いていたので、ちゃんと考慮したいと思った次第…