Performance tips

Optimize performance with Composable Commerce.

To optimize performance with Composable Commerce, it's important to understand how to effectively make use of its scalability and response time capabilities. This guide provides practical advice for solution implementers, focusing on code optimization strategies to keep response times low.

The performance tips outlined here vary in impact based on different use cases, so they are not ranked by priority. Keep in mind that the tips below are optimizations, and while it's beneficial to follow them, they should not compromise the desired user experience.

API request planning

You should plan your API requests according to the following guidelines (ordered by importance):

  1. Avoid sequential API requests wherever you can.
  2. Use Reference Expansion instead of parallel or sequential requests (nowadays, network roundtrip time is more crucial than the number of bytes transferred).
  3. Do parallel requests. All the Composable Commerce SDKs support these, as do most good generic HTTP clients. You will need to plan ahead concerning which identifiers to query because you will need them in the client request or session data to perform parallel requests.
  4. Avoid Reference Expansion if you do not necessarily need it (as it takes time to perform the expansion internally). It's better to not include expand parameters in your request defaults but to add them on a case-by-case basis.

In any case, if you have use cases where the querying and reference expansion options force you into sequential requests, please post a suggestion on the Support Portal so that we can polish the API to avoid such cases.

JSON document size

While the maximum size of JSON documents persisted through any API endpoint is 16 megabytes, we recommend limiting your average documents to no more than 100 KB.

Your largest documents (for example, a Product with many Product Variants, or an Order with many Line Items) should ideally not exceed 2 MB.

Querying and showing data

  • For a user-facing application, always go through the Product Projections endpoint. Only use the Products endpoint to administrate, manage, and import/export products. The API responses will be only half as large because the projections only contain the current or the staged version of the data (depending on how you set the staged parameter), whereas the Products endpoint always returns both the full current and the staged versions.
  • Use the Product Projection Search endpoint wherever you can (even for fetching single products by their id). Internally, the Product Projection Search endpoint is based on a different technology stack that delivers faster response times due to its read-only nature.
  • When querying objects:
    • Try to set a sensible limit to the number of objects returned where you can. This depends on the cost of the query and on the size of the objects retrieved. As a rule of thumb, start with a small value and increase it in small steps until performance starts to drop off.
    • Deactivate the field total when you are not interested in the total number of results matching the query. Refer to PagedQueryResult for more details.
    • Avoid expensive query predicate patterns and understand the indexing behavior. This and specific tips are documented in the Query Predicate Documentation
  • When doing bulk downloads, we recommend using the "process" implementation of our JavaScript SDK "base" service, which implements best practice defaults and patterns. Feel free to implement a similar pattern in your preferred stack.

Response size

Fetching a large amount of data from Product Projections and Product Projection Search results in longer response times. We recommend targeting the following response sizes to ensure the best performance:

  • 5 MB for Products
  • 1 MB for search results

We are aware that reaching these targets can sometimes be difficult. Here are some tips that may help to optimize your queries:

  • To reduce the number of products/results returned, and therefore, the response size, try using lower limit values in the query.
  • For multi-language projects, use the localeProjection parameter to limit the number of locales present in the Product endpoints and search responses.
  • Consider setting up Stores and use them in Carts and Orders to reduce their size. The storeProjection parameter can also be included when querying Product Projections or Product Projection Search to reduce the number of locales and prices in the response.

Indexing

When you create, update, or delete a product, it must be indexed before the change is available in the Product Projection Search endpoint, which happens after a delay.

This delay is not the same for all updates. Products with many variants, locales, prices, or searchable attributes need more time to get indexed than others, and thus, have a longer delay.

Additionally, when changing the configuration of your Project that affects products (for example, adding locales, currencies, countries, customer groups, or adding attributes to a product type), it will recreate the index for all products. Any following changes are put on hold until the process is completed and then applied later.

