Back to Blog
By

Building context for personalized recommendations with Rust and SurrealDB

A recommendation request looks small at the edge of the system. A customer asks for a product, or opens a page that needs a ranked set of suggestions. The difficult part is assembling the facts that make those suggestions useful: product variants, browsing history, previous purchases, returns, and the relationship between all of them.

In my SurrealDB presentation, published in October 2024, I described how we used Rust, Kafka, embeddings, and graph queries to build that context for a luxury fashion retailer. This case study revisits the system described in that talk. The figures below are the figures I reported then.

Start with the context the model needs

I used a deliberately awkward example in the talk: someone asks for boots like Nancy Sinatra's. An embedding can help connect that phrase to images and product descriptions. A useful recommendation also depends on the customer. Someone shopping for a wedding may need a different selection from someone planning a night out. An item they previously returned is another useful piece of context.

Those facts often live in different systems. The engineering problem was to bring them together in a representation that the application could query reliably, then pass the relevant information to the model.

The public examples included customer lifetime-value records, products, sizes, colors, and option groups. A perfume's options do not have the same structure as a boot's options. Explicit relationships between a master product, its variants, and its options made that complexity easier to query.

The data path

The architecture I described had four main parts:

  1. Kafka brought records from the company's different data sources into the system.

  2. SurrealDB held records, product relationships, and embedding fields.

  3. Rust application code retrieved relevant facts and assembled the model input.

  4. The model received the customer's request together with that retrieved context.

This was a way to keep the application's data model close to its retrieval logic. In the talk, I showed how related variants and option groups could be queried together. I also discussed storing multiple embedding representations, including CLIP and BERT, because a single representation does not serve every retrieval task equally well.

Separate the query layer from storage

The deployment I described used SurrealDB with FoundationDB as the distributed storage backend. I also discussed TiKV as an alternative supported by the version being presented.

That separation gave us a path to distribute storage while retaining the application's graph and document queries. It also created operational work: coordinators, storage processes, and the health of the underlying database became part of running the recommendation service. A distributed backend is a scaling option with an operating cost, not a promise that capacity planning disappears.

For local data-model tests, SurrealDB's in-memory engine made it possible to exercise record creation and retrieval without provisioning the production storage backend. Those tests were useful for checking application behavior; production capacity still required a separate deployment and load testing.

What I reported in the talk

At the time of the presentation, I reported ingesting at least 50 million records a day through Kafka and serving recommendations to approximately four million customers a month. The talk also described a baseline of roughly 1.5 million recommendations a week for the workload being discussed.

These are historical operating figures from the presentation, not a fresh benchmark or a claim about the system today. They describe different parts of the workload and should not be combined into a throughput estimate. The recording does not establish a controlled comparison against another database or isolate a conversion lift attributable to SurrealDB.

The tradeoff I would make explicit

A graph model can make relationships convenient to retrieve, but those relationships still have to be populated and kept current. Storing several embeddings preserves options for retrieval, but each representation adds ingestion, storage, and evaluation work. Adding more customer context gives a model more information, but the application still needs to decide which facts are relevant to a particular request.

The useful lesson from this project is to design retrieval around the decisions the application needs to make. In this case, product variants and customer context were central to those decisions. Rust supplied the application layer; Kafka supplied the incoming records; SurrealDB supplied a common way to represent and query the relationships.

Recording

Watch the presentation. The discussion of customer context starts around 6:20, product relationships around 12:10, distributed storage around 14:50, and the reported operating figures around 17:40 and 19:30. The recording was published by SurrealDB on October 17, 2024.