WooCommerce Subscriptions v2.3 introduced a persistent caching system to improve performance by avoiding slow database queries.
Subscriptions’ persistent caching system uses a caching layer for:
- a subscription’s related orders
- all the subscriptions for a customer
This system speeds up many functions on the Subscriptions extension, including the amount of time it takes to process a renewal payment, load the My Account > Subscriptions page, and in some cases, load customer facing product and shop pages.
This guide is designed for developers looking for a technical understanding of the caching system. It explains how the caching system works, what data is cached, why caching is needed as well as answers to other common questions about the system.
Subscription Related Order Cache
A subscription can have a variety of related orders, including:
- renewal orders
- resubscribe orders
- switch orders
Each of these relationships is recorded in the database by adding a meta key to the post meta table for the given WooCommerce order, which is a custom post type. The value of the meta is the ID of the subscription to which the order relates.
For example, to link an order with ID 456 as a renewal to subscription 123, a row in post meta with the following data would exist:
post_id
:456
meta_key
:_subscription_renewal
meta_value
:123
The persistent cache of related orders is also stored in the post meta table. However, all related order IDs are stored in a single row of post meta against the subscription.
For example, to link renewal orders with IDs 456 and 789 to a subscription with ID 123, a row in post meta with the following data would exist:
post_id
:123
meta_key
:_subscription_renewal_order_ids_cache
meta_value
:a:2:{i:0;i:456;i:0;i:789;}
To find all the renewal orders for a give subscription, a simple get_post_meta()
call can now be used to query a single row in the meta table.
Subscription Related Order Cache Meta Keys
The meta keys use for each related order cache are:
- Switch Orders:
_subscription_switch_order_ids_cache
- Renewal Orders:
_subscription_renewal_order_ids_cache
- Resubscribe Orders:
_subscription_resubscribe_order_ids_cache
Customer’s Subscription Cache
A subscription’s data is a superset of a WooCommerce order’s data. As a result, the way a subscription is linked to a customer is the same as the way an order is linked to a customer.
With WooCommerce 3.5 or newer, the post_author
column in the main posts database table will be used. However, with WooCommerce versions prior to 3.5, orders are linked to a user using a post meta entry with the meta key _customer_user
.
For example, to link a subscription with ID 456 to a user with ID 123, a row in post meta with the following data would exist:
post_id
:456
meta_key
:_customer_user
meta_value
:123
The persistent cache of a customer’s subscriptions is not stored in the post meta table. Instead, it is stored in the user meta table. All subscription IDs are stored in a single row of user meta against the user’s ID.
For example, to link subscriptions with IDs 456 and 789 to a user with ID 123, a row in post meta with the following data would exist:
user_id
:123
meta_key
:_wcs_subscription_ids_cache
meta_value
:a:2:{i:0;i:456;i:0;i:789;}
To find all subscriptions for a given user, a simple get_user_meta()
call can now be used to query a single row of data.
Subscription Cache Management Tools
Tools are available to create and delete the subscription caches.
To use a subscription cache management tool:
- Go to WooCommerce > Status
- Click the Tools tab
- Scroll to the bottom of the tools to find the following subscription cache management tools:
- Generate Related Order Cache
- Delete Related Order Cache
- Generate Customer Subscription Cache
- Delete Customer Subscription Cache
- Click the button next to the tool you wish to run
Subscription Cache Generation via the Generator Tools
Cache generation will normally happen just-in-time, meaning the first time the data is required in normal operations after updating to Subscriptions version 2.3, the data will be pulled from the source and then cached for future use.
However, each cache can also be generated via the cache Generate Tools mentioned above.
Cache generation initiated via a Generator Tool is done in the background, over time, in small batches. This means it takes time to generate the cache. It could take minutes, hours, or even days to generate the cache for all subscriptions in your store. On large sites, with 100,000s of subscriptions, we’ve even seen it take over a week.
However, the cache debug tool is designed to generate the cache without interrupting normal site performance, no matter how large a site and how long it takes to generate the cache.
Cache Data Stores
To abstract the caching layer, and incorporate it in a way that can easily be extended, swapped or removed, the caching logic is implemented within data store classes for each type of data being cached.
- The
WCS_Related_Order_Store_Cached_CPT
class implements the caching layer for related orders. It extends and falls back to direct queries via theWCS_Related_Order_Store_CPT
class when no cached value is available. - The
WCS_Customer_Store_Cached_CPT
class implements the caching layer for a customer’s subscriptions. It extends and falls back to direct queries via theWCS_Customer_Store_CPT
class when no cached value is available.
Each of these classes are used as the data stores for accessing respective data, and each can be changed using available filters.
Using a Custom Related Order Data Store
The 'wcs_related_order_store_class'
filter can be used to change the data store used for accessing a subscription’s related orders. The value returned by this filter should be the name of the class to instantiate.
The class returned by that filter will be the class used via calls to WCS_Related_Order_Store::instance()
for accessing a subscription’s related orders.
For example, to remove the caching layer on related orders from your site, you could use the following line of code:
add_filter( 'wcs_related_order_store_class', 'WCS_Related_Order_Store_CPT' );
Using a Custom Customer Subscription Data Store
The 'wcs_customer_store_class'
filter can be used to change the data store used for accessing a customer’s subscriptions. The value returned by this filter should be the name of the class to instantiate.
The class returned by that filter will be used via WCS_Customer_Store::instance()
for accessing a user’s subscriptions.
For example, to remove the caching layer from your site, you could use the following line of code:
add_filter( 'wcs_customer_store_class', 'WCS_Customer_Store_CPT' );
FAQs
Why does subscription data need to be cached?
WooCommerce Subscriptions builds on WordPress custom post types to store subscription data. As a result, much of its data is stored in the same database tables as other content types, like blog posts, website pages, WooCommerce orders, and data from other plugins.
It is not uncommon to see subscription meta data in tables with hundreds of thousands or millions of rows of data. This makes particular queries on that data slow. Especially if it comes from a meta query like those generated by get_posts()
, which creates a JOIN
SQL statement.
For example, a renewal order is linked to a subscription via a field of meta data in this table. To find all the renewal orders for a subscription requires running a query against that data.
To address this, many items of known subscription data will be migrated to separate tables. This is a process that will be in development and testing for a substantial amount of time before being released publicly in the Subscriptions core plugin. In the meantime, Subscriptions can use a persistent cache to prevent slow queries needing to run.
How does the subscription cache work?
Normally, to find a piece of data, like the IDs of orders related to a subscription, a database query is run against the source of that data.
The persistent cache on the other hand will store the result of that query when it is first run, and then use that as the source of information in future. The cache will be updated whenever a cache invalidating even occurs, like a relevant piece of data being created or deleted.
For example, to link a subscription and a renewal order, a _subscription_renewal
meta key is added to the post meta table for a WooCommerce order with the meta value set to the subscription’s ID. To find these relationships, rows containing that meta data can be queried.
However, with the persistent cache system, each time a renewal order is created, and that relationship is stored in the database, the renewal order’s ID will be added to the existing cache. Similarly, when a renewal order is deleted, its ID will be removed from the cache. This means that cache can be used to find renewal orders in a more performant way than querying _subscription_renewal
meta key rows.
After updating to Subscriptions 2.3, known slow queries will run just once. From then on, the persistent cache will be used.
How long does the subscription cache last?
In some systems, like WordPress’ transient system, cached data will expired after a pre-defined period of time.
Data in Subscription’s persistent cache does not expire. The cached data will only be cleared when manually deleted, for example by using a cache deletion debug tool.
It is possible to maintain the cache indefinitely by keeping it up-to-date whenever a cache invalidating event occurs, like a new subscription or order being created.
Why isn’t the Parent Order in the related order cache?
Parent Orders are linked to a subscription using the post_parent
column in the posts table. This makes it far more performant to run queries to find both:
- all subscriptions with a given parent order
- the parent order for a given subscription
As a result, it is not necessary cache the parent orders for a subscription.