Forward-deployed / AI Knowledge Graph
Flowable

The history tables: audit trail vs runtime state

Motto — Two table families, two jobs: ACT_RU_* answers “what happens next”, ACT_HI_* answers “what happened” — every ops question starts by picking the right family.

Part of Phase 09 — Operations & observability. Concept lesson — no code required.

The Problem

Phase 2 showed that completed instances vanish from runtime tables. Every lesson since has quietly leaned on the other side of that split. Phase 1’s client read historic-activity-instances. Phase 8’s audit sentence depends on history pinning. The capstone printed a timeline after the instance was already gone.

This lesson makes the split explicit. Querying the wrong family is the most common ops mistake — someone says “the instance disappeared!” when it actually completed. History is also the table family that grows forever if nobody owns it (lesson 02).

The Concept

<style> .dgm-hist{background:var(–color-surface);border:1px solid var(–color-border);border-top:3px solid var(–color-border-danger-outline); border-radius:16px;padding:28px 26px 22px;box-shadow:0 1px 3px rgb(var(–shadow-color) / 0.04),0 8px 24px rgb(var(–shadow-color) / 0.04); margin:26px 0;max-width:100%;font-family:inherit;text-align:left;position:relative} .dgm-hist-chip{position:absolute;top:-11px;left:24px;background:var(–color-background-danger-solid);color:var(–white); font-size:10.5px;font-weight:700;letter-spacing:1px;text-transform:uppercase; border-radius:20px;padding:3px 12px;box-shadow:0 2px 6px color-mix(in oklab, var(–color-background-danger-solid) 28%, transparent)} .dgm-hist h4{margin:0 0 4px;font-size:11px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;color:var(–color-text-secondary)} .dgm-hist-sub{font-size:12px;color:var(–color-text-secondary);margin:0 0 16px} .dgm-hist-cols{display:grid;grid-template-columns:repeat(2,1fr);gap:12px} .dgm-hist-col{border-radius:12px;padding:12px 13px;border:1.5px solid} .dgm-hist-col.neutral{background:var(–color-surface-tertiary);border-color:var(–color-border)} .dgm-hist-col.accent{background:var(–color-background-danger-surface);border-color:var(–color-border-danger-surface)} .dgm-hist-col.blue{background:var(–color-background-info-surface);border-color:var(–color-border-info-surface)} .dgm-hist-col.green{background:var(–color-background-success-surface);border-color:var(–color-border-success-surface)} .dgm-hist-col h6{margin:0 0 8px;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.3px} .dgm-hist-col.neutral h6{color:var(–color-text-secondary)} .dgm-hist-col.accent h6{color:var(–color-text-danger)} .dgm-hist-col.blue h6{color:var(–color-text-info)} .dgm-hist-col.green h6{color:var(–color-text-success)} .dgm-hist-item{background:var(–color-surface);border:1px solid var(–color-border-info-surface);border-radius:8px;padding:6px 9px;font-size:10px; color:var(–color-text);line-height:1.35;margin-bottom:5px} .dgm-hist-item:last-child{margin-bottom:0} .dgm-hist-link{display:flex;justify-content:center;color:var(–color-text-tertiary);font-size:9.5px;font-weight:700; text-transform:uppercase;letter-spacing:.3px;padding:6px 0} @media (max-width:640px){.dgm-hist{padding:20px 16px 18px}.dgm-hist-cols{grid-template-columns:1fr}} </style> <div class=“dgm-hist”> <span class=“dgm-hist-chip”>Runtime vs. history tables</span> <h4>ACT_RU_* is small and hot; ACT_HI_* is append-only and grows forever</h4> <p class=“dgm-hist-sub”>On each commit, the runtime row is mirrored into (or closed in) its history counterpart.</p> <div class=“dgm-hist-cols”> <div class=“dgm-hist-col accent”> <h6>ACT_RU_* — runtime (hot, small, deleted on completion)</h6> <div class=“dgm-hist-item”>EXECUTION — live tokens</div> <div class=“dgm-hist-item”>TASK — open tasks only</div> <div class=“dgm-hist-item”>VARIABLE — current values</div> <div class=“dgm-hist-item”>JOB / TIMER_JOB / DEADLETTER_JOB</div> </div> <div class=“dgm-hist-col blue”> <h6>ACT_HI_* — history (append-only, grows forever)</h6> <div class=“dgm-hist-item”>PROCINST — every instance ever</div> <div class=“dgm-hist-item”>ACTINST — every step taken</div> <div class=“dgm-hist-item”>TASKINST — every task incl. completed</div> <div class=“dgm-hist-item”>VARINST — final variable values</div> <div class=“dgm-hist-item”>IDENTITYLINK — who was candidate/assignee</div> </div> </div> <div class=“dgm-hist-link”>on each commit, rows are mirrored / closed →</div> </div>

The rules of the split:

  1. Runtime is a working set, not a record. Its size tracks only live load, and that keeps every token operation fast (Phase 2’s design bet). A row leaving ACT_RU_TASK means success, not data loss.
  2. History is written in the same transaction as the runtime change, at the default history level. The audit trail can’t drift from what actually happened, and a rollback erases both sides together. Phase 2’s transaction boundaries apply to history too.
  3. Query routing is mechanical. Open work → runtime (/runtime/tasks, /query/tasks). Anything involving “completed”, “how long did”, “who did”, or a date range → history (/history/..., /query/historic-...). Dashboards that join both (throughput + backlog) make two queries, not one clever one.
  4. History is where the metrics live: cycle time (START_TIME_ to END_TIME_ on ACTINST), SLA attainment, per-step durations, and decision version per case (Phase 8). All of these are history reads. Lesson 04 builds the probe on exactly these.

Ship It

This lesson ships outputs/table-families-cheatsheet.md — the query-routing table and the “instance disappeared” triage flow.

Check Yourself

Q1. An ops dashboard needs average time-to-decision for last month. Which family?

  • A) runtime — it’s about instances
  • B) history — completed work and durations live only in ACT_HI_*
  • C) both
  • D) the jobs tables

<details><summary>Answer</summary>B — runtime rows for those instances no longer exist. Durations are history’s whole purpose.</details>

Q2. Why is history written in the same transaction as the runtime change?

  • A) performance
  • B) so the audit trail can never disagree with what actually committed — a rolled-back step leaves no phantom history
  • C) to save connections
  • D) it isn’t; a nightly job copies it

<details><summary>Answer</summary>B — audit integrity by construction. (Flowable’s async-history mode trades this for throughput — an explicit, documented trade.)</details>

Q3. “Instance 8801 vanished from /runtime/process-instances” most likely means…

  • A) data corruption
  • B) it completed (or was terminated) — check /history/historic-process-instances for its END_TIME_ and outcome
  • C) the engine restarted
  • D) wrong tenant

<details><summary>Answer</summary>B — the triage flow on the cheat sheet: history first, panic later.</details>

Challenge. Using only history endpoints, reconstruct the capstone driver’s closing summary (timeline and final variables) for an instance that finished yesterday. Then compute one number the driver didn’t: minutes spent waiting at each user task (ACTINST start/end deltas). You’ve just written the seed of lesson 04’s probe.

Related