MongoDB Academy · レッスン

Bucket パターンと Computed パターン

時系列データをバケットドキュメントにまとめてインデックスサイズを削減し、集計値を事前計算して高コストなリアルタイム計算を避けます。

レッスン 1/413 ステップ

「Bucket パターンと Computed パターン」はCoddyKit上の無料MongoDB Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはMongoDB Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 MongoDB Academyコースには全4レッスンが含まれています。

スキーマ設計パターンの概要

MongoDBの熟練エンジニアは、直感だけでスキーマを設計しません。繰り返し発生する課題を解決する、実績のあるパターンのライブラリを活用します。これらのパターンには、MongoDBのストレージエンジン、インデックス構造、集計パイプラインがさまざまなドキュメント形状とどのように作用するかについて、経験から得られた知見がまとめられています。Bucket PatternとComputed Patternは、パフォーマンスが重視されるアプリケーションで特に効果の大きい2つのパターンです。

問題:小さなドキュメントが多すぎる

各センサーの測定値を個別のドキュメントとして保存するIoTシステムを考えてみましょう。1秒に1回測定するセンサーは、1日に86,400個のドキュメントを生成します。つまり、センサー1台につき1日あたり86,400個のインデックスエントリが必要となり、インデックスサイズが非常に大きくなり、1日分のデータを検索する際の操作数も膨大になります。24時間分のデータを読み取るには、数万個の小さなドキュメントを取得しなければならず、非常に非効率です。

// Anti-pattern: one document per reading
{
  _id: ObjectId(),
  sensorId: 'sensor-42',
  timestamp: ISODate('2024-06-01T10:00:01Z'),
  temperature: 23.1
}
// 86,400 such documents per sensor per day
// 86,400 index entries per sensor per day

Bucket Pattern:バケットへのグループ化

Bucket Patternでは、関連する小さなドキュメントを1つの大きなドキュメント(「バケット」)にまとめます。測定値ごとに1つのドキュメントを作成する代わりに、1時間分の測定値を1つのドキュメントに格納します。これにより、ドキュメント数を3,600個から1個に、インデックスエントリ数を3,600個から1個に削減できます。バケットドキュメントには、測定値の配列と、書き込み時に計算したサマリー統計が含まれます。これにより、インデックスサイズが大幅に縮小し、範囲クエリのパフォーマンスが向上します。

// Bucket Pattern: one document per sensor per hour
{
  _id: ObjectId(),
  sensorId: 'sensor-42',
  date: ISODate('2024-06-01T10:00:00Z'),  // hour bucket boundary
  count: 3600,
  measurements: [
    { ts: ISODate('2024-06-01T10:00:01Z'), temp: 23.1 },
    { ts: ISODate('2024-06-01T10:00:02Z'), temp: 23.2 },
    // ... 3598 more readings ...
  ],
  // Pre-computed summaries
  avgTemp: 23.15,
  maxTemp: 24.1,
  minTemp: 22.8
}

$push と $inc でバケットに追加する

新しい測定値が届いたら、センサーと時刻に対応する現在のバケットドキュメントを検索し、$push で measurements 配列に追加し、$inc で count をインクリメントして、アトミックに更新します。upsert: true を使用すると、現在の時刻のバケットがまだ存在しない場合に MongoDB が新しいバケットドキュメントを作成します。

const now = new Date()
const hourBucket = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours())

db.sensorBuckets.updateOne(
  {
    sensorId: 'sensor-42',
    date: hourBucket,
    count: { $lt: 3600 }  // don't overfill a bucket
  },
  {
    $push: { measurements: { ts: now, temp: 23.5 } },
    $inc: { count: 1 },
    $min: { minTemp: 23.5 },
    $max: { maxTemp: 23.5 }
  },
  { upsert: true }
)

バケットをまたいでクエリする

あるセンサーの指定した時間範囲におけるすべての測定値を取得するには、sensorId と date の範囲でバケットドキュメントをクエリします。その後、集計結果には事前計算済みのサマリーを使用するか、測定値を1件ずつ取得するには measurements 配列に対して $unwind を使用します。24時間分のデータをクエリする場合、現在は個別のドキュメント86,400件ではなく、バケットドキュメント24件を読み取るため、取得するドキュメント数を3,600分の1に削減できます。

// Get hourly summaries for a day (reads 24 bucket docs)
db.sensorBuckets.find(
  {
    sensorId: 'sensor-42',
    date: {
      $gte: ISODate('2024-06-01T00:00:00Z'),
      $lt:  ISODate('2024-06-02T00:00:00Z')
    }
  },
  { _id: 0, date: 1, avgTemp: 1, maxTemp: 1, minTemp: 1, count: 1 }
).sort({ date: 1 })

Computed パターン: 結果を事前計算する

Computed パターンは、コストの高い集計を write 時に事前計算し、その結果をドキュメントに直接保存します。読み取り時にすべてのレビューを合計して商品の平均評価を計算する代わりに、新しいレビューが追加されるたびに計算し、商品ドキュメントの reviewCount と並べて avgRating に保存します。読み取りのコストはほぼゼロになり、事前計算済みのフィールドを返すだけで済みます。

// Product document with pre-computed stats
{
  _id: ObjectId(),
  name: 'Wireless Headphones',
  price: 149.99,
  // Pre-computed at write time
  reviewCount: 1247,
  avgRating: 4.3,
  ratingSum: 5362.1  // stored to recompute avg without fetching all reviews
}

