KoderKoder.ai
FiyatlandırmaKurumsalEğitimYatırımcılar için
Giriş YapBaşla

Ürün

FiyatlandırmaKurumsalYatırımcılar için

Kaynaklar

Bize UlaşınDestekEğitimBlog

Yasal

Gizlilik PolitikasıKullanım KoşullarıGüvenlikKabul Edilebilir Kullanım PolitikasıKötüye Kullanımı Bildir

Sosyal

LinkedInTwitter
Koder.ai
Dil

© 2026 Koder.ai. Tüm hakları saklıdır.

Ana Sayfa›Blog›PostgreSQL indeksleri SaaS uygulamaları için: B-tree vs GIN vs GiST
22 Kas 2025·6 dk

PostgreSQL indeksleri SaaS uygulamaları için: B-tree vs GIN vs GiST

SaaS uygulamaları için PostgreSQL indeksleri: filtreler, arama, JSONB ve diziler gibi gerçek sorgu şekillerini kullanarak btree, GIN ve GiST arasında seçim yapın.

PostgreSQL indeksleri SaaS uygulamaları için: B-tree vs GIN vs GiST

Gerçek SaaS ekranlarında indekslerin çözdüğü problem

Bir indeks, PostgreSQL'in satırları bulma şeklini değiştirir. İndeks yoksa, veritabanı genellikle tablonun büyük bir kısmını okumak (sequential scan) ve sonra çoğunu eler. Doğru indeksle, eşleşen satırlara doğrudan atlayabilir (index lookup) ve sadece ihtiyaç duyduğu verileri alır.

Bunu SaaS'ta erken fark edersiniz çünkü günlük ekranlar sorgu-ağırlıklıdır. Tek bir tıklama birkaç okuma tetikleyebilir: liste sayfası, toplam sayım, birkaç pano kartı ve arama kutusu. Bir tablo binlerden milyonlara büyüdüğünde, eskiden anlık hissettiren aynı sorgu gecikmeye başlar.

Tipik bir örnek, durum ve tarihe göre filtrelenen, en yeniye göre sıralanan ve sayfalandırma yapılan Orders sayfasıdır. PostgreSQL son 30 gündeki ödenmiş siparişleri bulmak için tüm orders tablosunu taramak zorunda kalırsa, her sayfa yüklemede ekstra iş olur. İyi bir indeks bunu doğru veri dilimine hızlı bir atlayışa çevirir.

İndeksler ücretsiz değildir. Her biri belirli sorgular için daha hızlı okumalar sağlar, ancak yazmaları yavaşlatır (INSERT/UPDATE/DELETE indeksleri güncellemek zorundadır) ve daha fazla depolama/önbellek baskısı kullanır. Bu takas, indeks tiplerinden ziyade gerçek sorgu kalıplarından başlamanız gerektiği sebeptir.

Zaman kaybını önleyen basit bir kural: yalnızca hızlandıracağı belirli, sık kullanılan bir sorguyu işaret edebiliyorsanız indeks ekleyin. Koder.ai gibi sohbet tabanlı bir oluşturucu ile ekranlar inşa ediyorsanız, liste sayfalarınızın ve panolarınızın arkasındaki SQL'i yakalamak ve bunu indeks istek listeniz olarak kullanmak faydalıdır.

B-tree vs GIN vs GiST basitçe

Çoğu indeks karışıklığı, özellikler (JSON, arama, diziler) yerine sorgu şeklini düşünmeye başladığınızda kaybolur: WHERE ne yapıyor ve sonuçları nasıl sıralamak istiyorsunuz?

B-tree: sıralama ve karşılaştırmalar için hızlı

Sorgunuz normal karşılaştırmalar gibiyse ve sıralama önemliyse B-tree kullanın. Eşitlik, aralıklar ve join'ler için iş atıdır.

Örnek şekiller: tenant_id = ?, status = 'active', created_at \u003e= ?, users.id = orders.user_id ile join yapmak veya ORDER BY created_at DESC ile "en yeni önde" göstermek.

GIN ve GiST: bir satır birden çok aranabilir değer içerdiğinde

GIN (Generalized Inverted Index), bir sütunda birçok üye olduğunda ve "içeriyor mu X?" sorusunu sorduğunuzda iyi bir uyumdur. Bu JSONB anahtarları, dizi elemanları ve tam metin vektörleriyle yaygındır.

