Search by

byte8 / module-migration-forecast

softcom

Forecast what setup:upgrade will do to your Magento 2 / Mage-OS database before you run it — pending DDL classified against MySQL's online-DDL matrix and costed by live row counts — and optionally gate the upgrade on it.

Package info

github.com/byte8io/magento-migration-forecast

Type:magento2-module

pkg:composer/byte8/module-migration-forecast

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v0.3.2 2026-10-12 15:44 UTC

README

CI Packagist Licence: MIT

Know what setup:upgrade will do to your database before you run it.

The module adds two commands:

Command What it does
bin/magento setup:db:forecast Lists every pending schema change, the MySQL algorithm each one will use, whether it blocks writes, and roughly how long it will take on the tables you actually have. Read-only.
bin/magento setup:guarded-upgrade Shows the same forecast, then runs the real setup:upgrade — only if the forecast is within the limits you set, or you confirm at the prompt.

Example output (illustrative figures):

$ bin/magento setup:db:forecast
+------------------+----------------+-------------------------------+-----------+----------+-----------+------+
| Table            | Change         | Name                          | Algorithm | Impact   | Rows      | Est. |
+------------------+----------------+-------------------------------+-----------+----------+-----------+------+
| sales_order_item | modify column  | sku                           | COPY      | blocking | 2,100,000 | ~42s |
| sales_order_item | add column     | fixture_note                  | INSTANT   | instant  | 2,100,000 | -    |
| sales_order_item | add index      | SALES_ORDER_ITEM_FIXTURE_NOTE | INPLACE   | online   | 2,100,000 | ~21s |
| sales_order_grid | add column     | fixture_tag                   | INPLACE   | blocking | 1,400,000 | ~14s |
+------------------+----------------+-------------------------------+-----------+----------+-----------+------+
  sales_order_item sku: Changed: length.
  sales_order_grid fixture_tag: INSTANT is unavailable on tables with a FULLTEXT index. ...

Pending data patches (not costed): 1
  - Acme\Widget\Setup\Patch\Data\SeedWidgets

DDL forecast: 2 blocking, 1 instant, 1 online; ~81s, ~78s write-blocking (confidence: high).
Plus 1 data patch not costed.

setup:db:forecast is read-only: it never alters the database, never writes a file, and never fails a build unless you give it a limit. Neither command replaces, wraps or alters setup:upgrade itself.

Why

setup:db:status tells you that an upgrade is needed. --dry-run tells you which SQL will run. Neither tells you whether that SQL finishes in a blink or holds a lock on sales_order_item for four minutes — which is the only thing you need to know to decide if a release can go out at 2 PM.

Install

composer require byte8/module-migration-forecast
bin/magento module:enable Byte8_MigrationForecast
bin/magento setup:upgrade

The only dependency is the framework itself. There is nothing to configure, no licence key, no telemetry and no network call of any kind.

Compatibility

Platform Versions How it is verified
Mage-OS 2.x, 3.x Clean install + end-to-end forecast in CI on every commit
Magento Open Source 2.4.9 Clean install + end-to-end forecast in CI on every commit
Magento Open Source 2.4.4 – 2.4.8 Same framework line (magento/framework ^103.0); not run in CI
Adobe Commerce 2.4.4 – 2.4.9 Same framework line; not run in CI
PHP 8.1 – 8.5 Unit tests in CI on every version
MySQL 8.4 CI database. The classification follows MySQL 8's online-DDL rules, so 8.0 behaves the same except where noted under Limits
MariaDB 10.6 Runs (developed against it); the matrix is MySQL's, so treat the class as an approximation

On Mage-OS the requirement on magento/framework is satisfied by mage-os/framework, so composer require works unchanged — no patches, no alternative package.

Commands

setup:db:forecast

Run it on the new code, before setup:upgrade:

bin/magento setup:db:forecast
Option What it does
--format=json Machine-readable output for CI and deploy tooling (see JSON output). Default text.
--max-seconds=N Exit 1 when the predicted DDL duration exceeds N seconds.
--max-blocking-seconds=N Exit 1 when the write-blocking part exceeds N seconds.
--fail-on-uncosted Exit 1 when any patch or setup script is pending, because its cost is unknown.
--inplace-rate=N Rows per second your host manages for in-place DDL. Default 100000.
--copy-rate=N Rows per second your host manages for table-copy DDL. Default 50000.
Exit code Meaning
0 Forecast built; no limit was given, or none was exceeded.
1 A limit was exceeded, the forecast could not be built, or --format was invalid.

Gate a deploy with it:

# Refuse to deploy in business hours if writes would be blocked for more than 10 seconds.
bin/magento setup:db:forecast --max-blocking-seconds=10 || exit 1
bin/magento setup:upgrade --keep-generated

setup:guarded-upgrade

Forecast and upgrade in one step. It prints the forecast and then runs the real setup:upgrade, in a separate process, exactly as if you had typed it.

bin/magento setup:guarded-upgrade --keep-generated

At a terminal it asks Run setup:upgrade now? [y/N] before proceeding. It does not ask when nothing is pending.

Unattended (CI, deploy scripts, anything without a terminal) it never prompts. The limits you pass decide:

bin/magento setup:guarded-upgrade --keep-generated --max-blocking-seconds=10
Option What it does
--max-seconds=N Do not run setup:upgrade when the predicted DDL duration exceeds N seconds.
--max-blocking-seconds=N Do not run setup:upgrade when the write-blocking part exceeds N seconds.
--fail-on-uncosted Do not run setup:upgrade when any patch or setup script is pending.
--inplace-rate=N, --copy-rate=N Host throughput, as for setup:db:forecast.
-y, --yes Do not ask for confirmation at a terminal.
--keep-generated Passed through to setup:upgrade.
Exit code Meaning
0 The forecast was within limits and setup:upgrade succeeded.
2 setup:upgrade was not run: a limit was exceeded, the forecast could not be built, or you answered no. Nothing was changed.
anything else setup:upgrade ran and failed; this is its own exit code.

