OriginChain docs
examples · ask · 5 / 6

5. Plan-cache hit on repeat call

← Ask examples
what this does

Send the same question twice. The first call compiles a plan and stores it under a key derived from the canonicalised question and the schemas list. The second call finds the plan already there and skips compilation - the cache field in the response flips from "miss" to "hit".

when to use it
  • The Ask cache is automatic - you don't opt in. This example just shows the behaviour you can rely on.
  • Useful as a quick check: see cache: "miss" on every call? Your questions are varying in a way that's defeating the canonical form (extra punctuation, embedded values, etc.).
  • If you want a fresh compile on purpose, change the question or change the schemas list - both are part of the key.
the schema
namespace   = "shop"
table       = "customers"
primary_key = ["id"]

[[columns]]
name = "id"
ty   = "str"
required = true

[[columns]]
name = "email"
ty   = "str"
seed data
# Any handful of customers is fine - the cache hit doesn't depend on the count.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/rows/shop.customers/_batch" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    { "id": "c_1", "email": "alice@example.com" },
    { "id": "c_2", "email": "bob@example.com" }
  ]'
the request
POST /v1/tenants/:t/ask - issued twice
# Call the same question twice - watch the cache field flip.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/ask" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "nl": "how many customers do I have?", "schemas": ["shop.customers"] }'

# Same body, same headers - just send it again.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/ask" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "nl": "how many customers do I have?", "schemas": ["shop.customers"] }'
what you get back

First call:

{
  "rows": [
    { "count": 2 }
  ],
  "cache": "miss"
}

Second call:

{
  "rows": [
    { "count": 2 }
  ],
  "cache": "hit"
}

The cache values are "hit", "miss", or "skip". "skip" means the engine bypassed the cache - for example when the question depends on state that can't be safely cached.

how it works
  • The cache key is (canonicalised_nl, sorted_schemas_list). Case, whitespace, and trailing punctuation are normalised before hashing.
  • Only the plan tree is cached - rows are always read fresh from the row store, so the count reflects current state on a "hit".
  • Schema migrations evict entries that reference affected schemas. After you alter shop.customers, the next call for that question re-plans.
  • The cache is per-tenant and durable across replicas - failover doesn't reset it.
common mistakes
  • Interpolating values into the question. "orders from customer c_1" and "orders from customer c_2" are different cache entries. Use a parameterised question: "orders for a given customer id" and bind the value separately when supported, or fall back to /v1/query with a saved plan.
  • Re-ordering the schemas list. ["a","b"] and ["b","a"] hash to the same key (the engine sorts), but adding or removing a schema does change it.
  • Expecting stale rows on a hit. The plan is cached. The data is not. A "hit" still runs the executor against current rows.