The migration is split into two deploys — add the new columns and dual-write, then cut reads over and drop the old ones — rather than one atomic change.
Rejected alternative: a single migration inside one transaction. It is simpler to write, simpler to review, and it was the first plan. What decided against it: the table is written to continuously, so the transaction would hold a lock long enough for writes to queue behind it, and the failure mode of "it took longer than expected" is an outage rather than a slow deploy.
The two-step version is more work and has an awkward middle state where both schemas are live. That middle state is the cost, and it is a cost we can see and reason about — unlike lock duration under production write volume, which we would only have measured by causing the problem.
The diagram above is the part worth internalising: the dual write exists for rollback, not for performance. For the whole window the old schema is still receiving every write, which means reverting is a config change rather than a restore. The moment you stop writing to the old schema, rollback stops being cheap — so the window ends when you are confident, not when the backfill finishes.
The general form: prefer the plan whose worst case you can describe to the plan whose worst case you would have to discover.