İlişkisel Veri Tabanlarında İndeksin Mühendislik Karşılığı: Kimlik, Erişim Yolu ve Eşzamanlılık

İlişkisel Veri Tabanlarında İndeksin Mühendislik Karşılığı: Kimlik, Erişim Yolu ve Eşzamanlılık

İndeks tasarımını yalnız sorgu hızıyla sınırlamadan; birincil anahtar, tekillik, ROWID, ORM kimliği, eşzamanlılık, RAC, CDC, bölümleme ve ölçülmüş erişim maliyeti üzerinden ele alan teknik inceleme.

İndeks çoğu zaman yalnızca sorguları hızlandıran bir veri yapısı olarak ele alınır. Üretim sistemlerinde ise konu daha geniştir: indeks tasarımı; satır kimliği, veri bütünlüğü, erişim planlarının öngörülebilirliği, eşzamanlılık ve yazma maliyeti ile birlikte değerlendirilmelidir.

Bu nedenle temel soru, “hangi kolona indeks eklenmeli?” değildir:

Hangi bütünlük kuralı veri modeli tarafından garanti edilmeli, hangi erişim yolu ölçülmüş iş yükü için gereklidir ve bu seçimin yazma, kilitlenme, bakım ve replikasyon maliyeti nedir?

İlişkisel modelde kimlik: birincil anahtar bir performans ayarı değildir

Codd'un ilişkisel modelindeki bir ilişki (relation), aynı tuple'ın birden fazla kez bulunmadığı bir kümedir. SQL tablosu ise varsayılan olarak bag/multiset semantiğine izin verir. Burada önemli ayrım şudur: bir tablonun ilişkisel anlamda kullanılabilmesi için mutlaka PRIMARY KEY sözcükleriyle tanımlanmış bir kısıt bulunması gerekmez; ancak satırları mantıksal olarak ayıran en az bir aday anahtarın gerçekten var olması gerekir. Hiçbir aday anahtarın bulunmadığı ve mükerrer satırların serbestçe oluşabildiği yapı, uygulama açısından kararlı satır kimliğini kaybeder.

Bu nedenle birincil anahtarın (PRIMARY KEY) ilk görevi sorguyu hızlandırmak değil, satır kimliğini tanımlamaktır. İş anahtarı ayrıca tekil olmak zorundaysa bu kural UNIQUE constraint veya eşdeğer veri tabanı mekanizmasıyla korunmalıdır.

Vekil anahtar ile iş anahtarı aynı şey değildir:

surrogate key  -> satırın teknik kimliği
business key   -> alan kuralına göre tekillik

Örneğin:

CREATE TABLE islem_kayit (
    kayit_id NUMBER GENERATED ALWAYS AS IDENTITY,
    kaynak_sistem VARCHAR2(32) NOT NULL,
    referans_no VARCHAR2(64) NOT NULL,
    olay_zamani TIMESTAMP NOT NULL,
    tutar NUMBER,
    CONSTRAINT pk_islem_kayit PRIMARY KEY (kayit_id),
    CONSTRAINT uq_islem_kayit_business UNIQUE (kaynak_sistem, referans_no)
);

kayit_id, ORM ve diğer teknik katmanlara değişmeyen bir nesne kimliği verir. UNIQUE (kaynak_sistem, referans_no) ise iki farklı teknik satırın aynı iş olayını temsil etmesini engeller. Biri diğerinin yerine geçmez.

Fiziksel satır adresi kimlik değildir

Mantıksal bir anahtar bulunmadığında tek bir satırı garantili biçimde adreslemek güçleşir. O anki veri üzerinde bütün kolonları içeren bir predicate tek satır döndürebilir; fakat tekillik constraint ile garanti edilmemişse bunun gelecekte de tek satır döndüreceğine ilişkin bir veri tabanı garantisi yoktur.

Oracle'daki ROWID ve PostgreSQL'deki ctid bu noktada sık kullanılan fiziksel konum belirteçleridir.

Oracle ROWID, satırın bulunduğu veri nesnesi, dosya, blok ve blok içi konuma ilişkin fiziksel adres bilgisini taşır. Sıradan bir UPDATE, row migration oluşsa bile baş satırın ROWID'sini çoğu durumda korur. Buna karşılık segmenti veya satırın fiziksel yerini yeniden kuran işlemler ROWID değerlerini değiştirebilir. ALTER TABLE ... MOVE, bazı shrink/redefinition/partition bakım işlemleri ve row movement buna örnektir.

PostgreSQL ctid daha kısa ömürlüdür. MVCC nedeniyle bir UPDATE yeni tuple sürümü oluşturduğundan ctid değişebilir; VACUUM FULL gibi fiziksel yeniden yazımlar da aynı sonucu üretir. Bu nedenle PostgreSQL belgeleri ctid değerinin uzun ömürlü satır tanımlayıcısı olarak kullanılmamasını açıkça belirtir.

Buradan çıkan pratik kural:

Fiziksel satır adresi, kısa ömürlü bir erişim belirtecidir; uygulama kimliği değildir.

ROWID'nin cache'e, dış mesaja, API cevabına veya daha sonra çalışacak bir kuyruğa kalıcı kimlik olarak taşınması bakım işlemleriyle geçersizleşebilen bir sözleşme yaratır.

ROWID ne zaman savunulabilir?

Devralınmış ve şeması değiştirilemeyen Oracle sistemlerinde ROWID tamamen reddedilecek bir araç değildir. Özellikle primary key bulunmayan legacy tablolarda, satır seçimi ile yazma aynı transaction ve kilit kapsamındaysa ROWID kontrollü bir teknik çözüm olabilir:

SELECT t.ROWID AS satir_adresi,
       t.kaynak_sistem,
       t.olay_zamani,
       t.tutar
FROM islem_kayit t
WHERE t.kaynak_sistem = :kaynak
  AND t.olay_zamani >= :baslangic
FOR UPDATE;

Ardından aynı transaction içinde:

UPDATE islem_kayit
SET tutar = :tutar
WHERE ROWID = :satir_adresi;

Bu örneğin güvenliği ROWID'nin “benzersiz anahtar” olmasından değil, seçilen fiziksel satırın transaction boyunca kilit altında tutulmasından gelir. Kilit bırakıldıktan sonra fiziksel adresi kalıcı kimlik gibi kullanmamak gerekir.

Hibernate ve Jakarta Persistence neden gerçek bir kimlik ister?

Jakarta Persistence entity modelinde her entity bir kimliğe sahiptir. Hibernate persistence context de pratikte bir identity map gibi davranır: yönetilen nesneler entity tipi ve identifier bileşimiyle izlenir.

Bu kimlik yalnızca find() çağrısı için gerekli değildir. Şunların tamamı kimlik varsayımına dayanır:

  • first-level cache,
  • dirty checking,
  • entity yaşam döngüsü,
  • lazy association çözümleme,
  • merge/detach semantiği,
  • cascade işlemleri,
  • flush sırasında doğru satırın hedeflenmesi.

Gerçekte tekil olmayan bir kolonu @Id gibi göstermek ORM'i “anahtarsız çalışır” hale getirmez; persistence context'e yanlış bir kimlik modeli verilmiş olur.

Bileşik anahtar

Gerçek bir iş anahtarı varsa @EmbeddedId veya @IdClass kullanılabilir. Ancak bileşenlerin gerçekten tekil kombinasyon oluşturması gerekir. Geniş composite key'ler daha büyük indeks, daha geniş foreign key ve daha pahalı karşılaştırmalar üretebilir.

ROWID'yi entity kimliği yapmak

ROWID'yi @Id yerine geçirmek kısa vadede cazip görünür; ancak fiziksel adresin yaşam süresini entity yaşam süresiyle eşitler. Segment bakımından detached entity'ye, ikinci seviye cache'ten mesaj kuyruğuna kadar birçok katmanda kırılganlık üretir.

Hibernate'in @RowId özelliği de anahtarsız entity'ye kimlik üretmez. Amaç, zaten tanımlı identifier'a sahip entity üzerinde desteklenen veri tabanlarında DML sırasında row-id tabanlı doğrudan erişimden yararlanmaktır.

Anahtarsız tabloyu entity yapmamak

Şema değiştirilemiyorsa en temiz tasarım çoğu zaman tabloyu writable entity gibi modellememektir. Okuma projeksiyon/DTO üzerinden yapılabilir; kontrollü yazma ise ürünün yeteneklerine ve transaction sınırlarına göre ayrı repository/DAO akışında ele alınabilir.

Bu yaklaşımın avantajı, veri modelinde bulunmayan bir garantiyi ORM'e varmış gibi bildirmemesidir.

Mükerrer veri: temizlikten önce bütünlük kuralı meselesi

Bir defalık duplicate temizliği problemi çözmez. Asıl invariant şudur:

aynı business key ile iki committed satır bulunamaz

Uygulama tarafındaki klasik akış bunu garanti etmez:

SELECT -> yok
INSERT

İki transaction aynı anda SELECT çalıştırıp ikisi de “yok” sonucunu görebilir. Daha sonra ikisi de INSERT yapar. Isolation düzeyi ve explicit locking ile özel çözümler kurulabilir; ancak iş anahtarına ait temel tekillik kuralının en doğal ve yerel koruma noktası veri tabanındaki UNIQUE constraint'tir.

Oracle'da küçük veri kümelerinde duplicate temizliği için ROWID tabanlı örnekler görülebilir:

DELETE FROM islem_kayit t
WHERE t.ROWID NOT IN (
    SELECT MIN(k.ROWID)
    FROM islem_kayit k
    GROUP BY k.kaynak_sistem, k.olay_zamani, k.referans_no
);

Bu ifade küçük bir tablo için işe yarayabilir; büyük tablolarda ise tek transaction'da yüksek undo/redo, uzun çalışma süresi, büyük rollback maliyeti ve yoğun blok/row lock baskısı oluşturabilir. “Tabloyu tamamen kilitler” demek doğru değildir; Oracle normal DML'de satır kilitleri kullanır, fakat büyük delete işlemi eşzamanlı iş yükünü yine ciddi biçimde etkileyebilir.

Yüz milyonlarca satırlık tablolarda çözüm; veri hacmi, bakım penceresi, referential integrity, partitioning, redo bütçesi ve geri dönüş planıyla birlikte tasarlanmalıdır. CTAS + kontrollü swap, partition exchange, online redefinition veya batch delete seçenekleri aynı problem için farklı operasyonel maliyetler taşır.

İndeksin algoritmik maliyeti: O(log n) tek başına yeterli açıklama değildir

B-tree ailesindeki bir indekste noktasal arama, soyut veri yapısı düzeyinde yaklaşık O(log n) davranır; tam tarama ise satır/blok sayısıyla doğrusal büyür. Ancak gerçek optimizer kararı yalnızca Big-O üzerinden verilmez.

Oracle ve PostgreSQL gibi maliyet tabanlı optimizer'lar için daha belirleyici büyüklükler şunlardır:

  • cardinality tahmini,
  • selectivity,
  • table/index statistics,
  • clustering factor veya veri yerelliği,
  • beklenen satır sayısı,
  • random ve sequential I/O maliyeti,
  • cache durumu,
  • predicate yapısı,
  • join sırası ve join algoritması,
  • parallelism,
  • partition pruning.

Yaklaşık B-tree yüksekliği:

h ≈ log_f(N)

Burada N indeks girdisi, f ise bir branch block'a sığan ortalama pointer/key fan-out değeridir. Yüz milyonlarca kayıt içeren bir B-tree'nin yüksekliği yine birkaç seviye olabilir. Bu, noktasal erişimin neden çok az logical I/O ile tamamlanabildiğini açıklar.

Fakat sorgu tablonun büyük bölümünü döndürüyorsa index range scan + table access kombinasyonu, çok sayıda dağınık block erişimi nedeniyle full scan'den pahalı olabilir. Dolayısıyla:

İndeksin varlığı ile indeksin optimizer tarafından kullanılması aynı şey değildir.

Doğru optimizer'ın full table scan seçmesi bir hata değildir.

Küçük tabloya indeks gerekir mi?

“Tablo küçük, indekse gerek yok” ifadesinde iki ayrı problem birbirine karışır.

Erişim yolu açısından: birkaç block'luk bir tabloyu full scan etmek gerçekten en ucuz plan olabilir.

Bütünlük açısından: primary key ve unique constraint gereksinimi tablo boyutundan bağımsızdır.

Beş satırlık bir lookup tablosunda bile aynı kodun iki kez eklenmesi iş kuralını bozuyorsa UNIQUE gereklidir. Öte yandan yalnız performans amacıyla eklenen secondary index gerçekten gereksiz olabilir.

