Melampaui =~: Bagaimana Milvus 3.0 Mengubah Regex menjadi Filter Database Native
Pencarian vektor dapat menemukan log yang secara semantik mirip dengan sebuah insiden. Namun, saat terjadi gangguan, kemiripan hanyalah penyaringan awal. Engineer sering kali perlu mempersempit hasil tersebut dengan batasan struktural, bukan semantik.
Pertimbangkan kegagalan pada layanan checkout. Kita menginginkan log yang terkait dengan insiden yang sedang ditangani, tetapi hanya dari checkout. Pesan harus berisi kode error dalam bentuk E diikuti empat digit, dan kode tersebut harus muncul sebelum timeout.
results = client.search(
collection_name="logs",
data=[incident_embedding],
anns_field="embedding",
filter=r'service == "checkout" and message =~ r"E[0-9]{4}:.*timeout"',
limit=20,
output_fields=["timestamp", "service", "message"],
)
Pencarian vektor dan regex melakukan pekerjaan yang berbeda dalam kueri ini. Kemiripan vektor mengambil log yang menyerupai insiden; filter regex hanya mempertahankan log yang cocok dengan signature kegagalan yang kita pedulikan.
Milvus 3.0 menambahkan operator =~ dan !~ untuk jenis filter ini. Mendukung operator tersebut dalam database melibatkan lebih dari sekadar mem-parsing sintaks regex: Milvus harus memvalidasi pola yang diberikan pengguna, mempertahankan semantik null dan negasi, mengintegrasikan regex dengan perencanaan kueri dan pengindeksan, serta menjaga biayanya tetap dapat diprediksi di jutaan string.
Dengan rilis terbaru, Milvus 3.0 memindahkan regex dari post-processing di sisi aplikasi ke jalur kueri database, tempat regex dapat dikombinasikan dengan pencarian vektor, pencarian full-text, filter skalar, dan eksekusi terindeks. Artikel ini membahas pilihan desain tersebut, mulai dari semantik operator dan validasi RE2 hingga eksekusi mentah, pembuatan kandidat NGRAM, dan hasil benchmark.
Di mana regex masuk dalam filtering Milvus
Sebelum dukungan regex, Milvus sudah dapat mengekspresikan pola string sederhana dengan LIKE:
Plaintext
message LIKE "ERROR%"
message LIKE "%timeout%"
message LIKE "node_12_"
LIKE sederhana dan mudah dibaca. Untuk filter prefix, suffix, dan contains biasa, operator ini juga memberi engine eksekusi peluang untuk menggunakan jalur yang lebih langsung. Namun, jangkauan ekspresifnya berhenti pada dua aturan wildcard: % untuk nol atau lebih karakter dan _ untuk tepat satu karakter.
Like tidak cukup untuk pola seperti:
- Kode error:
E[0-9]{4} - Versi semantik:
v[0-9]+.[0-9]+.[0-9]+ - Beberapa state:
ERROR|WARN - Route URL:
^/api/v[0-9]+/users/[0-9]+$ - Urutan yang diwajibkan:
checkout.*timeout - Teks case-insensitive:
(?i)connection refused
Menguraikan struktur ini menjadi sekumpulan besar ekspresi LIKE, range, dan Boolean sulit dipelihara dan mudah salah di bagian tepi. Regex mengisi celah antara pencocokan wildcard sederhana dan retrieval berbasis token atau semantik. Regex mendeskripsikan struktur string; regex tidak menggantikan relevansi full-text atau kemiripan vektor.
Operator yang paling jelas biasanya adalah yang terbaik untuk digunakan:
| Kebutuhan | Operasi yang direkomendasikan |
|---|---|
| Nilai persis | == / IN |
| Prefix, suffix, atau substring sederhana | LIKE |
| Kelas karakter, pengulangan, anchor, atau urutan struktural | Regex =~ / !~ |
| Relevansi leksikal tingkat token | text_match / BM25 |
| Frasa berurutan | phrase_match |
| Variasi tipografis | Milvus saat ini tidak memiliki fuzzy match native; tangani di layer aplikasi |
Pola seperti ^ERROR atau timeout$ adalah regex yang valid, dan parser dapat menulis ulang pola tersebut ke jalur eksekusi yang lebih murah. Meski begitu, memilih operator yang menyatakan intent secara paling langsung membuat filter lebih mudah dibaca dan dipelihara.
Semantik yang terlihat oleh pengguna: =~, !~, anchor, dan raw string
Milvus 3.0 menggunakan =~ untuk pencocokan regex positif dan !~ untuk pencocokan negatif:
message =~ "E[0-9]{4}"mempertahankan baris yang string-nya berisi kode error yang cocok.message !~ "DEBUG|TRACE"mengecualikan baris yang string-nya cocok dengan salah satu alternatif.message =~ "timeout"menggunakan semantik substring dan mencocokkan string apa pun yang berisitimeout.message =~ "^timeout$"menggunakan^dan$untuk mewajibkan pencocokan seluruh string.
Regex dapat menargetkan beberapa jenis nilai string. Untuk field array dan StructArray, ekspresi harus mengidentifikasi elemen string atau subfield string tertentu:
- Field
VARCHAR:message =~ r"timeout" - Path JSON yang resolve ke string:
metadata["error_message"] =~ r"E[0-9]{4}:.*timeout" - Satu elemen dari
ARRAY<VARCHAR>:tags[0] =~ r"release-v[0-9]+" - Subfield
VARCHARdi dalam ekspresi StructArray:MATCH_ANY(events, $[name] =~ r"error.*timeout")
Raw string menjaga escaping tetap mudah dikelola
Pola regex sering berisi backslash. Milvus 3.0 mendukung literal raw string dalam ekspresi filter. Contohnya:
filter_expr = r'message =~ r"\d+.\d+.\d+"'
r"..." di bagian dalam memberi tahu parser ekspresi Milvus untuk mempertahankan backslash. Raw string Python di bagian luar kemudian mencegah layer bahasa aplikasi, ekspresi Milvus, dan regex melipatgandakan aturan escaping.
Template filter juga berfungsi dengan regex:
client.query(
collection_name="logs",
filter="message =~ {pattern}",
filter_params={"pattern": r"E[0-9]{4}:.*timeout"},
output_fields=["message"],
)
Meneruskan pola sebagai parameter template lebih aman daripada menggabungkan string ekspresi dan memungkinkan template kueri yang sama digunakan ulang.
Mengapa Milvus menggunakan RE2: regex database harus memiliki biaya yang dapat diprediksi
Banyak engine regex menggunakan backtracking. Pola yang dirancang dengan cermat dapat memaksa waktu eksekusinya tumbuh secara eksponensial terhadap panjang input, sebuah mode kegagalan yang umum disebut catastrophic backtracking atau regular expression denial of service (ReDoS).
Dalam skrip lokal, pola patologis dapat membuat satu proses macet. Dalam database, pola adalah input kueri. Satu request dapat menerapkannya ke jutaan baris sementara banyak pengguna mengirimkan pola lain secara bersamaan. Jika eksekusi kasus terburuk tidak terbatas, filter yang tampak biasa dapat menghabiskan CPU di seluruh layanan.
Milvus menggunakan RE2 karena RE2 menjamin waktu pencocokan yang linear terhadap panjang input. Tradeoff ini disengaja: RE2 tidak mendukung konstruksi yang bergantung pada backtracking, termasuk:
- Backreference
- Lookahead
- Lookbehind
Milvus mengutamakan eksekusi yang dapat diprediksi dibanding sintaks regex seluas mungkin. Sebelum filter mencapai jalur scan, parser ekspresi mengompilasi dan memvalidasi polanya. Regex yang tidak valid mengembalikan error sebelum Milvus mulai memindai data.
Dari sintaks ke eksekusi: jalur berlapis
Implementasi paling sederhana akan mengompilasi pola dan memanggil RE2 sekali untuk setiap baris. Itu akan menghasilkan kecocokan yang benar, tetapi akan mengabaikan struktur yang sudah diketahui database tentang pola, sisa filter, dan indeks yang tersedia.
Milvus sebagai gantinya menggunakan jalur eksekusi berlapis. Setiap layer hanya menerapkan reduksi yang mempertahankan kebenaran. Ketika engine tidak dapat membuktikan bahwa shortcut aman, engine kembali ke verifikasi RE2 penuh.
1. Tulis ulang regex yang sebenarnya merupakan operasi string yang lebih murah
Beberapa pola regex sama sekali tidak membutuhkan engine regex:
| Pola | Operasi hasil penulisan ulang |
|---|---|
^ERROR$ | Equality |
^ERROR | Prefix match |
ERROR$ | Suffix match |
Parser mengenali literal ber-anchor ini dan menulis ulangnya menjadi operasi yang lebih murah.
Literal tanpa anchor seperti ERROR memiliki semantik yang berbeda: literal tersebut menanyakan apakah ERROR muncul di mana saja dalam string. Dalam implementasi saat ini, pola sederhana ber-anchor menjadi operasi equality, prefix, atau suffix, sementara literal biasa tanpa anchor tetap menjadi ekspresi RegexMatch.
2. Perlakukan regex sebagai predikat berat
Pertimbangkan filter gabungan:
Plaintext
service_id == 42 and message =~ r"E[0-9]{4}:.*timeout"
Pemeriksaan equality numerik atau filter terindeks yang sudah ada biasanya lebih murah daripada regex. Milvus menandai regex sebagai predikat berat sehingga planner dapat menjalankan kondisi yang lebih murah terlebih dahulu dan menerapkan RE2 ke set kandidat yang lebih kecil.
Benchmark nanti dalam artikel ini secara sengaja mengecualikan pencarian vektor dan filter gabungan, sehingga tidak mencampurkan biaya approximate nearest neighbor (ANN), indeks skalar, dan regex. Dalam kueri produksi, pengurutan predikat tetap menjadi bagian dari jalur eksekusi lengkap.
3. Pada jalur mentah, kompilasi sekali dan prefilter literal yang diwajibkan
Tanpa indeks NGRAM, segmen sealed membaca string asli dan mengevaluasi kecocokan. Milvus menggunakan ulang pola RE2 yang sudah dikompilasi pada cakupan segmen, bukan mengompilasinya sekali per baris.
Untuk pola dengan literal wajib yang stabil, seperti:
Plaintext
ERROR.*timeout
Milvus dapat terlebih dahulu menggunakan pencarian string Volnitsky untuk menemukan baris yang mungkin berisi literal wajib tersebut. Hanya baris-baris itu yang lanjut ke verifikasi RE2 yang tepat.
Volnitsky adalah prefilter, bukan pengganti regex. String yang berisi ERROR dan timeout belum tentu memenuhi urutannya atau pola lengkapnya. Tugas prefilter hanya mengurangi jumlah panggilan RE2 yang lebih mahal.
4. Dengan NGRAM, buat kandidat terlebih dahulu dan verifikasi dengan RE2
Untuk dataset sealed yang besar, field string dapat memiliki indeks NGRAM:
index_params = client.prepare_index_params()
index_params.add_index(
field_name="message",
index_type="NGRAM",
params={"min_gram": 4, "max_gram": 4},
)
client.create_index("logs", index_params)
NGRAM tidak mengeksekusi regex itu sendiri. NGRAM memperkenalkan jalur dua fase:
- Ekstrak secara konservatif literal yang harus muncul dalam setiap kecocokan.
- Pecah literal tersebut menjadi n-gram dan lakukan intersect pada posting list-nya untuk menghasilkan bitmap kandidat.
- Baca string asli hanya untuk baris kandidat.
- Jalankan RE2 pada kandidat tersebut untuk menghasilkan hasil yang tepat.
Untuk ERROR.*timeout, misalkan hanya 0,1% dari 10 juta baris yang berisi literal wajib. Fase 1 dapat menghapus hampir seluruh dataset sebelum RE2 berjalan. Peningkatan performa berasal dari reduksi kandidat, bukan dari aproksimasi regex.
Pemisahan ini juga melindungi kebenaran. Kondisi yang tidak dapat diekstrak dengan aman tidak diizinkan mengeliminasi baris, dan RE2 tetap memutuskan setiap kecocokan akhir. Karena itu, coarse filter NGRAM tidak menimbulkan false negative.
5. Fall back ketika reduksi kandidat tidak dapat dibuktikan aman
Tidak setiap pola mengekspos literal tetap yang berguna:
Plaintext
E[0-9]{4}
ERROR|WARN
(?i)error.*timeout
Kelas karakter mungkin hanya menyisakan literal yang sangat pendek. Alternation memerlukan reasoning lintas beberapa cabang, dan pencocokan case-insensitive memerlukan case folding. Dalam implementasi saat ini, pola alternation dan (?i) melewati coarse filter NGRAM dan kembali ke verifikasi mentah.
Fallback tersebut adalah batas yang benar untuk optimasi indeks. Jika Milvus tidak dapat membuktikan bahwa reduksi kandidat aman, Milvus tidak menggunakannya.
!~ harus mempertahankan UNKNOWN, bukan sekadar membalik bitmap
Begitu regex masuk ke bahasa ekspresi database, kasus-kasus sulit tidak terbatas pada sintaks pola. Nullability juga penting.
Pertimbangkan tiga record JSON:
{"message": "request timeout"}
{"message": "request completed"}
{}
Record ketiga tidak memiliki message. Berdasarkan logika tiga nilai yang dijelaskan oleh desain ini, engine tidak dapat membuktikan bahwa nilai yang hilang tersebut cocok maupun tidak cocok, sehingga hasilnya adalah UNKNOWN:
| Input | message =~ "timeout" | message !~ "timeout" |
|---|---|---|
"request timeout" | TRUE | FALSE |
"request completed" | FALSE | TRUE |
| NULL / path hilang / tipe tidak valid | UNKNOWN | UNKNOWN |
Klausa WHERE atau filter hanya mengembalikan baris yang ekspresinya bernilai TRUE, sehingga baris terakhir dikecualikan oleh kedua operator.
Mengimplementasikan !~ sebagai inversi Boolean biasa akan salah. Path yang hilang yang direpresentasikan sebagai false akan menjadi true, dan entitas tanpa nilai akan muncul secara tidak terduga dalam kueri “does not match”. Milvus merepresentasikan !~ sebagai NOT (=~) sambil mempertahankan bitmap validitas, menjaga UNKNOWN tetap konsisten di jalur mentah dan terindeks.
Inilah perbedaan antara menempelkan library string ke executor dan mengimplementasikan regex sebagai predikat database.
Benchmark: NGRAM membantu ketika literal memperkecil set kandidat
Benchmark mengisolasi filtering regex di atas segmen sealed sehingga hasilnya mencerminkan scanning string mentah, pembuatan kandidat NGRAM, dan verifikasi RE2. Benchmark ini mengecualikan growing segment, pencarian vektor, dan filter gabungan.
Dataset dan selektivitas terkontrol
Pengujian menggunakan dataset publik Loghub HDFS_v1:
- 11.175.629 baris log sistem HDFS
- 1,47 GiB data mentah
- 38,7 jam log
- 10.000.000 baris valid pertama digunakan dalam eksperimen
Distribusi panjang log, token, dan pengulangan asli dipertahankan. Untuk mengontrol tingkat kecocokan secara presisi, pengujian menambahkan marker benchmark ke sejumlah kecil baris yang dipilih dengan seed acak tetap:
Plaintext
level=ERROR code=E4821 operation=checkout result=request_timeout
Target injeksi adalah 0,01%, 1%, 10%, dan 50%. Setiap varian dataset menggunakan pemilihan baris seeded dan aturan transformasi yang sama, sehingga data HDFS_v1 yang diberi marker dapat direproduksi. Pengujian konsistensi juga berjalan pada field log asli yang tidak dimodifikasi untuk memastikan bahwa tren optimasi yang diamati bukan artefak dari injeksi marker.
Lingkungan pengujian
| Item | Konfigurasi terukur |
|---|---|
| Commit Milvus | 03762320e8 |
| Deployment | Standalone, segmen sealed, 1 shard |
| Hardware | Apple M5 Pro, memori 48 GiB |
| Baris collection | 10.000.000 |
| Field | Lima field VARCHAR(max_length=512) ditambah placeholder BINARY_VECTOR(dim=8) |
| NGRAM | min_gram=4, max_gram=4 |
| Concurrency | 1 untuk latensi, 32 untuk throughput |
| Repetition | 2 warm-up run ditambah 5 run terukur; p50 / p95 / p99 dilaporkan |
Jalur mentah dan NGRAM menggunakan data dan urutan kueri yang sama. Hasil diukur setelah warm-up dan hanya mencakup perilaku warm-cache. Pengujian ini tidak mendukung kesimpulan tentang cold start atau latensi kueri pertama.
Matriks pola
| Pola | Jalur eksekusi utama | Apa yang diisolasi pengujian | |
|---|---|---|---|
^ERROR | Literal ber-anchor; pembuatan kandidat plus verifikasi prefix | Bagaimana verifikasi anchor berubah saat tingkat kandidat naik | |
ERROR.*timeout | Kaya literal; kandidat NGRAM plus RE2 | Rentang di mana NGRAM seharusnya memberikan peningkatan paling jelas | |
E[0-9]{4} | Literal wajib lemah; 4-gram tetap memberi reduksi kecil | Apakah literal lemah mendekati biaya raw-scan | |
| `ERROR | WARN` | Fallback alternation | Batas alternation saat ini |
(?i)error.*timeout | Fallback case-insensitive | Batas case-insensitive saat ini |
Setiap pola dijalankan pada dua jalur segmen sealed:
- Tanpa indeks skalar: scan string mentah
- Indeks NGRAM: pembuatan kandidat plus verifikasi RE2, dengan fallback otomatis ketika NGRAM tidak dapat digunakan
Latensi di berbagai tingkat selektivitas
Setiap sel di bawah melaporkan raw p50 ms / NGRAM p50 ms / speedup. Nilai lebih besar dari 1x berarti NGRAM lebih cepat.
| Pola | 0,01% | 1% | 10% | 50% | |
|---|---|---|---|---|---|
^ERROR | 12,65 / 6,99 / 1,81x | 12,98 / 9,63 / 1,35x | 12,73 / 10,98 / 1,16x | 13,96 / 20,27 / 0,69x | |
ERROR.*timeout | 24,31 / 7,47 / 3,26x | 26,06 / 12,65 / 2,06x | 39,86 / 30,44 / 1,31x | 100,24 / 85,73 / 1,17x | |
E[0-9]{4} | 17,24 / 16,43 / 1,05x | 16,77 / 16,91 / 0,99x | 22,14 / 22,18 / 1,00x | 44,90 / 44,73 / 1,00x | |
| `ERROR | WARN` | 287,31 / 280,79 / 1,02x | 282,81 / 285,40 / 0,99x | 292,34 / 290,07 / 1,01x | 315,69 / 312,57 / 1,01x |
(?i)error.*timeout | 73,66 / 72,58 / 1,01x | 75,14 / 75,89 / 0,99x | 86,58 / 86,35 / 1,00x | 136,78 / 134,94 / 1,01x |
Pada selektivitas 1%, ERROR.*timeout juga menunjukkan peningkatan di luar latensi median: p95 turun dari 26,47 ms menjadi 13,03 ms, throughput pada concurrency 32 naik dari 72,70 menjadi 117,60 QPS (61,77%), dan waktu CPU per kueri turun 52,14%.
Pola ERROR.*timeout yang kaya literal mendapatkan manfaat paling besar. Saat selektivitas naik dari 0,01% ke 50%, speedup-nya turun dari 3,26x menjadi 1,17x karena lebih banyak baris lolos pembuatan kandidat dan tetap memerlukan RE2.
Pola dengan literal lemah, alternation, dan case-insensitive tetap kurang lebih setara dengan raw scan. Pola ber-anchor ^ERROR membantu pada tingkat kandidat rendah tetapi turun menjadi 0,69x pada 50%. Dalam dataset ini, marker yang diinjeksi menempatkan ERROR di dekat akhir baris, sehingga jumlah final anchored match tetap nol. Fase 1 tetap menghasilkan set kandidat besar, dan kerja verifikasi tambahan membuat jalur terindeks lebih lambat.
Reduksi kandidat menjelaskan peningkatan
Tabel berikutnya mengisolasi ERROR.*timeout dan menempatkan jumlah kandidat Fase 1 di samping latensi p50:
| Tingkat injeksi | Kandidat Fase 1 | Reduksi kandidat | Raw p50 | NGRAM p50 | Speedup |
|---|---|---|---|---|---|
| 0,01% | 1.000 | 99,99% | 24,31 ms | 7,47 ms | 3,26x |
| 1% | 100.000 | 99% | 26,06 ms | 12,65 ms | 2,06x |
| 10% | 1.000.000 | 90% | 39,86 ms | 30,44 ms | 1,31x |
| 50% | 5.000.000 | 50% | 100,24 ms | 85,73 ms | 1,17x |
Hubungannya langsung. Mengurangi kandidat sebesar 99,99% menghasilkan speedup 3,26x; menguranginya hanya 50% menyisakan 1,17x. NGRAM memperoleh nilainya ketika literal wajib menghapus porsi besar baris sebelum verifikasi RE2.
Sumber data dan referensi:
- Repositori Loghub
- Dataset HDFS_v1
- Record dataset Zenodo
- Jieming Zhu, Shilin He, Pinjia He, Jinyang Liu, dan Michael R. Lyu. Loghub: A Large Collection of System Log Datasets for AI-driven Log Analytics. ISSRE 2023.
Batasan saat ini dan kemungkinan langkah berikutnya
Filtering regex Milvus 3.0 memiliki beberapa batasan eksplisit:
- RE2 tidak mendukung backreference atau lookaround.
- Regex adalah operasi filter; regex tidak menyediakan fungsi extract atau replace.
- Pola case-insensitive
(?i)saat ini tidak menggunakan coarse filter NGRAM. - Alternation saat ini tidak dipecah menjadi cabang NGRAM terpisah.
- Pola tanpa literal wajib yang dapat diekstrak dengan aman akan fallback ke raw scan.
- Keberadaan inverted index biasa tidak berarti filter regex akan menggunakannya; NGRAM adalah jalur utama percepatan regex yang dijelaskan di sini.
Batasan tersebut menunjukkan ekstensi alami: NGRAM dengan case-folding, pemisahan cabang untuk alternation, dan eksekusi multi-pola yang lebih kaya. Optimasi semacam itu harus mempertahankan kontrak yang sama dengan desain saat ini: pembuatan kandidat boleh konservatif, tetapi hanya pencocokan tepat yang dapat menentukan hasil akhir.
Regex sebagai kapabilitas database
Menambahkan =~ ke grammar ekspresi adalah bagian yang mudah. Implementasi database juga harus menjawab pertanyaan yang lebih sulit:
- Dapatkah pengguna mengirimkan pola arbitrer tanpa menciptakan pekerjaan yang tidak terbatas?
- Apakah null, path JSON yang hilang, dan negasi mempertahankan semantik yang konsisten?
- Pola mana yang dapat ditulis ulang?
- Baris mana yang harus mencapai RE2?
- Kapan reduksi kandidat dari NGRAM membenarkan penggunaan indeks?
Milvus 3.0 menjawabnya dengan jalur eksekusi berlapis. RE2 menyediakan batas keamanan yang dapat diprediksi. Parser mengenali operasi yang lebih sederhana. Planner menjalankan predikat yang lebih murah terlebih dahulu. Volnitsky dan NGRAM secara konservatif mengurangi kandidat. RE2 kemudian memverifikasi baris yang tersisa.
Itu membuat filtering regex lebih dari sekadar panggilan ke library regular expression di dalam database vektor. Regex menjadikan pola struktural bagian dari model eksekusi database yang sama dengan pencarian vektor, filter skalar, data JSON, dan retrieval terindeks.
Coba filtering regex pada workload Anda sendiri — dan bagian lain dari Milvus 3.0
Filtering regex tersedia di Milvus 3.0. Panduan Pattern Matching mencakup sintaks operator, target yang didukung, raw string, dan semantik pencocokan, sementara panduan NGRAM menjelaskan pola mana yang dapat dipercepat oleh indeks dan cara mengonfigurasinya.
Jika Anda mengevaluasi rilis Milvus 3.0 yang lebih luas, lihat:
- Milvus 3.0 blog peluncuran
- Milvus 3.0 catatan rilis
- Blog fitur Milvus 3.0: Milvus Snapshots: Tampilan Collection Point-in-Time Tanpa Menyalin Data
- Repo GitHub Milvus
Mari berbincang dengan kami
- Bergabunglah dengan komunitas Milvus Discord — cara tercepat untuk mendapatkan jawaban dari orang-orang yang membangunnya.
- Pesan Milvus office hour 20 menit jika Anda ingin menelusuri collection Anda sendiri bersama engineer.
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word



