- PostgreSQL’in tam metin araması, ayrı bir arama motoru olmadan
tsvector,tsquery,@@,ts_rank, GIN indeksi bileşenlerini birleştirerek arama işlevi kurma yaklaşımıdır - Arama terimlerini ve belgeleri lexeme düzeyinde normalize eder; AND, OR, NOT ve FOLLOWED BY gibi operatörlerle sorguları ifade ederek yaygın arama sözdiziminin büyük bölümünü uygulayabilir
- GIN indeksi, örnek ortamda arama süresini 200 ms’nin üzerindeyken yaklaşık 4 ms’ye düşürdü; ancak sonuç sayısı arttığında
ts_rankile sıralama ve puanlama maliyeti darboğaz olabilir - İlgililik ayarı; başlık ağırlığı, oy sayısı, puan, tür ve güncellik gibi sinyalleri sıralama ifadesine ekleyerek veya
setweightile sütun ağırlıkları vererek yapılabilir - Yazım hatası toleransı, fasetli arama, otomatik tamamlama, tam ifade araması ve hibrit arama da mümkündür; ancak PostgreSQL’de bileşenleri elle birleştirmek gerekir ve büyük veri kümelerinde performans sınırları doğrulanmalıdır
PostgreSQL tam metin aramasının yaklaşımı
- PostgreSQL, tam metin araması için düşük seviyeli bileşenler sunar ve bunlar birleştirilerek arama motoru özellikleri oluşturulabilir
- Bu yaklaşım esnektir; ancak tam metin aramasını ana kullanım alanı yapan Elasticsearch, Typesense ve Meilisearch’e göre daha fazla uygulama işi gerektirir
- Örnek sorgular, Kaggle’daki Wikipedia Movie Plots veri kümesini kullanır
- 34.000 film başlığı içerir
- CSV biçimindeki boyutu yaklaşık 81 MB’tır
Temel bileşenler
- PostgreSQL tam metin araması şu unsurlar etrafında çalışır
tsvector: Aranacak metni normalize edilmiş lexeme listesi olarak saklartsquery: Normalize edilmiş arama sorgusunu ifade eder@@:tsqueryiletsvectoreşleşiyor mu diye kontrol eden eşleştirme operatörüts_rank,ts_rank_cd: Arama sonuçlarının ilgililik puanını hesaplar- GIN indeksi:
tsvectorüzerinde verimli sorgulama için ters indeks
tsvector ve arama ayarları
tsvector, sıralanmış lexeme listesini saklar- Lexeme, token’a benzer; ancak aynı kelimenin farklı biçimlerini tek bir forma yaklaştıracak şekilde normalize edilmiş dizgedir
- İngilizce ayarında büyük harfler küçültülür ve ekler kaldırılarak normalizasyon yapılır
- İngilizce bir cümle
to_tsvectorile ayrıştırıldığında “I”, “to”, “an” gibi stop word’ler kaldırılır- “refuse” ve “Refusing” her ikisi de
refusbiçimine dönüşür - Noktalama işaretleri yok sayılır
- Kelimelerin özgün metindeki konumları ve ağırlıkları da kaydedilir
- “refuse” ve “Refusing” her ikisi de
englisharama ayarı yerinesimpleayarı kullanılırsa kelimeler metinde bulundukları biçimiyle dahil edilir- “refuse” ve “refusing” ayrı lexeme’ler olarak kalır
simpleayarı özellikle etiket veya tag içeren sütunlarda faydalıdır
- PostgreSQL birçok dil için yerleşik arama ayarı sunar; ancak CJK (Çince, Japonca, Korece) ayarı yoktur
- Desteklenmeyen dillerde
simpleayarı pratikte iyi çalışabilir - Yine de CJK için yeterli olup olmadığı net değildir
- Desteklenmeyen dillerde
tsquery ve sorgu ifadesi
tsquery, normalize edilmiş arama sorgusunu ifade eden veri tipidir- Arama terimleri önceden normalize edilmiş lexeme’ler olmalıdır
- Birden çok arama terimi AND, OR, NOT ve FOLLOWED BY operatörleriyle birleştirilebilir
to_tsquery,plainto_tsquery,websearch_to_tsquery; kullanıcının girdiği metni uygun birtsquerybiçimine dönüştürmeye yardımcı olur- Temel görevleri, giriş metnindeki kelimeleri normalize etmektir
websearch_to_tsquerykullanıldığında, sıradan bir arama kutusuna daha yakın sorgular oluşturulabilirdarth vader, her iki kelimenin de belgede bulunması gereken mantıksal AND olarak işlenir- OR araması ve kelime hariç tutma da mümkündür
- İfade araması, kelimelerin sırayla devam ettiği bir yapıyı ifade eder
- İngilizce ayarında “the” gibi stop word’ler kaldırıldığı için bazı ifade aramalarında neredeyse tüm ifade yok olabilir
- Bu durumda
simpleayarı beklenen sonucu verebilir
- Bu durumda
@@operatörü,tsqueryiletsvectoreşleşmesini kontrol etmek için kullanılır
GIN indeksi ve arama performansı
- GIN, Generalized Inverted Index’in kısaltmasıdır ve bileşik değerlerin içindeki öğe değerlerini bulmaya yönelik sorgular için tasarlanmış bir indeks türüdür
- GIN yalnızca metin aramasında değil, JSON sorgularında da kullanılabilir
- Aranabilir birden çok sütunu birleştiren bir
tsvectorsütunu oluşturulabilir ve bu sütun üzerinde GIN indeksi yaratılabilir - Örnek ortamda GIN indeksi, arama süresini 200 ms’nin üzerinden yaklaşık 4 ms’ye düşürdü
Sıralama ve ilgililik hesaplama
- İyi bir arama deneyimi için sonuçların ilgililik temelinde sıralanması gerekir
- PostgreSQL, ön tanımlı iki sıralama fonksiyonu sunar:
ts_rankvets_rank_cd- Her iki fonksiyon da arama terimlerinin geçiş sıklığını dikkate alır
ts_rank_cd, eşleşen lexeme’ler arasındaki yakınlığı da yansıtır
- İlgililik, uygulamanın doğasına büyük ölçüde bağlıdır
- Varsayılan sıralama fonksiyonları bir başlangıç noktasıdır; gerekirse özel sıralama fonksiyonları yazılabilir veya başka unsurlarla birleştirilebilir
ts_rank, her sonucunsearchsütununa erişmek zorundadırWHEREkoşuluna çok sayıda satır uyarsa PostgreSQL, sıralama hesabı ve düzenleme için bu satırların tamamını ziyaret etmek zorunda kalır- Örnek ortamda bir sorgu 5–7 ms’de dönerken,
darth OR vadergibi 1.000’den fazla sonucu sıralaması gereken sorgular yaklaşık 80 ms sürdü
İlgililik ayarlama
- Kelime sıklığına dayalı ilgililik iyi bir varsayılan olsa da veride sıklıktan daha önemli sinyaller bulunabilir
- Film veri kümesinde şu sinyaller ilgililiğe yansıtılabilir
- Başlıkta eşleşen sonuçları, açıklama veya özetten eşleşen sonuçlardan daha önemli saymak
- Puan veya oy sayısına göre daha popüler filmleri yükseltmek
- Kullanıcı komediyi seviyorsa komedi filmlerini daha üstte göstermek
- Eski yapımlara göre yeni yapımları daha ilgili kabul etmek
- Özel arama motorları, farklı sütun veya alanların sıralamayı etkilemesini ayarlayan özellikler sunar
- İlgili dokümantasyon örnekleri: Elastic, Typesense, Meilisearch
Sayı, tarih ve tam değer tabanlı yükseltme
- PostgreSQL başka sütunlara dayalı yükseltmeyi doğrudan sunmaz; ancak sıralama sonuçta bir sıralama ifadesi olduğu için kendi sinyallerinizi ekleyebilirsiniz
- Oy sayısını yansıtmak için sıralama puanına oy sayısına dayalı bir yükseltme eklenebilir
- Örnekte etkiyi yumuşatmak için logaritma kullanılır
0.01katsayısı, yükselticiyi sıralama puanıyla benzer ölçeğe getirmek için kullanılır
- Oy sayısı belirli bir düzeyin üzerindeyse puanı artıran daha karmaşık fonksiyonlar da oluşturulabilir
- Belirli bir türü öne çıkarmak isterseniz, değer yalnızca belirli bir sütun değeriyle eşleştiğinde katsayı döndüren
valueBoosterbenzeri bir fonksiyon kullanılabilir
Sütun ağırlıkları
tsvectoriçindeki lexeme’lere ağırlık verilebilir- PostgreSQL dört ağırlık sınıfını destekler: A, B, C ve D
- En yüksek ağırlık A’dır
- En düşük ağırlık ve varsayılan değer D’dir
setweightfonksiyonu,tsvectorsütunu oluşturulurken ağırlıkları kontrol etmek için kullanılabilir- Başlık sütununa daha yüksek ağırlık verilirse arama terimini başlıkta içeren filmler sonuçların üstüne çıkar ve sıralama puanı artar
- Yalnızca dört ağırlık sınıfı olması sınırlayıcıdır ve ağırlıklar
tsvectorhesaplanırken uygulanmalıdır
Yazım hatası toleransı ve fuzzy arama
- PostgreSQL,
tsvectorvetsquerykullanırken fuzzy arama veya yazım hatası toleransını doğrudan desteklemez - Sorgu tarafında yazım hatası olduğu varsayımıyla şu yaklaşım uygulanabilir
- İçerikteki tüm lexeme’leri ayrı bir tabloda indekslemek
- Sorgudaki her kelime için benzerlik ya da Levenshtein distance ile aday kelimeleri aramak
- Bulunan kelimeleri içerecek şekilde sorguyu değiştirmek
- Değiştirilen sorguyla arama yapmak
- Örnekte, arama motorlarının fuzzy arama için kullandığı yöntemlerden biri olduğu için Levenshtein distance kullanılır
- Aday kelime listesi elde edildikten sonra sorgu, bu kelimelerin tümünü içerecek şekilde ayarlanmalıdır
Fasetli arama
- Fasetli arama, özellikle e-ticaret sitelerinde kullanıcıların arama alanını tekrar tekrar daraltabilmesi için yaygın olarak kullanılır
- PostgreSQL’de kategoriler elle tanımlanıp aramanın
WHEREkoşuluna eklenerek uygulanabilir - Mevcut veriden kategorileri algoritmik olarak üretmek de mümkündür
- Örnekte filmlerin yılına göre “Decade” faseti oluşturulur
- Her on yıllık dönem için eşleşme sayısı da hesaplanıp parantez içinde gösterilebilir
- Birden fazla faseti tek sorguda almak için CTE’ler birleştirilebilir
- Bu yaklaşım küçük ve orta ölçekli veri kümelerinde iyi çalışabilir; ancak çok büyük veri kümelerinde yavaşlayabilir
PostgreSQL arama motorunun kapsamı ve sınırları
- PostgreSQL’in tam metin arama bileşenleri birleştirilerek oldukça gelişmiş bir arama motoru oluşturulabilir
- PostgreSQL tabanlı arama, join’leri ve ACID transaction desteğini de birlikte sunar
- Bu, diğer arama motorlarında genellikle bulunmayan bir özelliktir
- Ele alınabilecek diğer gelişmiş arama konuları şunlardır
- Öneriler ve otomatik tamamlama
- Tam ifade eşleştirme
pg-vectorile birleştirilmiş hibrit arama
- Bu özellikler PostgreSQL ile mümkündür; ancak bileşenleri elle birleştirmek gerekir
- Bazı durumlarda çok büyük veri kümelerinde performans düşebilir
- Devam yazısı olan part 2, PostgreSQL üzerinde arama kurmayı; altyapıya Elasticsearch ekleyip verileri senkronize etmeyle karşılaştırır
1 yorum
Hacker News yorumları
Bunu Elasticsearch işlevlerini taklit edecek şekilde yapmaya çalışmamak gerekir
2000'lerde MySQL 3.x ile görsel EXIF verilerini ayrıştırıp 3 aşamalı sınıflandırma yapısı ve sayaçları indeksleyen bir arama motoru yapmıştım; bu, birçok pahalı satıcının bile düzgün başaramadığı bir işti ve Autonomy en üst düzey sınıflandırmayı bile yapamıyordu
6 hafta boyunca, yalnızca SELECT sütunlarının sırasını değiştirmenin bile performansı etkilediği kırılgan SQL yazarak zar zor başardım ama bir daha yapmak istemem. Veritabanları özünde büyük ölçüde benzer olsa da arama motorları çok daha ileri seviyeye geldi
Entelektüel merakla denenebilir ama arama, yalnızca tokenization yapıp biten bir şey değil. Kısa süre sonra sınıflandırma gezintisi, çok dilli destek, otomatik eşanlamlılar, "Did you mean" yazım önerileri, büyük ölçekte performans gibi gereksinimler geliyor ve insan kendini çıkmaz sokakta buluyor. Akıl sağlığı için bir arama motoru kullanmak daha iyi; ayrıca PG ile ES senkronizasyonu için ZomboDB veya PGSync gibi araçlar da var
Buna karşılık veri analistleri veya geliştiriciler büyük bir veritabanındaki metin sütunlarında
LIKE/ILIKE'den daha esnek arama yapmak istiyorsa, aynı veritabanı içinde bir tam metin arama indeksi/tablosu oluşturarak %90 sonuca ulaşmak daha kolay ve hızlı olabilirHer 5 dakikada bir cron, DB'de
last_modified_at > last_indexing_started_timestampolan indekslenecek nesneleri buluyor, bunları Elasticsearch'e indeksliyor ve ardındanlast_indexing_started_timestampdeğerini senkronizasyon başlangıç zamanına güncelliyordu. Böylece çalışmanın başı ile sonu arasında değişen nesneler bir sonraki çalışmada yakalanıyorduElasticsearch'ü yeniden kurmak gerekirse son indeksleme zamanını temizleyip en baştan yeniden senkronize etmek yeterli oluyor; böylece sistem kendi kendini onarabiliyor ve senkronizasyon raydan çıkmıyor
Her asenkron işte olduğu gibi izleme ve yeniden deneme gerekiyor ama ES kararlı ve hızlıydı, bu yüzden sorunlar nadirdi. Yine de tutarlılık gereksinimimiz gevşekti; PG'nin en güncel durumunun makul bir süre içinde ES'ye ulaşması yeterliydi, dolayısıyla gereksinimler farklıysa durum da farklı olabilir
Postgres tam metin aramasında indeks ve sorgu ağırlıkları kullanmaya geçtikten sonra, yalnızca update trigger'ları ve çok hızlı arama sorgularıyla ihtiyaç duyduğumuz her şeyi tamamen Postgres içinde halledebildik
Genel olarak başarılıydı ve operasyon yükünü ciddi biçimde azalttı; bu yüzden harcanan mühendislik zamanını fazlasıyla geri kazandırdı ama hafife alınacak bir başlangıç işi değil
İhtiyaca göre materialized view, normal view ve trigger'lar daha iyi olabilir. Yerleşik metin araması kullanım senaryosuna uymayabilir ve alternatif oluşturmak mutlaka zor olmak zorunda değildir
Gerçekçi yük altında p50/p99 sorgu süreleri olmadan bunun kanıtlandığını söylemek zor. Sonuçları 1 dakikada döndüren bir arama motoru "gelişmiş" değildir; Postgres gibi ilişkisel veritabanları da kâğıt üzerinde elbette bunu yapabilir
Genel olarak katılıyorum; özellikle büyük ölçekte, örneğin birkaç milyon kaydın üzerinde, muhtemelen bu yaklaşımı kullanmazdım. Asıl ilgim, ne kadar işlevi kopyalayabildiğini görmekti
Küçük arama kullanım senaryolarında daha az altyapı yönetimi gerekir ve güçlü tutarlılık, join gibi avantajlar vardır. Xata'da, küçük ölçekte Postgres kullanıp daha sonra minimum kırıcı değişiklikle Elasticsearch'e geçilebilen yumuşak bir geçişi de düşünüyoruz
Bu açıdan bakınca hızlı arama sonucu o kadar da şaşırtıcı değil. Arka plan işlerinde önbellek sonuçları sürekli güncellenip hazır tutulabilir ve istek geldiğinde olduğu gibi sunulabilir. Bu tür önbellekleme ve yanıt süresi, gerçek arama sonucu hesaplama hızından ayrı bir konu gibi görünüyor
Yavaş bağlantı kullananlar da var; ayrıca deprem gibi belirli sorgular yalnızca belli bölgelerde aniden artabilir
Sorgular fazla rastgele olursa önbellekte sonuç bulunmaz ve sistem gerçekte olduğundan daha kötü görünür; yeterince rastgele olmazsa da gerçekte olduğundan daha iyi görünür
Dolayısıyla ES vb. araçların avantajı, birden fazla düğüm arasında yatay ölçekleme yapabilmeleri veya ana indeksin üzerine ekledikleri özelliklerdir
Yalnızca
ts_rankkullanmak kusursuz ama genelde sıralamayı başka ilgililik ölçütleriyle ayarlamak istiyorsunuz. Bu ölçütü ana sıralama ölçütü yapamama sorununu çözdüğünüzde, sonuçlar indeksli normal DB tablo sorguları kadar hızlı oluyorGençlik yıllarımda, ne arama motorlarını ne de veritabanlarını iyi bilmeden, sıfırdan bir tane yapmayı denedim. Ne kadar ileri gidebileceğimi, ne kadar hızlı ve ne kadar ilgili sonuçlar döndürebileceğini görmek istedim
Temel bir veritabanı ve arama motorunu hızlıca yapmak, amatör bir programcı için bile muhtemelen oldukça kolay. Temel bilgisayar bilimi algoritmalarını ve işletim sistemi ile donanımdan nasıl yararlanılacağını anlarsanız, bir iki ay içinde yapılabilir
Üst seviye bir dille bile hız fena değildi; 2003 model bir dizüstünde yaklaşık 250 bin QPS görmüştüm. Sharding ile ölçeklenebilirlik de büyük bir sorun değil. Depolama ve sorgulamadan çok indeksleme, kilitleme ve tutarlılık daha karmaşık
Asıl büyük sorun, aramanın öznel olması. Birinin gerçekten ne bulmak istediğini, kişinin ne aradığını kendisi bile bilmiyorken nasıl yardımcı olunacağını, sistemi kötüye kullanmak isteyenlerin nasıl engelleneceğini, karmaşık sorgular ve veri kümelerinin nasıl ele alınacağını düşünmeye başlayınca zorluk birkaç kat değil, birkaç basamak artıyor
Gerçekten genel amaçlı bir veritabanı kullanıp kullanmadığını ve bu rakamın doğru olup olmadığını merak ediyorum. Bu motorları geçmek için hangi teknikleri kullandığını bilmek isterim
Bunun ötesine geçtiğinizde yaratıcı çözümler gerekir ve o noktadan sonra çeşitli trade-off’lar ortaya çıkar
Postgres içinde arama yaparken sorunlardan biri, aramanın CPU yoğun bir iş olması ve yönün giderek GPU kullanımına kayması. İdeal durumda, veritabanının CPU’sunu asıl veri modelinin işlemsel güncellemelerine ayırmak istersiniz
ES ve Solr kümelerinin yeniden indeksleme sırasında 10’dan fazla düğümde %100 çalıştığını ya da normal zamanda bile 10’dan fazla düğümde %30-50 CPU kullandığını çok gördüm. Buna karşılık gelen veritabanı ise örneğin AWS L/XL instance’larında 50-100GB veriyle %30 CPU kullanıyor olabilir
Tüm arama yükünü ana veritabanına taşırsanız, bu kez DB’yi shard etmeniz gerekir. Yine de yan projelerde arama, özyinelemeli join’ler, vektörler vb. için PG eklentileri eğlenceli ve basit oldukları için güzel
Bunu zamanında gerçekten yaptım ve oldukça hızlı hale getirdim
https://austingwalters.com/fast-full-text-search-in-postgres...
Şu anki web sitesi https://askhn.ai
[1]: https://en.wikipedia.org/wiki/Kerning
Bu teknikler pgvector ile birleştirilirse embedding’lerle ilgili içerik de bulunabilir. Oldukça sihirli hissettirmişti
Bu arada "Dark" Vader değil, Darth Vader. Ben de küçükken "Dark" sanıyordum
Çok iyi ve net bir yazı. SQLite da standart eklentileriyle gelişmiş indeksleme özellikleri ve stemming sunuyor
İngilizce için SQLite da gayet iyi çalışıyor
https://github.com/daitangio/knowledge
Denemeye değer. Oldukça güçlü
Güzel bir yazı, ancak PostgreSQL’in bulanık arama desteklemediği kısmı kısmen yanlış.
pg_trgmeklentisi ve GIN trigram indeksleri, bu yazıdaki örnekler gibi bulanık arama kullanım senaryolarını destekliyorhttps://www.postgresonline.com/article_pfriendly/169.html
Sorgular ciddi ölçüde hızlanabilir, ancak bunun karşılığında bellek kullanımı ve güncelleme sırasında yapılan iş miktarı artar