Heim >Web-Frontend >js-Tutorial >Umgang mit Sharded-Daten in verteilten Systemen: Ein tiefer Einblick in Joins, Broadcasts und Abfrageoptimierung
In modernen verteilten Datenbanken hat die Notwendigkeit der horizontalen Skalierung von Daten zur weit verbreiteten Einführung von Sharding geführt. Während Sharding dabei hilft, große Datensätze über mehrere Knoten hinweg zu verwalten, bringt es Herausforderungen mit sich, insbesondere bei der Durchführung von Joins und der Gewährleistung eines effizienten Datenabrufs. In diesem Artikel untersuchen wir verschiedene Konzepte und Techniken, die diese Herausforderungen angehen, und konzentrieren uns dabei insbesondere auf Broadcast-Joins, Shard-Key-Alignment und verteilte Abfrage-Engines wie Presto und BigQuery. Darüber hinaus zeigen wir, wie diese Probleme in realen Anwendungen mit Node.js und Express gelöst werden können.
So können Sie Sharding in PostgreSQL mithilfe von Node.js und Express.js implementieren.
Verwendung von Citus oder manuelles logisches Sharding mit Node.js:
Einrichtungstabellen für Shards:
Verwenden Sie Tabellen für Shards (user_data auf Shard1 und user_data auf Shard2).
Erstellen Sie eine Express.js-API:
Verteilen Sie Abfragen basierend auf einem Shard-Schlüssel (z. B. Benutzer-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'));
Sharding ist der Prozess der horizontalen Partitionierung von Daten über mehrere Datenbankinstanzen oder Shards, um Leistung, Skalierbarkeit und Verfügbarkeit zu verbessern. Sharding ist häufig erforderlich, wenn eine einzelne Datenbankinstanz das Daten- oder Datenverkehrsvolumen nicht bewältigen kann.
Wenn jedoch zusammengehörige Tabellen auf verschiedene Schlüssel aufgeteilt werden oder wenn eine Tabelle eine Verknüpfung mit einer anderen Tabelle über mehrere Shards erfordert, kann sich die Leistung aufgrund der Notwendigkeit von Scatter-Gather-Vorgängen verschlechtern. Hier ist das Verständnis von Broadcast-Joins und der Shard-Key-Ausrichtung von entscheidender Bedeutung.
Wenn sich Daten in verschiedenen Shards befinden, kann die Durchführung von Verknüpfungen zwischen diesen Shards komplex sein. Hier ist eine Aufschlüsselung der häufigsten Herausforderungen:
In vielen Systemen werden Tabellen auf verschiedene Schlüssel aufgeteilt. Zum Beispiel:
Beim Durchführen einer Verknüpfung (z. B. Bestellungen.Benutzer_ID = Benutzer.Benutzer_ID) muss das System Daten von mehreren Shards abrufen, da sich die relevanten Datensätze möglicherweise nicht im selben Shard befinden.
Bei einem Scatter-Gather-Join muss das System:
Ein Broadcast-Join tritt auf, wenn eine der zu verbindenden Tabellen klein genug ist, um an alle Shards gesendet zu werden. In diesem Fall:
Verteilte Abfrage-Engines wie Presto und BigQuery sind darauf ausgelegt, fragmentierte Daten zu verarbeiten und Abfragen effizient über verteilte Systeme hinweg zusammenzuführen.
Presto ist eine verteilte SQL-Abfrage-Engine, die für die Abfrage großer Datensätze aus heterogenen Datenquellen (z. B. relationale Datenbanken, NoSQL-Datenbanken, Data Lakes) entwickelt wurde. Presto führt Verknüpfungen über verteilte Datenquellen hinweg durch und kann Abfragen optimieren, indem es die Datenbewegung zwischen Knoten minimiert.
In einem Szenario, in dem Bestellungen nach Region und Benutzer nach Benutzer-ID aufgeteilt werden, kann Presto mithilfe seines verteilten Ausführungsmodells einen Join über verschiedene Shards hinweg durchführen.
Abfrage:
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'));
Presto wird:
BigQuery ist ein vollständig verwaltetes, serverloses Data Warehouse, das sich hervorragend für die Ausführung umfangreicher analytischer Abfragen eignet. Während BigQuery die Details des Shardings abstrahiert, partitioniert und verteilt es Daten automatisch auf viele Knoten, um die Abfrage zu optimieren. Es kann große Datensätze problemlos verarbeiten und ist besonders effektiv für analytische Abfragen, bei denen Daten nach Zeit oder anderen Dimensionen partitioniert sind.
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 übernimmt automatisch die Partitionierung und Verteilung und minimiert so den Bedarf an manuellem Sharding.
Beim Umgang mit Shard-Daten in Node.js-Anwendungen treten häufig Probleme wie falsch ausgerichtete Shard-Schlüssel und die Notwendigkeit von Scatter-Gather-Verbindungen auf. So können Sie diese Herausforderungen mit Node.js und Express angehen.
Wenn ein Join die Übertragung einer kleinen Tabelle (z. B. Benutzer) über alle Shards erfordert, können Sie den Join in der Anwendungsschicht implementieren, indem Sie die kleine Tabelle einmal abrufen und sie zum Joinen mit Daten aus Shard-Tabellen verwenden.
SELECT o.order_id, u.user_name FROM orders o JOIN users u ON o.user_id = u.user_id;
Bei Abfragen, die Scatter-Gather-Joins beinhalten (z. B. wenn Shard-Schlüssel falsch ausgerichtet sind), müssen Sie alle Shards abfragen und die Ergebnisse in Ihrer Anwendungsschicht aggregieren.
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';
Berücksichtigen Sie beim Umgang mit Sharded-Daten und der Durchführung von Verknüpfungen die folgenden Best Practices:
Shard-Schlüssel ausrichten: Stellen Sie nach Möglichkeit sicher, dass verwandte Tabellen denselben Shard-Schlüssel verwenden. Dies minimiert die Notwendigkeit von Cross-Shard-Joins und verbessert die Leistung.
Denormalisierung: In Szenarien, in denen Verknüpfungen häufig vorkommen, sollten Sie eine Denormalisierung Ihrer Daten in Betracht ziehen. Sie können beispielsweise Benutzerinformationen direkt in der Beitragstabelle speichern und so die Notwendigkeit einer Verknüpfung reduzieren.
Verwenden Sie Broadcast-Joins für kleine Tabellen: Wenn eine der Tabellen klein genug ist, übertragen Sie sie an alle Knoten, um Scatter-Gather-Abfragen zu vermeiden.
Daten vorab zusammenführen: Erwägen Sie bei häufig aufgerufenen Daten die Vorabzusammenführung und Speicherung der Ergebnisse in einer materialisierten Ansicht oder einem Cache.
Nutzen Sie verteilte Abfrage-Engines: Verwenden Sie für komplexe analytische Abfragen Systeme wie Presto oder BigQuery, die verteilte Verknüpfungen und Optimierungen automatisch verarbeiten.
In einem verteilten System mit einem solchen Sharding muss die cursorbasierte Paginierung sorgfältig gehandhabt werden, insbesondere weil die Daten über mehrere Shards verteilt sind. Der Schlüssel ist:
Lassen Sie uns durchgehen, wie wir dies mit Node.js und Express implementieren können, wobei wir berücksichtigen, dass sich die Daten auf verschiedenen Shards befinden und Post-Fetch-Joins auf Anwendungsebene erfordern.
Nehmen wir an, wir haben:
Wir möchten paginierte Beiträge für einen bestimmten Benutzer abrufen, aber da sich Benutzer und Beiträge auf unterschiedlichen Shards befinden, müssen wir die Abfrage aufteilen, die Paginierung durchführen und dann die Verknüpfung auf Anwendungsebene durchführen.
Fragen Sie die relevanten Shards ab:
Paginierungsstrategie:
Beitritt auf Anwendungsebene:
Umgang mit dem Cursor:
Hier führen wir Abfragen über verschiedene Beitrags-Shards aus und filtern nach einem Cursor (z. B. erstellt_at oder Beitrags-ID).
Sobald wir die relevante post_id und user_id aus der ersten Abfrage haben, werden wir Benutzerdaten von den relevanten Shards abrufen.
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'));
Die Verwaltung fragmentierter Daten in verteilten Systemen stellt einzigartige Herausforderungen dar, insbesondere wenn es um die Durchführung effizienter Verknüpfungen geht. Das Verständnis von Techniken wie Broadcast-Joins, Scatter-Gather-Joins und die Nutzung von verteilten Abfrage-Engines kann die Abfrageleistung erheblich verbessern. Darüber hinaus ist es bei Abfragen auf Anwendungsebene wichtig, Shard-Key-Ausrichtung, Denormalisierung und optimierte Abfragestrategien zu berücksichtigen. Durch die Befolgung dieser Best Practices und den Einsatz der richtigen Tools können Entwickler sicherstellen, dass ihre Anwendungen Sharded-Daten effektiv verarbeiten und die Leistung im großen Maßstab beibehalten.
Das obige ist der detaillierte Inhalt vonUmgang mit Sharded-Daten in verteilten Systemen: Ein tiefer Einblick in Joins, Broadcasts und Abfrageoptimierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!