AshScylla.PreparedStatementCache (AshScylla v1.9.0)

Copy Markdown View Source

ETS-based prepared statement cache for ScyllaDB/Cassandra queries.

Caches prepared statements keyed by {repo, cql, keyspace, opts} to eliminate repeated query parsing overhead on ScyllaDB. This is especially impactful for high-throughput workloads where the same queries are executed repeatedly.

Cache hits are served directly from ETS by the calling process (lock-free, no GenServer hop); only misses, invalidations, and eviction go through the GenServer so that writes remain serialized and race-free.

Usage

AshScylla.PreparedStatementCache.prepare(repo, "SELECT * FROM users WHERE id = ?")

Starting the Cache

Add to your supervision tree:

children = [
  AshScylla.PreparedStatementCache,
  # ... other children
]

Or start manually:

AshScylla.PreparedStatementCache.start_link([])

Limits

  • Max cache size: 10,000 entries
  • Cleanup interval: 5 minutes
  • Registered locally as __MODULE__ by default

Summary

Functions

Returns a specification to start this module under a supervisor.

Clears all cached prepared statements.

Evicts the oldest count entries from the cache.

Invalidates a specific cached statement by CQL string.

Returns the configured maximum cache size.

Prepares a CQL statement, using the cache if available.

Returns the number of cached statements.

Starts the prepared statement cache.

Returns the ETS table tid for inspection/testing.

Types

cache_entry()

@type cache_entry() :: {term(), term()}

cache_key()

@type cache_key() :: {module(), String.t(), String.t(), keyword()}

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

clear()

@spec clear() :: :ok

Clears all cached prepared statements.

evict_oldest(tid, count)

@spec evict_oldest(:ets.tid(), non_neg_integer()) :: :ok

Evicts the oldest count entries from the cache.

Exposed for testing the eviction logic.

invalidate(cql)

@spec invalidate(String.t()) :: :ok

Invalidates a specific cached statement by CQL string.

max_cache_size()

@spec max_cache_size() :: non_neg_integer()

Returns the configured maximum cache size.

Exposed for testing.

prepare(repo, cql, opts \\ [])

@spec prepare(module(), String.t(), keyword()) :: {:ok, term()} | {:error, term()}

Prepares a CQL statement, using the cache if available.

Cache hits are read straight from ETS by the calling process; misses fall back to the GenServer, which prepares the statement and caches it.

Returns {:ok, stmt} on success or {:error, reason} on failure.

size()

@spec size() :: non_neg_integer()

Returns the number of cached statements.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the prepared statement cache.

When no :name option is given, the GenServer is registered locally as __MODULE__ so that each node runs its own per-node cache. This is required in a clustered deployment (libcluster + K8s/gossip), where a global name would be contested by every node and cause repeated :reached_max_restart_intensity crashes.

Pass a {:global, name} tuple to register globally, or a custom local name to register under a different local name.

table()

@spec table() :: :ets.tid() | nil

Returns the ETS table tid for inspection/testing.