Dalam pangkalan data teragih moden, keperluan untuk menskalakan data secara mendatar telah membawa kepada penggunaan meluas sharding. Walaupun sharding membantu mengurus set data yang besar merentas berbilang nod, ia memperkenalkan cabaran, terutamanya apabila melakukan menyertai dan memastikan pengambilan data yang cekap. Dalam artikel ini, kami meneroka pelbagai konsep dan teknik yang menangani cabaran ini, terutamanya memfokuskan pada sambungan siaran, penjajaran kunci pecahan dan enjin pertanyaan teragih seperti Presto dan BigQuery. Selain itu, kami menunjukkan cara menangani masalah ini dalam aplikasi dunia nyata menggunakan Node.js dan Express.
Begini cara anda boleh melaksanakan sharding dalam PostgreSQL menggunakan Node.js dan Express.js.
Menggunakan Citus atau serpihan logik manual dengan Node.js:
Sediakan Jadual untuk Serpihan:
Gunakan jadual untuk serpihan (data_pengguna pada shard1 dan data_pengguna pada shard2).
Buat Express.js API:
Edarkan pertanyaan berdasarkan kunci serpihan (cth., 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'));
Sharding ialah proses membahagikan data secara mendatar merentas berbilang contoh pangkalan data, atau serpihan, untuk meningkatkan prestasi, kebolehskalaan dan ketersediaan. Perkongsian selalunya diperlukan apabila satu tika pangkalan data tidak dapat mengendalikan jumlah data atau trafik.
Walau bagaimanapun, apabila jadual berkaitan dipecahkan pada kekunci yang berbeza, atau apabila jadual memerlukan penyambungan dengan jadual lain merentas berbilang serpihan, prestasi boleh merosot kerana keperluan untuk operasi pengumpul-serakan. Di sinilah pemahaman siaran bergabung dan penjajaran kunci serpihan menjadi penting.
Apabila data berada dalam serpihan yang berbeza, melakukan cantuman antara serpihan tersebut boleh menjadi rumit. Berikut ialah pecahan cabaran biasa:
Dalam banyak sistem, jadual dipecahkan pada kekunci yang berbeza. Contohnya:
Apabila melakukan gabungan (cth., orders.user_id = users.user_id), sistem perlu mengambil data daripada berbilang serpihan kerana rekod yang berkaitan mungkin tidak berada dalam serpihan yang sama.
Dalam gabungan scatter-gather, sistem mesti:
Satu sambungan siaran berlaku apabila salah satu jadual yang dicantumkan cukup kecil untuk disiarkan ke semua serpihan. Dalam kes ini:
Enjin pertanyaan teragih seperti Presto dan BigQuery direka bentuk untuk mengendalikan data berpecah dan menyertai pertanyaan dengan cekap merentas sistem teragih.
Presto ialah enjin pertanyaan SQL teragih yang direka untuk menanyakan set data yang besar merentas sumber data heterogen (cth., pangkalan data hubungan, pangkalan data NoSQL, tasik data). Presto melakukan gabungan merentas sumber data yang diedarkan dan boleh mengoptimumkan pertanyaan dengan meminimumkan pergerakan data antara nod.
Dalam senario di mana pesanan dipecahkan mengikut wilayah dan pengguna dipecahkan oleh user_id, Presto boleh melakukan gabungan merentas serpihan yang berbeza menggunakan model pelaksanaan yang diedarkan.
Pertanyaan:
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 akan:
BigQuery ialah gudang data tanpa pelayan yang diurus sepenuhnya, yang cemerlang dalam menjalankan pertanyaan analisis berskala besar. Walaupun BigQuery mengasingkan butiran sharding, ia secara automatik membahagikan dan mengedarkan data merentas banyak nod untuk pertanyaan yang dioptimumkan. Ia boleh mengendalikan set data yang besar dengan mudah dan amat berkesan untuk pertanyaan analitikal yang mana data dibahagikan mengikut masa atau dimensi lain.
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 secara automatik mengendalikan pembahagian dan pengedaran, meminimumkan keperluan untuk pembahagian manual.
Apabila berurusan dengan data yang dipecahkan dalam aplikasi Node.js, isu seperti kekunci serpihan yang tidak sejajar dan keperluan untuk penyatuan-pengumpulan sering timbul. Begini cara anda boleh mengharungi cabaran ini menggunakan Node.js dan Express.
Jika gabungan memerlukan penyiaran jadual kecil (mis., pengguna) merentas semua serpihan, anda boleh melaksanakan gabungan dalam lapisan aplikasi dengan mengambil jadual kecil sekali dan menggunakannya untuk bergabung dengan data daripada jadual berpecah.
SELECT o.order_id, u.user_name FROM orders o JOIN users u ON o.user_id = u.user_id;
Untuk pertanyaan yang melibatkan cantuman scatter-gather (cth., apabila kekunci shard tidak sejajar), anda perlu menanyakan semua serpihan dan mengagregatkan hasil dalam lapisan aplikasi anda.
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';
Apabila berurusan dengan data yang dipecahkan dan melakukan gabungan, pertimbangkan amalan terbaik berikut:
Jajarkan Kekunci Shard: Apabila boleh, pastikan jadual berkaitan menggunakan kekunci shard yang sama. Ini meminimumkan keperluan untuk cantuman rentas serpihan dan meningkatkan prestasi.
Nyahnormalisasi: Dalam senario yang kerap disertai, pertimbangkan untuk menyahnormalkan data anda. Sebagai contoh, anda boleh menyimpan maklumat pengguna terus dalam jadual siaran, mengurangkan keperluan untuk menyertai.
Gunakan Broadcast Joins untuk Meja Kecil: Jika salah satu jadual cukup kecil, siarkan ke semua nod untuk mengelakkan pertanyaan serakan-kumpul.
Data Pra-Sertai: Untuk data yang kerap diakses, pertimbangkan untuk pra-menyertai dan menyimpan hasil dalam paparan nyata atau cache.
Memanfaatkan Enjin Pertanyaan Teragih: Untuk pertanyaan analitikal yang kompleks, gunakan sistem seperti Presto atau BigQuery yang mengendalikan gabungan dan pengoptimuman yang diedarkan secara automatik.
Dalam sistem teragih dengan sharding sedemikian, penomboran berasaskan kursor perlu dikendalikan dengan berhati-hati, terutamanya kerana data tersebar merentasi berbilang serpihan. Kuncinya ialah:
Mari kita lihat bagaimana kita boleh melaksanakan perkara ini dengan Node.js dan Express, dengan mengambil kira bahawa data berada pada serpihan yang berbeza dan memerlukan gabungan pasca ambil pada peringkat aplikasi.
Anggap kita ada:
Kami ingin mendapatkan semula siaran penomboran untuk pengguna tertentu, tetapi memandangkan pengguna dan siaran berada pada serpihan yang berbeza, kami perlu membahagikan pertanyaan, mengendalikan penomboran dan kemudian melakukan gabungan pada peringkat aplikasi.
Pertanyakan Serpihan Berkaitan:
Strategi Penomboran:
Sertai Peringkat Aplikasi:
Mengendalikan Kursor:
Di sini kami akan melaksanakan pertanyaan merentas serpihan siaran yang berbeza, menapis menggunakan kursor (cth., created_at atau post_id).
Sebaik sahaja kami mempunyai post_id dan user_id yang berkaitan daripada pertanyaan pertama, kami akan mengambil data pengguna daripada serpihan yang berkaitan.
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'));
Menguruskan data berpecah dalam sistem yang diedarkan memberikan cabaran yang unik, terutamanya dalam hal melaksanakan gabungan yang cekap. Memahami teknik seperti sambungan siaran, gabungan scatter-gather dan memanfaatkan enjin pertanyaan teragih boleh meningkatkan prestasi pertanyaan dengan ketara. Selain itu, dalam pertanyaan peringkat aplikasi, adalah penting untuk mempertimbangkan penjajaran kunci beling, nyahnormalisasi dan strategi pertanyaan yang dioptimumkan. Dengan mengikuti amalan terbaik ini dan menggunakan alatan yang betul, pembangun boleh memastikan bahawa aplikasi mereka mengendalikan data berpecah dengan berkesan dan mengekalkan prestasi pada skala.
Atas ialah kandungan terperinci Mengendalikan Data Berkongsi dalam Sistem Teragih: Penyelaman Mendalam ke dalam Gabungan, Penyiaran dan Pengoptimuman Pertanyaan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!