Infrastructure as a Database

The apply queue disappears when the lock does

Terraform gives a state one lock, so changes queue behind each other. Stategraph locks the resources a change touches, so disjoint changes apply at the same moment and only real overlap waits.

Resource-level locks Conflicts caught at commit Atomic across states

Where the concurrency comes from

The lock covers the change, not the file

Lock scope

Two applies, one state, same moment

Terraform locks the whole state file for every operation. Stategraph locks the resources a change touches, so two changes to different resources apply at the same moment.

Resource-level locking →
engineer a
dev-a
# editing aws_instance.web ❯ stategraph apply Locking aws_instance.web… Apply complete ✓
engineer b
dev-b
# editing aws_security_group.app ❯ stategraph apply Locking aws_security_group.app… Apply complete ✓

Illustrative. Same state, same moment. Different resources, so neither waits.

Conflicts

Overlap is settled at commit

Nothing is arbitrated up front. Everyone plans at once, and a real overlap is rejected at commit with the conflicting transaction ID, then re-planned.

Transaction model docs →
conflict check tx-a plan apply committed tx-b plan apply committed different resources, so nothing to settle tx-c plan apply rejected touched a resource tx-a changed → re-plan and retry time →

tx-a and tx-b touch different resources and commit. tx-c touched a resource tx-a changed, so it is rejected and re-planned.

Multi-state transactions

Producer and consumer in one plan

With a state file, you apply the producer first and hope. Stategraph plans the producer and the consumer together, and the consumer reads the producer's pending output.

Cross-state plans →
stategraph · multi-state plan
# edit rm1 only: http_proxy_port 8080 → 9090 ❯ stategraph plan State 0 · rm1 (producer) ~ terraform_data.http_proxy_port input: 8080 → 9090 ~ output.http_proxy_port: 8080 → 9090 State 1 · rm2 (consumer, reads rm1's pending output) ~ terraform_data.firewall_rule_port_min input: 8070 → 9080 Plan: 0 to add, 2 to change, 0 to destroy ✓ across 2 states, one plan, one apply

Real output. Same terraform_remote_state HCL.

Atomic apply

Every state commits, or none does

Terraform applies networking, compute, and application states one at a time, in an order you maintain. Stategraph commits every state in one transaction, and if anything fails, nothing applies.

Multi-state transactions →
stategraph · mtx
❯ stategraph tf mtx --out plan.json ./rm1 ./rm2 State 0 · ./rm1 + terraform_data.network + terraform_data.subnet + terraform_data.router State 1 · ./rm2 # reads rm1.network_id, pending + terraform_data.app + terraform_data.firewall + terraform_data.route + terraform_data.dns_record Plan: 7 to add, 0 to change, 0 to destroy # across 2 states ❯ stategraph apply plan.json ✓ transaction committed · 7 resources · 2 states · 1 txn

Illustrative. rm2 reads rm1's pending network_id inside the same transaction, so the order is derived, not hand-maintained.

Underneath

PostgreSQL's concurrency model

Reads never block writes

Reads work against a consistent MVCC snapshot, so a reader never blocks a writer and a writer never blocks a reader.

Plans pinned to a revision

Every plan is pinned to the state revision it was computed against, so a long plan does not become worthless because someone else applied first.

One state for the whole team

Contention tracks real overlap, not headcount, so adding engineers does not add queue time.

MVCC plus row-level locks, not a file mutex on a JSON blob. Resource-level locking docs →

Stop letting one lock decide who ships

Keep one coherent state for the whole team and let disjoint changes land together. Bring a state file and see it running against your own infrastructure.

Book a Demo How Stategraph works