Skip to content

Add warming query guardrails - #1052

Open
vim345 wants to merge 4 commits into
Yelp:mainfrom
vim345:u/km/warming-guardrails-v2
Open

vim345 wants to merge 4 commits into
Yelp:mainfrom
vim345:u/km/warming-guardrails-v2

Conversation

@vim345

@vim345 vim345 commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Pathological queries in the warming corpus (match all scans, very large result sets, slow queries) are cheap enough under warm caches to go unnoticed in live traffic, but replaying them against a cold index on startup can stall warming and leave replicas in a restart loop. Add four layers of protection, all disabled by default:

  • Collection filter (SearchHandler, SearchStreamHandler): do not collect match all queries (warmer.filterMatchAllQueries), queries with startHit + topHits above warmer.maxQueryTopHits, or queries slower than warmer.maxQueryLatencyMs.
  • Backup-time filter (Warmer): re-apply the match all and top hits checks to the corpus before upload, including padding from the downloaded corpus. Downloaded queries that timed out or failed during replay are not used for padding.
  • Per-query timeout (Warmer): warmer.queryTimeoutMs sets the replayed request's collection timeout and timeoutCheckEvery so Lucene stops the query cooperatively, plus a request deadline covering later phases when deadline cancellation is enabled. Parallel warming is unaffected.
  • Total warming cap (ShardState): with warmer.maxWarmingDurationMs, the warmer stops starting new queries at the deadline and startup continues even if a query is still blocked. The warming thread is not interrupted, since interrupting Lucene IO can close shared index file channels.

A replay query that throws is now logged and skipped rather than failing startup.

Metrics (registered in WarmingMetrics, served on /status/metrics; the scraper adds service/instance labels):

Metric Type Labels Emitted
nrt_warming_backup_success_total Counter index BackupWarmingQueriesHandler, after the upload succeeds
nrt_warming_backup_failure_total Counter index, reason = below_uptime_threshold | below_query_threshold | upload_error | other BackupWarmingQueriesHandler, for each rejected or failed backup (other: index or warmer not found)
nrt_warming_queries_total Counter index, type = full | basic, outcome = success | error | timeout Warmer, for every query replayed at startup
nrt_warming_query_latency_seconds Histogram index, type Warmer, timer around each replayed query
nrt_warming_queries_filtered_total Counter index, stage = collection | backup | replay, reason = match_all | top_hits | latency | timeout | error each guardrail check that drops a query
nrt_warming_duration_cap_reached_total Counter index ShardState, when startup stops waiting at maxWarmingDurationMs and the replica becomes ready with warming incomplete

The existing warming_time_seconds gauge (BootstrapMetrics) still records total warming time per index. Its help text, which was copied from plugin_initialization_time_seconds, is corrected.

kheyalimitra and others added 3 commits October 13, 2026 13:16
Pathological queries in the warming corpus (match all scans, very large
result sets, slow queries) are cheap enough under warm caches to go
unnoticed in live traffic, but replaying them against a cold index on
startup can stall warming and leave replicas in a restart loop. Add four
layers of protection, all disabled by default:

* Collection filter (SearchHandler, SearchStreamHandler): do not collect match all queries
  (warmer.filterMatchAllQueries), queries with startHit + topHits above
  warmer.maxQueryTopHits, or queries slower than warmer.maxQueryLatencyMs.
* Backup-time filter (Warmer): re-apply the match all and top hits checks
  to the corpus before upload, including padding from the downloaded
  corpus. Downloaded queries that timed out or failed during replay are
  not used for padding.
* Per-query timeout (Warmer): warmer.queryTimeoutMs sets the replayed
  request's collection timeout and timeoutCheckEvery so Lucene stops the
  query cooperatively, plus a request deadline covering later phases when
  deadline cancellation is enabled. Parallel warming is unaffected.
* Total warming cap (ShardState): with warmer.maxWarmingDurationMs, the
  warmer stops starting new queries at the deadline and startup continues
  even if a query is still blocked. The warming thread is not interrupted,
  since interrupting Lucene IO can close shared index file channels.

A replay query that throws is now logged and skipped rather than failing
startup. Rejections are counted in nrt_warming_query_rejected_count, and
cap hits in nrt_warming_duration_cap_exceeded_count.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Emit metrics for alerting on stale or failing warming query backups and
for comparing startup warming across replicas:

* nrt_warming_backup_success / nrt_warming_backup_failure (by reason:
  below_uptime_threshold, below_query_threshold, upload_error, other),
  counted in BackupWarmingQueriesHandler.
* nrt_warming_queries (by type full/basic and outcome
  success/error/timeout) and the nrt_warming_query_latency_seconds
  histogram, recorded for every replayed query.

Rename the guardrail counters to match: nrt_warming_query_rejected_count
is now nrt_warming_queries_filtered, and
nrt_warming_duration_cap_exceeded_count is now
nrt_warming_duration_cap_reached.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The help text was copied from plugin_initialization_time_seconds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Close gaps in the warming query filter:

* topHits already includes startHit, so the top hits check no longer
  adds them. A query is rejected when topHits or startHit exceeds
  warmer.maxQueryTopHits, which still filters deep pagination without
  rejecting pages that end within the limit.
* With warmer.filterMatchAllQueries, a match phrase query anywhere in
  the query tree with zeroTermsQuery ALL_ZERO_TERMS is rejected, since
  it matches all documents when analysis removes every term.
* Multi retriever requests are checked per retriever: each text
  retriever query for match all and ALL_ZERO_TERMS, and each text
  retriever topHits against maxQueryTopHits.
* New warmer.maxVectorCandidates (default 0, disabled) rejects queries
  with a knn query, top level or in a knn retriever, whose
  num_candidates exceeds the limit. Vector search runs during query
  rewrite, so the replay timeout cannot stop it.
* Stored queries are checked before replay, so a corpus backed up before
  the filter was configured is not replayed unfiltered. Basic queries
  are also checked after simplification, since removing a top level
  function filter query leaves a match all query.

New reason labels for nrt_warming_queries_filtered_total:
all_zero_terms and vector_candidates.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants