Skip to content

feat: add LRU-aware cache warming for KeyValues - #3592

Open
bblaszkow06 wants to merge 12 commits into
Logflare:mainfrom
bblaszkow06:bb/o11y-1890-kv-warmup-lru
Open

bblaszkow06 wants to merge 12 commits into
Logflare:mainfrom
bblaszkow06:bb/o11y-1890-kv-warmup-lru

Conversation

@bblaszkow06

@bblaszkow06 bblaszkow06 commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Warm KeyValues.Cache with the most recently used entries instead of streaming the whole table.

Usage tracking

  • New key_value_usages table (key_value_id, last_used_at). It is deliberately kept out of
    logflare_pub, so bumping usage does not go through CacheBuster and evict the entries being tracked.
  • KeyValues.UsageTouchWorker (Oban cron, every 15 min, same pattern as RecentEventsTouchWorker)
    streams {:lookup, …} entries written to KeyValues.Cache in the last 20 min and upserts
    last_used_at in chunks of 1,000. No hot-path tracking and no extra in-memory state.
  • The same worker prunes usage rows older than 30 days.

Warming

  • CacheWarmer.warm_top_n/0 replaces warm_full/0: orders by last_used_at DESC NULLS LAST, updated_at DESC, limited to warm_limit (default 500k, configurable).

Accuracy trade-off

The signal is the Cachex modified timestamp (time of write, not read), so this is an approximation
of LRU: it is good enough to order the warm set, not an exact access log.

Comment on lines +6 to +7
add :key_value_id, references(:key_values, on_delete: :delete_all), null: false
add :last_used_at, :utc_datetime_usec, null: false, default: fragment("now()")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm this also implies that if all 1 mil records are in use, then 1 mil records will also be created in this table. why would we want a separate table as compared to having these two fields directly on the :key_values table?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing that the separate table means that the table isn't tracked it isn't tracked as part of ContextCache since it is not included in the published tables, so bumping it would not trigger cache invalidations.
should make this clear in the KeyValuesUsage module

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably have some periodic truncation logic to keep the size of this table down

@@ -0,0 +1,110 @@
defmodule Logflare.KeyValues.UsageTracker do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of an ets-based tracker that gets routed through a genserver, a simpler and good enough solution would be periodically streamed from the KeyValues.Cache the Sources.recent_events_touch/1, and triggered with Oban via an Oban worker.
Since LRU cache warming is already limited to 500k results, we don't need 100% accuracy and only need a good-enough sort order during warming.
Furthermore, this would eat into memory, which the KeyValues would already be consuming quite a bit of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'll need to balance "correctness" for performance in this case. we should defer more accurate LRU tracking until the need arises, this would be too much overhead and overengineering for improved cache warming.

bblaszkow06 added a commit to bblaszkow06/logflare that referenced this pull request Jul 1, 2026
… KeyValues LRU

Per reviewer feedback on PR Logflare#3592: drop the hot-path ETS/GenServer tracker in
favour of periodically harvesting recently-written entries straight from
KeyValues.Cache via Cachex.stream!, driven by an Oban cron worker every 15 min.

- Remove UsageTracker (double-buffered ETS + GenServer), its bench scripts, and
  all related tests; remove it from ContextCache.Supervisor and test_helper.exs
- Add Cache.touch_recent_usages/1: streams cache entries with modified >= cutoff,
  deduplicates accessor-path variants of the same (user_id, key), chunks DB writes
  with 500 ms sleeps — zero per-lookup overhead, good-enough sort order for warming
- Add KeyValues.prune_usages/1: deletes key_value_usages rows older than 30 days
  to keep the table bounded (addresses reviewer comment on unbounded growth)
- Add UsageTouchWorker (Oban, queue: default, max_attempts: 1) on */15 cron
- Expand KeyValueUsage @moduledoc to make the logflare_pub exclusion explicit
- Refactor tests: shorter names, combined cases where same input covers both paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bblaszkow06 added a commit to bblaszkow06/logflare that referenced this pull request Jul 2, 2026
… KeyValues LRU

Per reviewer feedback on PR Logflare#3592: drop the hot-path ETS/GenServer tracker in
favour of periodically harvesting recently-written entries straight from
KeyValues.Cache via Cachex.stream!, driven by an Oban cron worker every 15 min.
@bblaszkow06
bblaszkow06 force-pushed the bb/o11y-1890-kv-warmup-lru branch from 12477ce to 5df497e Compare July 2, 2026 14:51
@bblaszkow06
bblaszkow06 requested a review from Ziinc July 2, 2026 14:51
@djwhitt djwhitt added enhancement New feature or request performance Performance and optimization changes labels Aug 17, 2026
bblaszkow06 and others added 4 commits September 22, 2026 10:18
Track key-value cache hits via a dual-buffer ETS UsageTracker GenServer
that periodically upserts a `key_value_usages` table. On startup,
CacheWarmer now warms only the top-N most-recently-used entries (ordered
by last_used_at, falling back to updated_at) instead of streaming the
full table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… KeyValues LRU

Per reviewer feedback on PR Logflare#3592: drop the hot-path ETS/GenServer tracker in
favour of periodically harvesting recently-written entries straight from
KeyValues.Cache via Cachex.stream!, driven by an Oban cron worker every 15 min.
@bblaszkow06
bblaszkow06 force-pushed the bb/o11y-1890-kv-warmup-lru branch from caf320f to 9f7dc11 Compare September 22, 2026 08:21
CacheWarmer records when the initial full warm completed; touch_recent_usages
only considers cache entries written after that point, so the boot-time
put_many of up to warm_limit rows no longer stamps every warmed key as
recently used.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request performance Performance and optimization changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants