Files, Database, Queue & Assets
A Magento rollback is more than flipping a symlink. The database, queue messages, static content, and Elasticsearch indexes each follow their own rules. Anyone who rolls back only the files risks inconsistencies that are harder to fix than the original problem.
Table of Contents
- 1. The Four Layers of a Magento Rollback
- 2. File Rollback: Symlink, Generated Code, and Static Content
- 3. Database Rollback: Backup Strategy and Expand-Contract
- 4. Queue State During Rollback: What Happens to RabbitMQ Messages
- 5. Static Assets and the Elasticsearch Index After a Rollback
- 6. Decision Tree: Which Rollback Strategy Fits?
- 7. Mapping Rollback Strategies in the GitLab Pipeline
- 8. Rollback Scenarios Compared
- 9. Summary
- 10. Common Mistakes in Rollback Strategies
- 11. FAQ
1. The Four Layers of a Magento Rollback
A Magento system consists of four independent layers that need to be treated differently during a rollback. The first layer is the application files: PHP code, Hyva templates, Composer dependencies, compiled DI code, and static content. This layer can be rolled back via symlink in seconds. The second layer is the database: Magento configuration, product data, orders, and every table touched by setup:upgrade. This layer is not automatically rollback capable.
The third layer is queue state: messages in RabbitMQ or the MySQL-based Magento queue that were placed by consumers of the new release and that may not be processable by the old code. The fourth layer is external services: Elasticsearch indexes, Redis cache contents, and Varnish cache objects. This layer is often the least considered, yet it can lead to hard-to-diagnose errors after a rollback.
The key insight: a rollback that only addresses the file layer is sufficient for releases without database migrations or queue changes. For releases with database migrations, a pure file rollback is dangerous. The old code may encounter tables or columns it does not expect, or it may be missing columns it needs. The rollback strategy must therefore be planned explicitly before every release.
2. File Rollback: Symlink, Generated Code, and Static Content
The file rollback via symlink switch is the fastest and safest part of the rollback process. The release directory of the previous state is fully present, including the Composer vendor folder, compiled DI code, and static content. The symlink switch is atomic and takes under a second. A cache flush afterward is mandatory, because Redis may have cached objects from the faulty release.
The generated/ folder contains the dependency injection code compiled by Magento. This code is release specific and lives in the release directory, not in the shared area. After a rollback, Magento automatically uses the generated code of the previous release, no manual step required. The same applies to pub/static/: the static content of the previous release lives in the previous release directory and is served again once the symlink switches back.
#!/usr/bin/env bash
# scripts/rollback-files.sh: file-only rollback via symlink switch
# Safe for releases WITHOUT database migrations
set -euo pipefail
readonly APP_PATH="${DEPLOY_PATH:?}"
readonly ROLLBACK_TARGET="${1:-}"
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash -s <<SSH
set -euo pipefail
# Auto-detect previous release if no target specified
if [[ -z "${ROLLBACK_TARGET}" ]]; then
current="\$(basename \$(readlink -f ${APP_PATH}/current))"
TARGET="\$(ls -1d ${APP_PATH}/releases/*/ \
| sort -Vr | grep -v "\${current}/" | head -1 | xargs basename)"
else
TARGET="${ROLLBACK_TARGET}"
fi
echo "[ROLLBACK] Target release: \${TARGET}"
test -d "${APP_PATH}/releases/\${TARGET}" \
|| { echo "[FAIL] Release \${TARGET} not found"; exit 1; }
# Brief maintenance window for cache consistency
cd "${APP_PATH}/current"
bin/magento maintenance:enable --ip="${ALLOWED_IP:-127.0.0.1}" 2>/dev/null || true
# Atomic symlink switch: zero-downtime file rollback
ln -sfn "${APP_PATH}/releases/\${TARGET}" "${APP_PATH}/current"
echo "[OK] Switched to \${TARGET}"
# Full cache flush: mandatory after any rollback
cd "${APP_PATH}/current"
bin/magento cache:flush
echo "[OK] Cache flushed"
# Restart queue consumers to pick up new application path
supervisorctl restart magento-consumer:* 2>/dev/null \
|| systemctl restart magento-queue-consumer 2>/dev/null \
|| echo "[WARN] Could not restart queue consumers, manual restart required"
bin/magento maintenance:disable 2>/dev/null || true
echo "[OK] File rollback complete, now running \${TARGET}"
SSH
3. Database Rollback: Backup Strategy and Expand-Contract
The database rollback is the hardest part of a Magento rollback. In the standard implementation, Magento database migrations are not reversible, there is no setup:downgrade. Anyone who needs to roll back a release that contained database migrations has three options: restore a full database backup (time consuming, data loss since the backup), apply an expand-contract pattern, or manually revert the migration.
The expand-contract pattern is the most elegant solution: before the actual release, a compatible database change is deployed that both the old and the new code can handle. Only once the new code is running stably and no rollback is needed anymore is the "contract" step deployed, which removes the transitional constructs. This pattern requires discipline during development, but it makes database migrations fully rollback capable without restoring a backup.
# scripts/db-backup.sh: create a timestamped database backup before deployment
# Run this BEFORE any release that includes database migrations
#!/usr/bin/env bash
set -euo pipefail
readonly BACKUP_DIR="${DEPLOY_PATH}/shared/backups/db"
readonly TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
readonly BACKUP_FILE="${BACKUP_DIR}/${CI_COMMIT_TAG:-manual}-${TIMESTAMP}.sql.gz"
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash -s <<SSH
set -euo pipefail
mkdir -p "${BACKUP_DIR}"
echo "[BACKUP] Creating database backup: ${BACKUP_FILE}"
mysqldump \
--single-transaction \
--quick \
--lock-tables=false \
--routines \
--triggers \
"${MAGENTO_DB_NAME}" \
| gzip -6 > "${BACKUP_FILE}"
backup_size="\$(du -sh "${BACKUP_FILE}" | cut -f1)"
echo "[OK] Backup created: ${BACKUP_FILE} (\${backup_size})"
# Keep only last 10 DB backups
ls -1t "${BACKUP_DIR}"/*.sql.gz 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null || true
echo "[OK] Old backups cleaned up"
SSH
4. Queue State During Rollback: What Happens to RabbitMQ Messages
The queue is the most frequently forgotten rollback layer. If a new release was active for a short time and placed messages in RabbitMQ or the Magento MySQL queue during that window, those messages need to be processed after the rollback, by the old code. That is possible if the message format is compatible. It becomes a problem if the new release introduced new message types or changed payload structures.
The practical approach: stop the queue consumers before the rollback, switch the symlink, flush the cache, and restart the consumers with the old code. Messages that the old code cannot process end up in the dead letter queue or an error table. These need to be reviewed manually after the rollback. In production systems with high queue throughput, the decision may also be to purge the affected queue for a short time, accepting the risk that a few messages are lost, rather than letting inconsistent messages be processed.
5. Static Assets and the Elasticsearch Index After a Rollback
Static content lives in the release directory and switches automatically with the symlink. That means Nginx immediately serves the static content of the previous release after the rollback, with no manual intervention and no cache warmup. Browser-cached assets from the faulty release are a short-lived problem that resolves itself through cache-busting mechanisms (asset hashes in filenames).
The Elasticsearch index, on the other hand, is not release specific. It is not rolled back when the symlink switches. If the new release changed the index mapping or the indexing routines, the index may become incompatible with the old code after the rollback. In that case a full reindex must be run: bin/magento indexer:reindex catalogsearch_fulltext. For large catalogs this takes anywhere from several minutes to hours, an important factor when planning a rollback.
6. Decision Tree: Which Rollback Strategy Fits?
The right rollback strategy depends on what the release changed. This decision should be made deliberately and documented before every release, not only once a rollback becomes necessary. The following logic helps with the classification:
If the release contains no database migrations and no queue changes, a pure file rollback via symlink is sufficient. If the release contains compatible database migrations (additive, without removing columns), a file rollback with a DB backup as a safety net afterward is sufficient. If the release contains incompatible database migrations (removing columns, changing types, breaking changes), a full DB backup must be made before deployment, and the rollback consists of a file rollback plus a DB restore.
# .gitlab-ci.yml: database backup job before migration-heavy releases
db:backup:before-migrate:
stage: deploy
script:
- ./scripts/db-backup.sh
rules:
# Only run when the release is tagged as containing DB migrations
# Set CONTAINS_DB_MIGRATIONS=true in GitLab pipeline variables when needed
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/ && $CONTAINS_DB_MIGRATIONS == "true"
when: always
# This job must succeed before the deploy job runs
allow_failure: false
rollback:with-db-restore:
stage: rollback
script:
- |
# Full rollback: files + database restore
BACKUP_FILE="${DEPLOY_PATH}/shared/backups/db/${RESTORE_BACKUP_FILE}"
ssh "${DEPLOY_USER}@${DEPLOY_HOST}" bash -s <<'SSH'
set -euo pipefail
# Step 1: stop consumers
supervisorctl stop magento-consumer:* 2>/dev/null || true
# Step 2: file rollback
ln -sfn "${DEPLOY_PATH}/releases/${ROLLBACK_TARGET}" "${DEPLOY_PATH}/current"
# Step 3: database restore
zcat "${BACKUP_FILE}" | mysql "${MAGENTO_DB_NAME}"
echo "[OK] Database restored from ${BACKUP_FILE}"
# Step 4: cache and search reindex
cd "${DEPLOY_PATH}/current"
bin/magento cache:flush
bin/magento indexer:reindex
# Step 5: restart consumers
supervisorctl start magento-consumer:* 2>/dev/null || true
SSH
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
allow_failure: true
7. Mapping Rollback Strategies in the GitLab Pipeline
The GitLab pipeline maps the different rollback strategies as separate manual jobs. There are at least two jobs: one for the simple file rollback and one for the full rollback with DB restore. Which job gets triggered is decided by the deployment team during an incident, based on information documented in advance about whether the release contained database migrations.
This information must be captured in the deployment documentation or as a pipeline variable per release. A practical convention: releases with database migrations get a -migrate suffix or a pipeline variable CONTAINS_DB_MIGRATIONS=true. The rollback job for DB releases is then only visible when that variable is set. This prevents accidental DB rollbacks for releases that contain only code changes.
8. Rollback Scenarios Compared
Different release types call for different rollback strategies. The overview below shows which strategy fits which scenario and which side effects need to be considered.
| Release Type | Rollback Strategy | Duration | Data Loss Risk |
|---|---|---|---|
| Code/templates only | Symlink + cache flush | < 2 minutes | None |
| Additive DB migration | Symlink + cache + backup as a net | < 3 minutes | Low (new data in new column) |
| Breaking DB migration | Symlink + DB restore | 30-90 minutes | All data since backup lost |
| Queue format change | Symlink + purge queue + consumer restart | < 5 minutes | Pending queue messages lost |
| ES index change | Symlink + reindex | 10 min to several hours | None (index gets rebuilt) |
9. Summary
A complete rollback for Magento covers four layers: files, database, queue, and external services. The file rollback via symlink is the simplest and fastest part, and it is fully sufficient for releases without database migrations. For releases with additive migrations, a DB backup as a safety net makes sense. For releases with breaking database changes, a full DB restore is the only safe option, with the risk of data loss since the backup.
The most important practice is classifying the rollback strategy before every release: what does this release contain, and which rollback strategy does it therefore require? This information must be documented and visible to the whole team. GitLab pipeline variables offer a pragmatic way to make this classification machine readable and to activate the right rollback jobs.
Rollback Strategies for Magento: The Essentials at a Glance
Four layers
Files (symlink), database (backup/restore), queue (stop/restart), and external services (cache, ES index). Each layer has its own rollback requirements.
Decide before the release
Classify the rollback strategy before every release. Does it contain DB migrations? Queue changes? That answer determines which steps are needed during an incident.
DB backup is mandatory
Create a full DB backup before every release with database migrations. Without a backup there is no real rollback path for DB changes.
Queue and ES separately
Always restart queue consumers after a rollback. Fully reindex Elasticsearch when the index changed. Automate both steps as part of the rollback script.
10. Common Mistakes in Rollback Strategies
The most dangerous mistake is a file rollback for a release that contained breaking DB migrations. The result: the old code runs against the new database structure. Magento tries to access columns that were removed, or it ignores columns that should not exist yet. The errors are subtle and sometimes only appear under load, for example when a checkout process accesses a column in sales_order that no longer exists.
A second mistake is a rollback without a cache flush afterward. Redis holds cached objects that may be incompatible with the old code, especially if the faulty release introduced new cache tags or changed object structures. A cache flush after a rollback is always mandatory, even when it seems unnecessary for releases without caching changes. The cost of a cache flush (a brief performance dip) is minimal compared to the cost of inconsistent cache data.