NAME

Punk::Observe::Warm - the settled chunks, computed where nobody is waiting

SYNOPSIS

my $out = Punk::Observe::Warm::run(
    db    => $backend,
    store => sub { $stores{ $_[0] } },
);

printf "%d computed, %d already warm\n", $out->{computed}, $out->{hits};

DESCRIPTION

Punk::Observe::Cache fills itself as a side effect of answering, which means the first person to open a dashboard after a restart pays for the whole window. This computes those entries in the background instead, so that the request finds them already there.

It warms the queries saved on dashboards, per tenant, because those are the queries that are re-run most and change least. Nothing else is warmed: an explorer query is asked once, and the chunk it would leave behind is one nobody comes back for.

One warm hour serves every range

A saved panel carries its own bucket(...), so its chunk width does not change with the range picker and the entries for an hour are the same entries whether the reader asked for the last hour or the last week. Warming a depth once therefore serves every preset over it, and there is nothing to warm per range.

What a pass costs, and what stops it

A pass is bounded twice, by a count of chunks computed and by a wall clock, because the work is unbounded by nature - one chunk of a busy store is a real scan, and a job that can run for ever can hold a queue worker for ever.

Neither bound can interrupt a scan already begun, so both are checked before one starts, and before the entry being replaced is touched. A pass that stops leaves the cache no colder than it found it and says which bound it hit; the next pass walks the same way and continues from what the last one left.

The walk is newest first. A pass that runs out should have spent what it had on the hours somebody is about to ask for.

Freshness

Telemetry arrives late, so the newest settled chunks can still gain records after they are first computed. Those are recomputed on every pass - two hours of them by default - and everything older is filled only where it is missing.

The entries outlive the depth deliberately: an entry that expired while still inside the window would be re-earned on a schedule rather than kept. The cost is the one Punk::Observe::Cache already names, widened: a record backfilled into a chunk older than the refresh window, and one deleted by retention, are not reflected until the entry expires.

FUNCTIONS

run

my $out = Punk::Observe::Warm::run(%opt);

One pass. db is the configuration backend and store a code reference returning the store for a tenant name. depth_ns, refresh_ns, ttl, budget and timeout override the defaults above, and now is injectable for a test.

Returns counts of tenants, queries, skipped, chunks, computed, hits, failed and unstorable, plus stopped naming the bound that ended the pass or the empty string.

unstorable is the one to watch. A value too large for the cache's own budget is refused rather than stored, so those chunks are recomputed on every pass and never kept - a cost that is otherwise invisible.

tenants

queries

my $names = Punk::Observe::Warm::tenants($db);
my $qs    = Punk::Observe::Warm::queries($db, $tenant);

The tenants that have dashboards, and one tenant's distinct panel queries.

warm_job

The Punk::Queue task body, run under the observe.warm lease.

cron_task

my $code = Punk::Observe::Warm::cron_task(db => $db, store => $for);

The closure a host schedules when it declares its own cron rather than letting the plugin register one. Takes the queue, runs one pass under the leader lease, returns how many chunks were computed.

SEE ALSO

Punk::Observe::Cache, Punk::Plugin::Observe, Punk::Queue

AUTHOR

LNATION, <email at lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under the Artistic License 2.0.