Veritabanı Temelleri
(tontinton.com)- Basit bir Bash anahtar-değer deposundan başlayarak, gerçek veritabanlarının dayanıklılık, atomiklik, izolasyon ve performansı neden ayrı tasarım sorunları olarak ele alması gerektiğini adım adım gösterir
fsync/fdatasync,flockve WAL; arıza ve eşzamanlılık durumlarında veriyi koruyan temel araçlardır, ancak garanti düzeyi yükseldikçe performans maliyeti artar- Depolama motorları, disk I/O ve arama maliyetini azaltmak için B-tree ve LSM tree gibi yapılar kullanır; her birinin ardından vacuum ve compaction gibi bakım işleri gelir
- Dağıtık veritabanları, erişilebilirlik ve yatay ölçekleme kazanırken CAP teoremi, ağ bölünmesi, tutarlılık ayarı ve çakışma çözümü gibi dağıtık sistem karmaşıklıklarını üstlenir
- Bir veritabanı seçerken veya uygularken ACID garantileri, izolasyon düzeyi, depolama yapısı, replikasyon yöntemi ve tutarlılık gereksinimleri iş yüküne göre ayarlanmalıdır
bashdb ile ortaya çıkan veritabanlarının temel sorunu
bashdb, iki Bash fonksiyonuyla yapılmış basit bir anahtar-değer deposudurdb_set, dosyayakey,valuebiçiminde append ederdb_get, son değeri okumak içingrep,sedvetailkombinasyonunu kullanır
- Öğrenme amaçlı olarak basittir; ancak yalnızca bu uygulama bile üretim veritabanlarının çözmesi gereken sorunları ortaya çıkarır
- Durability:
db_setbaşarılı olduktan sonra makine çökerse, diske flush edilmemiş veri kaybolabilir - Atomicity: Yazma sırasında çökme olursa veri yalnızca kısmen kaydedilip bozulabilir
- Isolation: Okuma ve yazma aynı anda aynı öğeye erişirse, okuma tarafı verinin yalnızca bir kısmını görebilir
- Performance:
db_get, tüm dosyayı satır satır aradığı içinO(n)’dir
- Durability:
ACID ve bashdb iyileştirme denemeleri
- ACID, birçok veritabanının garanti etmeye çalıştığı özellikleri bir araya getiren bir kısaltmadır
- Atomicity: Yazma sırasında arıza olursa, kısmi yazma durumu bırakmadan işlemin tamamını iptal eder veya geri alır
- Consistency: Geçersiz işlemler veritabanını bozmamalıdır
- Isolation: Aynı veriye eşzamanlı erişimde race condition olmamalıdır
- Durability: Başarılı yazmalar, güç kesintisi gibi durumlardan sonra da korunmalıdır
- Tüm veritabanı işlemlerinin mutlaka ACID garantisi sağlaması gerekmez; bazı kullanım durumlarında performans için garantiler düşürülebilir
-
Dayanıklılık ve
fsyncwritesistem çağrısı tamponu dosyaya yazar, ancak bu hemen kalıcı depolama aygıtına kaydedildiği anlamına gelmez- Çekirdek, tamponu page cache içinde dirty page olarak saklayıp daha sonra diske flush edebilir
- Disk aygıtı veya RAID sistemi de ayrı bir write cache’e sahip olabilir
fsyncvefdatasync, dirty page’leri kalıcı depolamaya flush etmek için kullanılan sistem çağrılarıdırfdatasync,writeile geçirilen raw buffer’ı flush ederfsync, verinin yanı sıramtimegibi dosya metadatasını da flush ederdb_setsonunasync -d databaseeklemek,fdatasync’e yakın bir davranışla dayanıklılığı artırabilir; ancak sync genellikle write’ın kendisinden daha yavaş olduğu için performansı düşürürfsync()başarısı, “son fsync’ten bu yana yapılan tüm write’lar diske ulaştı” anlamına gelir; yalnızca “son başarılı fsync’ten sonraki write’lar” anlamına gelmez- PostgreSQL 2018’de bu sorunu yaşadı ve fsync hatasında yeniden denemek yerine panic edecek şekilde davranışını değiştirdi
- Bu olay fsyncgate olarak bilinir ve ilgili kaynak olarak fsync failures makalesi bağlantısı verilir
- MongoDB’de yazmalar varsayılan olarak her 100 ms’de bir sync edildiğinden %100 dayanıklı değildir
-
İzolasyon ve
flockbashdb’de en basit çok süreçli izolasyon yöntemi, depolama dosyasını okumadan veya yazmadan önce lock almaktır- Linux’taki
flockdosyayı kilitler;-sbayrağı kullanılırsa shared lock ile birden fazla reader aynı anda okuyabilir - İyileştirilmiş
bashdb, yazma için exclusive lock, okuma için shared lock kullanır - Dezavantajı, her yazmada tüm veritabanını kilitlemesidir
- Yalnızca Bash ile atomikliği basitçe garanti etmek zordur;
mv -Tveyarenamekullanma olasılığı vardır, ancak tamamlanmamıştır bashdb,O(n)sorgulama sorununu hâlâ çözemez
Depolama motorunun rolü ve darboğazlar
- Depolama motoru, kalıcı depolamada veri okuma ve yazma için bir soyutlama sağlar; başlıca hedefleri yüksek throughput ve düşük gecikmedir
- En büyük kısıt, diskin kendi hız farklarından kaynaklanır
- Örnek gecikme tablosunda L1 cache referansı yaklaşık
0.5ns, SSD’den 4KB random read150,000ns, disk seek ise10,000,000nsolarak verilmiştir - L1 cache referansını yaklaşık 0,5 saniyelik bir kalp atışı olarak düşünürsek, SSD’den 1MB sıralı okuma yaklaşık 12 güne, diskten 1MB sıralı okuma ise yaklaşık 8 aya karşılık gelir
- Örnek gecikme tablosunda L1 cache referansı yaklaşık
- Bu yüzden depolama motoru tasarımı, disk I/O ve disk seek’i olabildiğince azaltma yönünde gelişmiştir
- Yaygın depolama motoru tasarım öğeleri şunlardır
- Öğeleri diskte saklayan temel veri yapısı
- ACID işlemleri
- Disk okumalarını azaltmak için cache
- SQL, document, graph gibi API katmanları
- Depolama motoru veri yapıları genel olarak değiştirilebilir yapılar ve değişmez yapılar olarak ikiye ayrılabilir
- Değiştirilebilir yapılar, dosyaya yazılmış verinin daha sonra üzerine yazılmasına izin verir
- Değişmez yapılar, dosyaya yazılmış veriyi yalnızca tekrar okur
Değiştirilebilir B-tree
- Veri artsa bile iyi performansı korumak için,
bashdbgibi doğrusal arama yapmak yerine öğeleri en fazla logaritmik zamanda bulabilmek gerekir - BST ile
O(log n)sorgulama mümkündür; ancak düğümler disk üzerinde birbirinden uzaksa arama sırasında çok sayıda disk seek oluşabilir - B-tree, bir düğümün ikiden fazla çocuğa sahip olabildiği BST genellemesidir ve spatial locality’den yararlanır
- Diskten genellikle 4KB veya 8KB page okunduktan sonra, içindeki birden fazla düğüm bellek ve CPU cache’inde sıralı olarak karşılaştırılır
- Bellek ve CPU cache erişimi diskten katbekat hızlı olduğu için, diskten okunan baytları olabildiğince çok kullanmak önemlidir
- Sıralı bellek erişimi; SIMD, instruction pipelining ve prefetching sayesinde çok güçlü olabilir
- B+ tree yalnızca leaf node’larda değer saklar, diğer düğümlerde ise yalnızca key saklayarak tek bir disk page’inde daha fazla key’in karşılaştırılmasını sağlar
-
Alan geri kazanımı ve vacuum
- B-tree, alan optimizasyonu için veri parçalanmasıyla oluşan boş alanları geri kazanmalıdır
- Büyük bir değerle güncelleme yapıldığında bir sonraki düğümün verisinin üzerine yazılabileceği için öğe başka bir konuma taşınır ve özgün page’de bir boşluk oluşur
- Küçük bir değerle güncelleme yapıldığında sonda bir boşluk kalır
- Silme işlemi, silinen değerin bulunduğu yerde bir boşluk oluşturur
- Bu tür alan geri kazanımı ve page yeniden yazma süreci vacuum, compaction, page defragmentation, maintenance vb. adlarla anılabilir
- Genellikle kullanıcı isteklerinde gecikme spike’ını önlemek için arka planda yürütülür
- PostgreSQL’de auto vacuum daemon yapılandırılabilir
- B-tree, PostgreSQL’in varsayılan index’i gibi index’lerin temel veri yapısı olarak yaygın şekilde kullanılır; DynamoDB’nin şaka yollu “distributed B-tree” diye anıldığı bir örnek de vardır
Değişmez LSM tree
- LSM tree, disk seek işlemlerinin pahalı olması fikrinden yola çıkan append-only bir veri yapısıdır
- Veriler yalnızca dosyanın sonuna eklendiğinde disk kafasının bir sonraki yazma konumuna büyük ölçüde hareket etmesi gerekmez; bu da yazma ağırlıklı iş yükleri için avantaj sağlar
Log Structured Merge tree, kısaca LSM tree, RocksDB, Cassandra, ScyllaDB gibi modern veritabanı depolama motorlarında kullanılır- Temel çalışma şekli şöyledir
- Yazmalar, bellekte sıralanabilir bir veri yapısında tamponlanır
- Örnekler
AVL tree,Red Black tree,Skip Listtir - Belirli bir kapasiteye ulaşıldığında sıralı bir dosya olan
Sorted String Table, yani SSTable olarak flush edilir
- SSTable, sıralanmış verileri sakladığı için binary search ve sparse index ile disk I/O’sunu azaltabilir
- Dayanıklılık için belleğe yazılan işlemler Write-Ahead Log, yani WAL’a kaydedilir
- Program başlarken WAL okunarak kapanma veya crash öncesindeki duruma geri yüklenir
- Silme işlemleri de normal yazma gibi append edilir; değer yerine tombstone saklanır
- tombstone, compaction sürecinde kaldırılır
-
LSM tree’de okuma ve compaction
- LSM tree’de okuma önce bellekteki veri yapısını arar; bulunamazsa diskteki SSTable’ları en yeni dosyadan en eski dosyaya doğru tarar
- Yazmalar arttıkça kontrol edilmesi gereken SSTable sayısı artar
- Her dosya sıralı olsa bile çok sayıda küçük dosyayı taramak, tek bir büyük dosyaya bakmaktan daha yavaş olabilir
- Karşılaştırma ifadesi
log(num_files * table_size) < num_files * log(table_size)şeklindedir - compaction, birden fazla küçük SSTable’ı tek bir büyük SSTable’da birleştiren ve tombstone’ları kaldıran arka plan işlemidir
- RocksDB, Leveled Compaction uygular
- Yeni flush edilmiş SSTable level 0’da yer alır
- Bir level’da ayarlanan sayıda dosya biriktiğinde, compaction sonrasında yeni dosya bir sonraki level’a yükseltilir
- tombstone kaldırma dikkatli yapılmalıdır
- Silinen bir öğenin daha eski dosyalarla yapılan compaction sırasında yeniden ortaya çıkması şeklinde data resurrection sorunu oluşabilir
- RocksDB, son level’a yükselten compaction’a kadar tombstone’u korur
- Rust ile yazılmış gerçek bir örnek için dbeel’in LSM tree kodu bağlantı verilmiştir
-
Bloom filter
- Bloom filter, bir öğenin bir kümede bulunmadığını verimli biçimde doğrulamaya yarayan olasılıksal bir küme veri yapısıdır
- Sorgu sonucu iki türlüdür
false: öğe kesinlikle kümede yokturtrue: öğe kümede olabilir
- Bloom filter, birden fazla hash function sonucunu bitmap’teki bit konumlarına eşleyip 1 olarak ayarlar
- Alan karmaşıklığı, sıradan set’lerdeki
O(n)den farklı olarakO(log n)şeklinde sunulur - Daha fazla bitmap belleği ayırarak ve hash function sayısını artırarak “yok olduğundan emin olma olasılığı” ayarlanabilir; bunun için bir hesaplayıcı da vardır
- LSM tree, her SSTable için Bloom filter saklayarak belirli bir key’in bulunmadığı doğrulanmış SSTable aramalarını atlayabilir
WAL ve transaction garantileri
- WAL, ani crash’lerden sağ çıkmak için tüm transaction işlemlerini özel bir dosyaya kaydetme yöntemidir
- Veritabanı süreci başladığında WAL dosyasını okur ve veri durumunu yeniden oluşturur
- commit log’u olmayan transaction’lar atlandığı için atomiklik elde edilir
- Kullanıcıya yanıt vermeden önce yazma isteğinin verisi WAL’a kaydedilip flush edilirse, başlangıçta mutlaka okunabildiği için dayanıklılık elde edilir
- WAL, transaction event’leri için event sourcing’in bir biçimi olarak görülebilir
İzolasyon seviyeleri ve eşzamanlılık kontrolü
- İzolasyonu sağlamanın yolları genel olarak üçe ayrılır
- Kötümser lock: Şu anda yazılmakta olan verilere erişimi engeller
- İyimser lock: Verinin bir kopyasını değiştirir; transaction sırasında orijinal değişmemişse commit eder, aksi halde retry yapar
- MVCC: Verinin üzerine yazmak yerine yeni bir version oluşturur; her kullanıcının belirli bir zamandaki snapshot’ı görmesini sağlar
- Tüm uygulamaların tam izolasyona, yani serializable isolation’a ihtiyacı yoktur
- ANSI/ISO SQL 92, bir transaction sırasında başka bir transaction aynı veriyi değiştirdiğinde ortaya çıkabilecek sonuçları üçe ayırır
- Dirty read: Henüz commit edilmemiş başka bir transaction’ın güncellemesini okuma
- Non-repeatable read: Aynı row iki kez okunurken arada başka bir transaction commit ettiği için değerin değişmesi
- Phantom read: Aynı koşuldaki row kümesi iki kez okunurken arada row eklenmesi veya kaldırılması
- ANSI/SQL 92 izolasyon seviyeleri yüksekten düşüğe şöyledir
- Serializable: Yalnızca commit edilmiş verileri okur; aralık tabanlı çoklu row yazmaları da dahil olmak üzere phantom read’i önler
- Repeatable reads: phantom read’e izin verilir
- Read committed: non-repeatable read’e izin verilir
- Read uncommitted: dirty read’e izin verilir
- Daha yüksek izolasyon seviyeleri genellikle performans fedakârlığıyla birlikte gelir
- ANSI/SQL 92 izolasyon seviyeleri eksik olmakla eleştirilir
- Birçok MVCC uygulaması serializable isolation değil, snapshot isolation sağlar
- Hızlı bir serializable MVCC algoritması olarak HyPer önerilir
Dağıtık sistemlere neden ihtiyaç duyulur ve CAP
- Dağıtık sistemler çok fazla karmaşıklık eklediğinden, dağıtık olmayan çözümler yeterliyken bunlardan kaçınılmalıdır
- Veriyi birden fazla makineye dağıtmanın yaygın iki nedeni vardır
- Kullanılabilirlik(Availability): Veritabanı makinesi crash olsa veya kullanıcıyla bağlantısı kesilse bile istekler başka bir makineye gönderilebilir
- Yatay ölçekleme(Horizontal Scaling): Daha büyük tek bir makineye ölçeklemek olan vertical scaling yerine, ağa bağlı birden fazla makinenin tek bir makine gibi davranmasını sağlar
- Dağıtık sistemler operasyonel karmaşıklık ve network partition sorunlarını beraberinde getirir
- CAP teoremi, bir sistemin aşağıdaki üç özellikten yalnızca ikisini garanti edebileceğini söyler
- Consistency: Okumalar en son yazmayı alır
- Availability: Arızalardan bağımsız olarak tüm istekler başarılı olur
- Partition Tolerance: Düğümler arası mesaj kaybı veya gecikme olsa bile sistem çalışmaya devam eder
- Tek makineli veritabanında network partition yoktur ve tutarlıdır; ancak makine arızasında yeni istekler başarısız olduğu için availability ihlal edilir
- İki makinenin ayrı CPU, bellek ve disklere sahip olup bir kabloyla bağlı olduğu durumda, arıza anında seçenekler ayrışır
- İstekler iptal edilirse availability’den feragat edilip consistency korunur
- Yalnızca çalışan makinede istekler işlenmeye devam edilirse consistency’den feragat edilip availability korunur
- consistency’den feragat edip daha sonra uzlaştıran sistemlere eventually consistent denir
- Network partition, kümeye dağılmış verilerin toplanmasını gerektirdiği için verimli
JOINişlemlerini de zorlaştırır; NoSQL çevresi bunu hafifletmek için denormalization önerir
Replikasyon ve Amazon Dynamo Örneği
- Amazon'un orijinal Dynamo makalesi, amazon.com alışveriş sepetinde availability'nin consistency'den daha önemli görüldüğü bir örnek olarak sunulur
- Kullanıcı alışveriş sepetinde aynı üründen iki tane görürse birini silebilir
- Bunun, satın almanın hiç mümkün olmadığı bir durumdan daha iyi olduğu değerlendirilmiştir
- Availability elde etmek için birden çok node'un veriyi paylaşması tek başına yeterli değildir; her öğenin en az bir kopyası bulunmalıdır
- Bir öğenin kopyalarını saklayan node replica, kopyalama süreci ise replication olarak adlandırılır
- Replica sayısını artırmak availability'yi yükseltir, ancak kopyaları saklamak için daha fazla kaynak gerekir
- Veri kopyaları bütün olarak saklanmak yerine erasure coding ile parçalara ayrılıp birden çok node'a dağıtılabilir; ilgili gecikme özellikleri için erasure coding yazısı bağlantılıdır
Consistent Hashing ve Veri Yerleşimi
- Birden çok node olduğunda, saklama isteğini hangi node'un işleyeceğini belirleyen bir load balancing veya veri partitioning yöntemi gerekir
- Basit bir yöntem, primary key'i hash'leyip node sayısına göre modulo almaktır
- Bir node eklendiğinde veya kaldırıldığında
len(nodes)değişir ve aynı key farklı bir node'u işaret eder - Bu durumda neredeyse tüm öğeleri migrate etmek gerekir; maliyeti yüksektir
- Bir node eklendiğinde veya kaldırıldığında
- Consistent Hashing, node'ları bir diziye değil bir ring'e yerleştirerek node ekleme/kaldırma sırasında taşınması gereken öğe sayısını azaltır
- Dynamo ve Cassandra gibi veritabanlarında kullanılır
- Consistent hashing'de node adının hash'i ring'e yerleştirilir; istek key'inin hash'inden sonra karşılaşılan node sahip olur
- Replica seçimi, ring üzerinde saat yönünün tersine ilerleyip kopyayı sonraki node'a kaydetme şeklinde yapılabilir
- Sahip node ölürse replica node isteği işleyerek availability'yi korur
- Bu yöntem Leaderless Replication olarak adlandırılır ve Cassandra gibi Dynamo tarzı veritabanlarında kullanılır
- Node eklenirken taşınması gereken ortalama key sayısı
num_keys / num_nodesdeğeridir - Virtual node, tek bir fiziksel node'u ring'e birden çok kez yerleştirerek bazı node'ların daha fazla öğeye sahip olma olasılığını azaltır
- Örnek, node adına
"half-0","half-1"gibi index'i suffix olarak ekleme yöntemidir
- Örnek, node adına
- Leader node ve replica node seçmenin başka bir yöntemi leader election'dır, ancak burada ele alınmaz
Leaderless Replication ve Tutarlılık Ayarı
- Leaderless yapı, yüksek availability elde ederken consistency'den ödün verir
- Sahip node write isteği sırasında down durumundaysa replica'ya yazılır; sahip node yeniden ayağa kalktıktan sonra read isteğinde eski veri okunabilir
- Belirli bir istekte consistency gerekiyorsa read isteği birden çok replica'ya ve sahip node'a paralel gönderilir, istemci en güncel veriyi seçer
- Write isteği genellikle tüm replica'lara paralel gönderilir, ancak yalnızca bazı node'lardan acknowledgement beklenir
- İstek düzeyinde consistency ayarlamak için
R + W > N/2 + 1doğrulanırN: Veri kopyasına sahip node sayısıW: Write'ın başarılı olması için acknowledgement vermesi gereken node sayısıR: Read'in başarılı olması için yanıt vermesi gereken node sayısı
WveyaRdeğeriN/2 + 1olan çoğunluk node istekleri quorum olarak adlandırılır-
Çakışma Çözümü
- En güncel write'ı seçme süreci Conflict Resolution'dır
- Yalnızca timestamp karşılaştırmak dağıtık sistemlerde güvenilir değildir
- Her makinenin kendi hardware clock'u vardır ve clock'lar tamamen doğru olmadığından drift oluşur
- NTP daha doğru bir zaman kaynağından zaman alır, ancak isteğin kendisi ağdan geçtiği için yanıta kadar geçen süre tam olarak bilinemez
- Cassandra timestamp kullanır; ilgili doküman Cassandra data versioning bağlantılıdır
- Google Spanner, özel yüksek hassasiyetli zaman donanımı ve timestamp belirsizlik aralığını açığa çıkaran bir API ile clock tabanlı consistency garantisi sağlamıştır; ilgili makale Spanner paper'dır
- Dynamo gibi sistemler Version Vectors ile bazı çakışmaları azaltır
- Her öğe version'ına
(node, counter)çifti eklenerek version'lar arasındaki nedensel ilişki bulunur - Kesin olarak daha güncel olan version bulunup bazı eski değerler kaldırılabilir
- Daha ayrıntılı kaynak olarak Dotted Version Vectors bağlantılıdır
- Riak KV'de olduğu gibi çakışan değerlerin tamamını uygulamaya döndürüp, uygulamanın veri hakkındaki bilgisine dayanarak çözmesini sağlamak da mümkündür
- Eventually consistent sistemlerde çakışmaları azaltmaya yönelik çeşitli teknikler genellikle Anti Entropy terimi altında toplanır
Anti Entropy Teknikleri
-
Read Repair
- İstemci birden çok node'dan gelen read sonuçları arasından en güncel değeri seçtikten sonra, bu değeri henüz saklamayan node'lara tekrar gönderip repair eder
-
Hinted Handoff
- Write isteği hedef node'a ulaşamazsa başka bir node'da hint olarak saklanır
- Hedef node yeniden available olduğunda saklanan hint iletilir
- Quorum write'ta bu yöntem
Sloppy Quorumolarak da adlandırılır ve quorum isteklerinin availability'sini daha da artırır
-
Merkle Trees
- Read repair yalnızca sorgulanan veriyi düzelttiği için çok sayıda veri uzun süre tutarsız durumda kalabilir
- Node'ların birbirleriyle senkronize olup tüm farkı bulması, veri çok olduğunda
O(n)maliyetlidir - Merkle tree, veri aralığı hash'ini leaf'lerde saklayan, parent'ın ise child hash'lerini birleştiren hash'i sakladığı hiyerarşik bir yapıdır
- Root hash aynıysa iki node'un verisi aynıdır; farklıysa alt hash'ler özyinelemeli olarak karşılaştırılarak tutarsız veri bulunur ve senkronizasyon
O(log n)hızına indirilebilir
-
Gossip Dissemination
- Event'leri cluster geneline basit ve güvenilir biçimde yayma yöntemidir
- Node, mesajı yapılandırılmış sayıdaki rastgele node'a, yani fanout'a gönderir; alan node da yeniden
Nrastgele node'a gönderir - Aynı gossip mesajı yapılandırılmış sayıda görüldüğünde artık broadcast edilmez
- Veri yakınsamasını hissedebileceğiniz bir simulator bağlantılıdır
- Gossip mesajları genellikle UDP ile iletilir
Daha Derinlemesine Ele Alınabilecek Alanlar
- Veritabanlarında burada ele alınanların dışında da birçok konu vardır
- Bir veritabanı seçerken veya uygularken storage engine, ACID, isolation level, distributed replication ve conflict resolution yöntemlerinin gerçek gereksinimlerle nasıl örtüştüğüne birlikte bakmak gerekir
1 yorum
Hacker News görüşleri
compactmetodunda bir hata var: tombstone kayıtları yalnızca son, yani en büyük seviye sıkıştırılırken atlanmalı; tüm seviyeler arasında kaldırılmamalıAksi halde üst seviyedeki tombstone’lar sıkıştırma sırasında kaybolur ve alt seviyelerdeki girdiler yeniden görünür hale gelir
LSM tabanlı veritabanlarında silme/tombstone kayıtlarının uzun süre kalması özelliklerden biridir; RocksDB gibi bazı veritabanları ise bundan kaçınmaya yönelik optimizasyonlar ekler
Aralık silme tarafındaki özelliği biliyorum ama tekil anahtar silme için pek bir şey okuduğumu hatırlamıyorum
Birçok kişi SQL öğrenirken veritabanlarını da öğreniyor, ama böyle bir dersi alıp B-tree’yi anlayarak öğrenmeyi öneririm
RDBMS’lerin artıları ve eksileri çoğunlukla B-tree’yi ve bunun anahtar ekleme, sorgulama ve sıralama üzerindeki etkisini bilince anlaşılır
Birçok kişi indeks ekleyerek veritabanını hızlandırmaya çalışıyor; ama sonuçta bu, bir ağacın üstüne başka bir ağaç koymaktan ibaret ve temel sorunu gizlemiş oluyor
Bazı problemler B-tree’ye iyi uyar, ama birçok problem uymaz
SQL, uzaktaki bir B-tree sistemine yönelik bir sorgu arayüzünden ibarettir
B-tree tek indeksleme stratejisi olmadığı gibi, indekslerin okuma performansını artırmak karşılığında yazma performansından feragat etmeye yarayan bir araç olduğu da iyi bilinir
Çünkü veritabanları genellikle yazmadan çok daha fazla okuma işler
“Bir ağacın üstüne başka bir ağaç koymanın” tam olarak hangi sorunu gizlediğini ve indekse dokunmadan bunu nasıl çözeceğinizi merak ediyorum
Makul büyüklükteki tablolar için indeksler fiilen zorunludur
B-tree ve hash indeksleri, G/Ç katmanı, süreç modeli gibi şeyleri öğrenmek gerekir
Günümüzde sütun yönelimli veritabanlarının genel stratejilerini öğrenmek de değerli: geç tuple materyalizasyonu, tembel yürütme, doğrusal tarama ve ikili arama, komut hattı işleme gibi şeyler
Bunlara aşina olunca, sahada kimi zaman bir DBMS yerine basit düz dosyaların ya da RocksDB gibi gömülü bir veritabanının yeterli olduğunu da görürsünüz
Elbette covering index de olabilir
“Dağıtık olmayan bir çözüm yeterliyse dağıtık sistemlerden kaçının” tavsiyesine tersinden yanıt vermek isterim
Önemsiz olmayan her operasyonel sistem bir dağıtık sistemdir
En azından veritabanınız bir replikasyon setiyse zaten dağıtık bir sistemdir; bu yüzden dağıtık sistemleri öğrenmemek risk almaktır
https://jepsen.io/ ve https://raft.github.io/ bakmaya değer
Ama bu, bunları her yere sokmanın sorun olmayacağı anlamına gelmez; böyle yaparsanız karmaşıklık gereğinden çok artar
Böyle söylemek, gereksiz karmaşıklıktan kaçınma tavsiyesini çürütmez. Asıl mesele teknik olarak dağıtık olup olmadığı değil, gerçekten gerekli olup olmadığıdır
Dağıtık sistemleri öğrenmek ile kullanmak farklı şeylerdir
Öğrendikten sonra da yalnızca uygun yerlerde uygulayacak ölçülülüğe sahip olup olmadığınız önemlidir
Günümüzde basit ve iyi çalışan sistemleri daha güçlü bir dağıtık modele taşımak için çok emek harcandığı ve bunun neredeyse maliyetsizmiş gibi ele alındığı çok oluyor
Oysa çözülmek istenen probleme ve ölçeğe bakınca tek bir Postgres örneği ve monolitin yeterli olacağı durumlar açıkça görülüyor
Orijinal tavsiye bana bu anlamda görünüyor
En azından mutlaka öyle olmak zorunda değil
Ben yine de basit çözümü seçme tavsiyesini verirdim
Sistemler “önemsiz ve basit depolarda” bile kalıcı durumu doğru şekilde saklama, yedekleme ve geri yükleme konusunda çoğu zaman başarısız oluyor
Felaket kurtarma durumunda dağıtık bir deponun durumunu geri yüklemeye çalışmak daha da zordur
Dağıtık çözüme geçmeden önce çalışan bir yedekleme çözümüne sahip olabilirsiniz
Salt okunur replikaları olan master yapılandırması da insanların genellikle “dağıtık” dediği şeyden farklıdır. Çünkü yazmalar dağıtık değildir
Pratikte dağıtık çoğunlukla verinin shard edildiği anlamına gelir ve gerçekten gerekmiyorsa kesinlikle kaçınmak isteyeceğiniz kısım budur
Veritabanı oluştururken ilgili birçok kavramı güzelce taradığı için keyifle okudum
Tek bir makineden performans çıkarmaya yönelik SIMD’den konsensüs algoritmalarına kadar uzanıyor
Veritabanı, güvenilirlik ve dağıtık sistemler konuşuluyorken, bu durumlara ve veritabanı iç uygulamalarına uygulanabilecek formal yöntemler hakkında da okumaya değer
S3 ekibinin TLA+ ile modellediği ilginç bir makale var
[0] Use of Formal Methods at Amazon Web Services
https://lamport.azurewebsites.net/tla/formal-methods-amazon....
[1] How Amazon Web Services uses formal methods
https://www.amazon.science/publications/how-amazon-web-servi...
Tutarlılığın veritabanı tutarlılığı ve uygulama tutarlılığı diye iki yönü vardır
Örneğin tek bir tablo düzeyinde atomiklik, izolasyon ve kalıcılık sağlanabilir; ama birden çok tabloya yayılan yazmalarda başarısız olunabilir
Birden çok tabloyu aynı anda güncelleyen işlemlerle uğraşmaya başladığınızda tutarlılık önemli hale gelir
Tüm tablolar aynı anda güncellenmeli ya da hiçbiri güncellenmemelidir
“MongoDB gibi bir belge API’si, Cassandra gibi lidersiz replikasyon, ScyllaDB gibi çekirdek başına iş parçacığı mimarisi var” şeklindeki tasarım çok hoş
Üstelik tamamı Rust ile yazılmış
“Kitaplar merakımı uyandırdığı için küçük bir veritabanını kendim yaptım” aşaması, sanırım pek çok geliştiricinin bir kez olsun geçtiği bir aşama
Bunu özellikle engellemeye çalışmam. Kendin yapınca nelerin çalışmadığını gerçekten çok öğreniyorsun
Zaman ayırabiliyorsan son derece değerli bir ders
Kendi veritabanımı yapmış olmam, mevcut çözümlere duyduğum saygıyı en çok artıran şey oldu
Baytları diske hızlı yazıp okumak zor kısım değil
Asıl zor olan, hayal bile etmediğin kullanım senaryolarını desteklerken yıllar boyunca kararlı biçimde çalışmasını sağlamak
Alan dışındaki kullanım senaryolarını yasaklayıp yok sayabileceğimiz varsayımıyla alana özgü DBMS tasarlasak nasıl bir verimlilik elde edebiliriz?
Örneğin bugün doğası gereği yalnızca eklemeli olan veri kümelerinde bile genel amaçlı veritabanları kullanıyoruz
Mevcut satırların güncellenmesi ya da silinmesi diye bir kavramın hiç olmadığı, yalnızca ekleme ve tablo/veri kümesinin tamamını silme gibi işlemlerin bulunduğu bir veritabanı nasıl olurdu?
Böyle bir veritabanı MVCC transaction'larını uygulamak zorunda kalmaz mıydı? Her tablo zaten write-ahead log olduğu için ayrı bir write-ahead log'dan kaçınabilir miydi? Daha verimli depolayabilir miydi? İndekslemeyi tüm tablo düzeyinde atomiklik yerine chunk düzeyinde atomiklikle yapıp kilitlemeyi azaltabilir miydi?
Bash sürümünde atomiklik, dosyayı geçici bir dosyaya kopyalayıp düzenledikten sonra
sync; mv; synckullanarak “basitçe” sağlanamaz mı?grepfiltresiyle yinelenenleri de önleyebilirsinKopyalarken sıralamayı da garanti edebilirsin ama bunu sadece “bash” ve temel yardımcı araçlarla yapmak pek mantıklı görünmüyor
Bu amaç için DJB'nin CDB'si, yani cdbget, cdbmake vb. var:
https://cr.yp.to/cdb.html
Harika bir yazı
Database Internals kitabı iyi görünüyor; iç uygulamaya derinlemesine giren benzer başka kitaplar var mı?
https://www.youtube.com/c/cmudatabasegroup
Hem giriş hem ileri düzey dersler çevrimiçi; ayrıca sektördeki ürünler hakkında sunumlar ve dersler de var
Çok faydalı
Daha üst düzey, teorik bilgisayar bilimi açısından bakan ve fiziksel uygulamaya daha az odaklanan bir kaynak olarak “Alice” kitabı, yani “Foundations of Databases” mükemmel
Çok yoğun ve matematiksel, ama ilişkisel cebiri ve Datalog'u, ayrıca Datalog'un ilişkisel cebire dönüştürülmesini ele alıyor
Basılı kitabı artık bulmak zor; ikinci el aldığım nüshanın cildi dağılmış, sayfaları kopmuş halde gelmişti ama kitabın tamamı çevrimiçi: http://webdam.inria.fr/Alice/
https://dsf.berkeley.edu/papers/fntdb07-architecture.pdf
Ancak Database Internals daha modern
Yazının “veritabanı”nı gizemlileştirmeden, Bash'te tek satırlık önemsiz bir uygulama göstererek başlaması hoşuma gidiyor
Harika bir giriş