Örnek şekiller: JSONB'de metadata @\u003e {'plan':'pro'}, tags @\u003e ARRAY['urgent'], veya to_tsvector(body) @@ plainto_tsquery('reset password').

GiST (Generalized Search Tree) ise değerlerin aralıklar veya şekiller gibi davrandığı, uzaklık veya örtüşme ile ilgili sorulara uyar. Genellikle aralık tipleri, geometrik veriler ve bazı "en yakın" eşleşme aramaları için kullanılır.

Örnek şekiller: aralık sütunları ile örtüşen zaman pencereleri, trigram operatörleriyle bazı benzerlik tarzı aramalar veya PostGIS kullanıyorsanız mekansal sorgular.

Pratik bir seçim yöntemi:

  • Normal sütunlarla filtreliyor veya sıralıyorsanız, B-tree ile başlayın.
  • İçerik veya üyelik kontrol ediyorsanız GIN'e bakın.
  • "Ne kadar yakın?" veya "örtüşüyor mu?" diye soruyorsanız GiST genellikle uygundur.
  • Bir sorgu nadir veya tablo küçükse, yeni bir indekse ihtiyaç olmayabilir.
  • Sorgu şeklini tanımlayamıyorsanız, eklemeden önce ölçün (EXPLAIN).

İndeksler okumaları hızlandırır, ama yazma zamanı ve diskte maliyeti vardır. SaaS'ta bu takas en çok events, sessions ve activity logs gibi sıcak tablolarda önem kazanır.

Filtreler, sıralama ve sayfalama için B-tree desenleri

Çoğu SaaS liste ekranı aynı şekli paylaşır: bir tenant sınırı, birkaç filtre ve öngörülebilir bir sıralama. B-tree indeksleri burada varsayılan seçimdir ve genellikle bakım için en ucuz olanlardır.

Yaygın bir desen WHERE tenant_id = ? artı status = ?, user_id = ? gibi filtreler ve created_at \u003e= ? gibi bir zaman aralığıdır. Bileşik B-tree indeksleri için eşitlik filtrelerini önce koyun (= ile eşleşen sütunlar), sonra sıraladığınız sütunu ekleyin.

Uygulamalarda iyi işleyen kurallar:

  • Her sorgu tenant-odaklıysa tenant_id ile başlayın.
  • Sonra = filtrelerini koyun (genellikle status, user_id).
  • ORDER BY sütununu en sona koyun (genellikle created_at veya id).
  • Liste sayfalarını kapsayacak şekilde genişletilmiş sütunlar için INCLUDE kullanın, böylece anahtar daha genişlemez.
  • Sayfalar derinleştiğinde offset yerine seek (keyset) paginasyonu tercih edin.

Gerçekçi bir örnek: durum ile filtrelenen ve en yeni ilk olan Tickets sayfası.

-- Query
SELECT id, status, created_at, title
FROM tickets
WHERE tenant_id = $1
  AND status = $2
ORDER BY created_at DESC
LIMIT 50;

-- Index
CREATE INDEX tickets_tenant_status_created_at_idx
ON tickets (tenant_id, status, created_at DESC)
INCLUDE (title);

Bu indeks hem filtreyi hem de sıralamayı destekler, böylece Postgres büyük bir sonuç kümesini sıralamaktan kaçınabilir. INCLUDE (title) kısmı liste sayfasının daha az tablo sayfasına dokunmasına yardımcı olurken, indeks anahtarlarını filtreleme ve sıralamaya odaklı tutar.

Zaman aralıkları için aynı fikir geçerlidir:

SELECT id, created_at
FROM events
WHERE tenant_id = $1
  AND created_at \u003e= $2
  AND created_at \u003c  $3
ORDER BY created_at DESC
LIMIT 100;

CREATE INDEX events_tenant_created_at_idx
ON events (tenant_id, created_at DESC);

Sayfalama, birçok SaaS uygulamasının yavaşladığı yerdir. Offset paginasyon (OFFSET 50000) veritabanının birçok satırı geçmesini zorlar. Seek paginasyon, son görülen sıralama anahtarını kullanarak hızlı kalır:

