Rust ile etkileşimli bir Datalog motoru oluşturmak
(github.com/frankmcsherry)- Mantık programlama atölyesinde ortaya çıkan Datalog araçlarının kullanılabilirlik ve performans sınırları nedeniyle, Rust tabanlı etkileşimli Datalog kabuğu datatoad deneyi başlatıldı
- datatoad, çalışma sırasında kurallar ekleyip yeni gerçekleri sürekli türeten bir yapıyı hedefliyor;
columnardepolama ve LSM katmanları ile gerçek kümelerinde yinelenen veriyi işleme maliyetini azaltıyor - Kural değerlendirmesi, Datalog gövdesini bir join problemine dönüştürerek yürütülüyor;
stabledurumuna göre tam değerlendirme ile artımlı değerlendirme ayrılıyor ve daha önce hesaplanmış stable-stable join'lerden kaçınılıyor - Graspan veri kümesi deneylerinde, yalnızca elle kural yeniden yazımı ve ara ilişkilerin eklenmesiyle aliasing analizi 736.34 saniye·50.13GB'dan 119.34 saniye·5.32GB'a düştü
- Sonraki çalışmalar e-graph tabanlı join planı optimizasyonu, layered trie, sabit genişlikli bayt gösterimi, disk spill, dağıtık değerlendirme, streaming join, demand transform yönünde ilerliyor
datatoad'un çözmeye çalıştığı sorun
- Memorial Day hafta sonu düzenlenen mantık programlama atölyesinde, program analizi için kullanılan Datalog araçlarının kullanım zorlukları belirginleşti ve bunun üzerine basit, kullanışlı ve hızlı bir Datalog uygulaması denemesi başladı
- Hedeflenen uygulama, statik bir örnek çalıştırıcı değil, etkileşimli bir Datalog kabuğu
- Büyük miktarda gerçeği yükleyebiliyor
- Çalışma sırasında yeni kurallar eklenebiliyor
- Eklenen kuralların sonuçlarını mevcut duruma sürekli yansıtıyor
- Kod, datatoad repository üzerinden takip edilebilir
- Mevcut datafrog, Datalog motorunun temel algoritmalarını sağlıyor ancak kullanıcının bunları kendisinin bağlamasını gerektiriyor; datatoad ise aynı fikirleri kullanımı daha kolay bir biçimde yeniden düzenliyor
httpdveri akışı grafiğinin nullability örneğinde datatoad,Vec<String>verisi ve derlenmemiş sorgularla 8.3 saniye sürdü; bu,(u32, u32)verisi kullanan datafrog örneğindeki yaklaşık 2 saniyeye kıyasla yaklaşık 4 kat daha yavaş- Reachability probleminde, datafrog uygulamasıyla aynı sayıda çıktı tuple'ı üretti ancak genel doğruluk doğrulaması henüz tamamlanmış değil
Datalog modeli ve kabuk yapısı
- Datalog, basit mantık kuralları yazıldığında bu kurallardan ulaşılabilen tüm gerçekleri türeten bir dil
- Kurallar head ve body'den oluşur
- Örnek:
tri(a, b, c) :- edge(a, b), edge(b, c), edge(a, c). tri,edgeilişkilerdir;a,b,cise değişkenlerdir- Head'de görünen değişkenler body'de de bulunmalıdır
- Örnek:
- Gerçekler, body'si boş olan kurallar olarak ele alınır
- Örnek:
edge(1, 2) :- . - Birden fazla head kullanarak bir seferde birden çok gerçek yazılabilir
- Örnek:
- Datalog'un monotonluk özelliği nedeniyle, kural veya gerçek eklendiğinde doğru gerçekler kümesi küçülmez; aynı girdi kural kümesi, kuralların sırasından bağımsız olarak aynı sonuca ulaşır
- Rust gösteriminde üç yapı merkezde yer alır:
Rule,Atom,TermRule { head: Vec<Atom>, body: Vec<Atom> }Atom { name: String, terms: Vec<Term> }Term::Var(String)veyaTerm::Lit(String)
- Literal depolamada
StringyerineVec<u8>kullanılır- Gereken özellikler, literal eşitliği ve rastgele bir sıralama düzenidir
- Baytların
String,(u32, u32)ya da başka bir anlam taşıması kullanıcıya bırakılmıştır
- Yorumlayıcı durumu kuralları ve gerçekleri birlikte tutar
rules: Vec<Rule>facts: facts::Facts
- Kabuk, giriş satırlarını Datalog olarak parse ettikten sonra
State::extendveState::updateçağırır;.listkomutu ise her ilişki adını ve gerçek sayısını yazdırır
Parse etme ve gerçek depolama
- Parser
parse.rsiçinde yer alır ve Soufflé sözdiziminden alınmış bir biçim kullanır - Değişkenler
?ile başlar - Token'lar
.,,,(,),:-,?ile sınırlıdır; geri kalan metin atom veya term adı olarak işlenir - Tokenizer boşlukları kaldırır ve
:-ifadesini tek bir sembol gibi taramak için←ile değiştirir - Kural parse etme, turnstile'a kadar head atom'larını, period'a kadar body atom'larını okuma şeklinde ilerler
- Atom; ad, sol parantez, term listesi ve sağ parantezden oluşur
- Term,
?varsa değişken; yoksa literaldir
- Hatalı kurallar
Nonedöndürür; şu anda hatanın hangi bölümde olduğunu ayrıntılı biçimde bildirmez - Negatif kuralları eklemek için
Exclamationtoken'ı gerekir ancak bu henüz ele alınmıyor
Gerçek kümelerinin yaşam döngüsü
- Basit
Vec<Vec<String>>depolama, iç içe tahsisler nedeniyle bellek yönetimi açısından elverişsizdir - datatoad, Rust türlerini düz bir yerleşime sahip az sayıdaki doğrusal tahsise dönüştürmek için
columnarkullanır- String baytları, string sınırları ve gerçek sınırları ayrı dizilerde tutulur
FactContainer, sıralanmış ve yinelemeleri kaldırılmış gerçek listesini sarar; wrapper türü, sıralama ve yineleme kaldırma değişmezlerini ima eder- Sütun tabanlı kapsayıcı pratikte yalnızca append-only olduğundan, ara değişiklikler için uygun değildir; bu yüzden yeni gerçek ekleme için log-structured merge-tree(LSM) benzeri bir yapı kullanılır
FactLSM { layers: Vec<FactContainer> }- Katman boyutları geometrik olarak büyüyecek şekilde yönetilir
- Boyutları 2 kat içinde kalan katmanlar birleştirilerek sıralı ve yinelemesiz durum korunur
FactBuilder, sıralanmamış ve yinelemeli olabilenactivealanı ile sıralanmış ve yinelemeleri kaldırılmışlayersalanlarını birlikte tutar- Her ilişkinin gerçekleri üç aşamadan geçer
to_add: Yeni gelmiş ancak gerçekten yeni olup olmadığı henüz doğrulanmamış gerçeklerrecent: Ayrık ve henüz işlenmesi gereken gerçeklerstable: Zaten tamamen işlenmiş ayrık gerçekler
FactSet::advance,recentverisinistablealanına taşır veto_addiçinden zatenstableiçinde olan gerçekleri çıkararak yenirecentoluşturur
Kural değerlendirmesi bir join problemidir
- Datalog kural gövdesi, ilişkisel veritabanlarındaki bir equi-join olarak görülebilir
- Üçgen kuralı örneği şöyledir
tri(?a, ?b, ?c) :- edge(?a, ?b), edge(?b, ?c), edge(?a, ?c).
- Tüm değişken atamalarını doğrudan listelemek, sonlu olsalar bile çok fazla olduğundan, ortak değişkenlerin anahtar sütunlarına göre sıralanıp ardından birleştirilirler
- Uygulama, gövdeyi sağdan sola doğru indirger
- Son iki ilişki join edilerek ara ilişki oluşturulur, ardından bu ilişki soldaki ilişkiyle yeniden join edilir
- Body atom'u yalnızca bir taneyse, sadece head biçimine dönüştürülür
JoinPlanşu bilgileri içerir- Body atom'larını join'e uygun biçimde yeniden düzenleyip filtreleyen
bodys - Her ara join'in key arity'si ile çıktı projection'ını tutan
joins - Head atom'una eklenecek koordinatları veya literalleri gösteren
heads - Son head üretim join'inin arity'si
- Body atom'larını join'e uygun biçimde yeniden düzenleyip filtreleyen
- Mevcut plan, basit bir right-linear join plan
JoinPlanoluşturulurken, her değişkenin leftmost ve rightmost görünüm konumu kullanılarak hangi değişken değerlerinin ne zamana kadar korunacağı belirlenir; body atom sütunları dead, key ve value sütunlarına ayrılır- Temel fonksiyon
implement_plan(rule, plan, pos, stable, facts)şeklindedir- Yeni bir kural eklendiğinde, tüm gerçekler üzerinde başlamak için
stable = truekullanılır - Mevcut kuralların tekrarlı uygulanmasında ise yalnızca yeni türetimleri hesaplamak için
stable = falsekullanılır
- Yeni bir kural eklendiğinde, tüm gerçekler üzerinde başlamak için
Artımlı join ve merge join
- Join işlemi çift doğrusal olduğundan şu şekilde ayrıştırılabilir
(A + a) ⋈ (B + b) = A ⋈ B + A ⋈ b + a ⋈ B + a ⋈ b
- Stable kümeler arasında zaten oluşturulmuş
A ⋈ Byeniden hesaplanmak zorunda değildir - Yalnızca yeni türetimler gerekiyorsa sadece üç join çalıştırılır
A ⋈ ba ⋈ Ba ⋈ b
join_with,stablebayrağına göre stable-stable join'i dahil eder ya da hariç tutar- Gerçek
join, sıralı iki girdiyi ardışık olarak tarayan bir merge join'dir- Anahtarlar eşitse, o anahtar için tüm kombinasyonlarda
actionçağrılır - Anahtarlar farklıysa,
gallopile bir sonraki olası eşleşme noktasına hızla atlanır
- Anahtarlar eşitse, o anahtar için tüm kombinasyonlarda
gallop, EmptyHeaded tarafındaki bir fikirden alınmıştır; tekdüze koşul doğru kaldığı sürece üstel olarak ilerler, ardından ikili aramayla aralığı daraltır
Nullability analizi deneyi
- Deney verisi Graspan projesinin verisidir ve Google Drive'da da durmaktadır
- Veri akışı analizi girdisinde
evenolmak üzere iki ilişki vardırn(?a, ?b):?adeğeri?bkonumuna yazılabilire(?a, ?b): Bir?akonumundaki değer başka bir?bkonumuna taşınabilir
- Erişilebilirlik kuralı şöyledir
n(?a, ?c) :- n(?a, ?b), e(?b, ?c) .
httpdgirdisinde başlangıç.listşu değerleri gösterire: 9,905,624n: 138,331
- Kural doğrudan çalıştırıldığında yaklaşık 15 saniye sürer ve
n9,393,283 kayda ulaşır - Yavaşlığın nedenlerinden biri,
n'in join anahtarına uyacak şekilde yeniden düzenlenmiş geçici ilişki.temp-0-0-in'in 9,393,283 kayda kadar büyümesidir - Kullanıcı kuralı yeniden yazarsa performans iyileşir
m(?loc, ?val) :- n(?val, ?loc) .m(?loc, ?val) :- m(?mid, ?val), e(?mid, ?loc) .
- Aynı
httpdgirdisinde yeniden yazımdan sonra ikinci kural yaklaşık 8.43 saniyede çalışır
| dataflow | httpd | psql | lnx_kernel |
|---|---|---|---|
| graspan | 684s | 8640s | 42840s* |
| datatoad | 8.43s | 24.33s | 55.01s |
| datafrog | 1.30s | 4.06s | 8.03s |
lnx_kerneliçin Graspan değerinin yanında*vardır; makale toplam süreyi birlikte raporlamış ve girdi tanımlayıcı çakışmaları nedeniyle bunun birebir aynı çalıştırma olduğunu söylemek zordur- Uygulayıcıların kullandığı araçlarla karşılaştırmak için Soufflé daha uygun bir hedef olabilir
Aliasing analizi ve elle optimizasyon
- İkinci analiz, Zheng ve Rugina'nın aliasing analizinin Graspan tarafından alınmış sürümüdür
- Girdi ilişkileri iki adettir
A(?val, ?loc):?loc <- ?valD(?val, ?loc):?loc,*?valbiçiminde kullanılır
- Amaç, memory alias ve value alias sonuçlarını çıkarmaktır
- memory alias: iki lvalue ifadesi aynı bellek konumunu gösterebilir
- value alias: iki ifade aynı pointer değerini değerlendirebilir
- Makaledeki gösterimde
^T,^?,^*yer alır^T: ilişki transpozu^?: isteğe bağlı terim^*: 0 veya daha fazla tekrar
- Datalog'da
^?iki kurala bölünerek ifade edilir;^*ise identity ilişkisinin açıkça eklenmesiyle ele alınır - Başlangıçtaki kural yürütmesi uzun sürer
- Son iki identity başlatma kuralı girildikten sonra 686.57 sn ve 736.34 sn sürer
- Süreç 50.13GB kullanır
V: 361,947,256M: 92,806,768F: 2,669,647
-V,-M,-a,-dtranspoz ilişkileri açıkça kullanılarak-ingeçici ilişkileri kaldırılır- Toplam 815.92 sn, yani yaklaşık 13.6 dakika
- Bellek 31.96GB
- Zheng ve Rugina'nın yaklaşımı demand-driven'dır ve pratikte yalnızca
MgerektiğindenV,Miçine inline edilir-Viçin 361,947,256 kayıt üretilmez- Bellek 18.96GB'a düşer
- Tekrarlanan join parçaları
Fdadıyla etiketlenip yeniden kullanılır; ardındanFyerine doğrudanFdhesaplanarak identity sorunu da azaltılır MFdara ilişkisini kullanan son biçim şu sonuçları verir- Çalışma süresi: 119.34 sn
- Bellek: 5.32GB
-M: 92,806,768Fd: 1,858,986MFd: 73,474,947
- Yalnızca elle yeniden yazımla, ilk denemeye kıyasla bellek kullanımı ve çalışma süresinde neredeyse 10 kat iyileşme elde edilir
- Adlandırılmış ara sonuçlar oluşturmak, istenen bushy-tree join planını bir ölçüde doğrudan kurmayı sağlar; ancak
Vgibi gerçekte gerekmeyen ilişkilere ad vermek, bunların yüksek maliyetle üretilmesine yol açar
Demand-driven sorgular ve magic sets
- Demand-driven sorgular, belirli bir hedef olgu için gerekli olan bölümleri keşfetme yaklaşımıdır
- Yaklaşık bir çözüm olarak magic sets kullanılabilir
- Hedef literal'i sorguya gömen bir dönüşümdür
- Tüm
d'lerden değil, yalnızca ilgilenilend'lerden başlayan bir ifade düşünülebilir; ancak bu basit haliyle uygulanırsa yanlış olabilir
- Magic sets en iyi çözüm değildir; daha verimli olabilecek yaklaşımları bulmak için ilgili makaleleri daha fazla okumayı planlıyor
- İlgili bağlantılar şunlardır
Join planı optimizasyonu ve veri paralel IR
- Datalog'un çekiciliği, Horn clause'un kendisinden çok, veri paralel hesaplamanın temel problemi olan data rendezvous kavramını saf biçimde ortaya koymasındadır
h(x, y, z) :- b1(x, y), b2(y, z) .kuralı, heryiçin ilgilixvezdeğerlerini tek bir yerde toplama problemi olarak görülebilir- Veri paralel hesaplamanın temel davranışı, kayıtları anahtara göre toplayıp kullanıcı mantığına teslim etmektir; join ise bunun içindeki seçici yönlendirmeyi ifade eder
- Basit bir IR şu opcode'ları kullanır
Var(String): adlı koleksiyonMap(Action): filtre, permütasyon, projectionKey(usize): öndeki belirli sayıda sütunu anahtar olarak işaretlemeMul(usize): aynı anahtar uzunluğuna sahip birden çok koleksiyonu birleştirme
- Sonrasında
MapveKey,Action.key_arityiçinde birleştirilir Action; literal filtrelerini, değişken eşitliği filtrelerini, projection'ı ve key arity'yi içerir- En basit başlangıç noktası, body atomlarının hepsini cross join ile birleştirip her head için filtre ve projection eklemektir; ancak bu yaklaşım doğru sonucu üretse de performansı çok kötüdür
e-graph tabanlı optimizasyon
- Optimizasyonda e-graph ve equality saturation kullanılıyor
- Başvuru kaynakları olarak egg webpage, önceki yazı ve
eggkullanılmış - term graph,
IdeklenmişENode<T>'lerin bir eşlemesi olarak ifade ediliyor ve aynı düğümler paylaştırılarak program daha kompakt biçimde gösteriliyor - Uygulanan e-graph kuralları üç tane
MulPermute:Mul(k)girdi permütasyonlarını eşdeğer kılarMulPartition:Mul(k)işlemini çeşitli şekillerde bölüp eşdeğer kılarMapPushdown:Map'iMul(2)altına iterek anahtarlı bir join biçimi oluşturur
- Örnek kural şu şekilde
head(?a, ?b) :- a(?x, ?a), b(?y, ?x), b(?y, ?z), a(?z, ?b) .
- equality saturation sonrasında extraction aşamasında maliyet atanıyor
Mapmaliyeti, çıktı sütunu sayısıMulmaliyeti, anahtar sütun sayısı ile girdilerin anahtar olmayan sütun sayılarının toplamıVarmaliyeti 0- Eşitlik durumunda önce
Mapsayısı, sonraMulsayısı en aza indiriliyor
- Örnekte seçilen plan, en fazla iki ilişkisiz sütuna sahip wave two içinde bulunuyor
a,bgirdileri için map- tek bir join
- ara projection
- kendisiyle join
- son projection
- Bu plan araması release build'de bile yaklaşık 40ms sürüyor ve zamanın büyük kısmı equivalence saturation için harcanıyor
Optimize edilmiş plan çalıştırma güncellemesi
- 2025-06-29 güncellemesinde optimize edilmiş planın çalıştırılması hayata geçirildi
- Plan
Vec<ENode<Op>>olarak çıkıyor ama gerçek çalıştırmada her düğüm bağımsız biçimde yürütülmüyor - Hedeflenen çalıştırma biçimi şöyle
- Her
Variçin, ona bağlı birden çokMap, tek bir dış koleksiyon taramasında uygulanıyor - Her
Muliçin, ona bağlı birden çokMap, tek bir join taramasında uygulanıyor
- Her
Op::Map(action), doğrudan yürütülen bir işlemden çok, bağımlı işleme kuyruğa alınan bir iş- Bunun için
TempActioneklendi- literal filtre
- değişken eşitliği filtresi
- sütun referansı veya string literal taşıyabilen projection
- Çalıştırma planı hazırlama aşaması, body ile head'i ayırıp
Mapeylemlerini giriş düğümüne göre topluyor Varçalıştırmasında ad işleme üç duruma ayrılıyor- head üretimi, head ilişki adına yazıyor
- identity dönüşümü, mevcut girdi adını yeniden kullanıyor
- trivial olmayan dönüşüm,
.temp-*geçici adına kaydediliyor
Mul(2)çalıştırması iki girdinin key arity ve adlarını denetliyor, ardından sonucu birden çok builder'a yığmak içinjoin_withçağrılıyor- Karmaşık bir aliasing örneğinde optimize edilmiş planın çalıştırılması 114.28 saniye sürdü; bu, önceki 119 saniye düzeyine göre yaklaşık 5 saniye daha hızlı, ancak nedeni net değil
- multi-rule optimizasyonu henüz uygulanmadı ve plan yaklaşımının kendisini yeniden düşünmek gerekebilir
Fact gösterimi optimizasyonu
- 50GB, 5GB'a düşürüldü ama hâlâ gerekenden yaklaşık 10 kat fazla bellek kullanıldığı düşünülüyor
- Büyük
-Milişkisi için en büyük layer 57,289,225 fact içeriyor ve yaklaşık 2,098,253,766 bayt kullanıyor- fact sınırları: 458,313,800 bayt
- term sınırları: 916,627,600 bayt
- gerçek bayt verisi: 723,312,366 bayt
- İlk optimizasyon, arity'nin sabit olmasından yararlanıyor
- Tüm fact'ler 2 sütunluysa, fact sınırları yalnızca stride ve length ile ifade edilebiliyor
- Böylece 458MB düzeyindeki sınır verisi fiilen ortadan kalkıyor
- İkinci optimizasyon, term uzunluklarını eşitlemek
- Sayılar 7 basamaklı sabit genişlikli string'lere çevrilirse term sınırları da stride ve length ile ifade edilebiliyor
- Bunun karşılığında gerçek bayt sayısı artabiliyor
- Üçüncü optimizasyon, sayıları metin yerine binary olarak göstermek
- 7 basamaklı bir sayı,
u32içinde 4 bayta sığıyor - 3 bayta da sığdığı için
57,289,225 × 2 × 3 = 343,735,350bayt oluyor - Yaklaşık 2GB'dan yaklaşık 350MB'a iniyor; yani yaklaşık 6.10 kat azalma
- 7 basamaklı bir sayı,
- İlk term tekrarını sıkıştırmak daha da fazla azaltıyor
- 57,289,225 fact var ama distinct first term sayısı yalnızca 1,147,612
(Term, Term)yerine(Term, [Term])biçiminde saklanırsa en büyük layer yaklaşık 184,491,407 bayta düşüyor- Bu da başlangıçtaki 2GB'a kıyasla yaklaşık 11.37 kat azalma demek
- 2025-07-02 güncellemesinde ilk optimizasyon uygulandı
- En büyük batch, kuramsal 343,735,350 bayta ek 32 bayt ile 343,735,382 bayt oldu
- Çalışma süresi yaklaşık 115 saniyeden yaklaşık 95 saniyeye indi; yani yaklaşık %20 iyileşme
Layered trie gösterimi
- 2025-07-20 güncellemesinde layered trie çalışır hâle geldi
- row-oriented gösterim ile column-oriented layered trie gösterimi karşılaştırılıyor
toad-rowtoad-col
| dataflow | httpd | psql | lnx_kernel |
|---|---|---|---|
| graspan | 684s | 8640s | 42840s* |
| toad-row | 3.88s | 11.30s | 25.67s |
| toad-col | 3.47s | 11.94s | 23.09s |
| datafrog | 1.30s | 4.06s | 8.03s |
| aliasing | httpd | psql | lnx_kernel |
|---|---|---|---|
| graspan | 8.4h | 6.0h* | 1.7h* |
| toad-row | 28.21s | 28.25s | 7.62s |
| toad-col | 19.39s | 21.96s | 9.48s |
| datafrog | UNK | UNK | UNK |
- layered trie, sıralı row gösteriminde öndeki sütunların tekrarını bastıran bir yöntem
- Her sütun, değer listelerinin bir listesi
- Her liste, önceki sütun prefix'ine karşılık gelen sıralı distinct değerler kümesini içeriyor
- Bir sütundaki liste sayısı, bir önceki sütundaki toplam öğe sayısıyla aynı
- Ağaç gibi görülebilir ama gerçek uygulama sütunlar üzerinden tutuluyor
- Avantajı, prefix düzeyinde arama, join, fark alma ve birleştirme yapılabilmesi
- distinct değerlerin neredeyse hiç olmadığı ve tüm satırları tek seferde görmenin daha iyi olduğu durumlarda row-oriented yaklaşım daha avantajlı olabilir
- Ortak soyutlama olarak
FactContainertrait'i eklendiformlenapplyjoinexceptmerge
apply, özyineleme yerine açık bir stack ile her layer'ın range'ini izleyip satır oluşturarak action'a iletiyoralign, iki layered trie'nin prefix'lerini hizalı biçimde görmeye yarayan ortak bir yardımcıOrdering::Less: yalnızca self'te bulunan aralıkOrdering::Greater: yalnızca other'da bulunan aralıkOrdering::Equal: arity uzunluğundaki prefix her iki tarafta da ortak
join,except,mergeişlemlerinin tümüalignüzerine kurulmuşjoin, ortak prefix üzerinde kalan extension'ları açıp cross join yapıyorexcept, yalnızca self'te bulunan aralıklarıTrieBuilderile graft ediyormerge, self-only, other-only ve equal aralıkları uygun şekilde birer kez graft ediyor
Sabit genişlik optimizasyonu ve performans
- Sabit genişlikli
[u8; 4]tipine yükseltilebildiğinde karşılaştırma performansı ciddi biçimde iyileşiyor - layered trie, sütun bazında sabit genişlik optimizasyonu uygulayabildiği için uzun vadede row gösterimine göre avantajlı olabilir
exceptvemergeiçinupgradevedowngradekolayca uygulanabildi amajoiniçin uygulama, Rust tür sorunları nedeniyle daha zordu
| dataflow | httpd | psql | lnx_kernel |
|---|---|---|---|
| graspan | 684s | 8640s | 42840s* |
| toad-row | 3.88s | 11.30s | 25.67s |
| ^-- +opt | 3.11s | 9.49s | 19.83s |
| toad-col | 3.47s | 11.94s | 23.09s |
| ^-- +opt | 2.55s | 9.13s | 15.95s |
| datafrog | 1.30s | 4.06s | 8.03s |
| aliasing | httpd | psql | lnx_kernel |
|---|---|---|---|
| graspan | 8.4h | 6.0h* | 1.7h* |
| toad-row | 28.21s | 28.25s | 7.62s |
| ^-- +opt | 23.31s | 23.08s | 6.73s |
| toad-col | 19.39s | 21.96s | 9.48s |
| ^-- +opt | 14.26s | 16.45s | 8.33s |
| datafrog | UNK | UNK | UNK |
- Şu anda sürenin yaklaşık 2/3'ü
joinaltında harcanıyor - Join optimizasyonunda en az ek 2 kat kadar iyileştirme alanı olduğunu düşünüyorum
- Sadece inner loop'u yeniden sıralamayı deneyen girişim, ölçülebilir bir iyileşme sağlayamadı
Derlenmiş kod düzeyinde özelleştirme
- Aynı uzunluktaki term'leri ve aynı arity'ye sahip fact'leri algılarsak,
Vec<u8>'iVec<[[u8; B]; T]>gibi görebiliriz - Bu biçim, Rust'ın verinin şeklini daha iyi bilmesini sağlayarak bounds·length kontrol maliyetini azaltır ve özellikle karşılaştırmaları çok ucuz hale getirir
- Karşılaştırmalar datatoad'ın birçok noktasında kullanılıyor
- fact batch'lerini sıralama·yinelenenleri kaldırma
- batch birleştirme
- join anahtarlarını birleştirme
- yeni fact'leri mevcut fact'lerle karşılaştırarak filtreleme
- Referans performansı şöyle
| dataflow | httpd | psql | lnx_kernel |
|---|---|---|---|
| graspan | 684s | 8640s | 42840s* |
| datatoad | 7.44s | 17.26s | 42.25s |
| datafrog | 1.30s | 4.06s | 8.03s |
| aliasing | httpd | psql | lnx_kernel |
|---|---|---|---|
| graspan | 8.4h | 6.0h* | 1.7h* |
| datatoad | 101.24s | 96.36s | 20.20s |
| datafrog | UNK | UNK | UNK |
- Sıralama optimizasyonu,
Vec<u8>'iVec<[u8; 8]>'eunsafetransmute ederek sort·dedup uygulama yöntemiyle denendi
| dataflow | httpd | psql | lnx_kernel |
|---|---|---|---|
| dt-orig | 7.44s | 17.26s | 42.25s |
| dt-sort | 4.99s | 13.55s | 32.15s |
| datafrog | 1.30s | 4.06s | 8.03s |
| aliasing | httpd | psql | lnx_kernel |
|---|---|---|---|
| dt-orig | 101.24s | 96.36s | 20.20s |
| dt-sort | 52.99s | 53.19s | 11.20s |
| datafrog | UNK | UNK | UNK |
- Birleştirme optimizasyonu, iki girdiyi birleştirip ardından sort·dedup yapan basit bir yöntemle uygulandı
| dataflow | httpd | psql | lnx_kernel |
|---|---|---|---|
| dt-orig | 7.44s | 17.26s | 42.25s |
| dt-sort | 4.99s | 13.55s | 32.15s |
| dt-both | 3.71s | 11.23s | 23.58s |
| datafrog | 1.30s | 4.06s | 8.03s |
| aliasing | httpd | psql | lnx_kernel |
|---|---|---|---|
| dt-orig | 101.24s | 96.36s | 20.20s |
| dt-sort | 52.99s | 53.19s | 11.20s |
| dt-both | 31.32s | 30.08s | 8.56s |
| datafrog | UNK | UNK | UNK |
- Hâlâ datafrog'un derlenmiş performansına ulaşabilmiş değilim
- Kalan karşılaştırma maliyeti join ve antijoin'de de var; trie tabanlı yapılara geçerken aynı optimizasyon fırsatlarına yeniden bakmayı planlıyorum
unsafe'i kaldırmanın bir yolunu arıyorum ve sonuç olarak unsafe koddan kaçınılması gerektiğini de ekliyorum
Hâlâ yapılacak işler var
-
Diske spill
columnardepolama, az sayıda büyük tahsisten oluşuyor- Oluşturulurken bellek yerine dosyaya yazılabilir ve sonra memory map ile yeniden kullanılabilir
-
Dağıtık değerlendirme
- Join, yinelemeleri kaldırma ve distinctness kontrolü anahtar eşitliğine dayanıyor
- Anahtarlar ve bunlara karşılık gelen veriler birden fazla worker'a dağıtılabilir
timely_communicationile birden çok prosese kadar ölçeklenebilir
-
Akış tabanlı kural değerlendirmesi
- Mevcut join, ikili join ve materialized output kullanıyor
- Gerekli indeksler varsa, iç durumu materialize etmeyen bir plan oluşturulabilir
- worst-case optimal join de burada ele alınacak
-
custom representation özelleştirmesi
- Transitive closure'ı algılayıp strongly connected component decomposition için özelleştirme fikri var
- Equivalence relation için union-find veri yapısı kullanılabilir
bddbddbve factorized databases de ilgili konuları ele alıyor
-
İlgili fact arama
- demand transform'u anlayıp uygulamak gerekiyor
- Etkileşimli Datalog keşfi için bunun gerekli bir dönüşüm olduğunu düşünüyorum
1 yorum
Hacker News yorumları
Bu yazının 1 numaraya çıktığını görmek ilginç
Şu anda Differential Datalog ve Rust ile gerçek zamanlı bir strateji oyunu yapıyorum; oyun mantığını DDL’in yönetmesini sağlıyorum
Bunun çoğu, yeni bir fikirle tanışıp sonu gelmeyen yak tıraşına girişmek için bir bahaneye yakın
https://github.com/vmware-archive/differential-datalog
Bu arada ddlog ekibi artık Feldera’yı kurdu; Rust’ta DBSP’yi doğrudan kullanmayı düşünmek de mantıklı olabilir
https://github.com/feldera/feldera
https://github.com/salsa-rs/salsa
DDLog artık aktif olarak bakımı yapılan bir durumda olmadığı için daha da ilginç
“Ben, kötü şöhretli bir kötü adam, çoktan hak ettiğim cezayı çekmeye gidiyor olduğuma yarı yarıya inanarak davet edildim.” — bu yıl okuduğum teknik blog yazıları içindeki en iyi ilk cümle
Anlatıcının araya giren sözleri de harikaydı; bu kadar teknik olarak derin olup aynı zamanda bu kadar eğlenceli okunan yazılar nadir
Alias sorgularını optimize etme yolculuğu bir polisiye roman gibi hissettirdi; okuyucu da 50 GB bellek kullanımı karşısında inleyip 5 GB’a düştüğünde sevinç çığlığı atıyor
Hem kod hem yazı harika
mangle datalog’u Rust’a portlama işinde biraz ilerledim
https://github.com/google/mangle/tree/main/rust adresinde, Go uygulamasıyla aynı depoda yer alıyor
Önceliği yüksek değil, ayrıca ikinci sistem sendromu da var; bu yüzden ilerleme yavaş
Mangle Rust, bellek eşleme ile olguları diskten okuyup yazarak keyfi boyutta verilerle çalışmayı hedefliyor; Go uygulaması ise bellek içi çalışıyor
Bu yazı Datalog’u ayrıştırmayı ve LSM ağacını da ele aldığı için güzel; datafrog ile ilgili materyallerden çok daha kolay takip ediliyor
Rust’ta ascent ve crepe gibi prosedürel makrolar kullanan çok sayıda Datalog uygulaması var, ama bunların çalışma zamanında sorgu almakta zorlanma gibi bir dezavantajı var
Sorguların ve programın sabit olduğu statik analiz kullanımları için prosedürel makro yaklaşımı daha iyi olabilir
Mevcut Datalog canlanması zayıflıyor gibi görünse de çekirdek meraklıların dayanmaya devam ettiğini görmek sevindirici
Son Datalog 2.0 konferansı eskisine göre epey küçüktü; ikinci HYTRADBOI konferansında da Datalog’un payı düşüktü
İlk HYTRADBOI’de gönderilerin dörtte biri Datalog ile ilgiliydi
Başkalarının yakın tarihli Datalog projelerini paylaşması da cesaret verici
Şu anda büyük ölçekli bir yazılım migrasyonuna hazırlanırken eski bir SQL veritabanı için veri kalitesi pipeline’ı oluşturuyorum
Sorguları iyi yapılandırırsanız çok okunabilir oluyor; veri kalitesi sorunlarını belirlemek ve bulmak için Datalog’un SQL’den çok daha kullanışlı olduğunu düşünüyorum
Datalog 2.0, görece daha az bilinen Avrupa konferansı LPNMR’nin uydu çalıştayıydı ve o konferans tesadüfen Dallas’ta düzenleniyordu
Bizzat katıldığımda da etkinlik biraz tenha hissettirdi; çalıştaya makale de gönderdim ama o alandaki insanlardan pek çoğunu göremedim
İstisna olarak Nemo çözücüsünü tanıtan Avrupalı kişiler göze çarpıyordu
Bu yıl katılımcının az olması, Datalog uygulamalarına ilgi eksikliğinden ziyade, zaten çok büyük itibarı olmayan bir konferansın uydu çalıştayı olması ve ana etkinliğin de ICLP olmasıyla daha iyi açıklanır bence
Elbette saf Datalog motoru uygulamalarında pek yenilik kalmadığı yönündeki daha büyük iddiaya karşı çıkmaya çalışmıyorum
Araştırma alanı çoktan bunun ötesine geçip akış işleme (HydroFlow), seçim (Dusa), genel chase’e yakın şeyler (Egglog’un chase motoru) gibi daha egzotik problemlere kaydı
Vanilla Datalog’un sıkıcı olduğu konusunda genel olarak pek ihtilaf yok; ancak monoton ileri doygunluk ve Horn cümleleri, performans mühendisliği zemini iyi anlaşılmış zengin bir temel çizgi sunduğu için semiring veya Z-set gibi daha ilginç teorileri inşa etmeye elverişli
Durum makinesi ve ayrıştırma kısmı ilginizi çektiyse Rob Pike’ın eski sunumu Lexical Scanning in Go’yu da öneririm
https://www.youtube.com/watch?v=HxaD_trXwRE
Go ile yapılmış ama büyük kısmı diğer dillere de kolayca uygulanabilir
Rust, Zig ve Go gibi modern dillerin Unicode/rune/grafem desteğini yerel olarak sunması güzel
Java, .NET, C++ veya betik dilleriyle karşılaştırınca birçok sorun kendiliğinden ortadan kalkıyor
Yazarın Datalog çalışmalarını genel olarak seviyorum ama giriş materyallerinde ikili join öğretilmemesini isterdim
İdeal durumların dışına çıkınca içi hızla karmakarışık hale geldi; genel join yöntemleri kafamda çok daha kolay genelleniyordu
https://en.wikipedia.org/wiki/Worst-case_optimal_join_algorithm
https://github.com/frankmcsherry/blog/blob/master/posts/2025-05-29.md
Uzun zaman önce öğrenciyken Prolog’a biraz değinmiştim; nerelerde kullanıldığını ve neye yararlı olduğunu kabaca biliyorum ama derinlemesine anladığımı söyleyemem.
O zamandan beri Datalog’un harika olduğuna dair şeyler duyup duruyorum; ama aslında Datalog’un Prolog’a göre neyi iyileştirdiğini tam oturtamıyorum.
Az önce Wikipedia’daki Datalog sayfasına göz attım; Prolog’un performansı görece zayıf, Datalog’un ise ifade gücünü ve özellikleri azaltma pahasına performansı ciddi biçimde artırarak daha büyük veri kümelerini ve daha paralel işlemeyi mümkün kıldığı izlenimini verdi.
Turing-tamlığın ortadan kalkması da buna dahil gibi görünüyor; bunun kilit nokta mı yoksa tamamen yanlış bir çıkarım mı olduğunu merak ediyorum.
cut yalnızca boşa hesaplamayı önlemekle kalmaz, sonuçları da etkileyebilir.
Datalog ise genel olarak sözdizimi farklı bir ilişkisel veritabanına daha yakındır.
Prolog’da önemsiz sayılabilecek devasa bir arama uzayı, Datalog’da fazla bellek tükettiği için baştan ifade edilemez hale gelebilir.
Datalog, CVT’li bir işe gidip gelme arabası; Prolog ise daha çok bir F1 arabası gibidir.
Bir iyileştirmeden ziyade, insanların kendi ayaklarını uçurmaması için Prolog’un kısmen budanmış hali gibidir ve başka uygulamalara uygulaması ve gömmesi de çok daha kolaydır.
Prolog’a alışkınsanız Datalog büyük ölçüde kısıtlayıcı gelecektir.
call/3yoktur, term/goal expansion yoktur; Datalog temelde Prolog’un en küçük ortak özellik kümesini alıp etkileşimli veritabanı araması için kullanmak üzere tasarlanmış sayılır.Hızlı Datalog kodu yazmak kolaydır, ama üst sınırı da çok daha düşüktür.
Prolog da eşzamanlılığa izin verecek şekilde yazılabilir; ancak uygulamayı anlamayı gerektiren orta seviye bir iştir.
Guarded Horn Clauses ve türev diller bu kısmı biçimselleştirmek için geliştirildi, fakat Prolog sonrasındaki Japonya kaynaklı gelişmeler oldukça anlaşılması güçtür.
Prolog performansı; programcıya, kullanılan implementasyona ve nerede kullanıldığına büyük ölçüde bağlıdır.
Lisp gibi Prolog da derleme zamanında bir DSL’den yerel makine kodu üretmek için kullanılabilir.
Alttaki implementasyonun nasıl çalıştığını anlayıp kodu onun yapısına uygun yazarsanız yeterince hızlıdır.
Ancak bunun için birkaç yıl boyunca tek bir implementasyonla Prolog kodu yazmanız gerekir.
Prolog derleyici optimizasyonu üzerine de çok sayıda araştırma ve tescilli implementasyon örneği vardır.
http://logicprogramming.stanford.edu/readings/ullman.pdf
https://www.ueda.info.waseda.ac.jp/AITEC_ICOT_ARCHIVES/ICOT/Museum/IFS/abst/078.html
https://www.sciencedirect.com/science/article/pii/S0743106696889813
https://link.springer.com/content/pdf/10.1007/3-540-18024-9_26.pdf
https://sicstus.sics.se/
Datalog ve Rust kullanmak istiyorsanız, cozodb Rust ile yazılmıştır ve Datalog sorgu sözdizimi de sunar.
2024 Kasım civarında incelerken SQLite depolama backend’inde kolayca yapılabilecek birkaç iyileştirme noktası bulmuştum.
https://github.com/cozodb/cozo/issues/285
Program statik analizinde de kullandım; içeride sıralama ağaçları ve tip teknikleri kullanıyor.
Belgeleri, blogdaki adım adım anlatımla ilk karşılaştırma yapmaya yetecek düzeyde; özellikle sorgu optimizasyonu çalışması ilginç.
Ancak Rust’ta bellek içi çalışmıyorsanız veri serileştirme maliyeti yüksek ve proje en iyi ihtimalle sessiz durumda.
Eskiden Clojure hayranları Datalog’un SQL’den daha iyi olduğunu ve ilişkisel veritabanlarının hepsinin SQL kullanmasının üzücü olduğunu söylerdi
Neden böyle düşündüklerini yeterince derinlemesine inceleyememiştim
http://canonical.org/~kragen/binary-relations basit, özyinelemesiz bir sorguyla başlamıştı; SQL çevirisi şimdiden suç sayılacak kadar kötü, düzgün ayrıştırılmış SQL çözümü ise idamlık
Son dönemde ANSI SQL’e özyineleme özelliği eklendiği için artık tamamen imkânsız değil, ama üç büyük dezavantajı var
Birincisi, yanlışlıkla SQL’i Turing-complete hale getirdiler; buna karşılık Datalog sorgularının sonlanması garanti edilir
İkincisi, kullanımı hâlâ son derece hantal
Üçüncüsü, ilk nedenden dolayı çoğu zaman tam olarak uygulanmadığından güvenip kullanmak zor
Datalog’u çevrimiçi, kullanıcı dostu bir notebook ortamında kurcalamak istiyorsanız Percival’ı öneririm
https://percival.ink/
Datalog uygulamalarının genelinde “ANSI SQL”e karşılık gelen bir standart yok, ama temel fikri kavradığınızda diğer Datalog’lar da çok zor gelmez
Datalog’u SQLite’a derleyen bir Percival fork’u da başlattım; ikisinin aynı şeyi nasıl ifade ettiğini görmek isterseniz bakabilirsiniz
https://percival.jake.tl/
Toplama işlemleri ve daha gelişmiş join’ler henüz tamamlanmadı, ama temel biçim iyi çalışıyor
Logica, bir Google araştırmacısının yaptığı çok daha ciddi ve olgun bir Datalog→SQL derleyicisi; BigTable, DuckDB ve birkaç SQL lehçesine derliyor
https://logica.dev/
Datalog’un bir büyüklük mertebesinden fazla kolaylaştığı alan özyinelemeli sorgular/kurallar ile uğraşırken ortaya çıkıyor
SQL’de de mümkün, ama pipetle oyun hamuru içmeye daha yakın bir his
Frank’in Materialize.com’unda, eski ANSI SQL özyineleme yönteminden çok daha iyi olan “WITH MUTUALLY RECURSIVE” adlı bir SQL biçimi var ve Notion’da sayfa yükleme sorguları ile veri senkronizasyonu için değerlendiriliyor
https://materialize.com/blog/recursion-in-materialize/
Feldera’da da özyinelemeli görünümler için benzer bir biçim var
https://www.feldera.com/blog/recursive-sql-queries-in-feldera
Feldera’yı sevmemin nedeni, her “kuralı” veya alt görünümü tek bir dev ifadeye koymak yerine ayrı ifadeler olarak yazabilmeniz
Test ederken gördüğüm başlıca dezavantaj, Feldera’nın SQL lehçesinin Apache Calcite’tan miras aldığı epey kısıta sahip olması; Materialize SQL lehçesi ise PostgreSQL uyumluluğunu çok sıkı biçimde yakalamaya çalışıyor
Yeni bir McSharry yazısı, harika
Son kontrol ettiğimde VMWare differential datalog’dan uzaklaşmış gibiydi
https://www.feldera.com/
differential Datalog’dan differential SQL’e geçmişler gibi görünüyor; muhtemelen Datalog’u satmanın gerçekten zor olduğunu fark ettikleri için