Without any limit, an unattended run always proceeds — set at least one for it to guard anything. Of setup:upgrade's own options only --keep-generated is passed through; for the others, run the two commands separately.

GitHub Actions

- name: Upgrade the database, unless it would block writes for too long
  run: |
    status=0
    bin/magento setup:guarded-upgrade --keep-generated --max-blocking-seconds=10 || status=$?
    if [ "$status" -eq 2 ]; then
      echo "::error::Migration forecast is over the limit. Schedule this release for a maintenance window."
    fi
    exit "$status"

Run it where setup:upgrade would run — on the server, or over SSH from the workflow — because the forecast needs the live database.

JSON output

bin/magento setup:db:forecast --format=json prints one object:

Field Type Meaning
up_to_date bool Nothing is pending: no DDL, no patches, no setup scripts.
predicted_secs int Bootstrap overhead plus all DDL. Patches and scripts are not included.
blocking_secs int Part of the DDL during which some table rejects writes.
confidence string high, medium or low — how far the DDL figure can be trusted (see Confidence).
uncosted int Number of pending patches and setup scripts, which are listed but not costed.
summary string The one-line summary shown at the end of the text output.
schema_changes array One entry per pending change: table, kind, name, algorithm (metadata, instant, inplace, copy), impact (metadata, instant, online, blocking), rebuilds_table, blocks_writes, rows (null when unknown), est_secs, note.
tables object Per affected table: rows, bytes, est_secs, blocks_writes.
data_patches, schema_patches array Class names of patches not yet applied.
legacy_scripts array Modules whose Install/Upgrade scripts will run.
warnings array Anything that makes the forecast incomplete, such as a module not yet in app/etc/config.php.

kind is one of new_table, drop_table, modify_table, recreate_table, add_column, drop_column, modify_column, add_index, drop_index, add_constraint, drop_constraint.

How it works

  1. The diff is Magento's own. Both commands ask the declarative-schema engine for the difference between the merged db_schema.xml files and the live database — the exact operation list setup:upgrade executes, db_schema_whitelist.json rules included.

  2. Each operation is classified against MySQL 8 / InnoDB's online-DDL matrix:

    Change Algorithm Rebuilds table Writes during it
    New / drop table metadata no allowed
    Add column INSTANT no allowed
    Add auto-increment column INPLACE yes blocked
    Drop column INPLACE yes allowed
    Modify column: comment or default metadata no allowed
    Modify column: extend VARCHAR within the same length-byte class INPLACE no allowed
    Modify column: nullability INPLACE yes allowed
    Modify column: anything else (type, shrink, unsigned…) COPY yes blocked
    Add index / unique / foreign key INPLACE no allowed
    Add FULLTEXT index INPLACE no blocked
    Add primary key INPLACE yes allowed
    Drop index / unique / foreign key metadata no allowed
    Drop primary key COPY yes blocked
    Table engine / charset / resource COPY yes blocked
    Any rebuild of a table that has a FULLTEXT index — yes blocked
  3. Row-scaled operations are costed from information_schema row counts and a throughput per class. Magento folds all changes to one table into a single ALTER, so a table is rebuilt once however many changes force it.

  4. What cannot be costed is listed, not guessed. Pending data patches, schema patches and legacy Install/Upgrade scripts are arbitrary PHP. They are named beside the DDL forecast and never folded into it: the seconds you see are DDL plus bootstrap, nothing else.

Confidence

Confidence describes the DDL figure only. A pending patch does not lower it — a release with a trivial data patch still gets a trustworthy DDL forecast, and the patch is reported on its own line for you to judge.

Level Meaning
high Every affected table's size was read.
medium Some table sizes could not be read; class defaults were used.
low A module not yet in app/etc/config.php ships a db_schema.xml the diff cannot see, so DDL is missing from the figure.

Use --fail-on-uncosted if you would rather stop a pipeline whenever a patch is pending than review it by eye.

Limits

  • It is an estimate. Row counts for InnoDB are approximate and the default throughputs are conservative guesses for mid-range hosting. Time one real ALTER on your host and pass --inplace-rate / --copy-rate.
  • When in doubt it assumes the slower class. A dropped column is costed as a rebuild although MySQL 8.0.29+ can often do it instantly; a foreign key is costed as an index build although one may already exist.
  • Metadata-lock waits are not modelled. Even an instant ALTER queues behind a long-running transaction on the same table.
  • Modules missing from app/etc/config.php are enabled by setup:upgrade but are invisible to the diff until then. The command warns about each one.
  • MariaDB follows the same matrix closely enough for the classification to hold, but it was written against MySQL 8's documentation.
  • Split databases: tables on a non-default connection are sized through their own connection, but this path is not covered by CI.

Open source and Mage-OS

This module is open source under the MIT licence and works on Mage-OS. It is developed in the open at github.com/byte8io/magento-migration-forecast and distributed through Packagist, which also lists it in the Mage-OS Extension Directory.

Its maintainer is a member of the Mage-OS Association. The module is an independent Byte8 project; it is not an official Mage-OS package.

Contributing

Issues and pull requests are welcome — above all, real measurements. If the forecast was wrong on your store, that is the most useful thing you can tell us.

The unit tests are framework-free and run from a plain clone with phpunit.

Credits

Built and maintained by Byte8. Extracted from the migration-intelligence engine in Orbit, Byte8's zero-downtime deployment service for Magento, where the same forecast sizes the traffic-hold window for each deploy.

Licence

MIT — see LICENSE.txt.