← Back to blog
Product 4 min read 炬鲸团队

Querying Logs with SQL: How We Redesigned the Search Engine

Log search has long been stuck with keywords and regex, forcing scripts for anything complex. Jujing OBSERVE treats logs as tables and fields as columns, so you filter, aggregate, and correlate across services with plain SQL. Here is the data model and its trade-offs.

Why log search stayed painful

For years, log tooling offered two ways in: keyword search and regular expressions. To answer "why did this user's order fail," you pull up the trace, copy the trace_id, grep for it, then stitch the timeline together by eye. As soon as your conditions multiply, the query turns into a chain of regexes, and correlating fields across services becomes copy-paste work. The problem is not that the tools are "not smart enough" — it is the data model. A log line is stored as a blob of text; fields exist only at render time, so the query layer never sees them.

Treat logs as a table

Jujing OBSERVE's search engine starts from the data model. Every log line is parsed into fields. Fields become columns, logs become rows, and the timestamp is the implicit sort key. Once that mapping exists, search is plain SQL:

SELECT service, status, COUNT(*)
FROM logs
WHERE level = 'error'
  AND ts > now() - INTERVAL '1 hour'
GROUP BY service, status
ORDER BY COUNT(*) DESC;

Field extraction happens in the collector — JSON parsing, key=value splitting, or regex capture — and you can also pull fields out of the message at query time with json_extract. There is no schema to declare up front and no index to configure. You just write the query.

Three scenarios where SQL pays off

Cross-service correlation. A slow request fans out across a dozen services. Instead of threading trace_ids by hand, join on them:

SELECT a.service, b.service, b.duration
FROM logs a JOIN logs b ON a.trace_id = b.trace_id
WHERE a.service = 'gateway' AND b.duration > 1000;

Aggregation to find patterns. Which endpoint, which region, which build is throwing 5xx? One GROUP BY locates it, with no export to a spreadsheet required.

Alerting and troubleshooting share one language. Alert rules can be written as SQL; the rule that fires is the same expression you debug with, so there is no gap between what triggered and what you are investigating.

Performance trade-offs

Running SQL directly over raw logs is obviously not viable at scale, so the engine does three things. First, it partitions by time, service, and a field hash so scans stay small. Second, it builds inverted indexes on high-frequency fields so equality filters hit the index. Third, it pushes aggregation down to the storage layer, so COUNT and SUM never pull raw rows into memory. On a three-billion-row corpus, an indexed equality filter returns in about 200 ms, and a full-table aggregate stays under three seconds. The cost is one extra field-extraction pass at ingest; we do it as a streaming step in the collector, and the throughput hit is under 5%.

When to use SQL, and when to use keywords

Not every lookup deserves SQL. Single keywords, fuzzy matches, and quickly paging through context are faster with keyword search. SQL earns its keep in three situations: multi-field combined conditions, aggregation, and cross-service correlation. Our guidance is simple — browse logs with keywords day to day, switch to SQL when you are hunting a root cause. Both share one field model, so switching costs nothing.

What to check before you buy

When you evaluate a log platform, ignore the dashboard count. Check three things instead: whether queries are structured (SQL or SQL-like), whether aggregation is pushed down rather than computed in the UI, and whether logs share a single field model with traces and metrics. Those three set the ceiling on how fast your team can debug. Everything else is decoration.