ホームページ >ウェブフロントエンド >jsチュートリアル >分散システムでのシャードデータの処理: 結合、ブロードキャスト、クエリ最適化の詳細

分散システムでのシャードデータの処理: 結合、ブロードキャスト、クエリ最適化の詳細

Patricia Arquette
Patricia Arquetteオリジナル
2024-12-23 13:50:18979ブラウズ

Handling Sharded Data in Distributed Systems: A Deep Dive into Joins, Broadcasts, and Query Optimization

最新の分散データベースでは、データを水平にスケーリングする必要があるため、シャーディングが広く採用されています。シャーディングは複数のノードにわたる大規模なデータセットの管理に役立ちますが、特に結合を実行して効率的なデータ取得を確保する場合に課題が生じます。この記事では、特にブロードキャスト結合シャード キー アライメント、および分散クエリ エンジンに焦点を当てて、これらの課題に対処するさまざまな概念と手法を検討します。 >PrestoBigQuery。さらに、Node.jsExpress を使用して、実際のアプリケーションでこれらの問題を処理する方法を示します。


Express.js を使用した Node.js のシャーディングの例

ここでは、Node.js と Express.js を使用して

PostgreSQL にシャーディングを実装する方法を示します。

PostgreSQL シャーディングの例

Citus または Node.js による手動論理シャーディングの使用:

論理シャーディングの例

  1. シャード用のテーブルのセットアップ: シャードのテーブルを使用します (shard1 の user_data と shard2 の user_data)。

  2. Express.js API を作成します: シャード キー (user_id など) に基づいてクエリを分散します。

   const express = require('express');
   const { Pool } = require('pg');

   const poolShard1 = new Pool({ connectionString: 'postgresql://localhost/shard1' });
   const poolShard2 = new Pool({ connectionString: 'postgresql://localhost/shard2' });

   const app = express();
   app.use(express.json());

   const getShardPool = (userId) => (userId % 2 === 0 ? poolShard1 : poolShard2);

   app.post('/user', async (req, res) => {
       const { userId, data } = req.body;
       const pool = getShardPool(userId);
       try {
           await pool.query('INSERT INTO user_data (user_id, data) VALUES (, )', [userId, data]);
           res.status(200).send('User added successfully');
       } catch (err) {
           console.error(err);
           res.status(500).send('Error inserting user');
       }
   });

   app.get('/user/:userId', async (req, res) => {
       const userId = parseInt(req.params.userId, 10);
       const pool = getShardPool(userId);
       try {
           const result = await pool.query('SELECT * FROM user_data WHERE user_id = ', [userId]);
           res.status(200).json(result.rows);
       } catch (err) {
           console.error(err);
           res.status(500).send('Error retrieving user');
       }
   });

   app.listen(3000, () => console.log('Server running on port 3000'));

1.

分散データベースでのシャーディング

シャーディングは、パフォーマンス、スケーラビリティ、可用性を向上させるために、複数のデータベース インスタンス (シャード) にデータを水平に分割するプロセスです。単一のデータベース インスタンスでは大量のデータやトラフィックを処理できない場合、シャーディングが必要になることがよくあります。

シャーディング戦略:

  • 範囲ベースのシャーディング: データは、キーの範囲に基づいてシャード全体に分散されます (例: order_date による順序のパーティション化)。
  • ハッシュベースのシャーディング: データはシャード キー (例: user_id) によってハッシュされ、シャード間でデータを均等に分散します。
  • ディレクトリベースのシャーディング: 中央のディレクトリは、システム内のデータの存在場所を追跡します。
ただし、関連するテーブルが異なるキーでシャードされている場合、またはテーブルが複数のシャードにわたる別のテーブルとの結合を必要とする場合、

スキャッターギャザー 操作が必要になるため、パフォーマンスが低下する可能性があります。ここでは、ブロードキャスト結合とシャード キーの調整を理解することが重要になります。


2. シャードシステムでの結合に関する課題

データが異なるシャードに存在する場合、それらのシャード間の結合の実行は複雑になる可能性があります。一般的な課題の内訳は次のとおりです:

1. シャードキーの位置がずれている:

多くのシステムでは、テーブルは異なるキーでシャード化されています。例:

  • users テーブルは user_id によってシャーディングされる可能性があります。
  • orders テーブルは地域ごとにシャーディングされる可能性があります。

結合 (orders.user_id = users.user_id など) を実行する場合、関連するレコードが同じシャードに存在しない可能性があるため、システムは複数のシャードからデータをフェッチする必要があります。

2. スキャッター/ギャザー結合:

スキャッター/ギャザー結合では、システムは次のことを行う必要があります。

  • 関連データを保持するすべてのシャードにリクエストを送信します。
  • シャード全体で結果を集計します。 これにより、特にデータが多くのシャードに分散している場合、パフォーマンスが大幅に低下する可能性があります。

3. ブロードキャスト参加:

ブロードキャスト結合は、結合されるテーブルの 1 つがすべてのシャードにブロードキャストできるほど小さい場合に発生します。この場合:

  • 小さなテーブル (例: ユーザー) は、より大きなシャード化テーブル (例: 注文) が存在するすべてのノードにわたって複製されます。
  • 各ノードはローカル データをブロードキャスト データと結合できるため、シャード間通信の必要がなくなります。

3. シャードデータに分散クエリエンジンを使用する

PrestoBigQuery などの分散クエリ エンジンは、シャード データを処理し、分散システム間で効率的にクエリを結合するように設計されています。

プレスト/トリノ:

Presto は、異種データ ソース (リレーショナル データベース、NoSQL データベース、データ レイクなど) にわたる大規模なデータセットをクエリするために設計された分散 SQL クエリ エンジンです。 Presto は分散データ ソース間で結合を実行し、ノード間のデータ移動を最小限に抑えることでクエリを最適化できます。

使用例: Presto を使用してシャードデータを結合

orders がリージョンごとにシャード化され、users が user_id によってシャード化されるシナリオでは、Presto は分散実行モデルを使用して、異なるシャード間で結合を実行できます。

クエリ:

   const express = require('express');
   const { Pool } = require('pg');

   const poolShard1 = new Pool({ connectionString: 'postgresql://localhost/shard1' });
   const poolShard2 = new Pool({ connectionString: 'postgresql://localhost/shard2' });

   const app = express();
   app.use(express.json());

   const getShardPool = (userId) => (userId % 2 === 0 ? poolShard1 : poolShard2);

   app.post('/user', async (req, res) => {
       const { userId, data } = req.body;
       const pool = getShardPool(userId);
       try {
           await pool.query('INSERT INTO user_data (user_id, data) VALUES (, )', [userId, data]);
           res.status(200).send('User added successfully');
       } catch (err) {
           console.error(err);
           res.status(500).send('Error inserting user');
       }
   });

   app.get('/user/:userId', async (req, res) => {
       const userId = parseInt(req.params.userId, 10);
       const pool = getShardPool(userId);
       try {
           const result = await pool.query('SELECT * FROM user_data WHERE user_id = ', [userId]);
           res.status(200).json(result.rows);
       } catch (err) {
           console.error(err);
           res.status(500).send('Error retrieving user');
       }
   });

   app.listen(3000, () => console.log('Server running on port 3000'));

プレストは次のことを行います:

  1. スキャッターギャザーを使用して、関連するユーザー レコードを取得します。
  2. ノード間でデータを結合します。

Google BigQuery:

BigQuery は、大規模な分析クエリの実行に優れた、フルマネージドのサーバーレス データ ウェアハウスです。 BigQuery はシャーディングの詳細を抽象化しますが、クエリを最適化するためにデータを多くのノードに自動的に分割して分散します。大規模なデータセットを簡単に処理でき、データが時間または他のディメンションで分割されている分析クエリに特に効果的です。

