Controlling Database Growth in Magento Stores
AI generated
InnoDB
SQL
MySQL · Magento · Maintenance · Monitoring
Controlling Database Growth in Magento Stores
from abandoned carts to a growth control routine

Magento databases rarely grow because of product data, they grow because of logs, abandoned carts, and URL rewrites that nobody actively deletes. Once you know the usual suspects, measure table sizes regularly, and establish a repeatable cleanup routine, you prevent backups, deployments, and reindex runs from suffering under database growth.

16 min read Table size · monitoring · cleanup · capacity planning Magento 2.4.x · MySQL 8 / MariaDB 10.6

1. Why Magento databases grow unbounded

Database growth in a Magento store rarely follows the product catalog. A store with 10,000 products can still have a 40 gigabyte database, because transactional and logging tables keep accumulating new rows without any active intervention. Every cart, every visitor session, every generated URL, and every completed cron job leaves a trace in the database, and Magento only partially removes those traces automatically by default.

The problem with database growth is rarely technical, it is an operational oversight: the built-in cleanup mechanisms exist but are often not enabled or misconfigured. A store that has been in operation for five years and never had a cleanup routine accumulates millions of rows in tables that are irrelevant to actual business operations. Database growth does not just affect storage space, it slows down backups, reindex runs, and even simple admin queries, because MySQL has to search larger indexes on every query.

The first step against uncontrolled database growth is identifying the tables that structurally tend to grow, and distinguishing them from tables that grow proportionally to real business volume. Only the former are candidates for active cleanup, the latter (such as sales_order) should be preserved for legal and business reasons.

2. The usual suspects: logs, quotes, URL rewrites

Four table groups are responsible for most of the unplanned database growth in Magento stores. First, log and report tables like report_event, report_viewed_product_index, and customer_visitor, which write new rows on every page view or customer interaction, without a standard cleanup kicking in automatically unless explicitly enabled. Second, the quote tables (quote, quote_item, quote_address), which create rows for every cart, including every guest cart that never converts, and are never automatically deleted by default.

Third, url_rewrite, which needs a separate entry for every combination of product, category, and store view, and grows multiplicatively when SEO features like category based product URLs are enabled. Fourth, cron and session related tables like cron_schedule and, in older setups, database based session storage, which can produce millions of rows in a short time under high traffic. All four groups share the trait that their database growth is proportional to traffic, not to the product catalog, which is why a growing store with a stable assortment still shows accelerated growth in exactly these tables.


-- Quick overview: the usual suspects for uncontrolled database growth
SELECT table_name,
       table_rows,
       ROUND((data_length + index_length) / 1024 / 1024, 1) AS size_mb
FROM information_schema.TABLES
WHERE table_schema = 'magento'
  AND table_name IN (
    'report_event', 'report_viewed_product_index', 'customer_visitor',
    'quote', 'quote_item', 'url_rewrite', 'cron_schedule',
    'catalog_compare_item', 'sales_order_status_history'
  )
ORDER BY size_mb DESC;

3. quote and quote_item: abandoned carts

Every visit to an online store where a product gets added to the cart creates a row in quote with related rows in quote_item and quote_address. If the visitor abandons the purchase process, which is the vast majority of cases for guests, that cart remains as an orphaned row in the database. Magento does not delete expired quotes automatically by default, unless the quote cleanup cron job is explicitly configured and active.

On a store with high traffic and a low conversion rate, which is typical for most e-commerce stores, the quote table can reach several million rows within a few months, while only a fraction of them ever result in an order. This database growth is particularly insidious because quote_item_option and quote_address create additional rows for every single quote, so a cart with five items and configuration options can easily generate twenty or more rows across several tables.


-- Find abandoned quotes older than 90 days that never converted to an order
SELECT COUNT(*) AS abandoned_quotes
FROM quote
WHERE is_active = 0
  AND updated_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
  AND entity_id NOT IN (SELECT quote_id FROM sales_order);

-- Estimate the row multiplier: quote_item + quote_address per quote
SELECT
  (SELECT COUNT(*) FROM quote) AS quotes,
  (SELECT COUNT(*) FROM quote_item) AS quote_items,
  (SELECT COUNT(*) FROM quote_address) AS quote_addresses;

4. url_rewrite growth from category and product combinations

The url_rewrite table does not grow linearly with the product count, it grows multiplicatively with the number of categories a product is visible in, combined with the number of store views. If "Use Categories Path for Product URLs" is enabled, Magento generates a separate URL for every product in every assigned category, which leads to significantly disproportionate database growth in url_rewrite for products with multiple category assignments.

