examples · ask · 5 / 6
5. Plan-cache hit on repeat call
← Ask exampleswhat 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"] }'first = db.ask("how many customers do I have?", schemas=["shop.customers"])
second = db.ask("how many customers do I have?", schemas=["shop.customers"])
print(first["cache"]) # "miss"
print(second["cache"]) # "hit"const first = await db.ask("how many customers do I have?", { schemas: ["shop.customers"] });
const second = await db.ask("how many customers do I have?", { schemas: ["shop.customers"] });
console.log(first.cache); // "miss"
console.log(second.cache); // "hit"first, _ := db.Ask(ctx, "how many customers do I have?", "shop.customers")
second, _ := db.Ask(ctx, "how many customers do I have?", "shop.customers")
fmt.Println(first.Cache) // "miss"
fmt.Println(second.Cache) // "hit" 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/querywith 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.