Dev mở Kibana lần đầu thường hoang mang vì Discover có hai chế độ query: KQL ở search bar mặc định, và tab riêng ES|QL mới xuất hiện từ 8.11. Cùng một filter Level : "Error" copy qua tab kia là 400 Bad Request. Không phải Kibana hỏng — đây là hai ngôn ngữ khác hẳn nhau về triết lý, đối tượng dùng, và kết quả trả về. Biết cái nào làm gì sẽ tiết kiệm rất nhiều thời gian trace log và dựng dashboard.
Đây là bài thứ 2 trong series Kibana từ A đến Z. Sau bài này bạn sẽ:
- Hiểu bản chất khác nhau giữa KQL và ES|QL, không còn gõ nhầm tab
- Viết được filter KQL đúng trong Discover, Saved Search, Dashboard và alert rule
- Viết được pipeline ES|QL cho aggregation, top-N, parse text, compute field
- Biết quy tắc tay chọn ngôn ngữ nào cho từng use case
- Tránh 5 pitfall phổ biến nhất của mỗi ngôn ngữ
Phần 1: Vì sao Kibana có 2 ngôn ngữ query
Kibana không sinh ra đã có KQL và ES|QL. Dòng thời gian ngắn:
- Trước 7.x: search bar dùng Lucene query syntax (di sản từ Elasticsearch). Mạnh nhưng cú pháp rườm rà, dễ lỗi với field có dấu chấm.
- 7.0+ (2019): KQL (Kibana Query Language) ra đời để đơn giản hoá filter. Tối ưu cho bộ lọc document, cú pháp giống natural language.
- 8.11+ (2023): ES|QL (Elasticsearch Query Language) ra đời ở tầng Elasticsearch. Pipeline-based, SQL-like, làm được cả aggregation và transformation trong một câu lệnh.
Tại sao song song mà không replace? Vì hai ngôn ngữ giải quyết hai bài toán khác nhau:
| KQL | ES|QL | |
|---|---|---|
| Mục đích chính | Filter documents | Query + transform + aggregate |
| Mô hình tư duy | Boolean expression (and/or/not) | Pipeline kiểu UNIX (command | command) |
| Kết quả trả về | List documents khớp filter | Bảng (rows + columns) có thể reshape |
| Dùng ở đâu | Search bar Discover, Saved Search, filter dashboard, alert | Tab ES|QL, Dev Tools, API |
| Tầng xử lý | Kibana parse → ES Query DSL | Chạy native trên Elasticsearch |
| Thời gian học | 15 phút | 1-2 giờ |
Tóm lại: KQL là “lọc tìm docs”, ES|QL là “phân tích và biến đổi”.
Phần 2: KQL — ngôn ngữ filter
Cú pháp cốt lõi
KQL xoay quanh 1 pattern duy nhất: <field> : <value> kết hợp boolean. Cheatsheet 8 pattern hay dùng nhất:
Level : "Error" # so sánh bằng
Level : ("Error" or "Fatal") # or trong cùng field
not Level : "Information" # phủ định
Level : "Error" and app : "order-service" # kết hợp 2 field
Properties.TenantId : * # field tồn tại
Message : "timeout" # phrase match trong text
status >= 400 and status < 500 # range số
@timestamp >= "2026-04-15T00:00:00Z" # range thời gian
Wildcard và phrase
*khớp 0 hoặc nhiều ký tự,?khớp đúng 1 ký tự.- Dấu ngoặc kép
"..."ép phrase match trên field typetext— các token phải liền kề và đúng thứ tự. - Không có ngoặc kép → term match (tách từ theo analyzer).
Message : "timed out" # khớp cụm "timed out" liền nhau
Message : timed and Message : out # khớp cả 2 từ bất kỳ thứ tự
path : "/api/v1/*" # wildcard trong phrase
Escape ký tự đặc biệt
Ký tự cần escape bằng \: \ ( ) : < > " * ? /. Ví dụ tìm URL có dấu ?:
path : "/search\?q=kibana"
5 pitfall KQL hay gặp
- Case sensitivity cả field name và value.
level : "error"vàLevel : "Error"là hai query khác nhau. - Field
textvskeyword. Nếu filter không gợi ý value từ dropdown hoặc count sai, đổiLevelthànhLevel.keyword. Fieldtextđi qua analyzer trước khi index, fieldkeywordgiữ nguyên — filter exact cầnkeyword. - Phrase match thiếu ngoặc kép.
Message : failed to processbị parse thànhMessage : failed AND Message : to AND Message : process— khớp document có 3 từ đó rải rác. Nếu cần liền nhau, gõ"failed to process". - Nested field dùng dot, không backtick.
Properties.ApplicationName : "order"— dot là syntax chuẩn, không cần`bọc quanh. - Field không tồn tại không phải null. Dùng
Exception : *để check “có Exception”, cònException : nullkhông hoạt động — KQL không có operator null.
Phần 3: ES|QL — ngôn ngữ pipeline
ES|QL đọc như shell script: bắt đầu từ FROM, rồi ghép lệnh bằng | (pipe), mỗi lệnh nhận output của lệnh trước làm input.
FROM <index>
| <command_1>
| <command_2>
| ...
Các command quan trọng
| Command | Vai trò | Ví dụ |
|---|---|---|
FROM | Chọn index | FROM app-logs-* |
WHERE | Filter rows | | WHERE Level == "Error" |
SORT | Sort | | SORT @timestamp DESC |
LIMIT | Giới hạn rows | | LIMIT 100 |
KEEP | Giữ column nhất định | | KEEP @timestamp, Level, Message |
DROP | Bỏ column | | DROP Exception |
RENAME | Đổi tên | | RENAME app AS service |
EVAL | Tạo column tính toán | | EVAL hour = DATE_EXTRACT("hour", @timestamp) |
STATS | Aggregation (GROUP BY) | | STATS c = COUNT(*) BY Level |
DISSECT | Parse text theo pattern cứng | | DISSECT Msg "%{ip} %{method} %{path}" |
GROK | Parse text theo regex | | GROK Msg "%{IP:ip} %{WORD:method}" |
5 ví dụ thực tế
Top 10 error mới nhất:
FROM app-logs-*
| WHERE Level == "Error"
| SORT @timestamp DESC
| LIMIT 10
Đếm error theo app, xếp giảm dần:
FROM app-logs-*
| WHERE Level IN ("Error", "Fatal")
| STATS errors = COUNT(*) BY Properties.ApplicationName
| SORT errors DESC
Phân vị response time theo endpoint:
FROM http-access-*
| STATS
avg_ms = AVG(response_time_ms),
p95_ms = PERCENTILE(response_time_ms, 95),
p99_ms = PERCENTILE(response_time_ms, 99)
BY path
| SORT p95_ms DESC
| LIMIT 20
Parse log Nginx bằng GROK rồi đếm theo IP:
FROM nginx-*
| GROK Message "%{IP:client_ip} - - \\[%{HTTPDATE:ts}\\] \"%{WORD:method} %{URIPATHPARAM:path}"
| STATS hits = COUNT(*) BY client_ip
| SORT hits DESC
| LIMIT 10
EVAL tính doanh thu rồi tổng theo customer:
FROM orders-*
| WHERE status == "completed"
| EVAL revenue = price * quantity
| STATS total = SUM(revenue) BY customer_id
| SORT total DESC
| LIMIT 50
5 pitfall ES|QL hay gặp
LIMITđặt trướcWHERE. Như bài 1 đã nói:FROM ... | LIMIT 1000 | WHERE Level == "Error"sẽ gần như luôn ra 0 kết quả.LIMITphải nằm cuối, sau khi đã filter và sort.- So sánh dùng
==không phải=.WHERE Level = "Error"báo syntax error. ES|QL bắt chước SQL về operator:==,!=,>,<,>=,<=,IN,LIKE. - Text field khó aggregate.
STATS count = COUNT(*) BY Messagecó thể lỗi vìMessagelàtext. DùngMessage.keywordhoặc rút gọn trước bằngDISSECT/GROK. - Giới hạn 10,000 row mặc định. Query trả nhiều hơn sẽ bị cắt. Điều chỉnh trong request Kibana (Settings panel) hoặc paginate qua API.
- Không có
SELECT *. Muốn giữ tất cả column thì khôngKEEP, khôngDROP— mặc định ES|QL đã trả hết. Khác SQL ở chỗ này.
Phần 4: Side-by-side - cùng task, 2 ngôn ngữ
Đối chiếu nhanh các filter phổ biến:
| Task | KQL | ES|QL |
|---|---|---|
| Tất cả error | Level : "Error" | FROM ... | WHERE Level == "Error" |
| Error + Fatal | Level : ("Error" or "Fatal") | FROM ... | WHERE Level IN ("Error", "Fatal") |
| Loại Info | not Level : "Information" | FROM ... | WHERE Level != "Information" |
| App + level | Level : "Error" and app : "order" | FROM ... | WHERE Level == "Error" AND app == "order" |
| Có Exception | Exception : * | FROM ... | WHERE Exception IS NOT NULL |
| Range date | @timestamp >= "2026-04-15" | FROM ... | WHERE @timestamp >= "2026-04-15" |
| Đếm theo field | Không làm được | FROM ... | STATS c = COUNT(*) BY field |
| Average / percentile | Không làm được | FROM ... | STATS avg = AVG(x), p95 = PERCENTILE(x, 95) |
| Compute column mới | Không làm được | FROM ... | EVAL total = a + b |
| Parse text | Không làm được | FROM ... | GROK msg "..." |
| JOIN-ish (8.14+) | Không làm được | FROM a | LOOKUP b ON key |
Mấu chốt: bất cứ thao tác nào biến đổi hoặc tổng hợp đều chỉ làm được bằng ES|QL. KQL không có khái niệm aggregation.
Phần 5: Quy tắc chọn ngôn ngữ nào
Dùng KQL khi
- Lọc docs trong Discover để trace log hoặc debug.
- Viết Saved Search cho team reuse.
- Đặt filter bar trong Dashboard (visualization tự xử aggregation).
- Đặt query filter cho alert rule dạng “threshold”.
- Gõ ad-hoc nhanh, cần kết quả ngay.
Dùng ES|QL khi
- Cần aggregation: count/sum/avg/percentile/top-N.
- Cần transformation: tạo field mới bằng EVAL, parse text bằng DISSECT/GROK.
- Làm ad-hoc analytics mà không muốn dựng visualization Lens/Canvas.
- Export bảng ra CSV để phân tích tiếp ở Excel/Python.
- Viết alert rule dạng ES|QL query — rule type mới chính xác hơn threshold truyền thống.
- Cần LOOKUP join 2 index (8.14+).
Quy tắc tay 1 câu
“Muốn xem docs nào thoả điều kiện → KQL. Muốn biết con số tổng hợp hoặc pattern → ES|QL.”
Phần 6: Mix hai ngôn ngữ trong cùng 1 dashboard
Không cần pick 1 cái rồi bỏ cái kia. Dashboard tốt thường có cả hai:
- Panel A (Saved Search, KQL): bảng error gần nhất để oncall trace nhanh.
- Panel B (Lens / ES|QL visualization): bar chart error theo giờ — tính bằng STATS.
- Panel C (ES|QL table): top 10 endpoint có p95 cao nhất — hoàn toàn không làm được bằng KQL.
Kibana 8.13+ cho phép tạo ES|QL visualization giữ dưới dạng saved object, embed vào dashboard giống panel Lens.
Phần 7: Migration và điểm yếu cần biết
Không có tool auto-convert
Tại thời điểm viết (Kibana 8.14), chưa có tính năng chuyển 1-click từ KQL sang ES|QL. Phải viết lại thủ công. Roadmap Elastic có hint về converter nhưng chưa release.
Alert dạng query: 2 rule type khác nhau
.es-queryrule (truyền thống): dùng ES Query DSL hoặc KQL..esqlrule (mới): dùng ES|QL trực tiếp.
Copy filter từ Discover (KQL) sang rule .esql phải viết lại — cú pháp không tương thích.
ES|QL không phải “thay thế SQL”
Có nhiều hàm SQL chưa có trong ES|QL: subquery, CTE (WITH), full JOIN nhiều bảng (LOOKUP chỉ là left-join đơn giản). Đừng kỳ vọng chạy query phức tạp kiểu data warehouse — ES|QL tối ưu cho log + metric analytics.
Field text gây phiền cho cả hai
Cả KQL và ES|QL đều khó chịu với text field khi cần exact match hoặc aggregate. Best practice khi design mapping:
- Field định danh (level, app, env, user_id) →
keyword. - Field cần full-text search (message, description) →
text+ sub-field.keyword(Elastic default). - Filter/aggregate luôn trỏ vào
.keyword; search trỏ vào basetext.
Cheatsheet
| Việc | KQL | ES|QL |
|---|---|---|
| Equality | : | == |
| Inequality | not | != |
| IN list | field : (a or b) | field IN (a, b) |
| Field exists | field : * | field IS NOT NULL |
| Range | @timestamp >= "..." | @timestamp >= "..." |
| Phrase | "timed out" | LIKE "*timed out*" |
| Wildcard | path : "/api/*" | path LIKE "/api/%" |
| Aggregation | KHÔNG | STATS ... BY ... |
| Parse text | KHÔNG | DISSECT / GROK |
| Compute field | KHÔNG | EVAL |
| Join | KHÔNG | LOOKUP (8.14+) |
| Giới hạn dùng ở | Discover, Saved Search, Dashboard filter, alert threshold | Tab ES|QL, Dev Tools, API, ES|QL alert rule |
| Độ dài học | 15 phút | 1-2 giờ |
Lời kết
KQL và ES|QL không phải hai phiên bản của cùng 1 thứ — chúng là hai công cụ cho hai công việc khác nhau. Dev backend nên xem KQL là “grep nâng cao” cho log, còn ES|QL là “awk/sed/cut pipeline” cho phân tích. Biết ranh giới và biết chuyển đúng lúc là đủ để tận dụng 90% khả năng query của Kibana mà không phải rời Discover.
Bài tiếp theo trong series Kibana từ A đến Z sẽ đi sâu vào Discover nâng cao: runtime fields (tính field on-the-fly không cần reindex), filter phức tạp với nested object, highlighting để nhanh mắt đọc log, và saved query cho team. Nếu có topic cụ thể bạn muốn mình ưu tiên viết — ví dụ “ES|QL alert rule thực chiến”, “migrate từ Lucene sang KQL”, “benchmark KQL vs ES|QL trên index lớn” — cứ drop comment hoặc email.