Membangun Mesin Datalog Interaktif dengan Rust
(github.com/frankmcsherry)- Berangkat dari keterbatasan kegunaan dan performa alat Datalog yang terlihat dalam lokakarya pemrograman logika, eksperimen datatoad, sebuah shell Datalog interaktif berbasis Rust, dimulai
- datatoad menargetkan struktur yang dapat menambahkan aturan saat berjalan dan terus menurunkan fakta baru, serta mengurangi biaya penanganan duplikasi himpunan fakta dengan penyimpanan
columnardan lapisan LSM - Evaluasi aturan memproses body Datalog dengan mengubahnya menjadi masalah join, lalu memisahkan evaluasi penuh dan evaluasi inkremental berdasarkan status
stableuntuk menghindari join stable-stable yang sudah dihitung - Dalam eksperimen dataset Graspan, hanya dengan penulisan ulang aturan secara manual dan pengenalan relasi perantara, analisis aliasing berkurang dari 736,34 detik dan 50,13 GB menjadi 119,34 detik dan 5,32 GB
- Pekerjaan berikutnya berlanjut ke optimisasi rencana join berbasis e-graph, layered trie, representasi byte berlebar tetap, spill ke disk, evaluasi terdistribusi, streaming join, dan demand transform
Masalah yang ingin dipecahkan datatoad
- Pada lokakarya pemrograman logika akhir pekan Memorial Day, ketidaknyamanan alat Datalog untuk analisis program tampak menonjol, dan hal ini memicu upaya membuat implementasi Datalog yang sederhana, layak dipakai, dan cepat
- Target implementasinya bukan eksekutor contoh statis, melainkan shell Datalog interaktif
- Dapat memuat fakta dalam jumlah besar
- Dapat menambahkan aturan baru saat berjalan
- Terus mencerminkan hasil aturan yang ditambahkan ke dalam status yang sudah ada
- Kodenya dapat diikuti di repository datatoad
- datafrog yang sudah ada menyediakan algoritme inti mesin Datalog, tetapi karena bentuknya mengharuskan pengguna merangkainya sendiri, datatoad menyusun ulang ide yang sama menjadi bentuk yang lebih mudah digunakan
- Dalam contoh nullability pada grafik dataflow
httpd, datatoad membutuhkan 8,3 detik dengan dataVec<String>dan query yang tidak dikompilasi, sekitar 4 kali lebih lambat daripada contoh datafrog yang menggunakan data(u32, u32)dan berjalan sekitar 2 detik - Pada masalah reachability, datatoad menghasilkan jumlah tuple keluaran yang sama dengan implementasi datafrog, tetapi verifikasi kebenaran umum belum selesai
Model Datalog dan struktur shell
- Datalog adalah bahasa yang, ketika aturan logika sederhana ditulis, menurunkan semua fakta yang dapat dicapai dari aturan tersebut
- Aturan terdiri dari head dan body
- Contoh:
tri(a, b, c) :- edge(a, b), edge(b, c), edge(a, c). tridanedgeadalah relasi, sedangkana,b,cadalah variabel- Variabel yang muncul di head juga harus ada di body
- Contoh:
- Fakta diperlakukan sebagai aturan dengan body kosong
- Contoh:
edge(1, 2) :- . - Beberapa head dapat digunakan untuk menulis beberapa fakta sekaligus
- Contoh:
- Karena monotonisitas Datalog, menambahkan aturan atau fakta tidak akan mengurangi himpunan fakta yang benar, dan himpunan aturan input yang sama akan mencapai hasil yang sama terlepas dari urutan aturan
- Representasi Rust berpusat pada tiga struktur:
Rule,Atom, danTermRule { head: Vec<Atom>, body: Vec<Atom> }Atom { name: String, terms: Vec<Term> }Term::Var(String)atauTerm::Lit(String)
- Untuk penyimpanan literal, digunakan
Vec<u8>alih-alihString- Sifat yang diperlukan adalah kesetaraan literal dan urutan pengurutan arbitrer
- Apakah byte tersebut bermakna sebagai
String,(u32, u32), atau hal lain diserahkan kepada pengguna
- Status interpreter menyimpan aturan dan fakta bersama-sama
rules: Vec<Rule>facts: facts::Facts
- Shell, setelah mem-parsing baris input sebagai Datalog, memanggil
State::extenddanState::update, lalu dengan perintah.listmenampilkan nama setiap relasi dan jumlah faktanya
Parsing dan penyimpanan fakta
- Parser berada di
parse.rsdan menggunakan bentuk yang diambil dari sintaks Soufflé - Variabel diawali dengan
? - Token dibatasi pada
.,,,(,),:-,?, sedangkan teks lainnya diperlakukan sebagai nama atom atau term - Tokenizer menghapus spasi putih dan mengganti
:-dengan←agar dapat dipindai seperti satu simbol - Parsing aturan dilakukan dengan membaca atom head hingga turnstile, lalu atom body hingga period
- Atom terdiri dari nama, kurung kiri, daftar term, dan kurung kanan
- Term menjadi variabel jika ada
?, dan menjadi literal jika tidak ada
- Aturan yang tidak valid mengembalikan
None, dan saat ini belum memberi tahu secara rinci bagian mana yang salah - Untuk menambahkan aturan negasi diperlukan token
Exclamation, tetapi itu belum ditangani
Siklus hidup himpunan fakta
- Penyimpanan sederhana
Vec<Vec<String>>memiliki alokasi bertingkat sehingga tidak menguntungkan untuk manajemen memori - datatoad menggunakan
columnaruntuk mengubah tipe Rust menjadi sejumlah kecil alokasi linear dengan layout datar- Byte string, batas string, dan batas fakta disimpan dalam array terpisah
FactContainermembungkus daftar fakta yang sudah diurutkan dan dihapus duplikatnya, serta melalui wrapper type mengisyaratkan invariant pengurutan dan penghapusan duplikat- Karena container kolumnar pada dasarnya append-only sehingga tidak cocok untuk perubahan di tengah, penambahan fakta baru menggunakan bentuk log-structured merge-tree (LSM)
FactLSM { layers: Vec<FactContainer> }- Dikelola agar ukuran lapisan tumbuh secara geometris
- Lapisan-lapisan yang ukurannya dalam rentang 2 kali digabung untuk mempertahankan keadaan terurut dan tanpa duplikat
FactBuildermemiliki areaactiveyang belum diurutkan dan dapat berisi duplikat, bersamalayersyang sudah diurutkan dan dihapus duplikatnya- Fakta pada setiap relasi berpindah melalui tiga tahap
to_add: fakta yang baru tiba tetapi belum diperiksa apakah novelrecent: fakta yang distinct dan masih perlu diprosesstable: fakta distinct yang sudah diproses sepenuhnya
FactSet::advancememindahkanrecentkestable, lalu menghapus darito_addfakta yang sudah ada distableuntuk membuatrecentbaru
Evaluasi aturan adalah masalah join
- Body aturan Datalog dapat dilihat sebagai equi-join dalam basis data relasional
- Contoh aturan segitiga adalah sebagai berikut
tri(?a, ?b, ?c) :- edge(?a, ?b), edge(?b, ?c), edge(?a, ?c).
- Jika semua assignment variabel didaftarkan secara langsung, jumlahnya akan terlalu besar meskipun berhingga, sehingga relasi diurutkan berdasarkan kolom kunci dari variabel bersama lalu digabung
- Implementasi mereduksi body dari kanan ke kiri
- Dua relasi terakhir dijoin untuk membuat relasi perantara, lalu dijoin lagi dengan relasi kiri
- Jika hanya ada satu body atom, ia hanya diubah ke bentuk head
JoinPlanmemuat informasi berikutbodysyang menata ulang dan memfilter body atom agar sesuai untuk joinjoinsyang memuat key arity dan projection keluaran setiap join perantaraheadsyang menunjukkan koordinat atau literal untuk dimasukkan ke head atom- arity dari join pembentukan head terakhir
- Rencana saat ini adalah right-linear join plan sederhana
- Saat membuat
JoinPlan, posisi kemunculan leftmost dan rightmost tiap variabel digunakan untuk menentukan sampai kapan nilai variabel harus dipertahankan, lalu kolom body atom dibagi menjadi kolom dead, key, dan value - Fungsi intinya adalah
implement_plan(rule, plan, pos, stable, facts)- Ketika aturan baru ditambahkan, ia dimulai dengan
stable = trueterhadap seluruh fakta - Dalam penerapan berulang aturan yang sudah ada, ia menghitung hanya turunan baru dengan
stable = false
- Ketika aturan baru ditambahkan, ia dimulai dengan
Join inkremental dan merge join
- Join bersifat bilinear sehingga dapat diuraikan seperti berikut
(A + a) ⋈ (B + b) = A ⋈ B + A ⋈ b + a ⋈ B + a ⋈ b
A ⋈ Byang sudah dibuat antarbagian stable tidak perlu dihitung ulang- Jika hanya derivasi baru yang diperlukan, cukup lakukan tiga join
A ⋈ ba ⋈ Ba ⋈ b
join_withmenyertakan atau mengecualikan join stable-stable sesuai flagstablejoinsebenarnya adalah merge join yang menelusuri dua input terurut secara berurutan- Jika kuncinya sama,
actiondipanggil untuk semua kombinasi pada kunci tersebut - Jika kuncinya berbeda,
gallopdipakai untuk melompat cepat ke titik kemungkinan kecocokan berikutnya
- Jika kuncinya sama,
gallopadalah ide yang diambil dari EmptyHeaded: ia maju secara eksponensial selama kondisi monoton bernilai benar, lalu mempersempitnya dengan cara seperti pencarian biner
Eksperimen analisis nullability
- Data eksperimen berasal dari proyek Graspan, dan masih tersedia juga di Google Drive
- Input analisis dataflow memiliki dua relasi,
edannn(?a, ?b): nilai?adapat digunakan di lokasi?be(?a, ?b): nilai dari suatu lokasi?adapat berpindah ke lokasi lain?b
- Aturan reachability adalah sebagai berikut
n(?a, ?c) :- n(?a, ?b), e(?b, ?c) .
- Pada input
httpd,.listawal menunjukkan hal berikute: 9.905.624n: 138.331
- Menjalankan aturan secara langsung memakan waktu sekitar 15 detik, dan
nmenjadi 9.393.283 entri - Salah satu alasan lambatnya adalah relasi sementara
.temp-0-0-in, yang menyusun ulangnagar sesuai dengan kunci join, membesar hingga 9.393.283 entri - Performa membaik jika pengguna menulis ulang aturannya
m(?loc, ?val) :- n(?val, ?loc) .m(?loc, ?val) :- m(?mid, ?val), e(?mid, ?loc) .
- Pada input
httpdyang sama, setelah ditulis ulang, aturan kedua berjalan sekitar 8,43 detik
| dataflow | httpd | psql | lnx_kernel |
|---|---|---|---|
| graspan | 684s | 8640s | 42840s* |
| datatoad | 8.43s | 24.33s | 55.01s |
| datafrog | 1.30s | 4.06s | 8.03s |
- Angka Graspan untuk
lnx_kerneldiberi tanda*; makalahnya melaporkan total waktu sekaligus, dan karena benturan identifier input, sulit menganggapnya sebagai eksekusi yang persis sama - Untuk perbandingan dengan alat yang dipakai praktisi, Soufflé mungkin menjadi target yang lebih tepat
Analisis aliasing dan optimasi manual
- Analisis kedua adalah analisis aliasing Zheng and Rugina yang diadopsi oleh Graspan
- Ada dua relasi input
A(?val, ?loc):?loc <- ?valD(?val, ?loc):?locdigunakan dalam bentuk*?val
- Tujuannya adalah memperoleh memory alias dan value alias
- memory alias: dua ekspresi lvalue dapat menunjuk ke lokasi memori yang sama
- value alias: dua ekspresi dapat dievaluasi menjadi nilai pointer yang sama
- Notasi di makalah memunculkan
^T,^?,^*^T: transpose relasi^?: term opsional^*: pengulangan nol kali atau lebih
- Dalam Datalog,
^?diekspresikan dengan memecahnya menjadi dua aturan, sedangkan^*ditangani dengan memasukkan relasi identity secara eksplisit - Eksekusi aturan awal memakan waktu lama
- Setelah dua aturan inisialisasi identity terakhir dimasukkan, masing-masing memakan waktu 686,57 detik dan 736,34 detik
- Proses menggunakan 50,13 GB
V: 361.947.256M: 92.806.768F: 2.669.647
- Dengan memakai relasi transpose
-V,-M,-a,-dsecara eksplisit, relasi sementara-indihilangkan- Total 815,92 detik, sekitar 13,6 menit
- Memori 31,96 GB
- Pendekatan Zheng and Rugina bersifat demand-driven dan dalam praktiknya hanya
Myang diperlukan, sehinggaVdi-inline keM- Tidak membuat 361.947.256 entri
-V - Memori turun menjadi 18,96 GB
- Tidak membuat 361.947.256 entri
- Potongan join yang berulang diberi nama
Fduntuk digunakan kembali, lalu setelah ituFddihitung langsung alih-alihF, sehingga masalah identity juga berkurang - Bentuk final yang memperkenalkan relasi antara
MFdmemberikan hasil berikut- Waktu eksekusi: 119,34 detik
- Memori: 5,32 GB
-M: 92.806.768Fd: 1.858.986MFd: 73.474.947
- Hanya dengan penulisan ulang manual, memori dan waktu eksekusi membaik hampir 10 kali dibanding upaya awal
- Jika membuat hasil antara yang diberi nama, kita bisa menyusun sendiri join plan bushy-tree yang diinginkan sampai batas tertentu; tetapi jika memberi nama pada relasi yang sebenarnya tidak diperlukan seperti
V, relasi itu akan dibuat dengan biaya besar
Kueri demand-driven dan magic sets
- Kueri demand-driven adalah cara menelusuri hanya bagian yang diperlukan untuk fakta target tertentu
- Sebagai solusi aproksimatif, magic sets dapat digunakan
- Ini adalah transformasi yang menanamkan literal target ke dalam kueri
- Kita bisa membayangkan ekspresi yang dimulai hanya dari
dyang diminati, bukan semuad, tetapi jika diterapkan secara sederhana hasilnya bisa salah
- Magic sets bukan jawaban optimal, dan penulis berencana membaca lebih banyak makalah terkait untuk mencari pendekatan yang mungkin lebih efisien
- Tautan terkait adalah sebagai berikut
Optimasi rencana join dan IR paralel data
- Daya tarik Datalog bukan pada Horn clause itu sendiri, melainkan pada kemampuannya menyingkap secara murni masalah inti komputasi paralel data, yaitu data rendezvous
- Aturan
h(x, y, z) :- b1(x, y), b2(y, z) .dapat dipandang sebagai masalah mengumpulkanxdanzyang terkait untuk setiapydi satu tempat - Operasi dasar komputasi paralel data adalah mengumpulkan record berdasarkan kunci dan menyerahkannya ke logika pengguna, sedangkan join mengekspresikan routing selektif di antaranya
- IR sederhana menggunakan opcode berikut
Var(String): koleksi bernamaMap(Action): filter, permutasi, projectionKey(usize): menandai beberapa kolom terdepan sebagai kunciMul(usize): menggabungkan beberapa koleksi dengan panjang kunci yang sama
- Setelah itu,
MapdanKeydigabungkan menjadiAction.key_arity Actionmemuat filter literal, filter kesetaraan variabel, projection, dan key arity- Titik awal paling sederhana adalah melakukan cross join pada semua atom body lalu menambahkan filter dan projection untuk setiap head; ini memang menghasilkan jawaban benar, tetapi performanya sangat buruk
Optimisasi berbasis e-graph
- Optimisasi menggunakan e-graph dan equality saturation
- Referensi yang digunakan meliputi egg webpage, tulisan sebelumnya, dan
egg - Term graph direpresentasikan sebagai map berisi
ENode<T>yang diberiId, dan node yang sama dibagikan agar program dapat dinyatakan lebih ringkas - Ada tiga aturan e-graph yang diterapkan
MulPermute: membuat permutasi inputMul(k)menjadi ekuivalenMulPartition: membagiMul(k)dengan berbagai cara dan menjadikannya ekuivalenMapPushdown: mendorongMapke bawahMul(2)untuk membentuk join yang memiliki key
- Contoh aturannya sebagai berikut
head(?a, ?b) :- a(?x, ?a), b(?y, ?x), b(?y, ?z), a(?z, ?b) .
- Setelah equality saturation, biaya diberikan pada tahap extraction
- Biaya
Mapadalah jumlah kolom output - Biaya
Muladalah jumlah kolom key ditambah jumlah kolom non-key dari input - Biaya
Varadalah 0 - Jika seri, jumlah
Mapdiminimalkan, lalu jumlahMul
- Biaya
- Pada contoh, rencana yang dipilih ditemukan di wave two dengan maksimal dua kolom tak berkorelasi
- Map untuk input
adanb - Satu kali join
- Projection perantara
- Join dengan dirinya sendiri
- Projection akhir
- Map untuk input
- Pencarian rencana ini memakan sekitar 40 ms bahkan pada release build, dan sebagian besar waktunya digunakan untuk equivalence saturation
Pembaruan eksekusi rencana optimisasi
- Pada pembaruan 2025-06-29, eksekusi rencana yang telah dioptimalkan diimplementasikan
- Rencana keluar sebagai
Vec<ENode<Op>>, tetapi eksekusi sebenarnya tidak menjalankan tiap node secara independen - Cara eksekusi yang dimaksud adalah sebagai berikut
- Untuk tiap
Var, beberapaMapyang bergantung padanya diterapkan dalam satu pemindaian koleksi eksternal - Untuk tiap
Mul, beberapaMapyang bergantung padanya diterapkan dalam satu pemindaian join
- Untuk tiap
Op::Map(action)lebih merupakan pekerjaan yang diantrekan ke operasi dependensi daripada operasi yang dijalankan langsung- Untuk itu,
TempActiondiperkenalkan- Filter literal
- Filter kesetaraan variabel
- Projection yang dapat memuat referensi kolom atau literal string
- Tahap persiapan rencana eksekusi memisahkan body dan head, lalu mengumpulkan aksi
Mapberdasarkan node input - Dalam eksekusi
Var, penanganan nama terbagi menjadi tiga kasus- Pembuatan head dipakai untuk nama relasi head
- Transformasi identity memakai ulang nama input yang ada
- Transformasi non-trivial disimpan dengan nama sementara
.temp-*
- Eksekusi
Mul(2)memeriksa key arity dan nama dari dua input, lalu memanggiljoin_withuntuk menumpuk hasil ke beberapa builder - Pada contoh aliasing yang kompleks, eksekusi rencana yang dioptimalkan berjalan 114,28 detik, sekitar 5 detik lebih cepat dari sebelumnya yang berada di kisaran 119 detik, tetapi alasannya tidak jelas
- Optimisasi multi-rule belum diimplementasikan, dan pendekatan rencana itu sendiri mungkin perlu dipikirkan ulang
Optimisasi representasi fact
- Setelah 50 GB dikurangi menjadi 5 GB, memori yang dipakai masih dipandang sekitar 10 kali lebih besar dari yang diperlukan
- Largest layer pada relasi besar
-Mberisi 57.289.225 fact dan menggunakan sekitar 2.098.253.766 byte- Batas fact: 458.313.800 byte
- Batas term: 916.627.600 byte
- Data byte aktual: 723.312.366 byte
- Optimisasi pertama memanfaatkan arity yang konstan
- Jika semua fact memiliki 2 kolom, batas fact dapat direpresentasikan hanya dengan stride dan length
- Data batas sekitar 458 MB praktis dihilangkan
- Optimisasi kedua adalah menyamakan panjang term
- Jika angka dibuat menjadi string fixed-width 7 digit, batas term juga dapat direpresentasikan dengan stride dan length
- Sebagai gantinya, jumlah byte aktual bisa bertambah
- Optimisasi ketiga adalah merepresentasikan angka sebagai binary, bukan teks
- Angka 7 digit muat dalam
u324 byte - Karena juga muat dalam 3 byte, ukurannya menjadi
57,289,225 × 2 × 3 = 343,735,350byte - Turun dari sekitar 2 GB menjadi sekitar 350 MB, berkurang sekitar 6,10 kali
- Angka 7 digit muat dalam
- Jika pengulangan term pertama dikompresi, ukurannya turun lebih jauh
- Ada 57.289.225 fact, tetapi distinct first term hanya 1.147.612
- Jika disimpan dalam bentuk
(Term, [Term])alih-alih(Term, Term), largest layer turun ke sekitar 184.491.407 byte - Berkurang sekitar 11,37 kali dibanding 2 GB awal
- Pada pembaruan 2025-07-02, optimisasi tahap pertama diterapkan
- Largest batch menjadi 343.735.382 byte, yaitu nilai teoretis 343.735.350 byte ditambah 32 byte
- Waktu eksekusi turun dari sekitar 115 detik menjadi sekitar 95 detik, membaik sekitar 20%
Representasi layered trie
- Pada pembaruan 2025-07-20, layered trie dapat berfungsi
- Representasi row-oriented dibandingkan dengan representasi layered trie column-oriented
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 adalah cara menekan pengulangan kolom depan dalam representasi row yang terurut
- Tiap kolom adalah list dari list nilai
- Tiap list adalah daftar distinct value yang terurut dan sesuai dengan prefix kolom sebelumnya
- Jumlah list dalam satu kolom sama dengan total item pada kolom sebelumnya
- Dapat dilihat seperti trie, tetapi implementasi sebenarnya tetap dipertahankan sebagai kolom
- Keuntungannya adalah pencarian, join, difference, dan merge dapat dilakukan per prefix
- Jika hampir tidak ada distinct value dan lebih baik melihat seluruh baris sekaligus, pendekatan row-oriented bisa lebih menguntungkan
- Sebagai abstraksi umum, trait
FactContainerdiperkenalkanformlenapplyjoinexceptmerge
applymelacak range tiap layer dengan stack eksplisit, bukan rekursi, lalu membentuk baris dan meneruskannya ke actionalignadalah helper umum untuk mencocokkan prefix dari dua layered trieOrdering::Less: range yang hanya ada di selfOrdering::Greater: range yang hanya ada di otherOrdering::Equal: prefix sepanjang arity yang sama-sama ada di kedua sisi
join,except, danmergesemuanya diimplementasikan di atasalignjoinmengembangkan extension yang tersisa pada prefix bersama lalu melakukan cross joinexceptmelakukan graft range yang hanya ada di self keTrieBuildermergemelakukan graft pada range self-only, other-only, dan equal masing-masing sekali secara sesuai
Optimisasi fixed-width dan performa
- Jika dapat di-upgrade ke fixed-width
[u8; 4], performa perbandingan membaik secara signifikan - Layered trie dapat menerapkan optimisasi fixed-width per kolom, sehingga dalam jangka panjang berpotensi lebih menguntungkan daripada row
upgradedandowngrademudah diterapkan padaexceptdanmerge, tetapi penerapannya padajoinlebih sulit karena masalah tipe Rust
| 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 |
- Saat ini sekitar 2/3 waktu dihabiskan di bawah
join - Diperkirakan masih ada ruang setidaknya sekitar 2x lagi untuk optimasi join
- Percobaan sederhana dengan menyusun ulang inner loop tidak menghasilkan peningkatan yang terukur
Spesialisasi hingga level kode terkompilasi
- Jika mendeteksi term dengan panjang yang sama dan fact dengan arity yang sama,
Vec<u8>dapat dipandang sepertiVec<[[u8; B]; T]> - Bentuk ini membuat Rust lebih memahami bentuk data, sehingga mengurangi biaya pemeriksaan bounds dan length, serta membuat perbandingan menjadi sangat murah
- Perbandingan digunakan di berbagai titik dalam datatoad
- Mengurutkan dan menghapus duplikat batch fact
- Menggabungkan batch
- Menggabungkan kunci join
- Memfilter fact baru dengan membandingkannya terhadap fact yang sudah ada
- Performa baseline adalah sebagai berikut
| 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 |
- Optimasi pengurutan diuji dengan melakukan
unsafetransmute dariVec<u8>menjadiVec<[u8; 8]>, lalu menjalankan sort dan dedup
| 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 |
- Optimasi penggabungan diimplementasikan dengan cara sederhana: menggabungkan dua input, lalu menjalankan sort dan dedup
| 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 |
- Masih belum mencapai performa terkompilasi milik datafrog
- Biaya perbandingan yang tersisa juga ada pada join dan antijoin; rencananya peluang optimasi yang sama akan ditinjau lagi saat berpindah ke struktur berbasis trie
- Sedang mencari cara untuk menghapus
unsafe, dan menambahkan kesimpulan bahwa kode unsafe sebaiknya dihindari
Pekerjaan yang masih tersisa
-
Spill ke disk
- Penyimpanan
columnarterdiri dari sejumlah kecil alokasi besar - Saat dibuat, data dapat ditulis ke file alih-alih memori, lalu digunakan kembali dengan memory map
- Penyimpanan
-
Evaluasi terdistribusi
- Join, penghapusan duplikat, dan distinctness check berbasis pada kesetaraan kunci
- Kunci dan data terkait dapat didistribusikan ke beberapa worker
- Dengan
timely_communication, ini dapat diperluas hingga beberapa proses
-
Evaluasi aturan streaming
- Saat ini join menggunakan binary join dan output yang dimaterialisasi
- Jika indeks yang diperlukan tersedia, dapat dibuat rencana yang tidak mematerialisasi state internal
- Worst-case optimal join juga akan dibahas di sini
-
Spesialisasi custom representation
- Ada ide untuk mendeteksi transitive closure dan mengkhususkan pada strongly connected component decomposition
- Equivalence relation dapat menggunakan struktur data union-find
bddbddbdan factorized databases juga dibahas sebagai topik terkait
-
Pencarian fact terkait
- Perlu memahami dan menerapkan demand transform
- Ini dipandang sebagai transformasi yang diperlukan untuk eksplorasi Datalog interaktif
1 komentar
Komentar Hacker News
Melihat tulisan ini naik ke peringkat 1, sepertinya memang menarik
Saat ini saya sedang membuat game strategi real-time dengan Differential Datalog dan Rust, dan logika game-nya dikelola oleh DDL
Sebagian besarnya lebih seperti alasan untuk menemukan ide baru dan melakukan yak shaving tanpa akhir
https://github.com/vmware-archive/differential-datalog
Sebagai referensi, tim ddlog sekarang telah mendirikan Feldera, jadi mungkin layak juga mempertimbangkan memakai DBSP langsung dari Rust
https://github.com/feldera/feldera
https://github.com/salsa-rs/salsa
DDLog sudah tidak lagi dipelihara secara aktif, jadi justru lebih menarik
“Saya, si penjahat terkenal, diundang dengan keyakinan setengah pasti bahwa saya sedang pergi untuk menerima hukuman yang seharusnya sudah saya terima sejak lama.” — ini adalah kalimat pembuka terbaik dari tulisan blog teknis yang saya baca tahun ini
Sisipan komentar naratornya juga luar biasa, dan jarang ada tulisan yang sedalam ini secara teknis tetapi tetap begitu menyenangkan dibaca
Perjalanan mengoptimalkan kueri alias terasa seperti novel detektif, dan pembaca ikut merintih pada penggunaan memori 50GB lalu bersorak ketika turun menjadi 5GB
Kode maupun tulisannya sama-sama hebat
Saya sempat mengerjakan sedikit port mangle datalog ke Rust
Ada di https://github.com/google/mangle/tree/main/rust dan berada di repositori yang sama dengan implementasi Go
Karena prioritasnya tidak tinggi, ditambah sindrom sistem kedua, progresnya lambat
Mangle Rust diarahkan untuk menangani data berukuran sewenang-wenang sambil membaca dan menulis fakta dari disk lewat memory mapping, sedangkan implementasi Go berbasis in-memory
Tulisan ini bagus karena juga membahas parsing Datalog dan LSM tree, dan jauh lebih mudah diikuti dibanding materi tentang datafrog
Di Rust ada banyak implementasi Datalog yang memakai procedural macro seperti ascent dan crepe, tetapi kekurangannya adalah sulit menerima kueri saat runtime
Jika dipakai untuk analisis statis dengan kueri dan program yang sudah tetap, pendekatan procedural macro mungkin lebih baik
Meski tampaknya kebangkitan Datalog saat ini mulai melemah, menyenangkan melihat para penggemar intinya tetap bertahan
Konferensi Datalog 2.0 terbaru cukup jauh lebih kecil dibanding dulu, dan konferensi HYTRADBOI kedua juga porsi Datalog-nya kecil
Pada HYTRADBOI pertama, seperempat dari karya yang diajukan berkaitan dengan Datalog
Juga menggembirakan melihat orang lain membagikan proyek Datalog terbaru mereka
Saat ini saya sedang menyiapkan migrasi perangkat lunak berskala besar sambil membuat pipeline kualitas data untuk basis data SQL lama
Jika kuerinya disusun dengan baik, hasilnya sangat mudah dibaca, jadi menurut saya Datalog jauh lebih berguna daripada SQL untuk mengidentifikasi dan menelusuri masalah kualitas data
Datalog 2.0 adalah workshop satelit dari konferensi Eropa LPNMR yang relatif kurang dikenal, dan kebetulan konferensi itu diadakan di Dallas
Saat saya hadir langsung pun acaranya terasa agak sepi, dan walau saya juga mengirim makalah ke workshop itu, saya tidak terlalu melihat banyak orang dari bidang tersebut
Sebagai pengecualian, orang-orang dari Eropa yang memperkenalkan solver Nemo cukup menonjol
Menurut saya, sedikitnya peserta tahun ini lebih menunjukkan bahwa ini adalah workshop satelit dari konferensi yang memang tidak terlalu terkenal, dengan acara utamanya ICLP, daripada kurangnya minat pada implementasi Datalog
Tentu saja saya tidak bermaksud membantah klaim yang lebih besar bahwa hampir tidak ada lagi hal baru yang tersisa dalam implementasi engine Datalog murni
Ruang risetnya sudah bergerak jauh ke isu-isu yang lebih eksotis seperti streaming (HydroFlow), choice (Dusa), dan hal-hal yang lebih mendekati general chase (engine chase milik Egglog)
Hampir semua orang tampaknya setuju bahwa Datalog vanilla itu membosankan, tetapi saturasi maju monotonic dan Horn clause adalah baseline kaya dengan lanskap rekayasa performa yang sudah dipahami dengan baik, sehingga bagus untuk membangun teori yang lebih menarik seperti semiring atau Z-set
Jika bagian state machine dan parsing terasa menarik, saya juga merekomendasikan presentasi lama Rob Pike Lexical Scanning in Go
https://www.youtube.com/watch?v=HxaD_trXwRE
Memang dalam Go, tetapi sebagian besarnya mudah diterapkan ke bahasa lain
Menyenangkan bahwa bahasa modern seperti Rust, Zig, dan Go mendukung Unicode/rune/grapheme secara native
Dibandingkan Java, .NET, C++, atau bahasa skrip, banyak masalah jadi langsung hilang
Saya secara umum menyukai pekerjaan Datalog penulis ini, tetapi saya berharap materi pengantarnya tidak mengajarkan binary join
Begitu keluar dari kasus ideal, bagian dalamnya cepat menjadi berantakan, dan metode dengan gaya join umum jauh lebih mudah digeneralisasi di kepala saya
https://en.wikipedia.org/wiki/Worst-case_optimal_join_algorithm
https://github.com/frankmcsherry/blog/blob/master/posts/2025-05-29.md
Dulu saat kuliah saya sempat sedikit bersentuhan dengan Prolog, dan kurang lebih tahu dipakai untuk apa serta apa kegunaannya, tetapi tidak pernah benar-benar memahaminya secara mendalam.
Sejak itu saya terus mendengar bahwa Datalog itu hebat, tetapi justru sulit menangkap apa yang diperbaiki Datalog dibanding Prolog.
Barusan saya membaca sekilas halaman Wikipedia tentang Datalog, dan kesannya Prolog punya performa yang relatif buruk, sedangkan Datalog secara drastis meningkatkan performa dengan mengurangi daya ekspresi dan fitur, sehingga bisa menangani dataset yang lebih besar dan pemrosesan yang lebih terparalelkan.
Sepertinya ini juga mencakup hilangnya kelengkapan Turing, tetapi saya penasaran apakah itu memang inti persoalannya atau saya sepenuhnya meleset.
cut bukan hanya mencegah komputasi yang terbuang, tetapi juga bisa memengaruhi hasil.
Sebaliknya, Datalog pada umumnya lebih mirip basis data relasional dengan sintaks yang berbeda.
Ruang pencarian besar yang sepele di Prolog bisa saja menghabiskan terlalu banyak memori di Datalog hingga tak bisa direpresentasikan sama sekali.
Datalog adalah mobil komuter dengan CVT, sementara Prolog lebih seperti mobil F1.
Jadi ini bukan benar-benar peningkatan, melainkan lebih seperti Prolog yang dipreteli sebagian agar orang tidak menembak kaki sendiri, dan jauh lebih mudah juga untuk diimplementasikan serta disematkan ke aplikasi lain.
Jika Anda terbiasa dengan Prolog, Datalog kemungkinan besar akan terasa membatasi.
Tidak ada
call/3, tidak ada term/goal expansion, dan pada dasarnya Datalog adalah himpunan fitur minimum bersama dari Prolog yang dirancang untuk pencarian basis data interaktif.Mudah menulis kode Datalog yang cepat, tetapi batas atasnya juga jauh lebih rendah.
Prolog juga bisa ditulis dengan cara yang memungkinkan konkurensi, tetapi itu pekerjaan tingkat menengah yang menuntut pemahaman terhadap implementasinya.
Guarded Horn Clauses dan bahasa-bahasa turunannya dikembangkan untuk memformalkan sisi tersebut, tetapi perkembangan pasca-Prolog dari Jepang sangat sulit dipahami.
Performa Prolog sangat bergantung pada programmer, implementasi yang dipakai, dan di mana ia digunakan.
Seperti Lisp, Prolog juga bisa dipakai pada waktu kompilasi untuk menghasilkan kode mesin native dari DSL.
Jika Anda memahami cara kerja implementasi dasarnya dan menulis kode yang sejalan dengannya, performanya bisa cukup cepat.
Hanya saja untuk sampai ke situ biasanya perlu menulis kode Prolog selama bertahun-tahun dengan satu implementasi.
Ada banyak penelitian tentang optimisasi kompilator Prolog, dan juga contoh implementasi proprietari.
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/
Jika ingin memakai Datalog dan Rust, cozodb ditulis dalam Rust dan juga menyediakan sintaks kueri Datalog.
Sekitar November 2024 saya sempat melihatnya dan menemukan beberapa perbaikan mudah pada backend penyimpanan SQLite.
https://github.com/cozodb/cozo/issues/285
Saya juga memakainya untuk analisis statis program, dan secara internal ia menggunakan pohon terurut serta teknik pengetikan.
Dokumentasinya cukup baik sampai layak dibandingkan dengan penjelasan blog langkah demi langkah di awal, dan khususnya pekerjaan optimisasi kuerinya menarik.
Hanya saja, jika tidak bekerja in-memory di Rust, biaya serialisasi datanya besar, dan proyeknya, kalau dinilai baik-baik pun, sedang sepi.
Dulu para penggemar Clojure mengatakan bahwa Datalog lebih baik daripada SQL, dan sayang sekali semua basis data relasional memakai SQL
Saya belum benar-benar mendalami alasan mereka berpikir begitu
http://canonical.org/~kragen/binary-relations dimulai dari kueri non-rekursif yang sederhana, tetapi terjemahan SQL-nya sudah terasa kriminal, dan solusi SQL yang dipisahkan dengan benar layak dihukum mati
ANSI SQL belakangan menambahkan fitur rekursi sehingga ini tidak lagi sepenuhnya mustahil, tetapi masih ada tiga kelemahan besar
Pertama, SQL tanpa sengaja jadi Turing-complete, sedangkan kueri Datalog dijamin berhenti
Kedua, tetap saja sangat canggung digunakan
Ketiga, karena alasan pertama, implementasinya sering tidak sepenuhnya lengkap sehingga sulit dipercaya untuk dipakai
Jika ingin mencoba Datalog secara online lewat lingkungan notebook yang ramah, saya merekomendasikan Percival
https://percival.ink/
Secara umum belum ada standar untuk implementasi Datalog yang setara dengan “ANSI SQL”, tetapi begitu memahami ide intinya, Datalog lain pun tidak akan terlalu sulit
Saya juga sudah mulai membuat fork Percival yang mengompilasi Datalog ke SQLite, jadi kalau ingin melihat bagaimana keduanya mengekspresikan hal yang sama, bisa dicek
https://percival.jake.tl/
Agregasi dan join yang lebih canggih masih belum selesai, tetapi bentuk dasarnya sudah bekerja dengan baik
Logica adalah kompiler Datalog→SQL yang jauh lebih serius dan matang, dibuat oleh peneliti Google, dan dapat mengompilasi ke BigTable, DuckDB, dan beberapa dialek SQL lainnya
https://logica.dev/
Area tempat Datalog menjadi jauh lebih mudah adalah saat menangani kueri/aturan rekursif
Di SQL ini juga mungkin, tetapi rasanya seperti minum Play-Doh dengan sedotan
Di Materialize.com milik Frank ada format SQL “WITH MUTUALLY RECURSIVE” yang jauh lebih baik daripada pendekatan rekursif ANSI SQL lama, dan sedang dievaluasi di Notion untuk kueri pemuatan halaman serta sinkronisasi data
https://materialize.com/blog/recursion-in-materialize/
Feldera juga punya format serupa untuk view rekursif
https://www.feldera.com/blog/recursive-sql-queries-in-feldera
Saya menyukai Feldera karena setiap “aturan” atau sub-view bisa dibuat sebagai pernyataan terpisah alih-alih harus memasukkannya semua ke dalam satu pernyataan raksasa
Kekurangan utama yang saya lihat saat mengujinya adalah bahwa dialek SQL Feldera membawa cukup banyak batasan dari Apache Calcite, sedangkan dialek SQL Materialize sangat berusaha menjaga kompatibilitas PostgreSQL
Tulisan baru dari McSharry, bagus sekali
Terakhir kali saya cek, VMWare tampaknya sudah menjauh dari differential datalog
https://www.feldera.com/
Sepertinya mereka beralih dari differential Datalog ke differential SQL, kemungkinan karena mereka sadar Datalog memang sangat sulit dijual