kura_query_cache (kura v2.20.7)

View Source

ETS-based cache for compiled query results.

Caches {SQL, Params} tuples keyed by {RepoMod, Query} so two repos with different dialects don't share entries. Identical queries through the same repo skip recompilation.

The key carries the whole query term rather than a hash of it: an entry holds bound parameters as well as SQL, so serving the wrong entry would hand a caller another query's rows rather than merely a slower path. ETS hashes the term itself, so hashing the key by hand only reintroduces a collision class the table had already eliminated.

Bound parameters are part of both key and value, so a parameterised query interns a fresh entry per distinct value set and the table only ever grows. Two limits bound it, both counted in words and both configurable:

  • query_cache_max_memory (default 8000000 words, ~64 MB on 64-bit) - once the table exceeds it the whole cache is dropped and refills.
  • query_cache_max_entry_size (default 4096 words) - a single result bigger than this is never interned.

The per-entry ceiling is the load-bearing one. A where {id, in, List} built from a caller-supplied array stores that list in both key and value, so capping entry count alone would still let a few thousand oversized entries exhaust the node.

See kura#163 for the shape-only cache that removes the need for either.

The ETS table is owned by kura_query_cache_owner (a gen_server under kura_sup), so the table survives any caller exiting.

Summary

Functions

Drop every cached entry. Required after swapping a repo's dialect at runtime, since the dialect is not part of the cache key. Also used by tests.

Look up a cached compiled query by key.

Initialize the query cache ETS table. No-op when the cache owner is already running (the normal app-startup path). Used by tests that exercise the cache without starting the kura app.

Store a compiled query result for a key. Skips entries larger than query_cache_max_entry_size, and drops the whole cache first when it has grown past query_cache_max_memory, so a workload of parameterised queries cannot grow the table without bound.

Functions

flush()

-spec flush() -> ok.

Drop every cached entry. Required after swapping a repo's dialect at runtime, since the dialect is not part of the cache key. Also used by tests.

get(Key)

-spec get(term()) -> {ok, {iodata(), [term()]}} | miss.

Look up a cached compiled query by key.

handle_call(Req, From, State)

handle_cast(Msg, State)

init()

-spec init() -> ok.

Initialize the query cache ETS table. No-op when the cache owner is already running (the normal app-startup path). Used by tests that exercise the cache without starting the kura app.

init/1

-spec init([]) -> {ok, []}.

put(Key, Result)

-spec put(term(), {iodata(), [term()]}) -> ok.

Store a compiled query result for a key. Skips entries larger than query_cache_max_entry_size, and drops the whole cache first when it has grown past query_cache_max_memory, so a workload of parameterised queries cannot grow the table without bound.

Dropping is not atomic with the insert, so concurrent putters can transiently overshoot the limit or wipe each other's entry. Both cost a recompile on the next lookup; neither can serve a wrong entry.

start_link()

-spec start_link() -> gen_server:start_ret().