SELECT id, created_at
FROM tickets
WHERE tenant_id = $1
  AND created_at \u003c $2
ORDER BY created_at DESC
LIMIT 50;

Doğru B-tree indeksi ile bu, tablo büyüdükçe bile hızlı kalır.

Tenant dostu indeksleme, aşırı indekslemeden kaçınma

Çoğu SaaS uygulaması çok kiracılıdır: her sorgu bir tenant içinde kalmalıdır. İndeksleriniz tenant_id içermiyorsa Postgres yine hızlı satır bulabilir, ama genellikle ihtiyaç duyulanlardan çok daha fazla indeks girdisi tarar. Tenant-dostu indeksler, her tenant'ın verilerini indekste kümeler, böylece yaygın ekranlar hızlı ve öngörülebilir olur.

Basit bir kural: sorgu her zaman tenant ile sınırlanıyorsa indeksi başlatırken tenant_id'yi öne koyun. Sonra en çok filtrelediğiniz veya sıraladığınız sütunu ekleyin.

Yüksek etki yapan sıkıcı indeksler genellikle şöyle görünür:

  • (tenant_id, created_at) — yeni öğeler listeleri ve cursor paginasyon için
  • (tenant_id, status) — durum filtreleri (Open, Paid, Failed)
  • (tenant_id, user_id) — bu kullanıcıya ait öğeler ekranları için
  • (tenant_id, updated_at) — "son değişiklikler" admin görünümleri için
  • (tenant_id, external_id) — webhook veya import kaynaklı lookuplar için

Aşırı indeksleme, her biraz farklı ekran için yeni bir indeks eklediğinizde olur. Yeni bir tane oluşturmadan önce mevcut bileşik indeksin ihtiyacınız olan en soldaki sütunları zaten kapsayıp kapsamadığını kontrol edin. Örneğin (tenant_id, created_at) varsa genellikle (tenant_id, created_at, id)'ye ayrıca ihtiyacınız yoktur, ancak gerçekten id'ye sonrasında filtre uyguluyorsanız gerekebilir.

Partial index'ler, çoğu satırın alakasız olduğu durumlarda boyutu ve yazma maliyetini azaltabilir. Soft delete ve sadece "aktif" veriler için iyi çalışırlar; örneğin yalnızca deleted_at IS NULL olanları veya sadece status = 'active' olanları indeksleyin.

Her ekstra indeks yazmaları ağırlaştırır. Insert'ler her indeksi güncellemek zorundadır ve güncellemeler tek bir sütunu değiştirseniz bile birden fazla indeksi etkileyebilir. Uygulamanız çok sayıda event alıyorsa (Koder.ai ile hızlıca kurulmuş uygulamalar dahil), indeksleri günlük kullanıcıların vurduğu birkaç sorgu şekline odaklı tutun.

JSONB indeksleme: GIN ve hedeflenmiş expression indeksleri

Ölçeklenen arama ekleyin
Postgres tam metin ve trigram indeksleme desenlerine uygun arama ekranları oluşturun.
Arama Oluştur

JSONB, uygulamanızın feature flag'leri, kullanıcı nitelikleri veya tenant başına ayarlar gibi esnek ekstra alanlara ihtiyaç duyduğunda kullanışlıdır. Sorun şu ki farklı JSONB operatörleri farklı davranır; bu yüzden en iyi indeks, nasıl sorguladığınıza bağlıdır.

İki şekil en çok önemlidir:

  • Kapsama: @\u003e kullanarak "Bu JSON bu anahtar-değerleri içeriyor mu?"
  • Yol çıkarımı: -\u003e / -\u003e\u003e ile belirli alanın değeri çıkarılır ve genellikle = ile karşılaştırılır.

GIN indeksi ne zaman uygundur

Sıkça @\u003e ile filtreleme yapıyorsanız, JSONB sütununa GIN indeksi genellikle işe yarar.

-- Query shape: containment
SELECT id
FROM accounts
WHERE tenant_id = $1
  AND metadata @\u003e '{\"region\":\"eu\",\"plan\":\"pro\"}';

-- Index
CREATE INDEX accounts_metadata_gin
ON accounts
USING GIN (metadata);