On top of that, old, no longer active redirects (redirect_type not equal to 0) remain in the table on every URL change of a product or category, in order to preserve SEO value. Over the years, this accumulates thousands of redirect entries, many of which are never accessed again. A store with 50,000 products, three store views, and an average of two category assignments per product can accumulate 300,000 base entries plus an unknown share of historical redirects in url_rewrite from this alone.


-- Breakdown of url_rewrite by entity type and store, to see where growth concentrates
SELECT entity_type, store_id, COUNT(*) AS rows_count
FROM url_rewrite
GROUP BY entity_type, store_id
ORDER BY rows_count DESC;

-- Count stale redirects that are no longer referenced by any current entity URL
SELECT COUNT(*) AS old_redirects
FROM url_rewrite
WHERE redirect_type <> 0
  AND is_autogenerated = 0;

5. Monitoring routine with information_schema

Without regular measurement, database growth stays invisible until it becomes an acute problem, usually when a backup fails or a deployment window no longer suffices. The information_schema.TABLES table provides data_length, index_length, and an estimated row count via table_rows for every table, which is entirely sufficient for monthly monitoring. It matters to look not just at absolute size but at the growth rate between two measurement points, because a large but stable table is less critical than a small table with exponential growth.

A simple but effective monitoring routine runs weekly via cron, writes the ten largest tables into a dedicated log table, and alerts if a table grows beyond a defined threshold within a week. This identifies outliers, such as a misconfigured import that unintentionally writes millions of rows into catalog_product_entity_varchar, far earlier than a purely reactive look at the next backup problem. For total database size, a single sum query over data_length and index_length is enough and works well as a metric for capacity planning.

6. Logging the growth trend over time

A single snapshot of table sizes shows only the status quo, not the dynamics of database growth. A better approach is a dedicated, small log table into which a weekly cron job writes the current sizes of the most important tables. Over a period of several months, this produces a real growth curve that shows whether growth is linear, seasonal, or exponential, and which table contributes the largest share of total growth.

This historical view is especially valuable when evaluating whether a cleanup measure actually works. After enabling a quote cleanup cron job, the growth rate of quote should visibly decrease, which can only be demonstrated with historical measurements. Without this logging, any statement about the effectiveness of a cleanup measure remains pure assumption.


-- Own tracking table for growth history, populated weekly via cron
CREATE TABLE db_growth_log (
  log_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  table_name VARCHAR(64) NOT NULL,
  table_rows BIGINT UNSIGNED NOT NULL,
  size_mb DECIMAL(12,2) NOT NULL,
  measured_at DATE NOT NULL,
  PRIMARY KEY (log_id),
  KEY IDX_TABLE_DATE (table_name, measured_at)
) ENGINE=InnoDB;

-- Weekly insert job (called from a cron wrapper)
INSERT INTO db_growth_log (table_name, table_rows, size_mb, measured_at)
SELECT table_name, table_rows,
       ROUND((data_length + index_length) / 1024 / 1024, 2), CURDATE()
FROM information_schema.TABLES
WHERE table_schema = 'magento';

7. Automated cleanup routine via cron

Manual cleanup does not scale, which is why every insight about database growth needs to be turned into an automated routine. A practical structure is a dedicated maintenance script that runs regularly via crontab.xml or system cron and works in clearly separated steps: delete expired quotes, clean up old report events, remove orphaned URL rewrites, and update the growth log table.

It matters to run every delete operation in manageable batches (for example 5,000 rows per pass with a short pause in between), instead of running a single DELETE statement across millions of rows, which creates long locks and noticeably slows down the production database during execution. A maintenance window outside peak hours further reduces the risk of the cleanup routine colliding with regular store operations.


#!/usr/bin/env bash
# db-growth-cleanup.sh: batched cleanup routine, called from cron
set -euo pipefail

MAGENTO_ROOT="/var/www/html"
BATCH_SIZE=5000

cd "$MAGENTO_ROOT"

# Remove abandoned quotes older than 90 days that never became an order
bin/magento maintenance:allow-ips 127.0.0.1 >/dev/null 2>&1 || true

mysql magento -e "
  DELETE FROM quote
  WHERE is_active = 0
    AND updated_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
    AND entity_id NOT IN (SELECT quote_id FROM sales_order)
  LIMIT ${BATCH_SIZE};
"

