Büyük veri içeren sorunlu bir PostgreSQL ile üç tam gün uğraşınca yazayım istedim ki bu sinir bozucu problemlerin çözümü belki birilerinin işine yarar. Sunucu diskinin dolması üzerine benden önce gelen arkadaş “tabloları boşalttığını (TRUNCATE/DELETE) ama disk alanının geri gelmediğini, üstelik 30 ile 60 GB arasında değişen garip dosyaların oluştuğunu” söylemesiyle başladı..
Sorun aslında, en başından veri girişleri yanlış tasarlanmış, güncelleme ve yeni versiyon geçişlerinde iyice içinden çıkılmaz hale gelmiş, yıllar içinde veri yüzmilyonlarca kaydı geçmiş encoding problemli PostgreSQL üzerinde çalışan bir uygulamanın, kullanıcıların zaten sorgu yavaşlığından, küçük/büyük harf veya Türkçe karakter aramalarının iyi sonuç vermemesinden şikayet ederken sonunda çalışamaz hale gelmesi. (Sistem: Debian / PostgreSQL 18.1)
Bu yazıda, sahada gerçekten işinize yarayacak şekilde; hem tuple/dead tuple ve WAL nedir, disk neden şişer, büyük dosyalar ne olabilir ve nasıl yönetilir, hem de Türkçe karakter ve ve büyük/küçük harf sorununu her zaman işinize yarayacak faydalı sorgularla birlikte anlatmaya çalışayım.
Aslında PostgreSQL’in bu davranışı normal ve tasarımınından kaynaklanıyor. Depolama modeli tuple mantığına ve işlem güvenliğini sağlayan WAL (Write-Ahead Log) mekanizmasına dayanıyor. Aşağıdaki sorgıyla hangi tablonun ne kadar yer kapladığı görüntülenebilir.
|
1 2 3 4 |
SELECT relname AS tablo_adi, pg_size_pretty(pg_total_relation_size(relid)) AS tablo_size FROM pg_catalog.pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC; |
Sadece tablolar değil, çok fazla UPDATE/DELETE yapılmışsa indeksler de şişebiliyor. Aşağıdakı sorguan indekslerin durumuna bakılabilir.
|
1 2 3 4 |
SELECT relname AS index_name, pg_size_pretty(pg_relation_size(indexrelid)) AS index_size FROM pg_stat_user_indexes ORDER BY pg_relation_size(indexrelid) DESC |
Eğer indeks boyutu tablonun kendisine yaklaşıyorsa REINDEX veya VACUUM FULL zamanı gelmiş demektir.
Aşağıdaki sorgu ile arka planda çalışan VACUUM veya ANALYZE gibi komutların çalışıp çalışmadığına veya ne zaman çalıştığına bakılabilir.
|
1 2 |
SELECT relname, last_autovacuum, last_vacuum, last_analyze, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC; |
Normal vakum VACUUM (ANALYZE) tablo_adı; dead tuple’ları kullanılabilir yapar fakar diskten yer kazandırmaz. Diskten kazanmak için VACUUM FULL tablo_adi; çalıştırmak lazım ki bu hem tabloyu kilitleyecek hem de uzun sürecektir.
Yine sistemi takip etmek için kullanabileceğiniz, örneğin uzun süren bir transaction var mı sorusunun cevaplayan aşağıdaki sorgu da faydalı olacaktır.
|
1 2 3 4 |
SELECT pid, usename, state, xact_start, now()-xact_start AS xact_age, query FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start ASC LIMIT 20; |
Yine WAL’da yer tutacabilecek replication slot’lar için:
|
1 2 |
SELECT slot_name, slot_type, active, restart_lsn FROM pg_replication_slots; |
Eğer slotun kullanılmadığından eminseniz SELECT pg_drop_replication_slot('SLOT_ADI'); sorgusu ve WAL’ın genel durumu için de SELECT * FROM pg_stat_wal; sorguları işinize yarayacaktır.
Tuple nedir?
PostgreSQL’de tablo satırı, içeride “row” diye değil tuple diye düşünülür. Bir tabloya her insert ettiğinde, disk üzerinde bir tuple oluşur. Satırı update ettiğinde PostgreSQL çoğu zaman “yerinde güncellemez”; yeni bir tuple yazar, eskisini “eski sürüm” olarak bırakır (MVCC). Bu sayede farklı transaction’lar aynı anda tutarlı veri görür. Özetle Tuple = PostgreSQL’in diskte tuttuğu satır versiyonudur.
Dead tuple nedir (neden olur)?
Dead tuple, artık hiçbir transaction’ın ihtiyaç duymadığı “ölü” satır versiyonudur. Dead tuple’un tipik sebepleri:
UPDATE: Eski satır versiyonu dead olur, yeni tuple canlıdır.
DELETE: Satır “silindi” olarak işaretlenir ama fiziksel olarak diskten kalkmaz.
Büyük batch işlemler: milyonlarca satırlık update/delete. Önemli nokta: PostgreSQL “hemen dosyadan kesip atmaz.” Bu alanı daha sonra tekrar kullanmak üzere içeride bırakır. Dead tuple’ları ölçmenin en kolay yolu PosgreSQL’in pg_stat_user_tables isimli istatistik tablosudur.
|
1 2 3 4 |
SELECT schemaname, relname, n_live_tup, n_dead_tup, round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS dead_pct FROM pg_stat_user_tables ORDER BY n_dead_tup DESC; |
Sorgu sonucunda göreceğiniz n_live_tup tahmini canlı tuple sayısı, n_dead_tup ise tahmini ölü tuple sayısıdır. Dead tuple oranı arttıkça, tablo şişer (bloat), indeksler şişer, IO artar ve sorgular yavaşlar.
DELETE/TRUNCATE yaptım ama…
DELETE: Satırları siler ama geride dead tuple bırakır. Disk alanı hemen küçülmez. Sonrasında VACUUM çalışırsa, dead tuple’lar temizlenir ve alan tablo içinde yeniden kullanılabilir hale gelir. Ancak çoğu zaman dosya boyutu OS düzeyinde küçülmez.
TRUNCATE: Mantıksal olarak tabloyu hızlıca boşaltır. Disk dosyasını her zaman anında küçültmez; fakat tekrar insert’lerde o alanı sıklıkla yeniden kullanır. “Disk geri gelsin” hedefi varsa, TRUNCATE tek başına garanti değildir.
Diskin OS’e “gerçekten” geri dönmesi içim VACUUM FULL tabloyu yeniden yazar, dosyayı küçültür ama yavaştır ve işlem boyunca tablolar kilitli kalır. Böyle bir vaktiniz yoksa “new table + swap” yani yeni tabloya doldurup adını değiştirmek veya en kesin ve hızlı disk geri kazanımı olarak: “drop+recreate”
WAL nedir?
WAL = Write-Ahead Log. PostgreSQL, veri dosyalarına yazmadan önce değişiklikleri WAL’a yazar. Bu sayede: crash olsa bile tutarlılık korunur ve replikasyon/backup süreçleri çalışır.
WAL dosyaları modern sürümlerde PGDATA/pg_wal/ eski sürümlerde ise PGDATA/pg_xlog/ klasöründe bulunur.
Dosya adları 24 karakterlik hex’e benzer; bazen 30 hatta 60GB’dan büyük WAL dosyaları görebilirsiniz. Bunun nedeni çok büyük INSERT sorguları, indeks oluşturmaki saatler süren işlemler, CHECKPOINT gecikmeleri ve arşivleme yapılandırması WAL birikimine yol açabilir.
PostgreSQL’de “disk yönetimi” pratikleri
Aşağıdaki maddeler “kitabi” değil, doğrudan operasyonel gerçeklerdir.
Bloat yönetimi: VACUUM ve autovacuum
-
VACUUMdead tuple’ları temizler, alanı tablo içinde yeniden kullanılabilir yapar. -
VACUUM (ANALYZE)istatistikleri günceller, sorgu planlarını iyileştirir. -
Autovacuum açık değilse (default olarak değil sanırım), büyük import sonrası bloat büyür.
Disk geri kazanma: ne zaman “FULL” gerekir?
-
Disk alanını OS’e geri almak istiyorsan, çoğu zaman
VACUUM FULLveya “swap-table” gerekir. -
Normal VACUUM sonucunda disk alanında değişiklik beklemeyin. PostgreSQL tabloları dosya olarak tutar. Bu dosya büyüdükçe işletim sistemi “tamam, bu kadar alan kullandın” der. Ama PostgreSQL dosya boyunu küçültmeyi sevmez, onun yerine dosya boyutunu sabit tutarak içinde boşluk bırakır
WAL Yönetimi
WAL’ın “kontrolden çıkması” genelde büyük yazma yükü + düşük checkpoint sıklığı, archiving açık olduğu halde arşiv hedefi/komutu çalışmaması, replikasyon slot’u olduğu halde subscriber’ın geride kalması ve uzun transaction’lar durumlarında gerçekleşir ve eski WAL’ın temizlenmesini engeller.
Büyük yüklemelerde disiplin
Bulk INSERT yapmadan önce şema/PK/unique/constraint tasarımını netleştirmek gerekiyor. Çünkü indeksler hem disk hem WAL maiyetini katlıyor. En güzeli test aşamasına gelene kadar hiç indeksleri karıştırmamak.
Bir sonraki büyük veri projemde yazıyı güncelleyene kadar, özetle
Tuple: Bir satırın diskteki versiyonu
Live tuple: Güncel/erişilebilir satır versiyonu
Dead tuple: Silinmiş ya da eski versiyon olduğu için artık gereksiz satır verisi
Bloat: Dead tuple + dolgu faktörleri nedeniyle tablo/indeksin şişmesi
WAL: İşlem logları; crash recovery ve replikasyon için şart
Checkpoint: WAL’dan veri dosyalarına senkronizasyon noktaları
kullandığım terimleri kısaca açıklayarak böyle bir durumla karşılaştmamak için yapabileceklerimize bakalım.. Öncelikle postgresql.conf dosyasında aşağıdaki değişikliklerden başlayalım.
|
1 2 3 4 5 6 |
autovacuum = on max_wal_size = 8GB min_wal_size = 1GB checkpoint_timeout = 10min checkpoint_completion_target = 0.9 |
SHOW autovacuum; → sorgusunun sonucu “off” ise, dead tuple birikmesi kaçınılmaz. Diğer ayarlar WAL boyutunu küçük tutmak ve CHECKPOINT çalışma aralıklarını hızlandırmak. Autovacuum demşişken
|
1 2 |
SHOW autovacuum_vacuum_scale_factor; SHOW autovacuum_analyze_scale_factor; |
Bu işlemlerden sonra PostgreSQL servisini yeniden başlatmanı gerekecek.
|
1 2 3 |
sudo systemctl reload postgresql # reload yetmezse: sudo systemctl restart postgresql |
KONFİGÜRASYON DOSYASI
Genellikle /var/lib/postgresql/18/main veya /etc/postgresql/18/main/ altında bulunan postgresql.conf dosyasıdır. Tam lokasyonu için SHOW config_file; ve SHOW data_directory; sorguları kullanılabilir.
AUTOVACUUM ayarlarından başlıyorum çünkü sanıldığından çok daha önemli olduğunu tecrübe ettim. Aslında yüksek sayıda tuple işlem gördüğünde devreye giren VACUUM ve ANALYZE komutlarının peşpeşe otomatik olarak çalışmasından ibaret. Opsiyonel olarak konulmuş bir komut ve default olarak aktif değil. İnternette optimum ayarın %20 + 50 tuple olarak çok sayıda tavsiye olsa da bence bu rakam tamamen donanım, disk, performans ve indekslerle de ilgili. Keza bu ayarı çok azaltırsanız geç kalıyor ve dead tuple’lara yetişemiyor, çok yüksek olunca da diske rakamlar düşünce geç kalabiliyor, çok yükselince de disk ve I/O’yu boğuyor.
Temel ayarlar, autovacuum = on ki kapalıysa bloat kaçınılmaz oluyor, autovacuum_max_workers aynı anda kaç tablo vacuum’lanabilir.autovacuum_naptime ise Autovacuum’un kaç saniyede bir devreye girdiği. Benim durumumda varsayılan değerlerin çok düşük kaldığı en önemli değer autovacuum_vacuum_scale_factor ki 0.1 (%10) ayarlı idi fakat yüzmilyonlarca satır ve onlarca kullanıcı için %10 çok büyük rakam. Minimum ölü tuple eşiği autovacuum_vacuum_threshold disk sınırlama ve agresiflik değerleri autovacuum_vacuum_cost_limit ve autovacuum_vacuum_cost_delay ayarlarında. Büyük veri ve çok kullanıcılı sistemlerde sorun yaşayanlar buradan başlayabilir.
WAL dosyalarının hedef büyüklük aralığı için max_wal_size / min_wal_size, Checkpoint sıklığı checkpoint_timeout ki çok uzun süre olursa WAL büyüyor, kısa olursa da I/O artıyor. Checkpoint yazımını zamana yayıp disk aktivitesini azaltan checkpoint_completion_target öte yandan WAL’in diske yazdığı veriyi sıkıştıran ama bunu yaparken CPU’ya aşırı yüklenen wal_compression ayarı da bu dosyada. Replikasyon slotları yüzünden WAL’in sınırsız büyümesini engelleyen max_slot_wal_keep_size ve wal_level ve replica/logical değerlerine pek dokunmayın derim.
Memory ile ilgili ayarlar tamamen üzerinde çalıştığı makine ile ilgili olan PG’nin ana cache’i shared_buffers ve en öndemlisi sorting/hashing, order, index ve join işlemleri için gereken memory work_mem. CREATE INDEX, VACUUM, REINDEX gibi bakım işleri için maintenance_work_mem ve planner’a “OS cache dahil toplam cache” için ise effective_cache_size
Planner ve ağ ayarları max_connections Panel tarafında runaway query’leri kesmek için ise statement_timeout var. Açık/uzun süre transactionlar WAL’ın büyümesini engelleyen idle_in_transaction_session_timeout ayarı dabu configürasyon dosyasından yapılabilir.
Disk ve IO ve güvenilirliği için synchronous_commit (on en güvenlisi, off daha hızlı ama crash anında veri kaybı riski var) son alarak çökme anında açık kalmasının işe yarayacağı full_page_writes var ve kalan çoğu loglarla ilgili ayarları geçiyorum.
TÜRKÇE İÇİN EN UYGUN ENCODİNG, İNDEX VE SORGULAR
Yeni kurulumlarda UTF-8’den şaşmamak lazım derdim ama bu biraz hangi UTF-8 (locale/collation) olduğuyla da ilgili. Yine de, tüm unicode’ı (ç, Ğ, İ, ı dahil) kapsadığı, neredeyse standart hale geldiği ve Web, API, JSON, Python, PHP, JS vb ne varsa sorunsuz çalıştığı için en iyi tercih.
Zaten sorun genellikle UTF-8’den değil, karşılaştırma ve sıralama kurallarından (locale/collation) kaynaklanıyor. Dilimizde İ ile I veya i ile ı eşit olmadığından lower(), upper(), ORDER BY veya LIKE davarınışları da locale’e bağlı.
Türkçe için, aslında sadece PG’de değil tüm veritabanlarında en güvenli ve pratik yol UTF-8 ile birlikte “basit / C” collation kullanıp arama mantığını uygulama ve index katmanında kontrol etmek. Çünkü hızlı, sürpriz yok ve lower(unaccent()) gibi normalize edilmiş alanlarla kontrol altında. Örnek tablo:
|
1 2 3 4 |
CREATE DATABASE mydb WITH ENCODING = 'UTF8' LC_COLLATE = 'C' LC_CTYPE = 'C'; |
Bu yaklaşım özellikle büyük veride, normalize edilmiş arama tabloları ile en sağlam yol görünüyor.
Yerel Türkçe collation “tr_TR.UTF-8”
|
1 2 |
LC_COLLATE = 'tr_TR.UTF-8' LC_CTYPE = 'tr_TR.UTF-8' |
ORDER BY’da doğru sıralamayı vermesi ve küçük/büyük harfleri doğru çevirmesiyle veri çok büyük değilse tercih edilebilir. Ancak performansı düşük olacaktır, indeks davaranışı veya farklı sistemlere geçişlerde sorun çıkarabilir.
PG’de MySQL/MariaDB’de olduğu gibi utf8mb4 yok dolayısıyla 3 byte’lık UTF-8 kullanmak durumundayız. Türkçe karakterli destekleyen alternatiflerden Latin1 / ISO-8859-9 var ama JSON/API’lerde uyumsuz, artık tarih olmak üzere ve emojiler yok ASCII adı üstünde Amerikan standartı ve Türkçe olmadığından bir seçenek değil. Dolayısıyla Türkçe dilini collation üzerinden ele alıp seçenekleri değerlendirmek zorunlu.
PG iki libc (glibc) ve ICU isimli iki collation sağlayıcıyla geliyor. libc tamamen işletim sistemine bağlı olduğu ve tutarsız çalıştığı için pas geçiyor ve daha tutarlı ve çoklu dillerde daha öngörülebilir olan ICU (Unicode Collation Algorithm) ye geçiyorum. Türkçe’de en klasik problem: “case folding”, yani basitçe İngilizcede I = i iken Türkçe’de olmaması. ICU özellikle sıralama ve karşılaştırmada doğru locale ile bu işi iyi kotarıyor. Burada deterministic ve nondeterministic kritik iki ayrım devereye giriyor, bu konu epey ayrıntılı, ilgilenenler araştırabilir. Ben sizi kısa yoldan Türkçe veri içeren veride hız ve eşleşme sorununa karşı (lower + unaccent + trigram) formülüyle tarif edebileceğim bulabildiğim en iyi çözüme götüreyim.
|
1 2 3 4 5 |
-- <em>Bu sorgu sonucunda "i" harfi görüyorsanız ICU hazır demektir.</em> SELECT collname, collprovider, collisdeterministic FROM pg_collation WHERE collprovider IN ('i','c') ORDER BY collprovider, collname LIMIT 50; |
Türkçe ICU Collation Oluşturma (deterministic vs nondeterministic )
Yukarıdaki sorguda ICU desteği açıksa Türkçe sıralama/karşılaştırma için deterministic bir örnekle başlayalım.
|
1 2 3 4 |
CREATE COLLATION IF NOT EXISTS tr_icu (provider = icu, locale = 'tr-TR', deterministic = true); SELECT * FROM person ORDER BY adsoyad COLLATE "tr_icu"; |
Bu sorgu özellikle ORDER BY için anlamlıdır. Bunu “case-insensitive” davranışa yaklaştırmak için ICU “strength” ayarlanır. ICU locale tag’lerinde sık kullanılan ks (strength) / “level” ve kf (case first) gibi parametreler ayarlanır. Genel amaçlı, “case” farkını daha az önemseyen nondeterministic bir örnek:
|
1 2 3 4 5 |
CREATE COLLATION IF NOT EXISTS tr_icu_ci (provider = icu, locale = 'tr-TR-u-ks-level2', deterministic = false); SELECT * FROM person WHERE adsoyad COLLATE "tr_icu_ci" = 'istanbul'; |
Bu yöntemin (deterministic = false) dezavantajı ise bazı index kullanımlarında yeterince hızlı sonuç vermemesi. Buraya tekrar döenceğim. Öncesinde, son projede kullanıcıların veriyi sadece telefon numarası, TC Kimlik veya ad soyada göre aradığını farkettim, artık tabloları temizleyip sunucudaki disk problemini çözmüştüm ve yeni yapılandırma için yer açılmıştı.. ,,Veritabanındaki 50 kadar tablodan tüm telefon numaralarını 10 haneli normalize ederek bir tablo, tüm TC kimlik numaraları için bir tablo ve isimler için bir tablo yaratarak 3 tane arama tablosu oluşturdum..
Böylece, öncesinde JOIN’lerle bir dolu tablodan veri getiren sistem yerine, büyük fakat muntazam indekslenmiş, kaynak tablosunun ID’si, normalize edilmiş sütun ve btree/trgm indeks ile daha kontrollü ve çok daha hızlı sonuç aldığım 3 arama tablosu sayesinde 30 saniyelik sorgular 10 saniyenin altına düştü. Türkçe karakter/harf varyasyonlarından kaynaklanan sorunlar için ise çözüm yukarıda bahsettiğim (lower + unaccent + trigram) formülüydü. Tek tek, teknik ama anlaşılır şekilde açıklamaya çalışayım.
unaccent(text) nedir, ne iş yapar?
unaccent, metindeki aksan/diakritik işaretlerini kaldırır. Örnek:
Çağlar -> Caglar, İstanbul → Istanbul (dikkat: Türkçe i/İ konusu ayrı), Şeker -> Seker, Özgür -> Ozgur, Ğ -> G gibi.. Buradaki amaç kullanıcı “caglar” yazsa da “Çağlar”ı bulabilmesi. Bu nedenle konu Türkçe ile doğrudan ilgili: ç, ğ, ı, İ, ö, ş, ü gibi harfler farklı yazılabildiği için kaça aramaları azaltmak için unaccent kullanılıyor.
unaccent(regdictionary, text) nedir?
PG’nin bazı kurulumlarında unaccent fonksiyonunun hangi sözlüğün kullanılacağının belirtilebildiği artı bir parametre alan bir sürümü daha var. Sözlükte, “hangi karakterleri nasıl dönüştüreceğini” belirleyen kurallar yer alıyor.
IMMUTABLE wrapper
Aynı girdiye her zaman aynı çıktıyı veren fonksiyonlara IMMUTABLE, ortama göre değişebilitorsa da VOLATILE deniyor. Bu durumda sözlüğe göre değişen unaccent fonksiyonu da değişebilir olduğundan PG indekleme yapmayıp hata verecektir. Yani değişken fonksiyonla index yaparsa, sonra davranış değişitinde indeks bozulacaktır.
Burada wrapper devereye giriyor, “sarıcı / kavrayıcı” anlamından da anlaşılacağı üzere değişken fonksiyonu sararak (ör: lower(unaccent(q)) ) sabit hale getiriyor ve bu sayede PG indeksleme yapabiliyor.
x bozulur bir expression indeks oluştururken indekse giren fonksiyonlar IMMUTABLE olmalıdır.
|
1 |
CREATE FUNCTION immutable_unaccent(t text) RETURNS text IMMUTABLE AS $$ SELECT unaccent(t) $$; |
lower() niye var? (case-InsensItIve)
lower(q) Büyük/küçük harf farkını kaldırır. (“MEHMET” = “Mehmet” => “mehmet”). Türkçede ekstra bir detay daha var: İ ve I / ı ve i dönüşümleri locale’a göre değişebileceğinden en sağlam yaklaşım normalize edilmiş tek bir form üretmektir.
GIN indeKS
.GIN = Generalized Inverted Index (ters indeks).
B-tree index “değerleri sırayla tutar”, GIN ise metni parçalara ayırır ve bu parçaların hangi satırlarda geçtiğini tutar. Bu indeks özellikle full-text search (to_tsvector), trigam (pg_trgm) ve array/JSON aramalarında kullanılır.
Trigram, metni 3 harflik parçalara böler. Örrnek: “mehmet” → meh, ehm, hme, met. GIN trigram index ise ILIKE '%meh%' gibi “içeren” aramalarda kullanılır ve yazım hatası / benzerlik araması (fuzzy) aramalarında oldukça iyi sonuç verir.
Fuzzy aramalara örnek olarak, diyelim ki kullanıcı “caglar yilmaz” şeklinde aradı fakat kaynaktaki kayıt “çağlar yılmaz” veya harf hatası yapmış olsun caglar yilmsz, bu durumda trigram ile “benzerlik” yakalanarak kaynağa ulaşılacaktır. Özellikle yazım hatalarına veya veritabanındaki kayıtlar farklı kişilerce standart dışı girilmişse trigram büyük fark yaratır.
Veritabanlarında Türkçe açısından temel olarak 3 sorun kaynağı bulunuyor. Aksanlı ç, ğ, ö, ş, ü gibi harfler (unaccent ile çözüldü), İ / I / ı / i meselesi (lower ile büyük oranda çözüldü) ve yanlış/eksik/benzer harfler. (trigam ile çözüldü)
Özetle;
unaccent: Türkçe ç/ş/ğ/ü/ö -> ASCII’ye yaklaştırır, kaçırmayı azaltır
lower: büyük/küçük farkını kaldırır
IMMUTABLE wrapper: Postgres’in expression index izni için teknik şart
GIN + trigram: “içeriyor mu / benziyor mu” gibi fuzzy aramayı hızlandırır
Unutmadan, bunların çalışabilmesi için aşağıdaki sorgularla 2 extension’ı kurmak gerekli.
|
1 2 |
CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm; |
Uzun bir yazı oldu ve umarım birilerinin işine yarar. Yazarken aklıma 1990’lı yıllar geldi, yaşı yeten PC kullanıcıları iyi hatırlayacaktır. O yıllarda hemen her veride Türkçe karakter problemi yaşar, bil bilgisayarda düzgün görünen karakter diğerinde bozulur , bir tarayıcı Türkçeleri gösteremez diğeri gösterir, veritabanı için sürücüler olmaz vs Türkçe karakterleri düzelteceğiz diye çok fazla mesai harcamıştık.. Hala devam ediyoruz..