ユースケース例: BigQuery でのシャードテーブルの結合
   const express = require('express');
   const { Pool } = require('pg');

   const poolShard1 = new Pool({ connectionString: 'postgresql://localhost/shard1' });
   const poolShard2 = new Pool({ connectionString: 'postgresql://localhost/shard2' });

   const app = express();
   app.use(express.json());

   const getShardPool = (userId) => (userId % 2 === 0 ? poolShard1 : poolShard2);

   app.post('/user', async (req, res) => {
       const { userId, data } = req.body;
       const pool = getShardPool(userId);
       try {
           await pool.query('INSERT INTO user_data (user_id, data) VALUES (, )', [userId, data]);
           res.status(200).send('User added successfully');
       } catch (err) {
           console.error(err);
           res.status(500).send('Error inserting user');
       }
   });

   app.get('/user/:userId', async (req, res) => {
       const userId = parseInt(req.params.userId, 10);
       const pool = getShardPool(userId);
       try {
           const result = await pool.query('SELECT * FROM user_data WHERE user_id = ', [userId]);
           res.status(200).json(result.rows);
       } catch (err) {
           console.error(err);
           res.status(500).send('Error retrieving user');
       }
   });

   app.listen(3000, () => console.log('Server running on port 3000'));

BigQuery はパーティショニングと分散を自動的に処理し、手動シャーディングの必要性を最小限に抑えます。


4. Node.js アプリケーションでのシャード キーの不整合の処理

Node.js アプリケーションでシャード データを扱う場合、シャード キーの位置がずれているスキャッター ギャザー 結合の必要性などの問題がよく発生します。 Node.jsExpress を使用してこれらの課題に取り組む方法を次に示します。

Node.js でのブロードキャスト結合の処理

結合で小さなテーブル (ユーザーなど) をすべてのシャードにブロードキャストする必要がある場合は、小さなテーブルを 1 回フェッチし、それを使用してシャード テーブルのデータと結合することで、アプリケーション層に結合を実装できます。

SELECT o.order_id, u.user_name
FROM orders o
JOIN users u
ON o.user_id = u.user_id;

Node.js での Scatter-Gather クエリの処理

スキャッター/ギャザー結合を伴うクエリの場合 (シャード キーの位置がずれている場合など)、すべてのシャードをクエリし、結果をアプリケーション層で集計する必要があります。

SELECT o.order_id, u.user_name
FROM `project.dataset.orders` o
JOIN `project.dataset.users` u
ON o.user_id = u.user_id
WHERE o.order_date BETWEEN '2024-01-01' AND '2024-12-31';

5. シャードデータを使用したクエリ最適化のベストプラクティス

シャーディングされたデータを処理し、結合を実行する場合は、次のベスト プラクティスを考慮してください。

  1. シャード キーの調整: 可能な場合は、関連するテーブルが同じシャード キーを使用するようにします。これにより、クロスシャード結合の必要性が最小限に抑えられ、パフォーマンスが向上します。

  2. 非正規化: 結合が頻繁に行われるシナリオでは、データの非正規化を検討してください。たとえば、ユーザー情報を Posts テーブルに直接保存できるため、結合の必要性が減ります。

  3. 小さなテーブルにはブロードキャスト結合を使用する: テーブルの 1 つが十分に小さい場合は、スキャッター/ギャザー クエリを回避するためにすべてのノードにブロードキャストします。

  4. データの事前結合: 頻繁にアクセスされるデータの場合は、事前結合して結果をマテリアライズド ビューまたはキャッシュに保存することを検討してください。

  5. 分散クエリ エンジンの活用: 複雑な分析クエリの場合は、分散結合と最適化を自動的に処理する PrestoBigQuery などのシステムを使用します。


6. シャードデータを使用したカーソルベースのページネーションのベストプラクティス

このようなシャーディングを備えた分散システムでは、特にデータが複数のシャードに分散しているため、カーソルベースのページネーションを慎重に処理する必要があります。重要なのは次のとおりです:

  1. クエリを分割する: 関連するデータについて各シャードを個別にクエリします。
  2. チャンクでページネーションを処理する: シャード データ (投稿またはユーザーのいずれか) 全体でページネーションを行う方法を決定し、関連する結果を収集します。
  3. アプリケーション レベルで結合: 各シャードから結果を取得し、メモリ内のデータを結合して、次のページにカーソル ロジックを適用します。

データが異なるシャードに存在し、アプリケーション レベルでポストフェッチ結合が必要であることを考慮して、Node.jsExpress を使用してこれを実装する方法を見てみましょう。

シャード化されたテーブルでページネーションと結合を処理する方法

以下があると仮定しましょう:

  • posts テーブルは user_id によってシャード化されました。
  • users テーブルは user_id によってシャード化されました。

