8

PricingBlog
All posts
engineering

The Scariest Part of a Magento Deploy Is the Database Migration. We Made It a Number.

2026-08-06 · Byte8 Team

Share

We tell people we deploy Magento at 2pm on a Tuesday. The most common reply isn't "no you don't" — it's "sure, but the *database migration* is the scary part." And they're right.

The cutover itself is trivial. Swapping a current symlink from one release directory to the next is atomic and instant; if it goes wrong you swap it back. Nobody loses sleep over the symlink. The part that actually makes a midday deploy frightening is setup:upgrade — and specifically *not knowing*, in advance, whether this release's schema change will finish in eight seconds or hold a lock on sales_order_item for four minutes. On a traditional pipeline you find that out live, in production, which is the worst possible place to learn it.

So we stopped guessing. Before setup:upgrade runs, Orbit now forecasts the block window and puts a number on the screen.

Why the block time is a coin flip

Since Magento 2.3, schema is declarative: each module ships an etc/db_schema.xml describing the tables it wants, and setup:upgrade diffs the declared state against the live database and emits the DDL to close the gap. That's a genuine improvement over hand-written InstallSchema scripts — but it hides the one thing you care about operationally, which is *how long the resulting ALTER will block writes.*

On MySQL 8 that answer swings enormously depending on the change:

  • Adding a column at the end of a table is instant — a metadata-only change, free regardless of table size.
  • Adding an index runs online — writes keep flowing, and the cost scales with row count.
  • Changing a column type forces a blocking table copy — MySQL rebuilds the whole table, and on a multi-million-row sales_* table that's minutes of held locks.
Same one-line diff in db_schema.xml. Wildly different consequences. And nothing in the standard Magento tooling tells you which one you're about to trigger until you're already triggering it.

The insight: everything you need to predict it is already sitting there

The forecast doesn't require a staging replay or a dry run. Three inputs that already exist on the box are enough:

1. The old release's declarative schema and the new release's — merge every module's db_schema.xml in each release tree exactly the way Magento does, then diff the two merged states. 2. MySQL 8's online-DDL rules — a well-documented matrix mapping each kind of change to instant / online / blocking. 3. Live table statistics — row counts straight from information_schema.tables, to turn "online index on a big table" into an actual number of seconds.

Put those together and the scariest unknown in Magento ops becomes arithmetic.

How Orbit forecasts the window

During the deploy, before it runs setup:upgrade, Orbit builds the merged declarative schema for both the outgoing and incoming release, diffs them, and classifies every change against the online-DDL matrix:

change in db_schema.xml            class       cost model
--------------------------------   ---------   ----------------------------
new / dropped table                metadata    ~instant
add column (tail)                  instant     ~instant, any table size
drop index / drop constraint       metadata    ~instant
add index / add constraint         online      ~ rows / 100k  (writes flow)
drop column                        online      ~ rows / 100k
modify column type                 blocking    ~ rows / 50k   (table rebuild)

Each online/blocking change is sized against the live row count for its table, the per-change estimates are summed, a little fixed overhead for Magento's own bootstrap is added, and the result is a single predicted hold in seconds. It reads like this in the deploy log:

> → "1 online index on sales_order_item (2.1M rows), ~12s. Within budget."

If it can't read a table's row count, it falls back to a conservative default and lowers its own confidence — reported honestly as high / medium / low rather than hidden. An arbitrary PHP data patch in Setup/Patch/Data can't be statically costed at all, so its presence pulls confidence down and says so. This is a forecast, and it's labelled like one.

What it does with the number

Two things, and it's worth being precise about the boundary between them.

It tells you before you commit. The prediction is advisory. It's written to the deploy record and surfaced in the log, and if the forecast exceeds the hold budget you set for that environment, Orbit warns you up front — "predicted hold ~90s exceeds the 25s budget; parked visitors will degrade to a waiting page." Whether to proceed now, or reschedule a genuinely long migration into a planned window, stays a human decision. Orbit's job is to make sure that decision is informed, not to quietly make it for you.

It makes the short windows a non-event. For environments that opt in, a bounded, fail-open traffic hold runs across the migration. It's a small buffer in front of the app that, for the few seconds the schema change needs, parks transactional requests — checkout, cart, customer actions — holding the connection open and replaying it against the app the instant the window clears. (Clients that give up are never replayed, so there are no double orders.) Everything else gets a lightweight waiting page with a live countdown driven by that same forecast, and anonymous browsing is served from cache. It's deliberately fail-open: if the buffer can't start for any reason, the deploy simply proceeds rather than blocking. A twelve-second online index becomes twelve seconds of "one moment" for a handful of in-flight checkouts — not a burst of 503s, and not a maintenance banner for everyone.

The two systems are independent by design. The forecast informs you and drives the countdown; the hold protects in-flight requests. Neither one silently decides to skip or defer your migration.

What it does *not* do

It doesn't replay your migration on a copy of production, so it won't catch a pathological trigger or a data patch that does something expensive in PHP — those show up as lowered confidence, not a precise number. It doesn't turn a four-minute table rebuild into a fast one; physics still applies. What it removes is the *surprise*. A blocking MODIFY COLUMN on a huge table is still slow — but now you know that at 1:55pm, before you click deploy, instead of at 2:03pm with the phones ringing.

The whole game

The reason "deploy at 2pm on a Tuesday" sounds reckless is that the DB migration has always been an unbounded unknown. Turn that unknown into a number on a screen — before anyone clicks Deploy — and the fear goes with it. That's the entire point: not bravado, just refusing to find out the hard way.

See how Orbit deploys Magento with zero downtime →


Enjoyed this? Share it with your team.

Share