# Helland

> The Pat Helland lens: treat durable data as immutable, append-only facts that carry their own identity and version; and design across service, machine, or org boundaries assuming you cannot have distributed transactions — only idempotent, at-least-once messages that must be reconciled. Apply this on in-place updates and overwrites, "just UPDATE the row," two-phase commit or distributed transactions at scale, exactly-once delivery assumptions, shared mutable state across services, and any data that crosses a boundary of service, machine, org, or time. Trigger even when Helland isn't named — the smell is mutation-in-place and pretending distributed systems can be transactional.

- Skill: `danmestas/helland` (Agent Skill)
- Install (CLI): `npx skillmds@latest add danmestas/helland`
- Raw SKILL.md: https://api.skillmd.com/api/skills/danmestas/helland/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: danmestas (https://skillmd.com/u/danmestas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/danmestas/helland

---


# The Helland Lens

Pat Helland has spent decades writing the plain-language field guide to large data
systems — immutability, idempotence, and what you actually give up when data leaves
one machine. This skill encodes his recurring principles as questions to ask of any
system that stores or moves data.

Two ideas anchor everything:

- **Immutability changes everything.** Storage is cheap; the truth is a growing set
  of immutable facts. You append; you do not overwrite. *Accountants don't use erasers.*
- **Inside vs outside.** Data *inside* a service is private, mutable, current, and can
  be relational. Data that goes *outside* — across a service, machine, org, or time
  boundary — is immutable, versioned, and self-identifying, like a published document.
  Confusing the two is the source of a great deal of distributed-systems pain.

## Iron Law

**Durable data is append-only, and data that crosses a boundary is immutable,
versioned, and self-identifying.** You record new facts rather than destroying old
ones, and once a fact has left home you never reach back and mutate it — you publish
a new version.

---

## The questions

### 1. Are you overwriting, or appending?
An in-place update destroys history and the ability to audit, cache, replay, or
time-travel. Prefer recording a new fact over mutating an old one.
- Ask: does this `UPDATE`/overwrite throw away information someone might need later?
  Could it be an append with a version or timestamp instead?
- Payoff: append-only data can be freely shared and cached because it never changes
  under you, and the log of facts becomes a source of truth you can re-derive from.

### 2. Is this data "inside" or "outside"?
The single most useful classification in the lens. Inside data plays by database
rules; outside data plays by document rules. Don't apply one's assumptions to the other.
- **Inside**: private to one service, mutable, "now," may be normalized/relational.
- **Outside**: shared across a boundary, immutable, versioned, has identity, self-describing.
- Ask: which is this? Is something treating a piece of outside data as if it were a
  mutable inside row (or vice versa)?

### 3. Does every message survive being delivered twice?
At scale, delivery is at-least-once. Exactly-once is a comforting fiction. Receivers
must therefore be idempotent.
- Ask: if this message/operation arrives twice, is the outcome the same as once? How
  is duplicate detection done — by a stable identity/key on the message?
- Red flag: any handler that assumes it will see each message exactly once.

### 4. Are you assuming a distributed transaction you can't have?
Across independent services you generally cannot hold a transaction. The pattern is
entities plus workflow plus reconciliation — not a distributed lock.
- Ask: does correctness here depend on two systems committing atomically together? Can
  it be restructured as independent local commits plus messaging and reconciliation?
- Red flag: two-phase commit or a distributed transaction proposed as the load-bearing
  mechanism at scale.

### 5. Does this fact carry its own identity and version?
For outside data to be referenced immutably, it needs a key (identity) and a version.
That's what lets others cache it, point at a specific version, and reason about staleness.
- Ask: can this datum be named and pinned to a version? Or is it an anonymous blob whose
  meaning depends on *when* you happened to read it?

### 6. Are you locking when you should be reconciling?
At scale you act on possibly-stale memories, make guesses, and occasionally have to
apologize — i.e. compensate. That's normal business reality, not a failure of rigor.
- Ask: is strong, synchronous consistency truly required here, or is eventual +
  reconciliation (with a compensating action for the rare conflict) the honest design?
- Note: the apology/compensation path is real work — design it, don't hand-wave it.

### 7. Can you rebuild derived state from the log of facts?
If the immutable facts are the truth, everything else — indexes, aggregates, caches,
materialized views — is derived and should be re-derivable.
- Ask: if a derived view were lost, could it be rebuilt purely from the append-only
  facts? If not, some truth is hiding in a mutable place where it shouldn't be.

---

## How to apply

**In review:** first classify each piece of data as inside or outside (question 2) —
most findings fall out of that. Then flag in-place mutations of would-be facts (1),
non-idempotent message handlers (3), and any assumed distributed transaction (4).

**In design:** decide the boundaries first, because the boundary is what makes data
"outside" and therefore immutable/versioned/identified. Make cross-boundary messages
idempotent by construction. Treat the append-only fact log as truth and everything
else as a re-derivable view (7).

**Output shape:** findings grouped as (a) mutation-in-place that should be append-only,
(b) inside/outside confusions, (c) delivery/idempotence gaps, (d) impossible-transaction
assumptions — each with the concrete restructuring. Lead with whichever most threatens
correctness under retries or failures.

## Anti-patterns (when NOT to over-apply)
- **Don't** treat immutability as free. Append-only data and its indexes grow; account
  for storage and compaction honestly (the ZFS/dedup lesson — sometimes bookkeeping
  costs more than the saving).
- **Don't** force everything "outside." Inside a service, mutable current-state data is
  correct and simpler — the discipline is about *boundaries*, not banning mutation.
- **Don't** invoke eventual consistency to dodge required invariants. Some things genuinely
  need strong consistency; question 6 is about honesty, not always choosing weak.
- **Don't** confuse this with `hickey`'s value-orientation — related, but Helland's scope is
  data *architecture across boundaries*, not language-level values.

## Complements
- `hickey` — values and immutability as a language-level worldview; Helland is the
  distributed-data-architecture level of the same instinct
- `hipp` — durability, integrity, and correctness of the store itself
- `tigerstyle` — safety and limits in the systems that implement these boundaries
- `bellard` — keeping the machinery that enforces all this minimal

