インデックスを追加すると、書き込みの負担が増える。
では、実際には何倍の時間がかかるのか。インデックスが5本になれば、処理時間も5倍になるのか。更新しない項目に設定したインデックスは、どれくらい影響するのか。
MongoDBとPostgreSQLで、インデックスの本数、データ件数、更新対象を変えて測定した。
今回の条件では、追加インデックス5本による処理時間の倍率は、100万件の処理で約1.02倍から約6.46倍まで差があった。同じ本数でも、操作やデータ件数によって負担は大きく変わった。
検証する条件
Goでベンチマーク用のプログラムを作成し、ローカルのDockerコンテナに対して書き込みを行った。
| 項目 | 条件 |
|---|---|
| ホスト | MacBook Pro、メモリ24GB、SSD |
| MongoDB | 8.0.16 |
| PostgreSQL | 17.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本 | 倍率 |
|---|---|---|---|---|
| INSERT | 4.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万件 |
|---|---|---|---|
| INSERT | 2.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万件 |
|---|---|---|---|
| INSERT | 2.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本 |
|---|---|---|---|
| MongoDB | 19.50秒 | 31.82秒 | 31.81秒 |
| PostgreSQL | 7.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、表示する指標を切り替え、各試行と最小〜最大の範囲も確認できる。

コメントを残す