Best Practices
Query performance and optimization tips
Use FROM for Source-Specific Focus
Use FROM when you intentionally want to focus on one or a few specific connectors (for example, when multiple connectors provide the same event type and you only want one source's perspective):
QUERY #network.**
WITH #network.dst_endpoint.port = 4444
FROM 'Zeek Logs'
SINCE 24hrsWithout FROM, Query can quickly eliminate connectors that cannot satisfy the requested event classes. In many cases, this default behavior is efficient and preferred.
For queries you intend to save or automate, prefer a connector's alias over its display name. Aliases are immutable, so a later rename won't silently break the query. To scope a query to a logical group of sources — all SIEMs, all production connectors — reference a tag with a # prefix, which expands to every connector carrying it:
QUERY detection_finding.**
WITH detection_finding.severity_id IN HIGH, CRITICAL
FROM #siem
SINCE 24hrsSee Data Sources (FROM) for the full set of connector reference types.
Choose the Right Level of Field Expansion
Field expansion has a direct impact on how much data is requested from each connector:
| Selector | What it returns | Relative cost |
|---|---|---|
| Specific fields | Only the fields you name | Lowest |
event.* | Top-level fields of one event type | Low |
event.** | All fields to full depth | Medium |
#category.** | All fields of all events in a category | High |
** | All fields of all event types | Highest |
Recommendation: Start with specific fields or event.* for production queries and investigations where you know what you're looking for. Use ** for exploration, but pair it with LIMIT.
-- Exploration: broad but bounded
QUERY authentication.**
LIMIT 100
-- Investigation: targeted
QUERY authentication.user.username, authentication.src_endpoint.ip, authentication.status_id
WITH authentication.status_id = FAILURE
SINCE 24hrsUse Tight Time Ranges
Time range is one of the biggest factors in query performance. Querying 30 days of data will usually be dramatically slower than querying 24 hours.
- For triage and active investigations, use hours (
SINCE 24hrs) or a few days. - For threat hunting, use days to weeks with specific filters.
- For historical research, use the narrowest window that covers your question.
-- Fast: tight window
QUERY authentication.**
WITH authentication.status_id = FAILURE
SINCE 4hrs
-- Slower: wide window
QUERY authentication.**
WITH authentication.status_id = FAILURE
SINCE 30dUse LIMIT for Exploration
When you're exploring data or testing a query, add LIMIT to avoid pulling back more results than you need. Once FSQL reaches the limit, it stops and terminates any outstanding connector queries:
QUERY process_activity.**
WITH process_activity.process.cmd_line CONTAINS 'powershell'
LIMIT 50Remove or raise the limit once you've validated the query and are ready for a full search.
Subquery Performance
Subqueries execute the inner query first, then use its results to filter the outer query. Keep these principles in mind:
- Keep inner queries selective. An inner query that returns thousands of values will slow down the outer query. Add filters and tight time ranges to the inner query.
- Test inner queries independently. Run the inner query as a standalone
QUERYfirst to verify it returns a manageable result set. - Use wider time windows in inner queries when needed, but keep the outer query tight — you typically want recent activity correlated with historical findings.
- Avoid deep nesting. Each level of subquery nesting adds latency. If you need 3+ levels, consider breaking the investigation into separate queries.
-- Good: selective inner query, tight outer query
QUERY #network.**
WITH #network.src_endpoint.hostname IN {
QUERY detection_finding.device.hostname
WITH detection_finding.severity_id IN HIGH, CRITICAL
AND detection_finding.status_id = NEW
SINCE 7d
}
FROM 'Zeek Logs', 'Palo Alto Firewall'
SINCE 1dFor more on subquery syntax and patterns, see Subqueries.
Debugging Slow Queries
If a query is running slowly:
- Narrow the time range — this is usually the highest-impact change.
- Use
FROMonly when you need source-specific focus (for example, isolating one connector among several that return the same event type). - Reduce field expansion — switch from
**to*or specific fields. - Add more specific filters — filter on
status_id,activity_id, or other low-cardinality fields. - Add
LIMITto cap results while iterating.
Debugging Empty Results
If a query returns no results:
-
Check your field paths. Use
EXPLAIN ATTRIBUTESto verify the path exists and expands to what you expect:EXPLAIN ATTRIBUTES authentication.user.username -
Check your connectors. Use
EXPLAIN CONNECTORSto verify the event type is supported by your connected sources. -
Widen the time range. If you're using
SINCEandUNTIL, make sure they define a valid window (SINCEshould be further back thanUNTIL). -
Validate the query. Use
VALIDATE QUERYto check for syntax errors:VALIDATE QUERY authentication.** WITH authentication.status_id = FAILURE -
Use
EXPLAIN QUERYto see exactly how FSQL interprets your query, including which fields are expanded and what filters are applied:EXPLAIN QUERY authentication.** WITH authentication.status_id = FAILURE -
Try a broader query first. Remove filters and see if the event type has any data at all, then add filters back one at a time.
Updated about 5 hours ago