Database branches live in the storage layer
Copy-on-write under the block device turns full clones into cheap point-in-time forks.
Branching a production database used to mean either a long restore or a managed product that hid the hard parts. PlanetScale made the developer workflow feel like Git. The interesting engineering question is what has to sit under that workflow so a 100 GB database does not require another 100 GB before the branch can start.
The answer is not inside the SQL engine. It is at the storage boundary: separate durable data from the database process, treat state as an immutable image, and create writable clones with copy-on-write (COW). That pattern is what projects like Homescale are re-implementing in the open, and it is the same separation that cloud database platforms already depend on at scale.
Images, containers, and lineage
Borrowing Docker’s vocabulary is more than branding. An image is an immutable starting point for database files. A container is a writable clone of that image. A branch is another container taken from a captured state of an existing one. The database process never learns about lineage. Each instance sees a normal filesystem on a block device that happens to share unchanged blocks with its parent.
Branching a writable container needs an intermediate step. You first freeze a point-in-time state of the parent, then clone that read-only state into a new writable volume. The freeze is storage’s job; the engine only has to be put into a recoverable condition so the snapshot is consistent. After that, both the original and the branch can diverge. Reads of untouched data walk the parent chain. Only dirty pages allocate new space.
That model is engine-agnostic as long as durable state lives on a filesystem backed by a block device and the engine can be prepared for a snapshot. Postgres is a natural first target. The engine-specific work belongs in a thin adapter: initialize an image, start the process, publish connection details, and quiesce for snapshot. Lifecycle, volumes, clones, and lineage stay in the platform layer.
Why storage must outlive compute
Standard managed engines already draw this line. Amazon RDS keeps data and logs on EBS. Google Cloud SQL attaches Persistent Disk or Hyperdisk to the VM. The process thinks it has local disk; the durable store is independent of the host. Aurora, AlloyDB, and Azure SQL Hyperscale push further into shared or distributed storage so compute can be replaced without full data copies.
A Homescale-style system sits closer to the first model. You still expose a block device and a filesystem. You only need the storage object’s lifecycle to be independent of the process that mounts it: create the volume before start, keep it after stop, clone it without involving the engine. Once that boundary exists, branching becomes a storage operation instead of a database feature request.
COW is the real product surface
Without COW, “branch” is marketing for restore. With COW, the branch starts by sharing the parent’s unchanged data and paying only for divergence. When the branch reads a block it has never written, the storage layer follows the chain to the captured parent state (and ultimately to the image). Writes land in the child’s own space and leave the parent untouched.
The practical constraints follow from that design:
- Snapshot consistency still matters. Storage can clone blocks instantly; it cannot invent a crash-consistent database if you snapshot mid-write without cooperation from the engine adapter.
- Chain depth and fragmentation are operational costs. Long-lived branches that rewrite large fractions of the dataset stop looking cheap.
- I/O path latency depends on how the COW layer is implemented (local thin volumes, distributed block, object-backed pages). Developers feel that as query tail latency, not as “branching API quality.”
Those trade-offs are why production platforms invest heavily in their storage stack. The CLI that looks like image create / container create / branch create is the easy part.
What platform engineers should actually do with this
If you are evaluating build-versus-buy for internal database branching, treat the storage model as the decision, not the CLI skin.
Buy when you need multi-tenant safety, automated failover, and a support contract more than you need to own the COW implementation. PlanetScale-class products already productize workflow (schema branches, deploy requests, safe migrations) on top of storage that can fork quickly. Neon and similar Postgres platforms sell the same developer shape for a different engine. Paying for that is rational if branching is a team productivity feature, not a core differentiator.
Build (or assemble) when you already run the data plane, have a storage system with real snapshots and thin clones (Ceph RBD, cloud volume snapshots with fast clone, ZFS/LVM thin in constrained environments), and the goal is an internal platform rather than a SaaS. The blueprint looks like this:
- Define a volume object that can be created, snapshotted, and cloned without the database process.
- Put engine knowledge behind an adapter interface: prepare image, start/stop, connection string, pre-snapshot hygiene.
- Record lineage explicitly (image → container → snapshot → branch) so garbage collection and access control are possible.
- Expose a small API or CLI that only manipulates those objects. Keep SQL clients pointed at ordinary host:port endpoints.
Expect the adapter work to dominate early effort. Postgres checkpoint and backup-mode details, permission on the data directory, and clean shutdown paths are where hobby prototypes stall. The storage clone is usually the part that looks magical in a demo and boring in production once monitoring and reclaim policy exist.
Also be honest about scope. A local COW stack can give developers disposable environments and point-in-time forks for feature work. It does not automatically give you PlanetScale’s multi-region Vitess topology, online schema change tooling, or tenant isolation story. Those are product layers on top of forking storage, not free gifts of COW.
The durable idea
Database branching that feels instant is a storage product wearing a developer-tools UI. Separate the block device’s life from the process, make state immutable at snapshot points, and let COW absorb the cost of divergence. Engines plug in through adapters; they do not need native branch primitives for the core fork to work.
Homescale-style systems make that architecture legible without the cloud console. Whether you ship your own control plane or keep paying a managed vendor, the threat model and cost model are the same: branches are cheap only while they share data, and the platform that owns the COW layer owns the real SLA. Everything above that is packaging.
Sources & further reading
Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.
Discussion 7
just use the standard library for simple storage needs
love how they're forking the traditional approach to database branching, using copy-on-write under the block device is a real storage-smart move 📈
totally agree with you @devops_dadjokes, using copy-on-write under the block device is a game changer, it's amazing how it enables cheap point-in-time forks without needing a full clone
so how does this handle backfills, does the copy-on-write mechanism allow for efficient historical data reconstruction or would that still require a full clone?
@data_eng_dee that's a great point, i'm wondering if the cow mechanism could be used to create a sort of 'branching history' that allows for efficient backfills, essentially using the same tech to recreate historical states without needing full clones, could be a game changer for data auditing and debugging
@data_eng_dee that's a great point, the cow mechanism should be able to handle backfills pretty efficiently, since it's only writing out the changed blocks, but i'd love to see some benchmarks on how that plays out in practice, especially with large databases
i'm still not convinced we need all this complexity, just use postgres and its built-in snapshotting, it's been doing this for years without all the extra abstraction layers