echo "[OK] cleanup batch of up to ${BATCH_SIZE} rows completed"

8. Partitioning and archiving for large stores

On very large stores with millions of orders, simply deleting old data is often not an option, because legal retention obligations forbid deleting order data. This is where controlling database growth without losing data helps: date based partitioning for tables like sales_order_status_history or report_event reduces the effective working set MySQL has to scan on queries, because the optimizer can skip old partitions entirely on time bounded queries.

For tables without legal retention obligations but with historical value for reporting, archiving into a separate database or a separate data warehouse system makes more sense than plain deletion. A monthly job moves rows older than a defined threshold from the production table into an archive table, keeping the production database small and performant while historical reporting remains possible.

9. Capacity planning: DB size, backup, and performance

Uncontrolled database growth has a direct relationship with backup times: a mysqldump over a 60 gigabyte database takes considerably longer than over a cleaned up 15 gigabyte database, and the same difference applies to restore time in an emergency. Growing backup windows eventually collide with maintenance windows or deployment schedules, which turns capacity planning into an operational, not just a technical, concern.

The InnoDB buffer pool sizing also depends directly on database size: once the active working set no longer fits into the buffer pool, disk I/O load increases noticeably, which shows up as slower admin queries and reindex runs. Regular capacity planning that matches database growth against available RAM, backup windows, and storage costs prevents these issues from becoming visible only during an acute outage.

Comparison: table types by growth behavior

Table group Growth driver Cleanup strategy Retention obligation
quote, quote_item Abandoned carts Batched DELETE after 90 days None
report_event Page views, interactions Built-in log cleaning None
url_rewrite Category x product x store Check and remove old redirects SEO consideration needed
sales_order Real orders Do not delete, archive if needed Legally required
cron_schedule Completed cron jobs history_cleanup_every None

Mironsoft

Magento database maintenance and capacity planning

Database growth getting out of control?

We build monitoring for your table sizes, set up automated cleanup routines, and plan capacity so backups and deployments reliably stay within their windows.

Growth audit

Identify the largest tables and growth drivers

Cleanup automation

Batched cleanup cron jobs without lock risk

Capacity planning

Plan backup windows, buffer pool, and storage ahead of time

10. Summary

Database growth in Magento stores rarely follows the product catalog, it is driven by transactional and logging tables like quote, report_event, and url_rewrite. Abandoned carts, unbounded event logging, and the multiplicative nature of URL rewrites with category paths enabled are the most common reasons databases end up considerably larger than actual business operations would require.

An effective countermeasure against uncontrolled database growth consists of three building blocks: regular monitoring via information_schema.TABLES with historical logging, batched cleanup routines via cron instead of one time manual interventions, and capacity planning that proactively matches backup times and buffer pool sizing to actual database size. Where legal retention obligations forbid deletion, partitioning and archiving replace plain deletion as the strategy against further growth.

Database Growth in Magento Stores: The Essentials at a Glance

Most common drivers

quote, report_event, customer_visitor, and url_rewrite grow proportionally to traffic, not to the catalog.

Monitoring

Write information_schema.TABLES weekly into a dedicated log table to spot trends.

Cleanup

Batched DELETE operations via cron, never a single deletion across millions of rows.

Legal limits

Archive sales_order and related tables instead of deleting them when retention rules apply.

11. FAQ: Database Growth in Magento Stores

1Why does the DB grow with a stable catalog?
Transactional tables like quote, report_event, and url_rewrite grow proportionally to traffic, not to the product count.
2Which tables to check first?
quote, quote_item, report_event, customer_visitor, url_rewrite, and cron_schedule.
3Allowed to delete abandoned carts?
Yes, if inactive, old enough, and not linked to an order. Always delete in batches via LIMIT.
4Why does url_rewrite grow so fast?
Multiplicative with category assignments and store views, especially with category path enabled.
5How do I monitor table sizes?
Regular query against information_schema.TABLES, logged into a dedicated log table.
6How often should cleanup run?
Daily outside peak hours, always batched instead of one large DELETE statement.
7Delete sales_order for space reasons?
Usually not, due to legal retention obligations. Archiving instead of deletion is the right strategy.
8Relationship with backup times?
mysqldump grows nearly linearly with database size, uncontrolled growth extends backup windows.
9Does partitioning help?
Does not reduce total size, but reduces effective query load on very large historical tables.
10Relationship with buffer pool?
If the working set no longer fits, disk I/O rises and admin queries become noticeably slower.