Computed フィールドを段階的に更新する

新しいレビューが届いたら、最初から再計算するのではなく、同じ操作の中で Computed フィールドをアトミックに更新します。$inc で reviewCount と ratingSum をインクリメントし、その後 avgRating を再計算します。MongoDB では、更新後の count と sum から平均を設定するために $divide を使用するパイプライン形式の更新(MongoDB 4.2 以降)でこれを実行できます。

// Atomic incremental update of computed stats
db.products.updateOne(
  { _id: productId },
  [
    {
      $set: {
        reviewCount: { $add: ['$reviewCount', 1] },
        ratingSum: { $add: ['$ratingSum', newRating] }
      }
    },
    {
      $set: {
        avgRating: { $divide: ['$ratingSum', '$reviewCount'] }
      }
    }
  ]
)

Computed パターンを使用するタイミング

Computed パターンは、write よりも read が圧倒的に多く、読み取り時の計算コストが高い場合に最も効果を発揮します。商品の平均評価、リーダーボードのスコア、記事の閲覧数、収益の合計などは、いずれも適した候補です。その代わり、write 処理がやや複雑になり、元データが変更されたときに Computed 値の整合性を維持する必要があります。read と write の両方が頻繁な場合は、同期的に更新するのではなく、定期的に再計算するバックグラウンドジョブを検討してください。

2つのパターンを組み合わせる

Bucket パターンと Computed パターンは、組み合わせて使われることがよくあります。センサーデータシステムでは、測定値を1時間ごとのバケットドキュメントにまとめ(Bucket パターン)、その日の全体について min/max/avg を保存した事前計算済みの日次サマリードキュメントを維持できます(Computed パターン)。この階層化されたアプローチにより、日ごとの傾向を表示するダッシュボードは単一のドキュメントを読み取るだけで済み、詳細表示のクエリも24個の時間単位のバケットドキュメントだけをスキャンすれば済みます。

// Daily summary document (Computed Pattern on top of Bucket Pattern)
{
  _id: ObjectId(),
  sensorId: 'sensor-42',
  date: ISODate('2024-06-01T00:00:00Z'),
  totalReadings: 86400,
  dailyAvgTemp: 22.8,
  dailyMaxTemp: 28.4,
  dailyMinTemp: 18.2,
  peakHour: 14  // hour with highest average temperature
}

バケットサイズのトレードオフ

バケットドキュメントは無制限に大きくならないようにする必要があります。MongoDB のドキュメントサイズには16 MBの上限があるためです。データレートの高いセンサーでは、時間で区切ったバケット(例: 1時間ごと、または1日ごと)を使用してください。更新フィルターの count: { $lt: 3600 } ガードにより、バケットが大きくなりすぎるのを防げます。バケットが容量に達すると、upsert によって新しいバケットが自動的に作成されます。本番環境でバケットの充填率を監視し、データレートに合わせてバケットサイズを調整してください。

Bucket パターンがインデックスに与える影響

バケットコレクションの主なインデックスは、クエリパターンをカバーする必要があります。{ sensorId: 1, date: 1 } のような複合インデックスを作成します。この複合インデックスにより、センサーと時間範囲でフィルタリングするクエリはインデックスを直接利用できます。86,400件の個別ドキュメントの timestamp フィールドにインデックスを作成する場合と比べ、バケットコレクションのインデックスは1センサー・1日あたり24個のエントリしか保持しません。インデックスサイズを3,600分の1に削減できるため、ワーキングセットを RAM に収めやすくなり、パフォーマンスが大幅に向上します。

// Create supporting index for bucket pattern queries
db.sensorBuckets.createIndex({ sensorId: 1, date: 1 })

// Explain query to verify IXSCAN usage
db.sensorBuckets.find({ sensorId: 'sensor-42', date: { $gte: ISODate('2024-06-01T00:00:00Z') } }).explain('executionStats')

理解度チェック

このレッスンで学んだ MongoDB と NoSQL Databases の概念について、理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、Bucket パターンによって多数の小さなドキュメントを少数の大きなドキュメントにまとめ、インデックスサイズを大幅に削減して範囲クエリのパフォーマンスを向上させること、Computed パターンによってコストの高い集計を write 時に事前計算し、読み取り時には保存済みの値を即座に返せるようにすること、そしてこの2つのパターンを効果的に組み合わせられることを学びました。次は Extended Reference パターンと Subset パターンについて学びます。

無料で開始

AI チューターと学ぶ JavaScript — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
30
レッスン
120

よくある質問

「Bucket パターンと Computed パターン」レッスンは無料ですか?

はい。「Bucket パターンと Computed パターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、MongoDB Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 MongoDB Academyコースには全4レッスンが含まれています。

「Bucket パターンと Computed パターン」で何を学びますか?

時系列データをバケットドキュメントにまとめてインデックスサイズを削減し、集計値を事前計算して高コストなリアルタイム計算を避けます。 ブラウザで直接実行するハンズオンコードでMongoDB Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

MongoDB Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのMongoDB Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「Bucket パターンと Computed パターン」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このMongoDB Academyレッスンでコードを書いて実行できますか?

はい。すべてのMongoDB Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Bucket パターンと Computed パターン
  2. Extended Reference パターンと Subset パターン
  3. Polymorphic パターンと Schema Versioning パターン
  4. Outlier パターンと Tree Structure パターン
← MongoDB Academyに戻る