Skip to main content
advancedUpdated July 11, 2026·8 min read

Solr Search in SAP Commerce: Architecture and Indexing Done Right

SAP Commerce search indexing: indexed types, full and partial runs, two-phase commits, value providers, and safe scheduling.

CCI

Commerce Cloud Integrations

SAP Commerce engineering team

Architecture, integrations, performance, upgrades, and Composable Storefront on SAP Commerce Cloud.

solrsearchindexingperformanceplatform

Search is where most storefront revenue decisions actually happen: a customer who searches converts at a multiple of one who browses, and a customer who gets "no results" for a product you stock is a refund you never got the chance to lose. Under the storefront, SAP Commerce delivers search through Apache Solr, with the platform contributing the indexing pipeline, business tooling, and query integration. This guide covers the half of the system that runs before any customer types a query: getting data into the index correctly and efficiently. A companion guide covers query tuning and troubleshooting.

The Shape of the System#

The database is normalized: products, prices, stock, classification attributes, and media live in separate tables connected by relations. Solr stores denormalized documents: one document per searchable item, carrying every field that search, faceting, or the listing page needs. Indexing is the transformation between the two worlds, and almost every search problem (stale prices, missing products, slow indexing, wrong facets) is a problem in that transformation.

The platform pieces:

  • Indexed types: any composed type can become a Solr indexed type. Products ship preconfigured; custom types (stores, articles, FAQs) are added through Solr Item Type Management in Backoffice or ImpEx. One deliberate practice: give Backoffice its own indexed type even for the same composed type, because back-office search (find items to edit, all catalog versions) and storefront search (find products to buy, online only) have different field lists, different queries, and different freshness needs.
  • Indexer queries: FlexibleSearch queries per indexed type that select what gets indexed, configured under Facet Search Config, Indexed Types, Indexer Queries.
  • Value providers: the converters of the indexing world, turning models into document field values.
  • Identity providers: define document identity in the index; the default works for standard product cases.
  • Results converters: transform Solr documents back into DTOs for the storefront, so listing pages render from the index without touching the database.

On CCv2 the Solr infrastructure is provisioned for you (a ZooKeeper ensemble plus Solr nodes), the Solr version is pinned per commerce update release, and your solrconfig.xml and schema customizations ship from the repository via the manifest's useConfig.solr block. Version compatibility is the first checkpoint of every platform upgrade: check the third-party compatibility matrix before assuming your Solr customizations carry over.

Indexing Strategies: Full, Update, Delete#

Three operations exist, and a healthy project uses all three on different schedules.

Full indexing rebuilds from scratch: delete everything, re-export, re-index. On a large catalog it is expensive, so it typically runs once a day, immediately after catalog synchronization completes (indexing before sync finishes indexes the old catalog and is a classic scheduling bug). Full indexing supports two commit strategies, and the choice matters:

StrategyBehaviourWhen
DirectDocuments are added or updated in place. On failure, previously committed documents survive, but the index is left in a mixed state. Replication pauses during the runStandalone master/replica setups
Two-phaseIndexing writes into a shadow core; on success an atomic swap brings the new index online, on failure everything rolls backThe safe default, and strongly recommended on Solr Cloud

The Solr Cloud detail that makes two-phase non-negotiable there: cloud nodes stay in sync, so a direct-mode full index deletes documents across all nodes at the start of the run. If the rebuild then takes forty minutes, your storefront searches an empty or partial index for forty minutes. Two-phase keeps the old index serving until the new one is complete.

Update indexing re-indexes only documents changed within a window. It does not pause replication, runs fast, and is the workhorse for freshness. The pattern: an update job every 5 to 15 minutes whose indexer query selects deltas:

sql
SELECT {p:PK} FROM {Product AS p}
WHERE {p:modifiedtime} >= ?lastIndexTime

Delete indexing removes documents. Remember that update indexing never removes anything: a product that went offline stays in the index until either the nightly full index rebuilds without it or a delete job takes it out. If products deactivate during the day and must vanish from search promptly, you need the delete job on a schedule, not just the full rebuild at 4 a.m.

Partial Updates: Fast Freshness for Volatile Fields#