In the following list, you can find some thresholds below which a project should have a good indexing performance. Exceeding any of these values does not render the search unusable, but it may require the solution implementation to carefully design the frequency and timing of configuration changes that affect products. In that regard, we recommend you apply all the configuration changes together before continuing with updating your products. This way, you optimize the effort for recreating the index of your project.

  • 1 000 000 products per project
  • 3 000 000 variants per project
  • 15 locales per project
  • 100 channels per project
  • 20 suggestion keywords per product
  • 25 searchable attributes across all product types (depending on the types of attributes and attribute values, for example, localizable texts have a bigger impact on indexing than boolean values)

These values represent recommendations that you keep under control and not limits that commercetools Composable Commerce imposes.

Filtering, faceting, and sorting

  • Consider reducing the number of faceting attributes on the search request if you want to decrease the response time.
  • When possible, use filter.query filters to reduce the number of items to be faceted and/or sorted, as larger collections cause faceting and sorting to take proportionally longer.
  • Use the filter by category subtree functionality whenever you want to retrieve products assigned to a specified category plus its subcategories.

Matching Product Variants

For each search request, you can enable the marked-matching-variants functionality that leads to marking those variants within the returned products which are matching the search criteria.

As matching variants can be complex to calculate, this feature is deactivated by default.

Since this feature may have an impact on the search performance, we let you control for which requests you want to enable it and when you don't need it.

Modifying data

Multiple update actions

  • It is possible to add a large number of update actions into one update request as long as they change the same Object (for example Update Product).
  • If you want to publish the updated product right away, you can include the respective publish update action into the list of update actions. It must be the last action on the list though.

Bear in mind that all update actions in one update request are treated as one transaction. That means that the whole list of update actions will fail if one update action in the list fails. Thus, please think carefully about which update actions you should include in the list of actions and which actions are best spread across different update requests.

Caching parts of the model

Composable Commerce has been built for stateless frontend implementations, it is meant to pass every user request without caching in the frontend implementation. We recommend doing this until you encounter serious and specific performance problems. You may waste a large amount of time debugging cache staleness and the invalidation efforts will likely not pay off.

Nevertheless, many UI scenarios aren't doable without caching some data. This refers for example to the page header, especially category-driven menu trees and metadata like product types and channels.

  • Although you could query the whole category tree every time, it makes sense to cache the menu as a final menu object across users. We suggest auto-refreshing it frequently (for example, every five seconds).
  • Concerning User and Cart data, it depends on how much information is directly visible in the UI's initial state. In most cases, the information can be stored in an encrypted User Cookie (or a classic stateful server-side session) and the full User and/or Cart Object is pulled from the API on demand.

A notable exception to the general recommendation to avoid caching is that in some development frameworks (especially in the content management system space) caching is built into the basic assumptions on a low level. In these cases, it's not worth fighting against the framework's assumptions.

Import / Export / Sync integrations

Most real-world projects have a number of backend system integrations. In addition to the general advice above, one efficient way to implement sync jobs is by setting up a Subscription to notify about changes to a resource.

This approach allows you to be notified in near real-time about new events through your preferred message queue without the need for periodical polling. This reduces the load and lowers the number of requests to our APIs. Subscription destinations are also cloud-native, highly available, and auto-scalable. This provides generally better performance than other polling solutions.

To ensure data integrity, we recommend executing a full sync periodically.

Whether you can also do this pattern for information that is synced with Composable Commerce depends on the capabilities of the other involved systems. As previously mentioned, the JavaScript SDK provides mature helpers for bulk operations.

The Composable Commerce Import API allows you to bulk import data for creating and updating resources. You can learn how to use the Import API effectively, including limits and how to optimize performance, on the Import API best practices page.

GraphQL

Fetch only the fields that you need. This will result in smaller payloads and is better for the network and the serialization/deserialization process.

Use <fieldName>Ref fields where the expanded resource is not needed.

Optimize your queries towards low Query complexity.