Neptune Serverless で MERGE 大量投入したら $309 請求が来た話

記事タイトルとURLをコピーする

要約

  • Neptune Serverless(I/O-Optimized)で約 9 万件のノード・リレーションを openCypher MERGE で投入したら 約 $309 の請求が来た
  • 最初に「I/O-Optimized だから I/O 課金で高くなった」と思ったが、それは誤りだった
  • 正しい原因は「MERGE を含む大量トランザクション実行 → NCU が長時間高止まり → NCU-hour 課金が膨らんだ」こと
  • さらに MaxCapacity を 64 NCU に設定していたことで上限いっぱいまでスケールアップした
  • 対策は Bulk Loader(S3 + CSV)を使うこと

何をやったか

ナレッジグラフ構築プロジェクトで、Amazon Neptune Serverless(I/O-Optimized モード)を使っていました。

ノードとリレーション、合計約 9 万件 のデータを初期投入するにあたり、openCypher の MERGE 文を使った Python スクリプトを書きました。

# こういうコードを書いた(ダメな例)
for item in data:  # ~90,000件
    query = "MERGE (n:Person {id: $id}) SET n += $props"
    execute_query(query, {"id": item["id"], "props": item})

件数はそこそこ多かったですが、「Serverless だし自動スケールするだろう」と深く考えずに実行しました。

起きたこと

初回投入で約 $309 USD の請求が発生しました。

さらに興味深いことに、同じスクリプトを 2 回目に実行したところ、処理時間もコストも大幅に下がりました。

実行 処理時間 コスト
初回 長時間 約 $309
2 回目 短時間 大幅減

2 回目が安い理由は「既存データにヒットして MERGE の write 処理がスキップされたから」です。この差が、後の原因分析のヒントになりました。

原因:NCU-hourが膨らんだ

Neptune Serverlessの料金の本体は「NCU(Neptune Capacity Units)× 稼働時間」です。

コスト = NCU × 稼働時間 × 単価

東京リージョンのI/O-OptimizedモードでのNCU単価は約$0.253/NCU-hourです。

大量のMERGEがNCUをスケールアップしてしまっていた

MERGE は「なければ作る、あれば何もしない(または更新)」という命令です。内部的に:

  1. Read:対象ノード/エッジが存在するかルックアップ
  2. Write:存在しなければ作成

このread-then-writeが 1 文ごとに個別トランザクション として実行されます。

9 万件を 1 文ずつ送ると:

[リクエスト 1]     MERGE → トランザクション(read + write)
[リクエスト 2]     MERGE → トランザクション(read + write)
...
[リクエスト 90000] MERGE → トランザクション(read + write)

Neptune Serverless はトランザクションの負荷を見てリアルタイムにNCUを調整します。書き込みトランザクションが途切れなく来続けると NCU はスケールアップしたまま下がりません

結果として:

  • 投入中ずっと NCU が高い値で維持される
  • 設定していた MaxCapacity も高かった(後述)
  • 長時間 × 高 NCU = 高額な NCU-hour 請求

2回目が安かったのも同じ理由です。既存データが存在するのでMERGEのwriteがほとんどスキップされ、処理がすぐ終わる → NCU 高止まりの時間が短い → 安い。

もう一つの原因:MaxCapacity を深く考えずに設定した

AI アシスタントとの会話の中でNeptune Serverlessを構築していたのですが、MaxCapacityを「64 NCU」に設定していました。

64 NCU のコストを計算すると:

NCU コスト(東京、I/O-Optimized)
1 約 $0.253/時間
4 約 $1.01/時間
64 $16.19/時間

最大で 1 時間あたり約 $16 課金される設定を入れていたわけです。

MERGEの連打でNCUが上限付近まで張り付き、それが長時間続いた結果が今回の$309です。

Neptuneを使い始めたばかりで課金モデルへの理解が浅いまま構築を進めてしまったことが、根本的な原因の一つでした。ちなみにMaxCapacityの設定範囲は 2.5〜128NCUです。初めて触るサービスほど、パラメータの意味とコストへの影響を最初に把握しておくことが重要だと痛感しました

