MongoDB を解き放つ: カーソルベースのページネーションが常にオフセットベースのページネーションよりも優れている理由!

王林
リリース: 2024-09-04 22:43:02
オリジナル
1051 人が閲覧しました

ページネーション は、大規模なデータセットを扱う場合のデータベース操作の重要な部分です。データを管理可能なチャンクに分割できるため、参照、処理、表示が容易になります。 MongoDB は、オフセット ベースとカーソル ベースという 2 つの一般的なページネーション メソッドを提供します。どちらの方法も同じ目的を果たしますが、特にデータセットが大きくなるにつれて、パフォーマンスと使いやすさが大きく異なります

2 つのアプローチを詳しく見て、なぜカーソルベースのページネーションがオフセットベースのページネーションよりも優れているのかを見てみましょう。

1.

オフセットベースのページネーション

オフセットベースのページネーションは簡単です。指定されたオフセットから開始して特定の数のレコードを取得します。たとえば、最初のページはレコード 0 ~ 9 を取得し、2 番目のページはレコード 10 ~ 19 を取得する、というようになります。

ただし、この方法には重大な欠点があります。上位のページに移動すると、クエリが遅くなります。これは、データベースが前のページのレコードをスキップする必要があり、レコードをスキャンする必要があるためです。

オフセットベースのページネーションのコードは次のとおりです:


async function offset_based_pagination(params) {
  const { page = 5, limit = 100 } = params;
  const skip = (page - 1) * limit;
  const results = await collection.find({}).skip(skip).limit(limit).toArray();
  console.log(`Offset-based pagination (Page ${page}):`, results.length, "page", page, "skip", skip, "limit", limit);
}
ログイン後にコピー
2.

カーソルベースのページネーション

カーソルベースのページネーションは、キーセットページネーションとも呼ばれ、一意の識別子 (ID やタイムスタンプなど) に基づいてレコードのページネーションを行います。一定数のレコードをスキップする代わりに、最後に取得したレコードを次のセットを取得するための参照ポイントとして使用します。

このアプローチは、現在のページの前にレコードをスキャンする必要がなくなるため、より効率的です。その結果、データセットの深さに関係なく、クエリ時間は一貫したままになります。

カーソルベースのページネーションのコードは次のとおりです:


async function cursor_based_pagination(params) {
  const { lastDocumentId, limit = 100 } = params;
  const query = lastDocumentId ? { documentId: { $gt: lastDocumentId } } : {};
  const results = await collection
    .find(query)
    .sort({ documentId: 1 })
    .limit(limit)
    .toArray();
  console.log("Cursor-based pagination:", results.length);
}
ログイン後にコピー
この例では、

lastDocumentId は前のページの最後のドキュメントの ID です。次のページをクエリする場合、データベースはこの値より大きい ID を持つドキュメントをフェッチし、次のレコード セットへのシームレスな移行を保証します。

3.

パフォーマンスの比較

これら 2 つのメソッドが大規模なデータセットでどのように実行されるかを見てみましょう。


async function testMongoDB() {
    console.time("MongoDB Insert Time:");
    await insertMongoDBRecords();
    console.timeEnd("MongoDB Insert Time:");

  // Create an index on the documentId field
  await collection.createIndex({ documentId: 1 });
  console.log("Index created on documentId field");

  console.time("Offset-based pagination Time:");
  await offset_based_pagination({ page: 2, limit: 250000 });
  console.timeEnd("Offset-based pagination Time:");

  console.time("Cursor-based pagination Time:");
  await cursor_based_pagination({ lastDocumentId: 170000, limit: 250000 });
  console.timeEnd("Cursor-based pagination Time:");

  await client.close();
}
ログイン後にコピー

Image description

パフォーマンス テストでは、

オフセット ベースのページネーションでは、ページ番号が増えるにつれて時間が長くなりますが、カーソルの場合は時間がかかることがわかります。 -ベースのページネーションは一貫性を維持するため、大規模な

データセットに適しています。この例では、インデックス作成の力も示しています。インデックスを削除して、結果も確認してみてください!

インデックス作成が重要な理由

インデックスがないと、MongoDB はコレクション スキャンを実行する必要があります。つまり、コレクション内の各ドキュメントを調べて、関連するデータを見つける必要があります。これは、特にデータセットが増大する場合には非効率的です。インデックスを使用すると、MongoDB はクエリ条件に一致するドキュメントを効率的に見つけられるようになり、クエリのパフォーマンスが大幅に高速化されます。

カーソルベースのページネーションのコンテキストでは、インデックスにより、次のドキュメント セット (documentId に基づく) のフェッチが迅速になり、コレクションにドキュメントが追加されてもパフォーマンスが低下しないことが保証されます。

結論

オフセットベースのページネーションは実装が簡単ですが、大規模なデータセットではレコードをスキャンする必要があるため非効率になる可能性があります。一方、カーソルベースのページネーションは、よりスケーラブルなソリューションを提供し、データセットのサイズに関係なくパフォーマンスの一貫性を保ちます。 MongoDB で大規模なコレクションを操作している場合、よりスムーズで高速なエクスペリエンスを実現するために、カーソルベースのページネーションを検討する価値があります。

ローカルで実行できる完全なindex.jsは次のとおりです:

const { MongoClient } = require("mongodb");
const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri);
client.connect();
const db = client.db("testdb");
const collection = db.collection("testCollection");

async function insertMongoDBRecords() {
  try {
    let bulkOps = [];

    for (let i = 0; i < 2000000; i++) {
      bulkOps.push({
        insertOne: {
          documentId: i,
          name: `Record-${i}`,
          value: Math.random() * 1000,
        },
      });

      // Execute every 10000 operations and reinitialize
      if (bulkOps.length === 10000) {
        await collection.bulkWrite(bulkOps);
        bulkOps = [];
      }
    }

    if (bulkOps.length > 0) {
      await collection.bulkWrite(bulkOps);
      console.log("? Inserted records till now -> ", bulkOps.length);
    }

    console.log("MongoDB Insertion Completed");
  } catch (err) {
    console.error("Error in inserting records", err);
  }
}

async function offset_based_pagination(params) {
  const { page = 5, limit = 100 } = params;
  const skip = (page - 1) * limit;
  const results = await collection.find({}).skip(skip).limit(limit).toArray();
  console.log(`Offset-based pagination (Page ${page}):`, results.length, "page", page, "skip", skip, "limit", limit);
}

async function cursor_based_pagination(params) {
  const { lastDocumentId, limit = 100 } = params;
  const query = lastDocumentId ? { documentId: { $gt: lastDocumentId } } : {};
  const results = await collection
    .find(query)
    .sort({ documentId: 1 })
    .limit(limit)
    .toArray();
  console.log("Cursor-based pagination:", results.length);
}

async function testMongoDB() {
  console.time("MongoDB Insert Time:");
  await insertMongoDBRecords();
  console.timeEnd("MongoDB Insert Time:");

  // Create an index on the documentId field
  await collection.createIndex({ documentId: 1 });
  console.log("Index created on documentId field");

  console.time("Offset-based pagination Time:");
  await offset_based_pagination({ page: 2, limit: 250000 });
  console.timeEnd("Offset-based pagination Time:");

  console.time("Cursor-based pagination Time:");
  await cursor_based_pagination({ lastDocumentId: 170000, limit: 250000 });
  console.timeEnd("Cursor-based pagination Time:");

  await client.close();
}

testMongoDB();

ログイン後にコピー

以上がMongoDB を解き放つ: カーソルベースのページネーションが常にオフセットベースのページネーションよりも優れている理由!の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!