View Source ActiveMemory.Candidates (ActiveMemory v0.8.2)

Finds database tables whose workload — read constantly, written rarely — makes them candidates for an ActiveMemory.Table.

This is the engine behind mix active_memory.candidates, which runs it against the host application's own Ecto repo. The analysis itself is pure, so it can also be fed rows gathered any other way.

the-heuristic

The heuristic

A table is a candidate when its read/write ratio is high and it is small enough to hold in memory. Reads and writes come from the database's own statistics:

  • PostgreSQL: pg_stat_user_tables — reads are scans (seq_scan + idx_scan, each query touching the table counts once), writes are rows changed (n_tup_ins + n_tup_upd + n_tup_del).
  • MySQL/MariaDB: performance_schema.table_io_waits_summary_by_table — reads are row fetch operations (COUNT_FETCH), writes are row change operations (COUNT_INSERT + COUNT_UPDATE + COUNT_DELETE).

The two databases count reads differently (queries vs rows), so ratios are not comparable across databases — only between tables of the same one, which is what matters for finding candidates.

Statistics are cumulative: since the last statistics reset on PostgreSQL, and since server start on MySQL. Run against a database that has seen production-like traffic, or the ratios describe nothing.

Link to this section Summary

Functions

Classify raw statistics rows ([table, rows, bytes, reads, writes]).

Render analyzed results as a text report.

The statistics query for an Ecto adapter.

Link to this section Types

@type t() :: %ActiveMemory.Candidates{
  bytes: non_neg_integer(),
  ratio: float() | :infinity,
  reads: non_neg_integer(),
  rows: non_neg_integer(),
  table: String.t(),
  verdict:
    :strong
    | :candidate
    | :too_large
    | :write_heavy
    | :infrastructure
    | :no_traffic,
  writes: non_neg_integer()
}

Link to this section Functions

Link to this function

analyze(rows, opts \\ [])

View Source
@spec analyze(
  [list()],
  keyword()
) :: [t()]

Classify raw statistics rows ([table, rows, bytes, reads, writes]).

Options:

  • :min_ratio — reads per write to call a table a candidate (default 10; ten times that is a strong candidate)
  • :max_rows — above this a table is :too_large for the in-memory sweet spot regardless of its ratio (default 50000)
  • :min_reads — below this many total operations a table's ratio carries no signal (one stray read of an untouched table is an infinite ratio), so it is reported as having too little traffic to judge (default 100)

Results are sorted candidates first, then by reads.

@spec render([t()]) :: String.t()

Render analyzed results as a text report.

@spec sql_for(module()) :: {:ok, String.t()} | {:error, String.t()}

The statistics query for an Ecto adapter.

Returns {:ok, sql} whose result rows are [table, row_estimate, total_bytes, reads, writes], or {:error, message} for an adapter without table-level read/write statistics.