Subscription Caches

WooCommerce Subscriptions has a persistent caching system to improve performance by avoiding slow database queries. This system speeds up many functions on the WooCommerce 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.

Subscriptions’ persistent caching system uses a caching layer for:

  • a subscription’s related orders
  • all the subscriptions for a customer

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.

↑ Back to top

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.

↑ Back to top

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

↑ Back to top

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, where the post_author column in the main posts database table is used.

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

↑ Back to top

To use the subscription cache management tool to create and delete the subscription caches:

  1. Go to WooCommerce > Status
  2. Click the Tools tab
  3. Scroll down until you find the following subscription cache management tools:
    1. Generate Related Order Cache
    2. Delete Related Order Cache
    3. Generate Customer Subscription Cache
    4. Delete Customer Subscription Cache
  4. Click the button next to the tool you wish to run
There are 4 WooCommerce Subscriptions cache management tools available under the Tools tab of WooCommerce Status.
Subscription Cache Management Tools

Subscription Cache Generation via the Generator Tools

↑ Back to top

Cache generation will normally happen just-in-time, meaning the first time it’s required, 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 one of the caching tools 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.

That’s because the cache tools are 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

↑ Back to top

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 the WCS_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 the WCS_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.

↑ Back to top

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

↑ Back to top

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

↑ Back to top

Why does subscription data need to be cached?

↑ Back to top

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?

↑ Back to top

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?

↑ Back to top

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.

↑ Back to top

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.

Use of your personal data
We and our partners process your personal data (such as browsing data, IP Addresses, cookie information, and other unique identifiers) based on your consent and/or our legitimate interest to optimize our website, marketing activities, and your user experience.