Sharding vs. Partitioning
Veri tabanları bölümleme ve parçalama özellikleri sayesinde performans artışı sağlar. Aşağıda daha ayrıntılı olarak bunların farklarını elimden geldiğince aldığım notları paylaşarak anlatıyor olacağım.
Partitioning
Çok büyük veri kümelerinde, sık sık belirli bir filtreleme yapıyor (örneğin, tarihe göre sorgulamalar) ve performans sorunları yaşamaya başladıysanız BÖLÜMLEME (partitioning) tam size göre.
Partitioning, bir veritabanındaki veya sistemdeki veriyi, daha kolay yönetmek ve daha hızlı erişmek için mantıki olarak bölmelere ayırma işlemidir. Fiziki olarak tüm veri aynı yerde saklanır, ancak mantıki bölümler sayesinde veriye daha hızlı erişim sağlanır.
Örneğin, büyük bir depo düşünün:
- Tek bir devasa depo var ve içinde milyonlarca ürün yer alıyor.
- Bu ürünlerin hepsi karışık bir şekilde yığılmış durumda. Eğer belirli bir ürünü bulmak isterseniz, tüm depoyu aramanız gerekebilir.
- Ancak depoyu bölümlere ayırırsanız işler kolaylaşır. Örneğin, her bölümü belirli bir kategoriye (örneğin, elektronik, gıda, kırtasiye) ayırabilirsiniz.
Partitioning işte bunu yapar: Veriyi bir mantığa göre bölerek, o veriye daha hızlı erişmenizi ve veriyi daha düzenli bir şekilde yönetmenizi sağlar.
Bölümlemek için aralık, liste, hash ve bunların kompozisyonlarını yöntem olarak kullanır.
Örneğin;
- Aralık (RANGE) temel alınarak bir bölümlemeyle 2020 ve öncesi siparişleri ayırabilirsiniz.
- Aralık temelinde öğrenci kayıtlarını 1000'erli ID değerlerine göre bölerek tablolar oluşturabilirsiniz.
- Farklı ülkelerdeki şubelerinize sevkiyatları tuttuğunuz tabloyu ülke kodlarına göre (TR,DE,BR…) LİSTE (LIST) temelinde bölümleyebilirsiniz.
- Nginx’in IP adreslerine göre hash oluşturup istekleri yönlendirmesi gibi, kayıtlar için üretilen HASH fonksiyonu çıktısına göre (Cassandra’nın özellikle kullandığı) bölümleyebilirsiniz.
- Önce veriyi ülke koduna göre (list partitioning) sonra o ülke için yıllara göre (range partitioning) ile composite partitioning yöntemini kullanabilirsiniz.
Yatay Bölümleme (Horizontal Partitioning):
Bir tablodaki satırları farklı tablolara veya veritabanlarına ayırma işlemidir.
- Örnek: Bir “Siparişler” tablosunu sipariş tarihlerine göre farklı tablolara ayırmak.
- Amaç: Veri büyüklüğünü yönetmek, sistemin ölçeklenebilirliğini artırmak ve performansı iyileştirmek. Sharding, yatay bölümlemenin bir türüdür.
Bir kütüphanede yüz binlerce kitap olduğunu düşünelim:
- Eğer kitapları kategoriye (örneğin, roman, tarih, bilim) göre ayırırsanız, bu list partitioning olur.
- Eğer kitapları yayın yılına (örneğin, 1900–1950, 1951–2000) göre ayırırsanız, bu range partitioning olur.
- Eğer kitapları ISBN numarasına göre rastgele bölgelere dağıtırsanız, bu hash partitioning olur.
Bir kitabı ararken, doğru bölüme gidip çok daha hızlı bulabilirsiniz. İşte partitioning bu mantığı sağlar.
Yatay Bölümleme Vs. Sharding
Şimdi yatay bölümleme yapıp farklı sunucularda saklama ile — parçalama (sharding) neden aynı kabul ediliyor ve aralarındaki ince farkı anlayalım.
Bir MySQL tablosunu tek bir sunucuda yatay partitioning ile bölersiniz:
- Tablo, fiziki olarak
user_id
aralığına göre iki bölüme ayrılır ama hâlâ tek bir MySQL sunucusunda çalışır.
Aynı tabloyu sharding ile bölerseniz:
- Shard 1:
user_id < 5000
→ Sunucu A’da bir MySQL örneğinde. - Shard 2:
user_id >= 5000
→ Sunucu B’de bir MySQL örneğinde.
Sharding, verinin tamamen farklı sunuculara dağıtılmasını içerir. Bu yüzden sharding, dağıtık yatay bölümlemeye (distributed horizontal partitioning) olarak düşünülebilir.
Dikey Bölümleme (Vertical Partitioning):
Bir tablodaki sütunları farklı tablolara ayırma işlemidir.
- Örnek: Bir “Kullanıcı” tablosunu “KullanıcıBilgileri” ve “KullanıcıAdresleri” gibi iki tabloya ayırmak.
- Amaç: İlgili olmayan verileri farklı tablolara taşıyarak sorguları hızlandırmak ve disk kullanımını optimize etmek.
Bölümleme/Parçalama Stratejileri
Anahtar alanınız üzerinden sorgulama yapacağız. Bu yüzden anahtar alanınız sorgularınızda en çok kullandığınız sütun olmalı. Shard’lar üzerinde eşit yük dağıtmaya çalışmalıyız.
Örneğin kent isimleriyle bir shard yapısı oluşturup, siparişlerinizin %90'ı büyük kentlerden olursa 81 ilin yükü eşit dağılmayacaktır. Aynı şekilde musteri_id
değeriyle shard yaparsanız en çok sipariş veren müşterileriniz ‘hotspot’ oluşturacak, bulunduğu shard üzerinde kapasite sorunu ortaya çıkacaktır. Oysa siparis_id
alanıyla shard oluşturursanız, otomatik artan bir değer olduğu için yük eşit dağılacaktır. Belirli bir (specific demedim!) siparişi tek bir shard üzerinden bir kerede çekebileceksiniz.
List Stratejisi:
TC Kimlik numaralarına göre bir shard seçimi yapmak istersek öncelikle, TC kimlik numarasının ilk rakamına göre bir LIST belirleriz.
Örneğin:
- İlk rakam
1
,2
, veya3
olanlar Shard Grubu 1'e gider. - İlk rakam
4
,5
, veya6
olanlar Shard Grubu 2'ye gider. - İlk rakam
7
,8
, veya9
olanlar Shard Grubu 3'e gider.
Range Stratejisi:
Her shard grubunun içinde, TC kimlik numarasının geri kalan kısmına (örneğin son 4 basamağa) göre bir RANGE tanımlayarak shard’ları belirleriz:
0000–3333
: Shard A3334–6666
: Shard B6667–9999
: Shard C
Hash Stratejisi:
- Tablonuzun ID alanı üzerinden hash fonksiyonunu işleterek hangi shard üstünde verinizi saklamak istediğinizi bulabilirsiniz.
- Eğer eposta alanı sizin için anahtar sütun olacaksa, eposta adresinin ilk karakterinin ASCII değerinin shard sayınızla modunu alabilirsiniz.
- Kompozit bir anahtar için isim, soyisim ve TC kimlik numarasını kullanıyor olun (malum kimlik numarasının eşsiz olmadığı durumlar vardı). Aşağıdaki nodejs kodu
sha256
ile hash fonksiyonundan geçiripInt
türüne dönüştürüp ve 4'ün modunu alarak hangi sunucuya gitmeniz gerektiğini verecektir:
const crypto = require('crypto');
// Shard sayısı
const SHARD_COUNT = 4;
// Hash fonksiyonuyla shard ID bulma
function getShardId(isim, soyisim, kimlikNumarasi) {
const compositeKey = `${isim}|${soyisim}|${kimlikNumarasi}`;
const hash = crypto.createHash('sha256').update(compositeKey).digest('hex');
// Hash değerini sayıya dönüştür ve shard sayısına göre mod al
const shardId = parseInt(hash.slice(0, 8), 16) % SHARD_COUNT;
return shardId;
}
// Örnek kullanım
const shardId = getShardId('Ali', 'Yılmaz', '12345678901');
console.log(`Bu kayıt shard ${shardId} üzerinde saklanacak.`);
const { Pool } = require('pg');
const shards = [
new Pool({ host: 'shard1', user: 'user', password: 'password', database: 'shard1' }),
new Pool({ host: 'shard2', user: 'user', password: 'password', database: 'shard2' }),
new Pool({ host: 'shard3', user: 'user', password: 'password', database: 'shard3' }),
new Pool({ host: 'shard4', user: 'user', password: 'password', database: 'shard4' }),
];
async function insertUser(isim, soyisim, kimlikNumarasi) {
const shardId = getShardId(isim, soyisim, kimlikNumarasi);
const shard = shards[shardId];
await shard.query(
'INSERT INTO users (isim, soyisim, kimlik_numarasi) VALUES ($1, $2, $3)',
[isim, soyisim, kimlikNumarasi]
);
console.log(`Kayıt shard ${shardId} üzerine eklendi.`);
}
// Örnek:
insertUser('Ali', 'Yılmaz', '12345678901');
Peki sorgunuzu eğer anahtar alan üzerinden yapmadığınızda tüm shard’larda sorgunuzu çalıştırmayacak mısınız? Evet !
const searchUserByName = async (name) => {
const results = await Promise.all(
shards.map(shard =>
shard.query('SELECT * FROM users WHERE name = $1', [name])
)
);
// Sonuçları birleştir
const combinedResults = results.flatMap(result => result.rows);
return combinedResults;
};
// Kullanım:
const users = await searchUserByName('User789');
console.log(users);
Performans Ayarları
Query Router ve Merkezi Endeksleme Sunucusu
MySQL için ProxySQL, MariaDB için MaxScale. PostgreSQL için pgBouncer, Citus. Redis, Couchbase, Cassandra, Elasticsearch ve MongoDB kendi içi mekanizmasıyla sorguları yönlendirir.
Mimarisine uygun sharding örneğini kod depomdan çekerek deneyebilir, shard sayılarını arttırarak farklı işleyişe daha fazla hakim olabilirsiniz.
Şimdi MongoDB ile verileri parçalayalım. MongoDB doğal olarak sharding (bölümlendirme) mekanizmasını destekler. Sharding, verileri farklı fiziksel sunuculara dağıtarak büyük veri kümelerinin yönetimini kolaylaştırır.
MongoDB Sharding için Gereksinimler
MongoDB sharding ortamı için:
- Config Server Cluster: Sharding yapılandırma bilgilerini tutar.
- Shard Server Cluster: Verileri depolayan MongoDB sunucuları.
- Mongos Router: Uygulama ve shardlar arasında aracı görevi görür.
Mimari
+-------------------+
| İstemci(ler) | <-- Kullanıcı ya da uygulama
+-------------------+
|
v
+-------------------+
| Query Router | <-- İstemcilerin bağlandığı nokta
| (mongos) | Sorgu alır ve yönetir
+-------------------+
|
v
-------------------------------------------------------
| |
v |
+-------------------+ |
| Config Server | <-- Shard yerleşim bilgilerini tutar |
| (Meta Veri Deposu)| ve Query Router’a rehberlik eder |
+-------------------+ |
| v
| +-------------------------------------------------------------------------------------+
--> | Shard Server(lar) (Parçalanmış Veri) |
| +-----------+ +-----------+ +-----------+ |
| | Shard 1 | | Shard 2 | ... | Shard N | <-- Veriler parçalanmış (sharded) |
| | (shard1) | | (shard2) | | (shardN) | halde burada depolanır |
| +-----------+ +-----------+ +-----------+ |
| |
+-------------------------------------------------------------------------------------+
MongoDB Varsayılan Portları
Default Port: 27017
Description: The default port for mongod
and mongos instances. You can change this port with port or --port
.
Default Port: 27018
Description: The default port for mongod
when running with --shardsvr
command-line option or the shardsvr
value for the clusterRole setting in a configuration file.
Default Port: 27019
Description: The default port for mongod
when running with --configsvr
command-line option or the configsvr
value for the clusterRole setting in a configuration file.
Default Port: 27020
Description: The default port from which mongocryptd
listens for messages. mongocryptd
is installed with MongoDB Enterprise Server and supports automatic encryption operations.
Roller ve İlişkiler
Query Router (mongos):
- İstemciler bu bileşene bağlanır.
- Sorguları alır ve analiz eder.
- Config Server’dan aldığı bilgilerle hangi shard’lara sorguyu yönlendireceğine karar verir.
- Sonuçları istemciye döner.
Config Server:
- Shard’ların hangi veriyi tuttuğuna dair meta veriyi saklar (örneğin, bir doküman hangi shard üzerinde?).
- Query Router, veriyi bulmak için Config Server’a danışır.
- Bir replikasyon kümesi olarak yapılandırılır, çünkü bu bilgiler kritik öneme sahiptir.
Shard Server’lar:
- Verinin parçalarını (shard) saklar.
- Sorgular doğrudan shard’lara yönlendirilir ve veri burada depolanır.
Örnek Senaryo:
- İstemci,
db.users.find({age:25})
sorgusunu gönderir. - Query Router sorguyu alır ve Config Server’a danışır:
- “Hangi shard’lar age: 25 aralığını tutuyor?”
- Config Server, shard’ların konumunu bildirir.
- Query Router, doğru shard’lara bağlanarak sorguyu çalıştırır ve sonuçları birleştirir.
- Sonuç, istemciye geri döner.
Sorgu Rotalayıcı (Query Router)
MongoDB sharding yapısında, istemciler yalnızca Query Router (mongos) üzerinden veritabanına bağlanır. Ancak iç yapıda diğer bileşenlerin (config server, shard server) hem birbirleriyle hem de Query Router ile iletişim kurması gerekir.
1. İstemciler Sadece Query Router’a mı Bağlanır?
Evet. Sharding yapısında istemciler yalnızca Query Router (mongos) süreçlerine bağlanır.
Query Router:
- Gelen sorguları analiz eder.
- Config server’dan shard yerleşim bilgilerini alır.
- Veriyi ilgili shard’lara yönlendirir.
İstemcilerin shard server veya config server ile doğrudan bir bağlantısı olmaz.
2. Config Server’a Bir İstemciyle Bağlanılabilir mi?
Bir MongoDB sunucusunu Config Server
olarak ayaklandıralım:
docker run -it --rm \
-p 27017:27017 \
mongo:latest \
mongod --configsvr --replSet configReplSet --bind_ip_all --port 27017
Bu komut, MongoDB’yi bir config server olarak başlatır. Config server
'lar, MongoDB sharded cluster'larında metadata'yı yönetir ve normal client bağlantıları için kullanılmaz. Bu nedenle, bu şekilde başlatılan bir MongoDB config server'ına normal bir client ile bağlanamazsınız.
- Dahili Ağ İletişimi:
- Config server, shard server’lara ve Query Router’a (mongos) bağlanmalıdır.
- Shard server’lar da, gerektiğinde diğer shard’larla ve Query Router’la iletişim kurar.
3. Port Ayarlarına Örnek:
Eğer her şeyi aynı sunucuda çalıştırıyorsan, yapı şu şekilde olabilir:
Süreç Açıklama Port
mongos Query Router 27017
configsvr Config Server Replika Set 27019
shard1 Shard 1 27018
shard2 Shard 2 27020
4. Dahili İletişimde Örnek Senaryo:
- Bir sorgu istemciden gelir:
- İstemci, 27017 portunda çalışan Query Router’a bağlanır.
- Query Router ne yapar?
- Sorguyu işler ve gerekli bilgileri almak için Config Server’a bağlanır (27019).
- Config Server’dan hangi shard’ların kullanılacağını öğrenir.
- Query Router shard’lara bağlanır:
- Veriyi sorgulamak veya yazmak için ilgili shard server’lara (27018, 27020) bağlanır.