特定のユーザーのページ分割された投稿を取得したいのですが、ユーザーと投稿は異なるシャードにあるため、クエリを分割し、ページ分割を処理してから、アプリケーション レベルで結合を実行する必要があります。

アプローチ:

  1. 関連するシャードをクエリします:

    • まず、シャード全体で Posts テーブルにクエリを実行して、投稿を取得する必要があります。
    • 関連する投稿を取得した後、投稿の user_id を使用して、users テーブルをクエリします (ここでもシャード全体で)。
  2. ページネーション戦略:

    • 投稿のページネーション: created_at、post_id、または別の一意のフィールドを使用して、posts テーブルのページネーションを行うことができます。
    • ユーザーのページ分割: ユーザー データを個別に取得するか、user_id をカーソルとして使用してユーザーをページ分割する必要がある場合があります。
  3. アプリケーションレベルの結合:

    • 関連するシャード (投稿とユーザーの両方) からデータを取得した後、それらをアプリケーション レベルで結合します。
  4. カーソルの処理:

    • 最初のページを取得した後、(投稿からの) 最後の created_at または post_id を次のクエリのカーソルとして使用します。

実装例

1. シャード間で投稿をクエリする

ここでは、カーソル (created_at または post_id など) でフィルタリングして、さまざまな投稿シャードにわたってクエリを実行します。

2. 投稿データを使用してシャード全体でユーザーをクエリする

最初のクエリから関連する post_id と user_id を取得したら、関連するシャードからユーザー データを取得します。

   const express = require('express');
   const { Pool } = require('pg');

   const poolShard1 = new Pool({ connectionString: 'postgresql://localhost/shard1' });
   const poolShard2 = new Pool({ connectionString: 'postgresql://localhost/shard2' });

   const app = express();
   app.use(express.json());

   const getShardPool = (userId) => (userId % 2 === 0 ? poolShard1 : poolShard2);

   app.post('/user', async (req, res) => {
       const { userId, data } = req.body;
       const pool = getShardPool(userId);
       try {
           await pool.query('INSERT INTO user_data (user_id, data) VALUES (, )', [userId, data]);
           res.status(200).send('User added successfully');
       } catch (err) {
           console.error(err);
           res.status(500).send('Error inserting user');
       }
   });

   app.get('/user/:userId', async (req, res) => {
       const userId = parseInt(req.params.userId, 10);
       const pool = getShardPool(userId);
       try {
           const result = await pool.query('SELECT * FROM user_data WHERE user_id = ', [userId]);
           res.status(200).json(result.rows);
       } catch (err) {
           console.error(err);
           res.status(500).send('Error retrieving user');
       }
   });

   app.listen(3000, () => console.log('Server running on port 3000'));

主な詳細:

  1. 投稿のページネーション: カーソルは、created_at フィールドまたは投稿内の別の一意のフィールドに基づいており、結果のページネーションに使用されます。
  2. シャードを個別にクエリする: 投稿とユーザーは異なるキーでシャード化されているため、各シャードを個別にクエリし、アプリケーション レベルで結合を実行する前にすべてのシャードからデータを収集します。
  3. カーソル処理: 結果を取得した後、投稿の last created_at (または post_id) を使用して、次のページのカーソルを生成します。
  4. アプリケーション レベルで結合: 関連するシャードからデータを取得した後、メモリ内の user_id に基づいて投稿をユーザー データと結合します。

結論

分散システムでのシャードデータの管理には、特に効率的な結合の実行に関して、特有の課題が伴います。 ブロードキャスト結合スキャッター/ギャザー結合などの手法を理解し、分散クエリ エンジンを活用すると、クエリのパフォーマンスを大幅に向上させることができます。さらに、アプリケーションレベルのクエリでは、シャード キーの配置非正規化、および最適化されたクエリ戦略を考慮することが不可欠です。これらのベスト プラクティスに従い、適切なツールを利用することで、開発者はアプリケーションがシャード データを効果的に処理し、大規模なパフォーマンスを維持できるようになります。

以上が分散システムでのシャードデータの処理: 結合、ブロードキャスト、クエリ最適化の詳細の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。