Task counters

The task grid shows a count next to every filter (status, assignee, group, …). Counting with a DynamoDB query on each page load would be slow and expensive, so counts are cached per filter and kept current from the tasks table stream (actionstools_app/ddb_counts/).

Counter keys

A counter’s key is the JSON of its filter list, e.g. [{"name": "status", "op": "in", "val": ["new", "in-progr"]}], stored in em_actions_tasks_counters as {counter_seq_key, current_value, ExpirationTime}. created_at values are rounded down to the day so that similar queries share a counter.

Lifecycle

  1. First read. get_count(query) (used by GET /tasks/ and /tasks/filters/counts/) finds no counter, creates one with value 0 and a 15-minute TTL, queues async_calculate_count, and returns 0.

  2. Calculation. On the Worker, async_calculate_count counts matching tasks of instance 8001 (instance_id-uuid-index with a filter expression) and stores the real value with a 4-day TTL.

  3. Stream updates. For every batch from the em_actions_tasks stream, task_db_trigger loads all counters, evaluates each rule against the old and new image, and atomically increments or decrements every counter whose rules now (or no longer) all hold. Soft-deleted tasks count as removed.

  4. Cleanup. Every 4 days remove_counters scans the table and deletes all filter counters (keys starting with [); the grid rebuilds them on its next query, so drift from missed stream records cannot last longer than that. Each counter also carries an ExpirationTime, which DynamoDB uses to expire it when TTL is enabled on that attribute.

item_number (the human task number) is a different counter: a single atomic sequence in em_actions_counters, incremented on task creation.

Operational notes

  • A brand-new filter shows 0 for a moment; the next request shows the calculated value.

  • task_db_trigger reads the whole counters table on every batch, so its duration grows with the number of distinct filters in use. Watch the Worker’s duration metric if the grid gains filters.

  • In staging the schedule is off (as it was under Zappa); counters there only expire.

  • During the Zappa cutover the stream mapping and the schedule must be active in exactly one deployment — see Zappa → SAM cutover.