PostgreSQL "index row size exceeds maximum" — Neden Uzun Metin İndekslenemez?
Uzun metin kolonunu indekslerken çıkan "index row size exceeds" hatasının nedeni, prefix/ifade indeksi, MD5 ve GIN trigram alternatifleri.
Operasyonel Doğrulama & Güvenilirlik Standardı
Hata mesajı semptomdur, kök neden değildir. Sunucuyu yeniden başlatmadan veya rastgele ayar değiştirmeden önce aşağıdaki "Nedeni ayırt eden tek sorgu"yu çalıştırarak gerçek senaryoyu netleştirin.
▸Belirti
ERROR: index row size 3112 exceeds btree version 4 maximum 2704 for index "idx_makale_ozet"
DETAIL: Index row references tuple (0,14) in relation "makale".
HINT: Values larger than 1/3 of a buffer page cannot be indexed.Hata, indeks oluştururken ya da yeni veri eklerken ortaya çıkar. Sebep mimaridir: standart PostgreSQL btree indeksinde tek bir girdi, 8 KB'lık buffer sayfasının üçte birini geçemez (sayfa başlıkları ve overhead düşüldüğünde yaklaşık 2.704 bayt). Bu sınır keyfi değildir; bir btree indeks sayfasının B-Tree dengesini koruyabilmesi için her yaprak sayfada en az üç girdi barındırabilmesi zorunludur.
▸Nedeni Doğrulayan Teşhis Sorgusu
Tabloda hangi satırların bu sınırı aştığını tespit edin:
SELECT id, length(ozet) AS karakter_sayisi, octet_length(ozet) AS bayt_boyutu
FROM makale
ORDER BY octet_length(ozet) DESC
LIMIT 10;ğ, ü, ş, ı, ö, ç) 2 bayt, özel semboller ve emojiler 4 bayt yer tutar. Dolayısıyla varchar(2000) tanımlı bir kolon 4.000 bayta kadar çıkabilir; yani "2.000 karakter sınırım var" varsayımı indeksin patlamasını engellemez.▸Çözüm: Hangi Erişim Desenine İhtiyacınız Var?
Çözüm, o kolonu nasıl sorguladığınıza bağlıdır. İhtiyaca uymayan bir yöntem indeksi hatasız oluştursa bile sorgu planlayıcısının indeksi yoksaymasına yol açar.
1. Eşitlik Araması Yapıyorsanız → MD5 / SHA-256 Hash İndeksi
CREATE INDEX idx_makale_ozet_md5 ON makale (md5(ozet));
-- Sorgu da aynı ifade ile yazılmalıdır:
SELECT * FROM makale WHERE md5(ozet) = md5('aranan uzun metin...');32 karakterlik sabit çıktı üretir ve 2.704 bayt sınırına asla takılmaz. Yalnızca birebir eşitlik (=) için çalışır; aralık veya LIKE sorgularında kullanılamaz.
2. Ön Ek (Prefix) Araması Yapıyorsanız → İfade İndeksi
CREATE INDEX idx_makale_ozet_prefix ON makale (left(ozet, 200));
-- Sorgu kullanımı:
SELECT * FROM makale WHERE left(ozet, 200) = left('aranan metin...', 200);Bu indeks WHERE left(ozet, 200) = ... veya ORDER BY left(ozet, 200) sorgularında kullanılır. Sorguda left() ifadesini açıkça belirtmezseniz planner indeksi görmez.
3. LIKE '%kelime%' Araması Yapıyorsanız → GIN Trigram
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_makale_ozet_trgm ON makale USING gin (ozet gin_trgm_ops);GIN trigram indeksi metni 3'er karakterlik parçalara (trigram) böler; sayfa sınırı problemi yaşanmaz ve ILIKE '%arama%' sorguları dramatik şekilde hızlanır. Dezavantajı: İndeks boyutu ve yazma maliyeti btree'ye göre yüksektir.
4. Tam Metin Araması Yapıyorsanız → tsvector
Kelime bazlı doğal dil aramaları için en doğru araçtır:
ALTER TABLE makale ADD COLUMN arama tsvector
GENERATED ALWAYS AS (to_tsvector('turkish', coalesce(baslik,'') || ' ' || coalesce(ozet,''))) STORED;
CREATE INDEX idx_makale_arama ON makale USING gin (arama);5. İndekslemeye Gerçekten Gerek Var mı?
Uzun metin kolonları çoğunlukla yalnızca kullanıcı arayüzünde gösterilmek üzere okunur. WHERE koşullarında filtreleme veya sıralama için kullanılmıyorsa doğrudan indeksten çıkarın; disk alanı ve yazma (HOT tuple) kazancı elde edersiniz.
▸Ortak Karşılaştırma: PostgreSQL vs. MySQL İndeks Boyut Sınırları
| Mimari Kriter | PostgreSQL (B-Tree) | MySQL InnoDB (DYNAMIC) | MySQL InnoDB (COMPACT/REDUNDANT) |
|---|---|---|---|
| Azami Anahtar Boyutu | 2.704 bayt (8 KB / 3) | 3.072 bayt | 767 bayt |
| Karakter Sınırı (utf8mb4) | ~676 karakter | 768 karakter | 191 karakter |
| Sınır Aşım Hatası | index row size exceeds maximum | Error 1071: Specified key too long | Error 1071: max key length is 767 |
| Önerilen Çözüm | MD5 hash / left(col, n) / GIN Trigram | Prefix index col(191) / Generated Hash | ROW_FORMAT=DYNAMIC geçişi |
| İlgili Kardeş Rehber | Bu Rehber (H-21) | MySQL Hata 1071 Rehberi (H-22) | MySQL Hata 1071 Rehberi (H-22) |
▸Bunu Bir Daha Yaşamamak İçin
Sınırsız uzunluktaki (text, limitsiz varchar) kolonları doğrudan indekslemeyi mimari bir anti-pattern kabul edin. Şema tasarımında veri girişi katmanına koruma ekleyin:
ALTER TABLE makale ADD CONSTRAINT chk_ozet_bayt_siniri
CHECK (octet_length(ozet) <= 1000);Bu kural, sorunu indeks oluşturma aşamasında değil veri yazma anında yakalayarak operasyonel sürprizleri engeller.
- •Kardeş Rehber (MySQL): MySQL Error 1071: Specified key was too long
- •İlgili Hata: PostgreSQL duplicate key violates unique constraint
- •Veritabanı Hub: PostgreSQL Üretim Mimarisi Rehberi
Diğer Veritabanı Hata Teşhisleri
Kriz anında başvurabileceğiniz diğer doğrulanmış hata rehberleri.
PostgreSQL Bağlantı Tükenmesi ve too many clients Teşhisi
FATAL: sorry, too many clients already ve remaining connection slots hatalarının üç temel kök nedeni, yetki sınırlarıyla salt okunur pg_stat_activity teşhisi ve kalıcı iki katmanlı havuzlama çözümü.
PostgreSQL password authentication failed Hatası: Neden ve Çözüm
psql password authentication failed hatasının dört nedeni: yanlış şifre, md5/SCRAM uyuşmazlığı, pg_hba.conf sırası ve yanlış cluster. Adım adım teşhis.
PostgreSQL connection refused: 8 Adımda Kesin Teşhis
Connection refused, timeout ve "no pg_hba.conf entry" arasındaki fark; listen_addresses, port, firewall ve Docker port yayınlama kontrolleri.