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 24hrs

Without 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 24hrs

See 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:

SelectorWhat it returnsRelative cost
Specific fieldsOnly the fields you nameLowest
event.*Top-level fields of one event typeLow
event.**All fields to full depthMedium
#category.**All fields of all events in a categoryHigh
**All fields of all event typesHighest

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 24hrs

Use 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 30d

Use 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 50

Remove 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 QUERY first 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 1d

For more on subquery syntax and patterns, see Subqueries.

Debugging Slow Queries

If a query is running slowly:

  1. Narrow the time range — this is usually the highest-impact change.
  2. Use FROM only when you need source-specific focus (for example, isolating one connector among several that return the same event type).
  3. Reduce field expansion — switch from ** to * or specific fields.
  4. Add more specific filters — filter on status_id, activity_id, or other low-cardinality fields.
  5. Add LIMIT to cap results while iterating.

Debugging Empty Results

If a query returns no results:

  1. Check your field paths. Use EXPLAIN ATTRIBUTES to verify the path exists and expands to what you expect:

    EXPLAIN ATTRIBUTES authentication.user.username
  2. Check your connectors. Use EXPLAIN CONNECTORS to verify the event type is supported by your connected sources.

  3. Widen the time range. If you're using SINCE and UNTIL, make sure they define a valid window (SINCE should be further back than UNTIL).

  4. Validate the query. Use VALIDATE QUERY to check for syntax errors:

    VALIDATE QUERY authentication.** WITH authentication.status_id = FAILURE
  5. Use EXPLAIN QUERY to 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
  6. 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.