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:

KQLES|QL
Mục đích chínhFilter documentsQuery + transform + aggregate
Mô hình tư duyBoolean expression (and/or/not)Pipeline kiểu UNIX (command | command)
Kết quả trả vềList documents khớp filterBảng (rows + columns) có thể reshape
Dùng ở đâuSearch bar Discover, Saved Search, filter dashboard, alertTab ES|QL, Dev Tools, API
Tầng xử lýKibana parse → ES Query DSLChạy native trên Elasticsearch
Thời gian học15 phút1-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 type text — 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

  1. Case sensitivity cả field name và value. level : "error"Level : "Error" là hai query khác nhau.
  2. Field text vs keyword. Nếu filter không gợi ý value từ dropdown hoặc count sai, đổi Level thành Level.keyword. Field text đi qua analyzer trước khi index, field keyword giữ nguyên — filter exact cần keyword.
  3. Phrase match thiếu ngoặc kép. Message : failed to process bị parse thành Message : 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".
  4. Nested field dùng dot, không backtick. Properties.ApplicationName : "order" — dot là syntax chuẩn, không cần ` bọc quanh.
  5. Field không tồn tại không phải null. Dùng Exception : * để check “có Exception”, còn Exception : null khô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

CommandVai tròVí dụ
FROMChọn indexFROM app-logs-*
WHEREFilter rows| WHERE Level == "Error"
SORTSort| SORT @timestamp DESC
LIMITGiới hạn rows| LIMIT 100
KEEPGiữ column nhất định| KEEP @timestamp, Level, Message
DROPBỏ column| DROP Exception
RENAMEĐổi tên| RENAME app AS service
EVALTạo column tính toán| EVAL hour = DATE_EXTRACT("hour", @timestamp)
STATSAggregation (GROUP BY)| STATS c = COUNT(*) BY Level
DISSECTParse text theo pattern cứng| DISSECT Msg "%{ip} %{method} %{path}"
GROKParse 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

  1. LIMIT đặt trước WHERE. Như bài 1 đã nói: FROM ... | LIMIT 1000 | WHERE Level == "Error" sẽ gần như luôn ra 0 kết quả. LIMIT phải nằm cuối, sau khi đã filter và sort.
  2. 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.
  3. Text field khó aggregate. STATS count = COUNT(*) BY Message có thể lỗi vì Messagetext. Dùng Message.keyword hoặc rút gọn trước bằng DISSECT/GROK.
  4. 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.
  5. Không có SELECT *. Muốn giữ tất cả column thì không KEEP, không DROP — 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:

TaskKQLES|QL
Tất cả errorLevel : "Error"FROM ... | WHERE Level == "Error"
Error + FatalLevel : ("Error" or "Fatal")FROM ... | WHERE Level IN ("Error", "Fatal")
Loại Infonot Level : "Information"FROM ... | WHERE Level != "Information"
App + levelLevel : "Error" and app : "order"FROM ... | WHERE Level == "Error" AND app == "order"
Có ExceptionException : *FROM ... | WHERE Exception IS NOT NULL
Range date@timestamp >= "2026-04-15"FROM ... | WHERE @timestamp >= "2026-04-15"
Đếm theo fieldKhông làm đượcFROM ... | STATS c = COUNT(*) BY field
Average / percentileKhông làm đượcFROM ... | STATS avg = AVG(x), p95 = PERCENTILE(x, 95)
Compute column mớiKhông làm đượcFROM ... | EVAL total = a + b
Parse textKhông làm đượcFROM ... | GROK msg "..."
JOIN-ish (8.14+)Không làm đượcFROM 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 patternES|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-query rule (truyền thống): dùng ES Query DSL hoặc KQL.
  • .esql rule (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 base text.

Cheatsheet

ViệcKQLES|QL
Equality:==
Inequalitynot!=
IN listfield : (a or b)field IN (a, b)
Field existsfield : *field IS NOT NULL
Range@timestamp >= "..."@timestamp >= "..."
Phrase"timed out"LIKE "*timed out*"
Wildcardpath : "/api/*"path LIKE "/api/%"
AggregationKHÔNGSTATS ... BY ...
Parse textKHÔNGDISSECT / GROK
Compute fieldKHÔNGEVAL
JoinKHÔNGLOOKUP (8.14+)
Giới hạn dùng ởDiscover, Saved Search, Dashboard filter, alert thresholdTab ES|QL, Dev Tools, API, ES|QL alert rule
Độ dài học15 phút1-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.