【データベース】インデックスを増やすと書き込みはどれだけ遅くなるのか(MongoDB/PostgreSQLで検証)

インデックスを追加すると、書き込みの負担が増える。

では、実際には何倍の時間がかかるのか。インデックスが5本になれば、処理時間も5倍になるのか。更新しない項目に設定したインデックスは、どれくらい影響するのか。

MongoDBとPostgreSQLで、インデックスの本数、データ件数、更新対象を変えて測定した。

今回の条件では、追加インデックス5本による処理時間の倍率は、100万件の処理で約1.02倍から約6.46倍まで差があった。同じ本数でも、操作やデータ件数によって負担は大きく変わった。

検証する条件

Goでベンチマーク用のプログラムを作成し、ローカルのDockerコンテナに対して書き込みを行った。

項目条件
ホストMacBook Pro、メモリ24GB、SSD
MongoDB8.0.16
PostgreSQL17.6
各DBコンテナの上限2 CPU、メモリ3GB
DBのキャッシュ設定WiredTigerキャッシュ1GB、shared_buffers 1GB
データ件数1万・10万・100万件
追加インデックス0・1・5本
バッチ件数1,000件
同時実行数1
試行回数各条件5回

キャッシュ設定の数値は揃えているが、WiredTigerキャッシュとshared_buffersが同じ意味のメモリ領域というわけではない。

データはID、counter、field1〜field5の7項目で、すべて64ビット整数を使う。IDは連番、counterは0、残りの5項目は固定seedから生成した疑似乱数である。

追加インデックスの対象は次のとおり。

追加本数対象
0本IDのインデックスのみ
1本IDに加えてfield1
5本IDに加えてfield1〜field5それぞれ

本記事の「0本」は、追加インデックスが0本という意味である。MongoDBの_idインデックス、PostgreSQLの主キーインデックスは常に存在する。

また、5本のインデックスは、それぞれ別の項目に設定した単一フィールド・単一列インデックスである。5項目をまとめた複合インデックスではない。

測定する操作

操作内容
INSERT空のコレクション・テーブルへN件投入
非インデックス項目の更新counterに1を加算
1項目の更新field1に1を加算
5項目の更新field1〜field5すべてに1を加算

更新ではN件を初期投入した後、IDを指定して全件を1回ずつ更新する。更新順はシャッフルし、同じ件数では全条件で同じ順序を使う。

各試行でコレクション・テーブルを作り直し、初期投入やインデックス作成の時間は測定に含めない。測定対象は、各バッチのドライバー呼び出し開始から応答までの時間を合計したものである。

処理時間の倍率で比較する

結果は各条件5回の中央値を使う。倍率は次のように計算する。

処理時間の倍率=対象条件の時間中央値÷同じDB・件数・操作の追加0本の時間中央値

2倍なら、追加0本の条件に対して2倍の時間がかかったことになる。

MongoDBの倍率はMongoDB自身の0本条件、PostgreSQLの倍率はPostgreSQL自身の0本条件を基準にする。倍率だけでDB間の処理速度は比較できない。

結果1:同じ5本でも、操作によって負担が違う

まず100万件について、追加インデックスを0本から5本へ増やした結果を見る。

操作MongoDB:0本 → 5本倍率PostgreSQL:0本 → 5本倍率
INSERT4.24秒 → 25.21秒5.94倍4.52秒 → 11.82秒2.61倍
非インデックス項目の更新19.40秒 → 19.86秒1.02倍7.38秒 → 9.22秒1.25倍
1項目の更新19.50秒 → 31.81秒1.63倍7.39秒 → 22.30秒3.02倍
5項目の更新21.21秒 → 136.96秒6.46倍7.92秒 → 19.58秒2.47倍
※倍率は丸める前の実測値から計算している。

MongoDBでは、非インデックス項目の更新は約1.02倍だった。一方、5項目の更新は約6.46倍になった。

PostgreSQLでも、非インデックス項目の更新は約1.25倍、1項目の更新は約3.02倍と、操作によって差がある。

「インデックスを5本追加した場合の負担」を一つの倍率で表すのは難しい。INSERTなのかUPDATEなのか、UPDATEなら何を更新するのかまで分けて見る必要がある。

結果2:少量データの倍率を、そのまま大量データには当てはめられない

追加5本の処理時間倍率を、データ件数ごとに比較する。

MongoDB

操作1万件10万件100万件
INSERT2.31倍2.25倍5.94倍
非インデックス項目の更新0.91倍1.02倍1.02倍
1項目の更新1.22倍1.21倍1.63倍
5項目の更新1.43倍1.60倍6.46倍

INSERTでは、1万・10万件の倍率は約2.3倍だったが、100万件では約5.9倍になった。

5項目の更新でも、10万件では約1.6倍、100万件では約6.5倍となっている。

ここで比較しているのは、同じ件数における追加0本に対する倍率である。単に処理する件数が増えた分だけ時間が長くなった、という話ではない。インデックス追加による相対的な負担も増えている。

一方、非インデックス項目の更新は、10万件でも100万件でも約1.02倍だった。

PostgreSQL