JSON yapınız öngörülebilirse ve çoğunlukla üst düzey anahtarlarla @\u003e kullanıyorsanız, jsonb_path_ops daha küçük ve hızlı olabilir, ama daha az operatör türünü destekler.

Expression indeksi ne zaman daha iyidir

UI'niz belirli bir alana (ör. plan) sıkça filtre uyguluyorsa, o alanı çıkarıp indekslemek geniş GIN'den daha hızlı ve daha ucuz olabilir.

SELECT id
FROM accounts
WHERE tenant_id = $1
  AND metadata-\u003e\u003e'plan' = 'pro';

CREATE INDEX accounts_plan_expr
ON accounts ((metadata-\u003e\u003e'plan'));

Pratik bir kural: JSONB'yi esnek ve nadiren filtrelenen özellikler için tutun, ama stabil ve sık kullanılan alanları (plan, status, created_at) gerçek sütunlara taşıyın. Hızlı iterasyon yapan bir uygulamada, hangi filtrelerin her sayfada göründüğünü gördüğünüzde bu genellikle kolay bir şema değişikliği olur.

Örnek: {\"tags\":[\"beta\",\"finance\"],\"region\":\"us\"} gibi JSONB saklıyorsanız, birden çok özellik kümesiyle (@\u003e) filtreliyorsanız GIN; en çok yönlendiren birkaç anahtar için expression indeksleri ekleyin (plan, region).

Dizileri indeksleme: GIN'in parladığı yer

Diziler saklamak kolay olduğu için caziptir. users.roles text[] veya projects.labels text[] sütunu, genellikle "bu satır bir değeri içeriyor mu?" sorusunu sorduğunuzda iyi çalışır. İşte GIN burada faydalıdır.

GIN, dizi öğelerini ayrı parçalar halinde ele alır ve onları içeren satırlara hızlı bir arama sağlar.

Diziler için fayda sağlayan sorgu şekilleri:

  • Bir değeri veya seti içerir: @\u003e (dizi içerir)
  • Bir set ile örtüşür: && (dizi herhangi bir öğeyi paylaşır)
  • Bazen: = ANY(...), ama @\u003e genellikle daha öngörülebilirdir

Kullanıcıları rol ile filtreleme için tipik örnek:

-- Find users who have the "admin" role
SELECT id, email
FROM users
WHERE roles @\u003e ARRAY['admin'];

CREATE INDEX users_roles_gin ON users USING GIN (roles);

Ve etiket kümesine göre projeleri filtreleme (hem etiketleri içermeli):

SELECT id, name
FROM projects
WHERE labels @\u003e ARRAY['billing', 'urgent'];

CREATE INDEX projects_labels_gin ON projects USING GIN (labels);

İnsanların şaşırdığı yerler: bazı desenler indeksin beklediğiniz gibi kullanılmamasına neden olur. Diziyi string'e (array_to_string(labels, ',')) çevirip LIKE çalıştırırsanız, GIN indeksi yardımcı olmaz. Ayrıca etiket içinde "başlıyor" veya bulanık eşleşmeler gerekiyorsa, bu metin arama alanına girer, dizi üyeliği değil.

Diziler aynı zamanda küçük bir veritabanı haline geldiklerinde yönetimi zorlaşır: sık güncellemeler, öğe başına metadata (kimi ekledi, ne zaman, neden) veya öğe başına analiz ihtiyaçları. O noktada project_labels(project_id, label) gibi bir join tablosu genellikle doğrulaması, sorgulanması ve evrilmesi daha kolaydır.

Arama indeksleme: tam metin ve bulanık eşleşme (GIN ve GiST)

Arama kutuları için iki desen sıkça çıkar: tam metin arama (bir konuda kayıtları bulma) ve bulanık eşleşme (yazım hataları, kısmi isimler, ILIKE desenleri). Doğru indeks "anında" ile "10k kullanıcıda zaman aşımı" arasındaki farktır.

Tam metin arama: tsvector + GIN

Kullanıcılar gerçek kelimeler yazdığında ve sonuçları alaka sırasına göre getirmek istediğinizde tam metin aramayı kullanın. Yaygın kurulum, bir tsvector saklamak (çoğunlukla generated column) ve bunu GIN ile indekslemektir. @@ ve bir tsquery ile arama yaparsınız.

-- Tickets: full-text search on subject + body
ALTER TABLE tickets
ADD COLUMN search_vec tsvector
GENERATED ALWAYS AS (
  to_tsvector('simple', coalesce(subject,'') || ' ' || coalesce(body,''))
) STORED;

CREATE INDEX tickets_search_vec_gin
ON tickets USING GIN (search_vec);

-- Query
SELECT id, subject
FROM tickets
WHERE search_vec @@ plainto_tsquery('simple', 'invoice failed');

-- Customers: fuzzy name search using trigrams
CREATE INDEX customers_name_trgm
ON customers USING GIN (name gin_trgm_ops);

SELECT id, name
FROM customers
WHERE name ILIKE '%jon smth%';

Vektörde ne saklanmalı: yalnızca gerçekten aradığınız alanları. Her şeyi (notlar, dahili loglar) dahil ederseniz indeks boyutu ve yazma maliyeti artar.

Bulanık eşleşme: trigramlar ile GIN veya GiST

Kullanıcılar isim, e-posta veya kısa ifadeler arıyorsa ve kısmi eşleşme veya yazım hatalarına tolerans gerekiyorsa trigram benzerliği kullanın. Trigramlar ILIKE '%term%' ve similarity operatörleriyle iyi çalışır. GIN genellikle "eşleşiyor mu?" sorguları için daha hızlıdır; GiST ise benzerliğe göre sıralama da önemli olduğunda daha uygun olabilir.

Kuralı:

  • Alaka-temelli metin araması için GIN + tsvector kullanın.
  • ILIKE ve yazım hatalarına tolerans için trigramları kullanın.

Dikkat edilmesi gereken tuzaklar:

  • Öncü joker karakterler olmadan (ILIKE '%abc') taramalar yapılır.
  • Çok kısa arama terimleri (1-2 karakter) trigramlarla iyi çalışmayabilir.
  • Stop kelimeler ve kök bulma (stemming) tam metin sonuçlarında kullanıcıyı şaşırtabilir; ürününüzün diline uygun bir konfigürasyon seçin.

Arama ekranlarını hızlı gönderecekseniz, indeksi özelliğin bir parçası olarak ele alın: arama UX ve indeks seçimi birlikte tasarlanmalı.

Adım adım: yavaş sorgudan doğru indekse

JSONB filtrelerini kontrol altına alın
JSONB alanları üzerinde yineleyin, sonra sık kullanılan anahtarları filtreler sürdürdüğünde sütun haline getirin.
Prototip Oluştur

Tahmin yerine uygulamanızın çalıştırdığı tam sorgu ile başlayın. "Yavaş ekran" genellikle WHERE ve ORDER BY içeren belirli bir SQL ifadesidir. Bunu loglardan, ORM debug çıktısından veya kullandığınız sorgu yakalamadan kopyalayın.

Gerçek uygulamalarda işe yarayan bir iş akışı:

  • WHERE, ORDER BY ve LIMIT dahil tam SQL'i yakalayın.
  • Aynı sorguda EXPLAIN (ANALYZE, BUFFERS) çalıştırın.
  • İşi yapan operatörlere (=, \u003e=, LIKE, @\u003e, @@) odaklanın, sadece sütun isimlerine değil.
  • Bu operatörlere uyan en küçük indeksi ekleyin.
  • Gerçekçi veri hacmiyle EXPLAIN (ANALYZE, BUFFERS) tekrar çalıştırın.

Somut bir örnek: Customers sayfası tenant ve status ile filtreliyor, en yeniye göre sıralıyor ve paginasyon yapıyor:

SELECT id, created_at, email
FROM customers
WHERE tenant_id = $1 AND status = $2
ORDER BY created_at DESC
LIMIT 50;

Eğer EXPLAIN sıralama ve sequential scan gösteriyorsa, filtre ve sıralamaya uyan bir B-tree indeksi genellikle bunu düzeltir:

CREATE INDEX ON customers (tenant_id, status, created_at DESC);

Yavaş kısım JSONB filtreleme gibi metadata @\u003e '{\"plan\":\"pro\"}' ise bu GIN'e işaret eder. Tam metin arama to_tsvector(...) @@ plainto_tsquery(...) ise o da GIN destekli arama indeksine işaret eder. "En yakın eşleşme" veya örtüşme tarzı operatör setiyse GiST uygun olabilir.

İndeksi ekledikten sonra takası ölçün. İndeks boyutunu, insert/update süresini ve bunun sadece birkaç yavaş sorguyu mu yoksa tek bir kenar durumunu mu iyileştirdiğini kontrol edin. Koder.ai gibi hızlı ilerleyen projelerde bu tekrar kontrol, kullanılmayan indekslerin birikmesini önler.

Zaman ve para israfına yol açan yaygın indeksleme hataları

Çoğu indeks sorunu B-tree vs GIN vs GiST seçimiyle ilgili değildir. Asıl sorun, uygulamanın tabloyu nasıl sorguladığıyla eşleşmeyen bir indeks oluşturmaktır.

En çok zarar veren hatalar:

  • Hiç kullanılmayan indeksler. Sorgu, indeksin desteklemediği farklı bir operatör kullanıyor veya bileşik indeks yanlış sütun sırasına sahip. WHERE cümleniz tenant_id ve created_at ile başlıyorsa ama indeks created_at ile başlıyorsa planner onu atlayabilir.
  • Düşük kardinaliteli sütunları tek başına indekslemek. status, is_active veya boolean üzerinde tek başına indeks genellikle az fayda sağlar çünkü çok fazla satırla eşleşir. Bunu seçici bir sütunla eşleştirin veya atlayın.
  • Örtüşen indeksler, depolamayı şişirir ve yazmaları yavaşlatır. Aynı tablo üzerinde benzer indeksler depolamayı iki katına çıkarabilir.
  • OFFSET'a dayalı paginasyon tuzakları. OFFSET desteklemek için indekslemek sık yapılan bir hatadır. Keyset paginasyon kullanıyorsanız, sıralamaya ve son görülen filtreye uyan bir indeks gerekir.
  • Güncel olmayan istatistikler ve tablo karışıklığı. Autovacuum yetişmiyorsa veya ANALYZE yakın zamanda çalışmadıysa, doğru indeks olsa bile planner kötü planlar seçebilir.

Somut örnek: Invoices ekranınız tenant_id ve status ile filtreleyip sonra created_at DESC ile sıralıyorsa, sadece status üzerinde bir indeks neredeyse hiç yardımcı olmaz. Daha iyi bir uyum, tenant_id, sonra status, sonra created_at (filtre önce, sıralama sondadır) içeren bileşik bir indekstir. Bu tek değişiklik genellikle üç ayrı indeks eklemekten üstündür.

Her indeksi bir maliyet olarak değerlendirin. Gerçek sorgularda karşılığını vermelidir, teoride değil.

İndeks değişikliklerini gönderme öncesi hızlı kontrol listesi

Daha hızlı SaaS ekranları oluşturun
SaaS liste sayfaları oluşturun ve hızlı yüklemeler için hangi SQL sorgularını indekslemeniz gerektiğini görün.
Koder'ı Deneyin

İndeks değişiklikleri gönderilmesi kolay ama geri alınması can sıkıcı olabilir çünkü yazma maliyetini artırabilir veya yoğun bir tabloyu kilitleyebilir. Merge etmeden önce bunu küçük bir sürüm gibi ele alın. Loglardan veya monitoring'den en çok çalışan sorgular ve en yüksek gecikmeli sorgular olmak üzere iki kısa sıralama çekin. Her biri için tam şekli (filtre sütunları, sıralama, join'ler ve kullanılan operatörler) yazın. Bu tahmini önler ve doğru indeks tipini seçmenize yardımcı olur.

Gönderim öncesi kontrol listesi:

  • Optimize ettiğiniz şeyin ne olduğunu onaylayın. Sorguların en sık ve en geç çalışanlarını listeden alın.
  • İndeksi operatöre göre eşleştirin: B-tree eşitlik/aralık/sıralama için, GIN üyelik (diziler, JSONB, tam metin) için, GiST örtüşme veya uzaklık tarzı vakalar için.
  • Ortak filtre + sıralamaya uyan tek bir bileşik indeksi tercih edin, birden fazla tek sütunlu indekse göre.
  • İndeksi dar tutun: sorgunun gerçekten kullandığı sütunları ekleyin.
  • Dağıtımı planlayın: indeks oluşturma yazmaları engeller mi, arka plana almanız veya düşük trafikte yapmanız gerekir mi?

İndeksi ekledikten sonra gerçekten yardımcı olup olmadığını doğrulayın. Aynı sorguda EXPLAIN (ANALYZE, BUFFERS) çalıştırıp önce/sonra karşılaştırması yapın. Ardından üretimi bir gün izleyin:

  • Hedef ekranlarda okuma gecikmesi düştü mü?
  • Yazma gecikmesi arttı mı (insert/update)?
  • Yeni indeks nedeniyle CPU veya depolamada sıçrama oldu mu?
  • İndeks gerçekten kullanılıyor mu, yoksa gereksiz ağırlık mı?

