最新の分散データベースでは、データを水平にスケーリングする必要があるため、シャーディングが広く採用されています。シャーディングは複数のノードにわたる大規模なデータセットの管理に役立ちますが、特に結合を実行して効率的なデータ取得を確保する場合に課題が生じます。この記事では、特にブロードキャスト結合、シャード キー アライメント、および分散クエリ エンジンに焦点を当てて、これらの課題に対処するさまざまな概念と手法を検討します。 >Presto と BigQuery。さらに、Node.js と Express を使用して、実際のアプリケーションでこれらの問題を処理する方法を示します。
PostgreSQL にシャーディングを実装する方法を示します。
Citus または Node.js による手動論理シャーディングの使用:
論理シャーディングの例シャード用のテーブルのセットアップ:
シャードのテーブルを使用します (shard1 の user_data と shard2 の user_data)。
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'));
スキャッターギャザー 操作が必要になるため、パフォーマンスが低下する可能性があります。ここでは、ブロードキャスト結合とシャード キーの調整を理解することが重要になります。
データが異なるシャードに存在する場合、それらのシャード間の結合の実行は複雑になる可能性があります。一般的な課題の内訳は次のとおりです:
多くのシステムでは、テーブルは異なるキーでシャード化されています。例:
結合 (orders.user_id = users.user_id など) を実行する場合、関連するレコードが同じシャードに存在しない可能性があるため、システムは複数のシャードからデータをフェッチする必要があります。
スキャッター/ギャザー結合では、システムは次のことを行う必要があります。
ブロードキャスト結合は、結合されるテーブルの 1 つがすべてのシャードにブロードキャストできるほど小さい場合に発生します。この場合:
Presto や BigQuery などの分散クエリ エンジンは、シャード データを処理し、分散システム間で効率的にクエリを結合するように設計されています。
Presto は、異種データ ソース (リレーショナル データベース、NoSQL データベース、データ レイクなど) にわたる大規模なデータセットをクエリするために設計された分散 SQL クエリ エンジンです。 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'));
プレストは次のことを行います:
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 はパーティショニングと分散を自動的に処理し、手動シャーディングの必要性を最小限に抑えます。
Node.js アプリケーションでシャード データを扱う場合、シャード キーの位置がずれている や スキャッター ギャザー 結合の必要性などの問題がよく発生します。 Node.js と Express を使用してこれらの課題に取り組む方法を次に示します。
結合で小さなテーブル (ユーザーなど) をすべてのシャードにブロードキャストする必要がある場合は、小さなテーブルを 1 回フェッチし、それを使用してシャード テーブルのデータと結合することで、アプリケーション層に結合を実装できます。
SELECT o.order_id, u.user_name FROM orders o JOIN users u ON o.user_id = u.user_id;
スキャッター/ギャザー結合を伴うクエリの場合 (シャード キーの位置がずれている場合など)、すべてのシャードをクエリし、結果をアプリケーション層で集計する必要があります。
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';
シャーディングされたデータを処理し、結合を実行する場合は、次のベスト プラクティスを考慮してください。
シャード キーの調整: 可能な場合は、関連するテーブルが同じシャード キーを使用するようにします。これにより、クロスシャード結合の必要性が最小限に抑えられ、パフォーマンスが向上します。
非正規化: 結合が頻繁に行われるシナリオでは、データの非正規化を検討してください。たとえば、ユーザー情報を Posts テーブルに直接保存できるため、結合の必要性が減ります。
小さなテーブルにはブロードキャスト結合を使用する: テーブルの 1 つが十分に小さい場合は、スキャッター/ギャザー クエリを回避するためにすべてのノードにブロードキャストします。
データの事前結合: 頻繁にアクセスされるデータの場合は、事前結合して結果をマテリアライズド ビューまたはキャッシュに保存することを検討してください。
分散クエリ エンジンの活用: 複雑な分析クエリの場合は、分散結合と最適化を自動的に処理する Presto や BigQuery などのシステムを使用します。
このようなシャーディングを備えた分散システムでは、特にデータが複数のシャードに分散しているため、カーソルベースのページネーションを慎重に処理する必要があります。重要なのは次のとおりです:
データが異なるシャードに存在し、アプリケーション レベルでポストフェッチ結合が必要であることを考慮して、Node.js と Express を使用してこれを実装する方法を見てみましょう。
以下があると仮定しましょう:
特定のユーザーのページ分割された投稿を取得したいのですが、ユーザーと投稿は異なるシャードにあるため、クエリを分割し、ページ分割を処理してから、アプリケーション レベルで結合を実行する必要があります。
関連するシャードをクエリします:
ページネーション戦略:
アプリケーションレベルの結合:
カーソルの処理:
ここでは、カーソル (created_at または post_id など) でフィルタリングして、さまざまな投稿シャードにわたってクエリを実行します。
最初のクエリから関連する 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'));
分散システムでのシャードデータの管理には、特に効率的な結合の実行に関して、特有の課題が伴います。 ブロードキャスト結合、スキャッター/ギャザー結合などの手法を理解し、分散クエリ エンジンを活用すると、クエリのパフォーマンスを大幅に向上させることができます。さらに、アプリケーションレベルのクエリでは、シャード キーの配置、非正規化、および最適化されたクエリ戦略を考慮することが不可欠です。これらのベスト プラクティスに従い、適切なツールを利用することで、開発者はアプリケーションがシャード データを効果的に処理し、大規模なパフォーマンスを維持できるようになります。
以上が分散システムでのシャードデータの処理: 結合、ブロードキャスト、クエリ最適化の詳細の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。