Webhook icon
Schedule icon
If icon
Queries icon
Query icon
Fail icon
Set icon
Switch icon
SlackIncomingWebhook icon
Pause icon
Exit icon

Know When Your Vector Search Stops Finding the Right Results

Compare pgvector HNSW or IVFFlat results with exact search, find the ef_search or probes value that meets your recall target, and apply it after approval.

Categories
AIData

An approximate nearest-neighbour index (HNSW or IVFFlat) makes vector search fast by not looking at every row. The price is recall: some of the true top-k neighbours are missed. Nothing reports it. Queries still return k rows, only some of them are the wrong ones, and a RAG answer quietly gets worse after a bulk load, a re-embedding, an index built with cheap settings, or an IVFFlat index created before most of the data arrived.

This blueprint measures recall the only reliable way, by asking the same questions with and without the index, and turns the result into a decision.

How it works

  1. after_bulk_load (Webhook) or weekly (Schedule, shipped disabled) starts a check. concurrency runs one at a time.
  2. demo_setup (If, demo mode) creates recall_demo.documents: 30,000 clustered 64-dimension vectors with an HNSW index built cheaply (m = 8, ef_construction = 32).
  3. find_index reads the catalog: the HNSW or IVFFlat index on the column, its operator class (so the query uses <=>, <->, <#> or <+> like your application), the build options, and the hnsw.ef_search / ivfflat.probes value your application actually gets (role setting, database setting or the pgvector default). index_guard fails early when there is no usable index.
  4. measure (one Queries transaction) samples query vectors, finds the true top-k with index scans turned off, then repeats each query through the index at every sweep value, timing both. It checks with EXPLAIN that the index is really used.
  5. The result is stored in the KV store, and decide (Switch) picks one of four outcomes:
    • ok: recall at the current setting meets target_recall. A one-line report.
    • dropped: still above target, but more than max_drop lower than at the last run.
    • tune: a higher value in the sweep meets the target. Slack shows the sweep (recall and latency for each value), and wait_for_approval (Pause, 24 h) offers "Apply hnsw.ef_search = 640" or "Keep 40". Applying runs ALTER ROLE ... IN DATABASE ... SET (or ALTER DATABASE ... SET), then confirm_setting reads the catalog again.
    • rebuild: no value reaches the target. Slack says what to rebuild with, and the run ends WARNING.

Error handling

  • No index, an invalid index, or a missing table stops at index_guard with the reason ("no HNSW or IVFFlat index on recall_demo.plain(embedding): searches are exact scans").
  • The planner skipped the index (a tiny table, or a query shape it cannot serve): the measurement would compare exact with exact, so it fails instead: "the planner did not use index tiny_hnsw …".
  • A broken sample_sql fails with the database error, quoted in the alert.
  • Input never becomes SQL text. The table, column and every other input travel as one JSON value whose $ signs are escaped, inside a dollar-quoted literal; identifiers are quoted by PostgreSQL with format('%I') and the table is resolved with regclass. find_index uses bind parameters.
  • Nothing is changed without approval, and only the two search settings can be changed. No answer within 24 hours keeps the current value.
  • Every failure sends one Slack message naming the task and the database error.

What this blueprint teaches about Kestra

  • Switch on a value computed in SQL, with a different path for each outcome, including a Pause with a dynamic SELECT whose choices are built from the measurement.
  • Flow variables as reusable SQL: find_sql runs before the change and again after it.
  • The JDBC Queries task running a PL/pgSQL DO block and a SELECT in one transaction, so SET LOCAL-style settings and temporary tables stay inside the run.
  • The KV store as memory between runs (recall last time), and Exit with WARNING for a finding that is not an error.

Prerequisites

  • PostgreSQL with the vector extension (pgvector 0.5+ for HNSW). Demo mode runs CREATE EXTENSION IF NOT EXISTS vector and creates the recall_demo schema.
  • A role that can read the table. To apply settings it must own the database (ALTER DATABASE) or have CREATEROLE (ALTER ROLE).
  • A Slack incoming webhook.

Secrets

  • POSTGRES_URL (JDBC URL, e.g. jdbc:postgresql://db:5432/search), POSTGRES_USER, POSTGRES_PASSWORD.
  • SLACK_WEBHOOK_URL.
  • RECALL_CHECK_WEBHOOK_KEY: a long random string, the key in the webhook URL.

Inputs

  • mode (demo or existing), table, column, id_column.
  • k (default 10), sample_size (default 100), sample_sql (optional: real query vectors as column v).
  • target_recall (default 0.95), max_drop (default 0.02).
  • settings (values to sweep; defaults to 20–640 for hnsw.ef_search, 1–100 for ivfflat.probes).
  • app_role: the role your application connects as. Empty applies to the whole database.

Outputs

  • recall (JSON): the sweep (recall and ms per query for each value, exact scan ms), recall at the current setting, the best setting and the outcome.

Quick start

  1. Add the secrets and run the flow with the defaults. The demo index misses some of the true neighbours at the pgvector default hnsw.ef_search = 40 (8 to 12% in our runs; HNSW builds are not deterministic), so the run pauses and offers the lowest value that reaches 0.95.
  2. Resume with "Apply …". The next run reports "recall OK".
  3. Switch mode to existing, point table / column / id_column at your data, set app_role, and enable weekly. Call the webhook after bulk loads.

How to extend

  • Use real query vectors: log them to a table and pass sample_sql: SELECT embedding AS v FROM search_log ORDER BY created_at DESC LIMIT 200.
  • Measure a filtered search (WHERE tenant_id = …) by putting it in your own copy of the measure SQL; filtered HNSW searches often need hnsw.iterative_scan (pgvector 0.8+).
  • Point POSTGRES_URL at a replica to measure off the primary; apply the setting on the primary.

Plugins used

See How

New to Kestra?

Use blueprints to kickstart your first workflows.