Bir başka ayrıntı çalıştırma frekansıdır. Beş block'luk tablo saniyede on bin kez taranıyorsa tek sorgunun ucuz olması toplam CPU ve consistent-get maliyetini önemsiz yapmaz. Ölçümde “bir sorgu ne kadar sürüyor?” kadar “birim zamanda kaç kez çalışıyor?” sorusu da yer almalıdır.

Kapsayıcı indeks ve yalnız indeks üzerinden erişim

Bir sorgunun ihtiyaç duyduğu tüm kolonlar indeks üzerinde bulunabiliyorsa table lookup ortadan kalkabilir. Bu durum özellikle yüksek frekanslı read path'lerde güçlüdür.

Oracle'da uygun plan INDEX FAST FULL SCAN, INDEX RANGE SCAN veya yalnız indeks bloklarıyla tamamlanan başka erişim yolları üretebilir. PostgreSQL'de Index Only Scan için indeks kolonlarının yeterli olması tek başına yetmez; heap sayfalarının visibility bilgisi de MVCC nedeniyle önemlidir. Visibility map uygun değilse heap fetch gerekebilir.

Bu ayrıntı, aynı SQL'in farklı bakım/vacuum durumlarında neden farklı maliyet gösterebildiğinin iyi bir örneğidir.

Bileşik indeks: kolon sırası erişim sözleşmesinin parçasıdır

Aşağıdaki indeksler eşdeğer değildir:

CREATE INDEX ix_a ON islem_kayit (kaynak_sistem, olay_zamani);
CREATE INDEX ix_b ON islem_kayit (olay_zamani, kaynak_sistem);

Kolon sırası gerçek predicate dağılımına göre belirlenmelidir. Equality predicate'ler, range predicate'ler, sort gereksinimi ve ürünün skip-scan benzeri yetenekleri birlikte değerlendirilir.

Örneğin:

WHERE kaynak_sistem = :k
  AND olay_zamani BETWEEN :a AND :b
ORDER BY olay_zamani

erişim deseninde (kaynak_sistem, olay_zamani) çoğu durumda doğal bir adaydır. Ancak cardinality ve sorgu profili ölçülmeden bunu evrensel reçeteye dönüştürmek doğru değildir.

Fonksiyon tabanlı ve ifade indeksleri

Predicate kolonun kendisini değil bir fonksiyon sonucunu kullanıyorsa normal indeks erişilemeyebilir:

WHERE UPPER(kullanici_adi) = :deger

Oracle'da function-based index, PostgreSQL'de expression index gibi yapılar bu erişim desenini indeksleyebilir. Ancak uygulamada daha iyi bir normalize edilmiş veri modeli veya case-insensitive veri tipi/collation mümkünse önce model düzeyindeki seçenek değerlendirilmelidir.

İndeks, kötü veri modelini görünmez hale getiren bir yama değildir.

Bölümleme ve yerel/genel indeks ayrımı

Partitioning, indeks yerine geçen bir mekanizma değildir. İki farklı işi çözer:

partitioning -> veri kümesini fiziksel/lojik bölümlere ayırır
index        -> bölüm içinde veya bölümler arasında erişim yolu sağlar

Oracle'da local index partition yapısıyla hizalıdır; partition bakım operasyonlarını kolaylaştırabilir. Global index ise partition key'den bağımsız global erişim desenleri için değerlidir, fakat partition maintenance operasyonlarında ek bakım yükü doğurabilir.

Zaman serisi veya büyük olay tablolarında doğru partition pruning, milyarlarca satırlık tablonun yalnız ilgili tarih bölümlerinin değerlendirilmesini sağlar. Buna rağmen ilgili partition içinde noktasal veya dar aralık erişimi gerekiyorsa indeks hâlâ önemlidir.

Yazma maliyeti: her indeks DML yükü getirir

Her secondary index ilgili DML için ek çalışma üretir:

  • leaf block değişikliği,
  • gerekirse block split,
  • redo/WAL üretimi,
  • undo/MVCC sürüm maliyeti,
  • cache invalidation,
  • ek buffer kullanımı,
  • bakım ve istatistik maliyeti.

Bu nedenle sekiz indeksli bir tabloya yapılan insert, yalnız heap/table block'una yazmaz; indeks yapılarını da günceller.

Read-heavy sistemlerde bu maliyet kabul edilebilir. Write-heavy ingest tablolarında aynı indeks kümesi throughput'u ve tail latency'yi belirleyen ana etkenlerden biri olabilir.

İyi indeksleme “çok indeks” değil, iş yüküne yetecek minimum doğru indeks kümesi demektir.

Monoton anahtarlar ve sağ kenar sıcak noktaları

Sequence, identity veya monoton timestamp ile büyüyen anahtarlarda B-tree insert'leri ağacın sağ kenarındaki leaf block'larda yoğunlaşabilir. Tek instance'ta latch/buffer contention, Oracle RAC'ta ise cache fusion nedeniyle global block transferleri daha görünür hale gelebilir.

Olası teknikler:

  • sequence cache'i büyütmek,
  • reverse key index,
  • hash partitioned index,
  • farklı key dağılımı,
  • partitioning.

Ancak reverse key index range scan yeteneğini kısıtlar; hash partitioning operasyonel karmaşıklık ekler; rastgele anahtarlar ise locality ve indeks boyutu açısından başka maliyetler yaratabilir. Çözüm workload ölçümüyle seçilmelidir.

Yabancı anahtar indeksleri ve eşzamanlılık

Oracle'da foreign key kolonunun indekslenmemesi özellikle parent key üzerinde DELETE veya key değişikliği olduğunda child table erişimi ve TM enqueue davranışını olumsuz etkileyebilir. Yoğun eşzamanlı sistemlerde bu durum bekleme zincirine, ardından connection pool doygunluğuna kadar büyüyebilir.

PostgreSQL foreign key tanımlandığında referencing kolonlar için otomatik indeks oluşturmaz. Parent tarafındaki delete/update sırasında referans kontrolünün child table üzerinde pahalı taramalar yapmaması için uygun indeks çoğu gerçek OLTP iş yükünde gereklidir.

Bununla birlikte “her foreign key'e koşulsuz indeks” de mekanik bir kural olmamalıdır. Parent satırları hiç silinmiyor/değişmiyor, child tablo çok küçük veya write amplification kritikse gerçek workload incelenmelidir.

Tam tablo taraması SSD ömrünü tüketir mi?

Bu iddia teknik olarak doğru kurulmalıdır.

NAND flash dayanıklılığı esas olarak program/erase döngüleri ve yazılan veri miktarıyla ilişkilidir. Normal read I/O doğrudan P/E döngüsü tüketmez. Bu nedenle “FTS SSD ömrünü doğrudan kısaltır” ifadesi genellenemez.

Savunulabilir mühendislik etkisi başkadır:

  • paylaşılan I/O bandwidth tüketilir,
  • buffer cache yararlı bloklardan arındırılabilir,
  • CPU ve memory bandwidth harcanır,
  • eşzamanlı workload'ların latency'si artar,
  • sort/hash işlemleri temp spill üretirse dolaylı yazma oluşur,
  • storage queue depth ve tail latency büyüyebilir.

Kritik sistemlerde asıl mesele disk ömründen çok kapasite izolasyonu ve gecikmenin öngörülebilirliğidir.

Explicit cursor: sorun imlecin kendisi değil, satır satır algoritmadır

PL/SQL explicit cursor başlı başına anti-pattern değildir. Problem, set tabanlı yapılabilecek milyonlarca satırlık işlemi procedural row-by-row döngüye çevirmektir.

Şu düşünce daha doğrudur:

mümkünse set-based SQL
gerekliyse bulk processing
gerçekten satır durumuna bağımlıysa procedural işlem

Oracle'da BULK COLLECT ve FORALL gibi mekanizmalar SQL/PLSQL context switch maliyetini azaltabilir. Ancak en iyi optimizasyon çoğu zaman algoritmayı tek SQL ifadesine veya sınırlı sayıda set tabanlı ifadeye dönüştürmektir.

OLTP ile OLAP aynı indeks stratejisini kullanmaz

OLTP iş yükünde tipik hedef:

  • kısa transaction,
  • yüksek concurrency,
  • dar predicate,
  • düşük p95/p99 latency,
  • küçük ve kararlı working set,
  • hızlı commit.