Koder.ai ile inşa ediyorsanız, yapılan değişikliğin yanına bir veya iki yavaş ekran için oluşturulan SQL'i koymak, indeksi uygulamanın gerçekten çalıştırdığı sorguyla eşleştirmenize yardımcı olur.

Örnek: tipik bir SaaS iş akışını indeksleme + sonraki adımlar

Bir admin ekranı hayal edin: tenant scopu, birkaç filtre, son aktifliğe göre sıralama ve bir arama kutusu içeren bir Users listesi. İndeksler teoriden çıkarak gerçek zamanda zamanı kurtarmaya başlar.

Bu ekranda genellikle göreceğiniz üç sorgu şekli:

-- 1) List page with tenant + status filter + sort
SELECT id, email, last_active_at
FROM users
WHERE tenant_id = $1 AND status = $2
ORDER BY last_active_at DESC
LIMIT 50;

-- 2) Search box (full-text)
SELECT id, email
FROM users
WHERE tenant_id = $1
  AND to_tsvector('simple', coalesce(name,'') || ' ' || coalesce(email,'')) @@ plainto_tsquery($2)
ORDER BY last_active_at DESC
LIMIT 50;

-- 3) Filter on JSON metadata (plan, flags)
SELECT id
FROM users
WHERE tenant_id = $1
  AND metadata @\u003e '{\"plan\":\"pro\"}'::jsonb;

Bu ekran için küçük ama kasıtlı bir indeks seti:

  • Liste görünümü için B-tree bileşik: (tenant_id, status, last_active_at DESC).
  • Arama için GIN: bir generated tsvector sütunu ve GIN indeksi.
  • JSONB kullanımı bazında indeksleme: @\u003e çok kullanılıyorsa GIN (metadata), yok çoğunlukla tek anahtara göre filtreliyorsanız ((metadata-\u003e\u003e'plan')) gibi expression B-tree.

Karışık ihtiyaçlar normaldir. Bir sayfa filtre + arama + JSON yapıyorsa, her şeyi tek bir dev indeks içine sıkıştırmaktan kaçının. B-tree'yi sıralama/paginasyon için tutun, sonra pahalı kısım için bir uzman indeks (çoğunlukla GIN) ekleyin.

Sonraki adımlar: bir yavaş ekran seçin, en önemli 2–3 sorgu şeklini yazın ve her indeksi amacına göre gözden geçirin (filtre, sıralama, arama, JSON). Bir indeks gerçek bir sorguyla açıkça eşleşmiyorsa, planınızdan çıkarın. Koder.ai ile hızlı iterasyon yapıyorsanız, yeni ekranlar eklerken bu incelemeyi yapmak, şema hâlâ değişirken indeks çoğalmasını önler.

SSS

Gerçek bir SaaS uygulamasında bir indeks neyi düzeltir?

Bir indeks, PostgreSQL'in eşleşen satırları bulmasını sağlar; böylece tablonun çoğunu okumak zorunda kalmaz. Listeler, panolar ve arama gibi yaygın SaaS ekranlarında doğru indeks, yavaş bir sıralı taramayı ölçeklenen ve hızlı bir aramaya dönüştürebilir.

B-tree, GIN ve GiST arasında fazla düşünmeden nasıl seçim yaparım?

Çoğu uygulama sorgusu için önce B-tree ile başlayın—= filtreleri, aralıklar, join'ler ve ORDER BY için en uygunudur. Sorgunuz ağırlıklı olarak kapsama (JSONB, diziler) veya tam metin arama ile ilgiliyse GIN'i düşünün; örtüşme veya "en yakın" tarzı sorgular için GiST daha uygundur.

