History levels & data growth: the retention decision
Motto — History is the only table family with no natural ceiling: choose what you record and how long you keep it on purpose, or the database chooses an outage for you.
Part of Phase 09 — Operations & observability. Concept lesson — no code required.
The Problem
The Docker image and Phase 2’s starter both run history-level=full. That’s right
for learning, but it’s a default nobody revisits. A loan book doing 50k
applications a month writes millions of ACTINST and VARINST rows. Two years later,
the history tables dwarf runtime a thousandfold, dashboards slow down, and backups
balloon. At the sharp end, the DPO asks why customer PAN values still sit in
ACT_HI_VARINST seven years past any retention basis.
Recording level and retention window are one decision with three stakeholders: ops (query speed), finance (storage), and compliance (a floor and a ceiling on keeping data).
The Concept
Lever 1 — what gets written (flowable.history-level):
| Level | Records | Honest use |
|---|---|---|
none |
nothing | pure-throughput engines where an external system owns audit |
activity |
instances + steps, no variables | metrics without payload custody — underrated for PII-heavy domains |
audit (engine default) |
+ variable values, form properties | the usual regulated-flow answer |
full |
+ variable updates (every intermediate value) | debugging, courses, never high-volume production by accident |
Two refinements help before you drop a level globally. A per-definition override
(flowable:historyLevel on the process) keeps audit for the loan book while the
notification workflow runs activity. And transient variables (Phase 2), plus
“store a document reference, not the document,” solve most PII-in-history
findings without touching the level.
Lever 2 — how long it stays. Growth is rate × record size × retention, and
retention is the only lever that bounds it:
<style> .dgm-hl{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-hl-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-hl h4{margin:0 0 4px;font-size:11px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;color:var(–color-text-secondary)} .dgm-hl-sub{font-size:12px;color:var(–color-text-secondary);margin:0 0 16px} .dgm-hl-row{display:flex;align-items:center;justify-content:center;gap:6px;flex-wrap:wrap} .dgm-hl-n{background:var(–color-surface-tertiary);border:1px solid var(–color-border);border-radius:9px;padding:9px 11px;text-align:center; font-size:10.5px;font-weight:700;color:var(–color-text);line-height:1.3} .dgm-hl-n.accent{background:var(–color-background-danger-surface);border-color:var(–color-border-danger-surface);color:var(–color-text-danger)} .dgm-hl-n.blue{background:var(–color-background-info-surface);border-color:var(–color-border-info-surface);color:var(–color-text-info)} .dgm-hl-n.green{background:var(–color-background-success-surface);border-color:var(–color-border-success-surface);color:var(–color-text-success)} .dgm-hl-n.red{background:var(–color-background-danger-surface);border-color:var(–color-border-danger-surface);color:var(–color-text-danger)} .dgm-hl-arr{color:var(–color-text-tertiary);font-size:12px;display:flex;flex-direction:column;align-items:center;gap:1px} .dgm-hl-arr .lbl{font-size:8.5px;font-weight:700;color:var(–color-text-tertiary);text-transform:uppercase;letter-spacing:.3px} @media (max-width:640px){.dgm-hl{padding:20px 16px 18px}.dgm-hl-row{flex-direction:column}} </style> <div class=“dgm-hl”> <span class=“dgm-hl-chip”>From written to deleted</span> <h4>History has a lifecycle too — it isn’t kept forever by default</h4> <p class=“dgm-hl-sub”>The compliance ceiling, not disk space, is usually what ends it.</p> <div class=“dgm-hl-row”> <div class="dgm-hl-n ">Written — at the chosen history level</div> <span class=“dgm-hl-arr”>→</span> <div class=“dgm-hl-n accent”>Kept — the query window (dashboards, disputes, audits)</div> <span class=“dgm-hl-arr”>→</span> <div class=“dgm-hl-n blue”>Archived — exported to warehouse/lake (cheap, joinable, immutable)</div> <span class=“dgm-hl-arr”>→</span> <div class=“dgm-hl-n red”>Deleted — past the compliance ceiling (DPDP/GDPR erasure basis ends)</div> </div> </div>
The floor comes from audit and regulatory obligations — Phase 8’s audit sentence must stay answerable. The ceiling comes from data-protection law: keeping everything forever is a compliance failure in the other direction.
Mechanically, use Flowable’s built-in history-cleaning job
(flowable.enable-history-cleaning=true plus a history-cleaning-after window),
or scheduled batch deletes keyed on END_TIME_. Always key on end time, never
touch open instances, and always archive before you delete if the warehouse is the
system of record for old cases.
The one metric: history growth per week, plotted against the deletion job’s throughput. If ingest exceeds cleanup, the ceiling is just an outage with a date.
Ship It
This lesson ships
outputs/retention-decision-guide.md —
the level table, the keep/archive/delete worksheet, and the PII checklist.
Check Yourself
Q1. Metrics matter, but variables carry PAN/Aadhaar and an external system holds the golden record. Best global level?
- A) full
- B) activity — timelines and durations without variable custody; references over payloads where audit needs data
- C) none
- D) audit
<details><summary>Answer</summary>B — the level is a data-custody decision, not a debugging convenience. Record what you’re prepared to protect.</details>
Q2. Why is retention both a floor and a ceiling?
- A) it isn’t; longer is safer
- B) audit obligations set a minimum keep; data-protection law sets a maximum — past the erasure basis, keeping is the violation
- C) storage pricing
- D) the engine enforces 7 years
<details><summary>Answer</summary>B — “keep everything forever” fails DPDP/GDPR exactly the way “delete after a month” fails the auditor.</details>
Q3. History cleanup jobs key on…
- A) START_TIME_
- B) END_TIME_ — only closed instances age out; an open 3-year-old mortgage keeps its full trail
- C) row count
- D) table size
<details><summary>Answer</summary>B — retention clocks start at completion. Open instances are runtime’s concern, and their history must survive them.</details>
Challenge. Fill the worksheet for the capstone: level per definition, keep window, archive target, and deletion basis. Then compute the steady-state row count at 50k applications a month and check it against your database sizing (lesson 05). If the number surprises you, that was the point.
Related
- Next: Async executor tuning
- The tables being governed: History tables