対策:.cypherファイルをCSVに変換してBulkLoaderを使った

Neptuneには S3 + CSV を使った Bulk Loader があります。HTTP APIを経由せずNeptune内部のローダーが直接ストレージに書き込むため、トランザクションのオーバーヘッドがほぼありません。

今回はすでにMERGE文で記述した .cypher ファイルが手元にあったため、AIと対話しながらBulkLoader用のCSV形式に変換する作業を行いました。ノードとリレーションをそれぞれ専用の CSVに分割し、S3にアップロードしたうえでBulkLoaderを実行する形に切り替えることで、同規模のデータを大幅に低いコストで投入できるようになりました。

既存の .cypher ファイルがある場合でも、AI を活用した変換作業自体は難しくありません。最初からBulkLoaderを前提に設計しておくことを検討しておけば良かった...

やっておくべきだったこと

今回は事後に気づきましたが、最初からリスクを洗い出しいくつかの設定をしておけば防げました。

① CloudWatchアラームでNCUの上昇を検知する AWS/Neptune の NeptuneServerlessDatabaseCapacityUsage メトリクスを監視し、NCU が一定値(例:4)を超えたらSNSで通知するアラームを仕込んでおけば「気づいたら膨らんでいた」を防げたと痛感しています。

② AWS BudgetsでNeptuneのコスト上限アラートを設定する サービスフィルターに Amazon Neptune を指定して月次予算を設定し、80%到達時点でメール通知が来るようになどしておく。CloudWatchが「今何NCUか」を教えてくれるのに対し、Budgetsは「今月いくら使ったか」を教えてくれるので両方セットで運用するのが出来ていれば早く気づくことができたと感じています。

③ 初めて触るサービスをAIと構築するときは、先にサービスの課金モデルを注意深く調べる AIはスムーズに構築を進めてくれますが、初めてのサービスでは「そのパラメータが何を意味するか」「課金にどう影響するか」を自分では把握できていないまま設定してしまいがちです。今回の MaxCapacity=64 もまさにそのパターンでした。AIに任せる前に、まず公式ドキュメントで課金モデルだけでも把握しておくことを強くおすすめします。

まとめ

誤解・ミス 正しい理解・対策
I/O-Optimized = I/O 従量で高くなった I/O-Optimized は I/O 課金なし。NCU-hour が本体
MERGE でも件数が同じならコストは同じ MERGE は read-then-write トランザクション。連打で NCU が長時間高止まりする
MaxCapacity=64 を深く考えずに設定 最大 $16/時間の設定。用途に応じた上限設定が必須
2 回目が安いのは謎 既存データヒット → write スキップ → 処理短縮 → NCU 高止まり時間が短い

NeptuneServerlessへの大量データ投入はBulkLoaderを推奨します

また、AIとの会話でインフラを構築する際は、提案されたパラメータのコスト・リスクを必ず自分で調べて別エージェントに同じ質問をしたり、ハルシネーションが起きていないか、ファクトチェックを何度か実施し調べてから設定することを強くおすすめします。(当たり前のことと言われればその通りですが...)

今回の $309 は「MERGE の仕組みへの理解不足」「I/O-Optimized の課金モデルの誤解」「MaxCapacity を深く考えずに設定した」という 3つのミスが重なった結果でした。どれか一つでも防げていれば、ここまでの金額にはならなかったはずです。

AIは構築の手を速めてくれますが、初めて触るサービスを AI と一緒に構築するときは特に注意が必要です。 AI の提案をそのまま採用するのではなく、「このパラメータは何か」「課金にどう影響するか」を自分で一度調べてから進める習慣をつけておくことで、今回のようなインシデントは防げたと思っています。

NeptuneServerlessは便利なサービスですが、スケールアップの自由度が高い分、設定ミスがそのままコストに直結します。これから使う方の参考になれば幸いです。


参考:Neptune Bulk Loader ドキュメント