Bir bileşik B-tree indeksinde sütunları sıralamak için en basit kural nedir?

= ile filtrelediğiniz sütunları önce koyun, sonra sıraladığınız sütunu en sona koyun. Bu sıra, planner'ın indeksi verimli şekilde okumasını sağlar; hem filtreyi hem de sıralamayı ek bir sort yapmadan karşılayabilir.

Çok kiracılı SaaS için indekslerime gerçekten tenant_id eklemeli miyim?

Her sorgu tenant_id ile sınırlanıyorsa, tenant_id'yi indeksin başına koymak her tenant'ın verilerini indekste gruplayarak günlük liste sayfalarında daha az veri okunmasını sağlar.

B-tree indeksinde INCLUDE ne zaman kullanılmalı?

INCLUDE, anahtarları genişletmeden liste sayfaları için indeks-only okuma sağlamak amacıyla ekstra sütunları eklemenizi sağlar. Filtre ve sıralama için birkaç sütun kullanıyorsanız ve ek alanları da görüntülüyorsanız faydalıdır.

Tüm tablo yerine kısmi indeks ne zaman daha iyidir?

Sadece ilginizi çeken alt küme için (örneğin deleted_at IS NULL veya status = 'active') bir partial index kullanın. Bu, indeksi daha küçük ve bakım maliyetini daha düşük tutar; sıcak tablolarda önemlidir.

JSONB'yi GIN ile mi yoksa expression indeksleriyle mi indekslemeliyim?

Sıkça metadata @\u003e '{"plan":"pro"}' gibi containment sorguları çalıştırıyorsanız JSONB sütununa GIN indeksi koyun. Eğer çoğunlukla tek bir JSON anahtarına göre filtreliyorsanız, (metadata-\u003e\u003e'plan') gibi bir expression B-tree indeksi genellikle daha küçük ve daha hızlıdır.

Diziler ne zaman GIN ile indekslenmeli, ne zaman normalizasyon tercih edilmeli?

Ana sorunuz "bu dizi X içeriyor mu?" ise GIN uygundur (@\u003e, && operatörleri). Eğer her öğe için metadata, sık güncellemeler veya öğe başına analiz gerekiyorsa normalleştirilmiş bir join tablosu (ör. project_labels(project_id, label)) genellikle daha sürdürülebilirdir.

Arama kutusu için doğru indeksleme yaklaşımı nedir?

Tam metin arama için bir tsvector saklayın (çoğunlukla generated column) ve GIN ile indeksleyin, sonra @@ ile sorgulayın. ILIKE '%name%' ve yazım hatalarına tolerans için trigram indeksleri (genellikle GIN) uygundur.

Yavaş bir sorgudan doğru indekse nasıl adım adım ulaşırım?

Uygulamanızın çalıştırdığı tam SQL'i alın ve EXPLAIN (ANALYZE, BUFFERS) ile nerede zaman harcandığını görün. Sorgunun operatörlerine ve sıralamasına uyan en küçük indeksi ekleyin, sonra aynı EXPLAIN'i tekrar çalıştırarak indeksin gerçekten kullanılıp kullanılmadığını ve planı iyileştirip iyileştirmediğini doğrulayın.

İçindekiler
Gerçek SaaS ekranlarında indekslerin çözdüğü problemB-tree vs GIN vs GiST basitçeFiltreler, sıralama ve sayfalama için B-tree desenleriTenant dostu indeksleme, aşırı indekslemeden kaçınmaJSONB indeksleme: GIN ve hedeflenmiş expression indeksleriDizileri indeksleme: GIN'in parladığı yerArama indeksleme: tam metin ve bulanık eşleşme (GIN ve GiST)Adım adım: yavaş sorgudan doğru indekseZaman ve para israfına yol açan yaygın indeksleme hatalarıİndeks değişikliklerini gönderme öncesi hızlı kontrol listesiÖrnek: tipik bir SaaS iş akışını indeksleme + sonraki adımlarSSS
Paylaş
Koder.ai
Build your own app with Koder today!

The best way to understand the power of Koder is to see it for yourself.

Start FreeBook a Demo