Route storage requests with a partition map

Design the control plane that maps key ranges to storage nodes while keeping routing caches fresh, safe, and fast.

Partition manager and map table

A partition map tells the frontend where to send an object request. When ownership changes, that answer must stay connected to two facts: the new owner has the accepted data, and the former owner can no longer change it.

We'll separate the manager's responsibilities from normal request routing, then follow one planned transfer through its final write and a stale-client retry.

Who maintains the assignment?

Component Responsibility --- --- Partition map Durably record each key range, its serving owner and ownership generation. Partition manager Assign and move ranges, split busy ranges, merge cold neighbors, and arrange recovery after failures. Partition server Serve its assigned ranges; one server can own several. Coordination authority Serialize manager decisions and establish valid ownership.

The frontend caches map entries and sends requests directly to the partition server. The manager stays off this ordinary request path. It examines load and health to decide when assignments should change.

An assignment carries an epoch , a generation that changes on handoff. The receiver must enforce that authority where a write takes effect. A cached address is only a routing hint; publishing a new map cannot stop an old process by itself.

For this design, a range has at most one active write owner . A transfer may briefly leave it with none. Allowing an unavailable interval is safer than accepting independent changes at two owners without a reconciliation protocol.

A owns epoch 1. Its append-only file contains r1: first and a newline, six bytes. We copy those bytes to B while A remains active. Then A accepts r2: second and a newline, another seven bytes.

B is now behind. Before switching owners, pause new writes at A, wait for any admitted effects to finish, copy the missing tail, and verify the destination. Only then activate B at epoch 2.

In the object-storage example, the append and cutover use the same Python lock. A request cannot pass its authority check, wait through cutover, and then append with stale permission. The check and effect occur inside one guarded section.

The delayed r3 attempt adds nothing at A. Retrying against B with epoch 2 appends the third record, bringing B to 19 bytes. If the copied files differ before cutover, the example raises an error and leaves the assignment paused.

This runs a chosen sequence of calls, not concurrent threads. The files are real, but authority and accepted-operation history live in memory. It has no durable transfer journal or power-loss recovery protocol.

How much must we pause?

Pausing for the whole transfer is simpler: finish earlier writes, copy a fixed source, verify it, then switch. The cost is an unavailable write interval covering the entire copy.

Copying while writes continue needs a snapshot boundary and a retained change history. Our append-only file uses a byte offset; a mutable database needs an equivalent agreement between snapshot and log. The final pause accounts for everything accepted after the snapshot. Measure that pause rather than assuming a small example proves acceptable downtime.