Default update indexing recreates the whole document: all 30 properties, all value providers, even if only the price changed. Partial indexing updates named properties only, leaving the rest of the document untouched. For the classic volatile pair, price and stock, this is dramatically cheaper: two value providers run instead of thirty, and the database sees a fraction of the queries.

Design rules for partial updates, learned the hard way:

  1. Pick properties that change often and independently: price, stock, promotion flags. Classification data that changes twice a year gains nothing from partial machinery.
  2. The delta query must actually catch the change. A price edit does not touch Product.modifiedtime unless something marks the product. Your indexer query for a price partial update has to detect PriceRow changes (join or subselect on the price table's modified time), or the job will run happily and index nothing.
  3. Removals are the trap. Partial update writes values the provider produces; when data is deleted, the provider often produces nothing, and the old value survives in the document. Concretely: remove a product's JPY price entirely and priceValue_jpy_double keeps its stale value, still visible to Japanese shoppers. Either avoid true removals (set price to zero or set an end date so the provider emits an explicit null-set for the field) or route removals through a full document update.
  4. Mind the limitations: partially updated fields must be stored="true" in the schema, and partial updates must not target fields used for spellchecking and suggestions.
  5. Agree the freshness SLA in requirements. "How stale may stock be?" has a business answer (say, 15 minutes), and that answer, not engineering taste, sets the partial update schedule.

Value Providers: Where Indexing Time Goes#

Every property of every document of every indexing run flows through a value provider, which makes providers the highest-multiplier code in the search stack. A provider that costs 5 extra milliseconds adds 40 minutes to a full index of half a million products with ten localized fields. Rules:

  • Question whether you need a custom provider at all. The SpEL value provider covers a surprising range declaratively:
plaintext
(#item instanceof T(de.hybris.platform.variants.model.VariantProductModel)) ? baseProduct.code : null
  • No per-item queries. A FlexibleSearch inside getFieldValues() runs once per product; on a big catalog that is hundreds of thousands of queries. Providers that need related data should load it in bulk (cached lookup tables, preloaded maps) rather than querying per item.
  • Unit test providers like the hot path they are, including null cases (product without price, variant without base) because indexing runs meet every data pathology your catalog contains.
  • Run indexer queries as admin, restrict at query time. Trying to encode visibility rules into indexing (running indexer queries as restricted users) produces an index you can never share across use cases. Index everything the storefront could ever show, and let search restrictions and query filters enforce visibility per request.

Scheduling: The Daily Rhythm That Works#

The reference cadence for a production catalog:

JobScheduleNotes
Catalog syncNightly, low trafficPrerequisite for full index
Full index (two-phase)Nightly, triggered after sync completesAs few runs per day as freshness allows
Update indexEvery 5 to 15 minutesDelta query on ?lastIndexTime
Partial update (price/stock)Every 5 to 60 minutes, per SLAOnly if these fields change intraday
Delete jobHourly or as neededOnly removals; full index also cleans up nightly

Chain the full index to the sync job's completion (a composite cronjob or trigger-on-success), never to a fixed clock time: the night the sync runs long is exactly the night a clock-scheduled index would index half a catalog. All indexing jobs belong on the background processing aspect on CCv2, pinned via node groups.

What to Verify Before Calling Search "Done"#

  • Two-phase commit for full indexing (mandatory on Solr Cloud), and the storefront keeps serving during a full rebuild
  • Update job catches every change class it claims to (product edits, price changes, stock movements), verified by editing an item and watching the document
  • A deactivated product disappears from search within the agreed window (delete job or documented acceptance of the nightly rebuild)
  • Partial update removal semantics tested with actual data deletion, not just value changes
  • Full index duration measured and trending; a run that grew from 40 to 90 minutes is telling you about a provider or the database
  • Separate indexed type for Backoffice search configured
  • Indexing jobs pinned to background processing and sequenced after catalog sync

Indexing is unglamorous exactly the way plumbing is: invisible while correct, catastrophic while wrong. Get the document freshness rules and the commit strategy right, keep value providers cheap, and the query-side tuning in the companion guide has a solid foundation to stand on.