Raxol.Agent.Cache.Postgrex (Raxol Agent v2.6.0)

Copy Markdown View Source

Postgrex-backed Raxol.Agent.Cache adapter.

Persists cache entries in a PostgreSQL table so agents on separate processes (or separate BEAM nodes) can share cache state. Values are stored as bytea via :erlang.term_to_binary/1, preserving arbitrary Erlang terms.

Optional dependency

Postgrex is declared optional: true in the umbrella's mix.exs. Consumers using this adapter must add :postgrex to their own deps and start a Postgrex connection (typically under their own supervision tree). The adapter does not start or own the connection; it expects :conn in the configuration to point at a live process.

Config

%{
  conn: MyApp.Postgrex,                # registered name or pid
  table: "raxol_agent_cache"           # optional, default below
}

:table defaults to "raxol_agent_cache". The table name is validated against a safe identifier pattern so config-level injection is not possible.

Schema

Run the SQL returned by create_table_sql/1 once (e.g. via Ecto migration or psql) before the first call:

iex> Raxol.Agent.Cache.Postgrex.create_table_sql()
"""
CREATE TABLE IF NOT EXISTS raxol_agent_cache (
  key        bytea NOT NULL,
  value      bytea NOT NULL,
  expires_at timestamptz,
  PRIMARY KEY (key)
);
CREATE INDEX IF NOT EXISTS raxol_agent_cache_expires_at_idx
  ON raxol_agent_cache (expires_at)
  WHERE expires_at IS NOT NULL;
"""

expires_at is nullable: rows with NULL never expire (matches the ttl_ms == 0 contract). The partial index makes the occasional eager-sweep query (DELETE WHERE expires_at <= now()) cheap; this adapter does not run that sweep automatically, but a consumer's supervised periodic job can.

Upsert semantics

put/4 uses ON CONFLICT (key) DO UPDATE so a second write to the same key replaces the previous value and expiry. Last-write- wins is the documented cache semantic; the append-only contract of Raxol.Workflow.Checkpoint.Saver does not apply here.

Lazy expiry

get/2 filters with WHERE key = $1 AND (expires_at IS NULL OR expires_at > now()) — a stale row stays in the table but is invisible to get/2. The caller can periodically delete expired rows via DELETE FROM <table> WHERE expires_at <= now() for retention; the partial index keeps that cheap.

Summary

Functions

Returns the CREATE TABLE IF NOT EXISTS + partial-index SQL for the cache table. Run once as part of the consumer's migration step.

Functions

create_table_sql(table \\ "raxol_agent_cache")

@spec create_table_sql(String.t()) :: String.t()

Returns the CREATE TABLE IF NOT EXISTS + partial-index SQL for the cache table. Run once as part of the consumer's migration step.

Pass a custom table name to match the value used in the cache config; defaults to "raxol_agent_cache".