Formula cache ============= A formula is normally evaluated every time it is read. For a heavy formula — a deep dependency tree, many fine-granularity (sub-quarter-hour) series pulled over the whole history, or costly operators — this can make reads slow (a sluggish *tsinfo*). The **formula cache** *materialises* the result of such a series: its values are pre-computed and stored, so a read serves them directly instead of re-evaluating the formula. When to use it -------------- Cache a formula when it is slow to display, typically because of: * a deep dependency tree; * many fine-granularity series read over a long history; * costly operators (``resample``, ``priority``, ``block-staircase``, or multi-site aggregates such as ``findseries`` + ``add``). How it works ------------ Three things are worth understanding. * **Refresh** — the cache is filled and kept up to date by a rework task, ``refresh_formula_cache``, run on each policy's schedule (``schedule_rule``); it walks the policy's series in dependency order and materialises new revision dates incrementally. Its on-demand counterpart, ``refresh_formula_cache_now``, is behind the *Refresh* button. Creating a policy and mapping series does not populate the cache — a refresh has to run. * **Freshness** — when a cached series is read, if its cache has not been refreshed for more than about twice its usual cadence, it is considered stale and recomputed live over the policy's window. In normal operation the cache is served directly, giving a fast read. * **Intermediate nodes speed up their parents** — when the engine evaluates a formula, it stops expanding the dependency tree as soon as it reaches a cached sub-series and reads the cached values directly. Caching an intermediate node therefore cuts out the computation of everything below it, for *every* formula that depends on it. Configuring a policy -------------------- Caching is driven by a **policy**, which has five parameters: +--------------------+-------------------------------------------------------+ | parameter | role | +====================+=======================================================+ | ``initial_revdate``| earliest revision date to cache from | +--------------------+-------------------------------------------------------+ | ``look_before`` | lower bound of the materialised value window | +--------------------+-------------------------------------------------------+ | ``look_after`` | upper bound of the materialised value window | +--------------------+-------------------------------------------------------+ | ``revdate_rule`` | cron rule: which revision dates to snapshot | +--------------------+-------------------------------------------------------+ | ``schedule_rule`` | cron rule: when the refresh job runs | +--------------------+-------------------------------------------------------+ ``initial_revdate``, ``look_before`` and ``look_after`` are date expressions (a small lisp), evaluated at refresh time — for example ``(date "2025-01-01")``, ``(today)``, ``(shifted now #:days -60)`` or ``(shifted now #:days 15)``. The two cron rules serve different purposes: ``revdate_rule`` selects which revision dates to snapshot (e.g. ``0 * * * *`` — every hour on the hour), while ``schedule_rule`` decides when the refresh task actually runs (e.g. ``5 * * * *`` — five past the hour). The small offset lets data pipelines finish before the snapshot is taken. .. image:: cache_policy_form.png .. warning:: **The value window is the main pitfall.** ``look_before`` / ``look_after`` define the range of *value dates* the cache materialises (and that the live recompute uses). It must cover the whole history you display. A window that is too narrow (for instance ``look_before = (today)``, ``look_after = (today)``) caches almost nothing, so the cache brings no benefit. Use a wide range, for example ``look_before = (date "2025-01-01")`` (or ``(shifted now #:days -180)``) and ``look_after = (shifted now #:days 15)``. **Examples.** .. code:: text # Hourly cache, a week of revisions, six months of value history initial_revdate (shifted now #:days -7) look_before (shifted now #:days -180) look_after (shifted now #:days 15) revdate_rule 0 * * * * # snapshot every hour schedule_rule 5 * * * * # refresh at 5 past the hour # Once-a-day cache, fixed history start initial_revdate (date "2025-01-01") look_before (date "2025-01-01") look_after (shifted now #:days 15) revdate_rule 0 6 * * * # one snapshot a day, 06:00 schedule_rule 15 6 * * * # refresh at 06:15 Cache operations ---------------- From the cache management interface: * **Refresh** — triggers an immediate refresh (the ``refresh_formula_cache_now`` task) over the window. It does not purge anything and does not rebuild an emptied cache. * **Activate / Deactivate** — schedules or removes the periodic ``refresh_formula_cache`` task. A policy can only be edited while it is deactivated. * **Delete** — purges the cache of **all** the policy's series and removes the policy and its series mappings (everything has to be recreated). * **Free series** — moving a series to the free list purges that series' cache and unmaps it from the policy. A series' cache is emptied in three cases: **deleting its policy** (every series), **moving the series to the free list** (that series only), and **editing its formula** — a change to the formula invalidates its own cache and those of its dependents. Programmatic access ------------------- A policy can be created and populated from the API: .. code:: python tsa.new_cache_policy( 'daily', initial_revdate='(shifted now #:days -7)', look_before='(shifted now #:days -180)', look_after='(shifted now #:days 15)', revdate_rule='0 * * * *', schedule_rule='5 * * * *', ) tsa.set_cache_policy('daily', ['my.heavy.formula']) Related methods: ``cache_policies()`` and ``cache_free_series()`` list the policies and the formulas still available for caching; ``cache_series_policy`` and ``has_cache`` inspect a series; ``delete_cache`` clears a series' cache; and ``refresh_series_policy_now`` triggers an immediate refresh of a policy. See :doc:`formula` for the full cache API reference.