Analitik iş yükünde ise:

  • büyük taramalar,
  • aggregation,
  • column pruning,
  • parallel execution,
  • yüksek throughput,
  • uzun sorgular

ön plana çıkar.

Bu nedenle analitik bir sorgunun full scan yapması kötü tasarım belirtisi olmayabilir. Columnar sistemlerde geniş tarama zaten temel çalışma modelidir. Sorun, ağır analitik taramaların gecikme duyarlı OLTP path'iyle aynı CPU, cache, I/O ve lock bütçesini paylaşmasıdır.

Oracle RAC, PostgreSQL veya başka bir ürün bu temel workload ayrımını ortadan kaldırmaz.

Oracle RAC kötü erişim desenini büyütebilir

RAC yatay erişilebilirlik ve ölçek seçenekleri sağlar; ancak paylaşılan veri blokları düğümler arasında dolaşmak zorundadır. Hot index block, hot table block veya kötü locality, tek instance'ta lokal contention iken RAC ortamında global cache transferine dönüşebilir.

Bu nedenle RAC performans incelemesinde yalnız SQL elapsed time değil şu sınıflar da önemlidir:

  • buffer busy/contention,
  • gc wait sınıfları,
  • hot block dağılımı,
  • interconnect latency,
  • service affinity,
  • sequence/index hot spot,
  • partition access locality.

Daha fazla node kötü bir veri modelinin deterministik çözümü değildir.

CDC için kimlik neden kritiktir?

Change Data Capture sistemleri UPDATE ve DELETE olayını hedef tarafta hangi satıra uygulayacağını bilmek zorundadır.

PostgreSQL logical replication REPLICA IDENTITY kavramıyla bu gereksinimi açıkça modeller. Uygun key yoksa REPLICA IDENTITY FULL, eski satır görüntüsünün daha geniş biçimde kullanılmasına neden olabilir.

Oracle GoldenGate gibi log tabanlı CDC araçlarında da güvenilir unique key bulunması hedef satır eşlemesini belirgin ve verimli hale getirir. Key bulunmadığında daha geniş kolon kümeleri ve supplemental logging gerekebilir.

Primary key eksikliğinin bedeli bu yüzden yalnız Hibernate katmanında değil, replikasyon ve veri entegrasyonu hattında da ödenir.

Yedekten dönüş, mutabakat ve adli izlenebilirlik

Kimlik problemi disaster recovery sırasında daha görünür hale gelir.

İki veri kümesi arasında:

hangi satır yeni?
hangi satır değişti?
hangi satır silindi?
hangi duplicate gerçek olay?

sorularını cevaplamak için eşleştirilebilir bir kimlik gerekir.

Aynı kolon değerlerine sahip iki satır arasında işlevsel olarak ayırt edici bir identifier yoksa, hangi fiziksel kopyanın hangi kaynak olaydan geldiği sonradan kanıtlanamayabilir. Bu durum reconciliation, audit trail ve kısmi restore süreçlerini zorlaştırır.

İndeks tasarımı iş yükü ölçülerek yapılmalıdır

İndeks önerisi production workload görülmeden tamamlanmış sayılamaz. Oracle için tipik inceleme araçları arasında execution plan, DBMS_XPLAN, AWR/ASH erişim desenleri ve segment/index istatistikleri; PostgreSQL için EXPLAIN (ANALYZE, BUFFERS), pg_stat_statements ve ilgili katalog/istatistik görünümleri bulunur.

Değerlendirilecek temel ölçüler:

| Alan | Örnek ölçü | |---|---| | Erişim sıklığı | executions/s | | Gecikme | p50, p95, p99 | | CPU | CPU time / execution | | Mantıksal I/O | buffer gets / shared hits | | Fiziksel I/O | reads, latency | | Sonuç cardinality | rows returned | | DML maliyeti | inserts/updates/deletes per second | | Contention | lock/latch/buffer waits | | İndeks verimi | scans, rows fetched, maintenance cost |

Ortalama süre tek başına yeterli değildir. Gerçek zamanlı sistemlerde p99 ve saturation davranışı, ortalama latency'den daha belirleyici olabilir.

Şema değiştirilemiyorsa savunulabilir geçiş planı

Legacy sistemde primary key/unique constraint eklemek her zaman mümkün olmayabilir. Bu durumda risk azaltma sırası şöyle kurulabilir:

  1. Aday iş anahtarlarını veri üzerinden ölçün.
  2. Duplicate oranını ve NULL dağılımını çıkarın.
  3. Yazma yapan tüm kod yollarını belirleyin.
  4. Tekilliğin uygulama tarafından hangi noktalarda varsayıldığını bulun.
  5. Şema değişmiyorsa fiziksel locator kullanımını tek transaction sınırına hapsedin.
  6. ORM entity yerine projection/read model kullanmayı değerlendirin.
  7. CDC, cache ve queue sistemlerine fiziksel adres taşımayın.
  8. İndeks önerilerini gerçek execution plan ve workload ile doğrulayın.
  9. Şema değişikliği mümkün olduğunda önce invariant'ları constraint ile veri tabanına taşıyın.
  10. Ardından read-path secondary index'lerini ölçülmüş sorgulara göre ekleyin.

Bu sıra önemlidir. Önce onlarca performans indeksi ekleyip daha sonra kimlik problemini çözmek, veri bütünlüğü borcunu ortadan kaldırmaz.

Sonuç

İndeks tasarımı “sorgu yavaşsa indeks ekle” düzeyine indirgenmemelidir.

Bir production veri tabanında üç ayrı soru birlikte cevaplanmalıdır:

Kimlik   -> Bu satırı kalıcı ve tekil olarak ne tanımlar?
Bütünlük -> Hangi iş kuralları veri tabanı tarafından zorlanır?
Erişim   -> Ölçülmüş workload için en düşük toplam maliyetli erişim yolu nedir?

Primary key ve unique constraint'ler önce doğruluğun; secondary index'ler ise esas olarak erişim ekonomisinin parçasıdır. Foreign key indeksleri, hot blocks, partitioning, covering index, CDC ve RAC etkileri de aynı kararın eşzamanlılık ve operasyon tarafıdır.

En iyi indeks stratejisi en fazla indeksi üreten değil; doğruluk invariant'larını veri modelinde açıkça koruyan, kritik erişim yollarını düşük ve öngörülebilir gecikmede tutan ve yazma maliyetini ölçülmüş sınırlar içinde bırakan stratejidir.

Kaynakça

  1. Codd, E. F. (1970). A Relational Model of Data for Large Shared Data Banks. Communications of the ACM, 13(6), 377-387. DOI: 10.1145/362384.362685
  2. Bayer, R.; McCreight, E. (1972). Organization and Maintenance of Large Ordered Indices. Acta Informatica, 1(3), 173-189. DOI: 10.1007/BF00288683
  3. Comer, D. (1979). The Ubiquitous B-Tree. ACM Computing Surveys, 11(2), 121-137. DOI: 10.1145/356770.356776
  4. Silberschatz, A.; Korth, H. F.; Sudarshan, S. Database System Concepts. 7th ed., McGraw-Hill, 2019.
  5. Garcia-Molina, H.; Ullman, J. D.; Widom, J. Database Systems: The Complete Book. 2nd ed., Pearson.
  6. Date, C. J. An Introduction to Database Systems. 8th ed., Addison-Wesley.
  7. Elmasri, R.; Navathe, S. B. Fundamentals of Database Systems. 7th ed., Pearson.
  8. Oracle. Oracle Database Concepts. https://docs.oracle.com/en/database/oracle/oracle-database/
  9. Oracle. Oracle Database SQL Tuning Guide. https://docs.oracle.com/en/database/oracle/oracle-database/
  10. PostgreSQL Global Development Group. PostgreSQL Documentation: System Columns. https://www.postgresql.org/docs/current/ddl-system-columns.html
  11. PostgreSQL Global Development Group. PostgreSQL Documentation: Indexes and Logical Replication. https://www.postgresql.org/docs/current/
  12. Hibernate. Hibernate ORM User Guide: Identifiers and RowId. https://docs.hibernate.org/
  13. Eclipse Foundation. Jakarta Persistence Specification. https://jakarta.ee/specifications/persistence/
  14. JEDEC. JESD218: Solid-State Drive Requirements and Endurance Test Method; JESD219: SSD Endurance Workloads. https://www.jedec.org/
Bu sayfanın QR kodu