操作1万件10万件100万件
INSERT2.46倍2.04倍2.61倍
非インデックス項目の更新1.07倍1.22倍1.25倍
1項目の更新2.22倍2.64倍3.02倍
5項目の更新1.84倍2.19倍2.47倍

PostgreSQLでは、今回の範囲でMongoDBのINSERTや5項目更新ほど大きな倍率の変化はなかった。ただし、1項目の更新では約2.22倍から約3.02倍へ増えている。

小規模な検証で「インデックスを追加しても2倍程度だった」と分かっても、件数を増やしたときに同じ倍率になるとは限らない。

今回の結果からは、少なくとも1万・10万件の測定だけで100万件の負担を見積もるのは難しかった。

結果3:更新しない項目のインデックスにも差が出た

次に、更新対象をfield1だけに固定し、追加インデックスを1本から5本へ増やす。

追加1本ではfield1にインデックスがある。追加5本では、さらにfield2〜field5にもインデックスがある。

更新する項目はどちらも同じである。

100万件の結果は次のとおり。

DB追加0本追加1本追加5本
MongoDB19.50秒31.82秒31.81秒
PostgreSQL7.39秒10.95秒22.30秒

MongoDBでは、追加1本と5本の中央値はほぼ同じだった。

PostgreSQLでは、追加1本の10.95秒に対して、追加5本は22.30秒だった。更新しない項目へのインデックスを増やした条件で、処理時間が約2倍になっている。

これは100万件だけの傾向ではない。10万件でも、MongoDBは1.85秒から1.91秒、PostgreSQLは0.93秒から1.67秒だった。

今回の条件では、更新対象に直接設定したインデックスの本数だけでは、書き込みの負担を判断できなかった。

なお、ここで測定したのは処理時間である。DB内部でどのインデックスにどれだけの処理が発生したかを直接計測したわけではないため、原因の特定までは行わない。

結果を読むときの注意点

5回の測定にもばらつきはある

例えば、MongoDBの追加5本・1万件のINSERTは、最小約0.10秒、最大約0.65秒だった。中央値は約0.14秒である。

100万件の5項目更新も、追加5本では約135.64〜177.68秒の範囲になった。ただし、追加0本は約20.66〜25.35秒であり、今回の5回では範囲が重ならなかった。

細かな倍率の差と、大きな処理時間の差は分けて読む必要がある。1倍未満の結果があっても、それだけでインデックス追加によって高速化したとは判断しない。

DBごとに書き込み方式が異なる

MongoDBはINSERTにInsertMany、UPDATEにBulkWriteを使う。いずれもorderedである。

PostgreSQLはpgx.SendBatchを使い、1行につき1つのINSERT・UPDATE文を送る。各バッチが暗黙のトランザクションになる。COPYや複数VALUESによるINSERTは使っていない。

また、MongoDBはw:1, j:false、PostgreSQLはsynchronous_commit=offで測定した。いずれも永続化応答を待たない設定だが、両DBで同じ保証になるわけではない。

このため、DB間の実時間の差には、インデックス以外の実装・通信・トランザクションの違いも含まれる。

今回は、初期投入と全件更新の測定である

INSERTは空の状態からN件を投入する。UPDATEはN件を初期投入した直後に、全件を1回ずつ更新する。

既存の大きなデータに少量ずつ追記する処理や、長期間更新を繰り返した状態とは条件が異なる。キャッシュも消去していない。

本記事の倍率は、そのまま本番環境の予測値として使うものではない。実際のデータ件数、インデックス構成、更新処理に近い条件で測る際の比較材料になる。

実際の設計で確認したいこと

今回の結果を踏まえると、インデックスを追加するときは、少なくとも次の条件で書き込みへの影響を確認したい。

  • INSERTとUPDATEを分ける。
  • UPDATEは、非インデックス項目の更新とインデックス対象の更新を分ける。
  • 更新対象以外も含め、実際に使うインデックス構成で測る。
  • 小規模なデータだけで判断せず、想定件数まで増やして測る。

倍率と実時間も両方見る。

例えば、MongoDBの100万件・5項目更新は約6.46倍だが、実時間では約21秒から約137秒への増加である。この差を許容できるかは、処理の実行頻度や完了までの時間要件によって変わる。

なお、今回測定したのは書き込みの負担であり、インデックスによる読み取りの改善は含んでいない。必要なインデックスを決めるには、読み取り側の効果と合わせて判断する必要がある。

ベンチマークのコード

測定用のコードは以下のリポジトリに置いている。

GitHub – taako-502/db-index-write-benc…

GitHub – taako-502/db-index-write-benc…

Contribute to taako-502/db-index-write-benchmark development by creating an account on GitHub.

Contribute to taako-502/db-index-write-benchmark development by creating an account on GitHub.

今回の測定コマンドは次のとおり。

docker compose up -d --wait

go run ./cmd/bench \
  -engines mongodb,postgresql \
  -sizes 10000,100000,1000000 \
  -indexes 0,1,5 \
  -repeats 5 \
  -batch 1000 \
  -seed 42 \
  -out results/full

結果はJSON・CSV・HTMLで出力する。HTMLではデータ件数、DB、表示する指標を切り替え、各試行と最小〜最大の範囲も確認できる。

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です