Normal view
-
Robotics & Automation News
- 9 Eastern European Node.js Development Companies Worth Shortlisting for Long-Term Builds in 2026
-
AI Infrastructure Archives - The New Stack
- Personalization is a ranking problem — architecture makes it work
Personalization is a ranking problem — architecture makes it work
Every product team is chasing the same moment: The user opens a page and thinks, this understands me.
A shopper who loves floral prints should see more floral prints. A user who follows local politics should open their app to see news about local politics. A job candidate who keeps clicking remote roles should not keep getting shown in-office jobs.
That is not a niche feature anymore. It is the baseline expectation. Users decide quickly whether a product system understands them, and they rarely care whether the failure came from search, recommendations, merchandising rules, or stale data.
Here is the uncomfortable truth: Most teams do not have a personalization quality problem. They have a personalization architecture problem.
Personalization is not a widget bolted onto search. It is a ranking decision. The system has to decide, for this user and this request, what deserves the next slot. That means weighing the user, the item, the context, and the business goal at the same time. In many stacks, the ranking layer is the one place that cannot see all of those signals together.
The hard part is not collecting signals. The hard part is combining them while the user is still there.
Why personalization is hard in the first place
To put the right item in the right slot, a system has to understand several things at once:
- Intent: What is the user asking for right now?
- Item quality: What does each candidate actually contain or represent?
- User history: What has this person clicked, bought, read, watched, or ignored?
- Availability: Is the item in stock, fresh, nearby, legal to show, or ready to ship?
- Business priority: What should the business promote, protect, or de-emphasize?
Those signals often disagree. The most relevant item may not be the most profitable. The most profitable item may be out of stock. The user may say “running shoes,” but their behavior says “trail running, wide fit, under $120.”
They also move on different clocks. Product attributes change slowly. Inventory and price can move throughout the day. Preferences shift with every click. External context — weather, breaking news, a championship game, a cultural moment — can matter without warning.
Personalization means folding all of that into one ordered list, on every request, in milliseconds. The signals themselves are not the bottleneck. Query-time ranking is.
The usual stack makes the problem harder
Most personalization systems are assembled from tools that were each designed for one slice of relevance.
Keyword search engines are excellent at lexical matching. They are good when the query language and catalog language line up. But shoppers, readers, and job seekers rarely speak in neat index terms. You indexed “athletic performance running footwear”; they typed “running shoes.” Synonym rules can help, but they do not scale gracefully across long-tail language, changing catalogs, and new user behavior.
Vector databases start from the opposite side. They are good at semantic similarity: “Find me things like this.” That is powerful, but nearest-neighbor search is not the same thing as personalization. Real ranking has to blend semantic similarity with live behavior, stock, price, margin, freshness, eligibility, and business rules.
Re-rankers, recommendation services, feature stores, and rule engines are usually added to glue everything together. That is where fragmentation creeps in.

When retrieval and ranking live in separate systems, the ranker often works from a partial, stale, or precomputed view of the world. Click history, session context, and the user’s live preference vector arrive too late. Business rules become filters or overrides instead of ranking signals. Fresh inventory or price changes require coordination across multiple systems.
Every hand-off adds latency. Every boundary creates another place for signals to drift. Every “quick rule” becomes another hard constraint that can accidentally turn “show the closest match” into “show nothing.”
“Every hand-off adds latency. Every boundary creates another place for signals to drift.”
The deeper issue is a timing assumption. Many architectures were built around offline ranking: process the catalog, compute scores in a batch job, and serve those scores until the next rebuild. That works when preferences are stable. It breaks when the most valuable signal is the click that happened two seconds ago.
What changes when ranking happens in one real-time pipeline
A real-time personalization architecture treats retrieval, ranking, and inference as one serving problem.
That is the core idea behind Vespa’s approach: Text search, vector similarity, structured filtering, ranking expressions, tensor computation, and model inference can live inside one query pipeline. Instead of retrieving somewhere, enriching somewhere else, and ranking at the end, the system can rank with the relevant signals while it is still deciding what to return.
That architectural choice changes the shape of the problem.
1. Retrieval is hybrid from the start
Lexical search, semantic search, and structured filtering can run together instead of being reconciled after the fact. A product query can combine text, embeddings, filters, session behavior, and item attributes in one request.
That matters because personalization is rarely one signal. The user’s query still matters. So does semantic similarity. So do category, availability, price, and business constraints. Hybrid retrieval keeps those signals in play before ranking starts.
2. Ranking can express the actual objective
A personalization score should not be trapped inside one similarity function. It should be a formula that reflects the product’s goals.
That formula might combine BM25, vector similarity, user affinity, stock level, margin, popularity, discount depth, freshness, rating, distance, or a weather term. Some of those signals need normalization first. Some should matter only for certain categories or users. Some should be tested as weights.
The important part is that they are all terms in the same ranking expression, not scattered across services.
A simplified version might look like this:
final_score =
0.30 * lexical_relevance +
0.25 * semantic_similarity +
0.25 * user_affinity +
0.10 * availability +
0.10 * business_priority
In production, the formula can be more nuanced. But the principle is simple: personalization, relevance, and business logic belong in the same scoring decision.
3. Model inference can run where the data lives
Some signals should come from learned models rather than hand-tuned rules: propensity to buy, churn risk, quality prediction, fraud risk, query classification, or a learned-to-rank model.
When inference runs in the serving path, those model outputs can become ranking features instead of delayed batch scores. That reduces the need to ship data to a separate inference service, wait for a response, and stitch the score back into ranking.
4. Updates become immediately useful
“Real time” should not mean “after the next index rebuild.” If inventory changes, stock should be rankable immediately. If a user clicks two yellow dresses, “yellow” should matter on the next request. If a merchandising team adjusts a ranking weight if the weight is exposed as a query-time input, the experiment should start producing useful feedback right away.
That is the difference between personalization as a nightly job and personalization as a live ranking decision.
Tensors make the personalization concrete
The most useful mental model is simple: represent the user and the item in the same feature space, then rank by how well they match.
In Vespa, tensors make that practical. A tensor can be a scalar, a dense vector, a sparse map of feature-weight pairs, a matrix, or a more complex structure. That means the same framework can represent semantic embeddings, product attributes, user preferences, business objectives, and model features.

For example, each item can carry a sparse feature tensor:
{
"floral": 0.90,
"yellow": 0.70,
"short_sleeve": 0.80,
"crew_neck": 0.65
}
Each user can carry a tensor with the same feature names:
{
"floral": 1.00,
"yellow": 0.37,
"short_sleeve": 0.33,
"crew_neck": 0.31
}
Because the two tensors share a shape, personalization becomes a dot product: multiply matching features, sum the result, and use that score inside ranking.
In a Vespa rank profile, the core expression is compact:
# schema: item attributes stored as a sparse tensor
field item_features type tensor<float>(feature{}) {
indexing: attribute | summary
}
# rank profile: the user's live preferences arrive as a query tensor
rank-profile personalized {
inputs {
query(user_features) tensor<float>(feature{})
}
first-phase {
expression: sum(query(user_features) * attribute(item_features))
}
}
That one expression is not the whole ranking function. It is the personalization term. BM25, vector similarity, stock, margin, freshness, distance, or a model score can be added as other terms with their own weights.
The user tensor is where real-time behavior becomes powerful. Click a floral item, and the “floral” weight rises. Click two yellow items, and “yellow” rises; the application feeds click events into the user profile. The next query can use those updated preferences immediately, without waiting for a nightly profile build.
Business goals stop fighting personalization
In fragmented stacks, business rules often become blunt instruments: boost this category, hide that brand, force these items to the top, filter these out. That can satisfy a short-term merchandising goal while damaging relevance.
When business logic is part of the ranking expression, it can be more subtle. You can boost overstocked inventory without ignoring intent. Promote umbrellas when rain is forecast without turning every search into an umbrella search. Give new sellers a small exploration boost. Prioritize destocking before a new product line launches. Surface team merchandise during a championship run.
“When business logic is part of the ranking expression, the user still gets relevant results. The business still influences outcomes.”
The user still gets relevant results. The business still influences outcomes. The difference is that both are expressed as ranking signals instead of competing systems.
That also makes experimentation easier. A merchandising or growth team can test weights, traffic splits, and ranking profiles without asking engineering to rewrite the whole pipeline. Relevance becomes a controllable growth lever rather than a fragile side effect.
The same pattern applies beyond commerce
The examples above are easy to picture in apparel, but the architecture is not commerce-specific. Personalization is the same ranking problem in many products:
- Content feeds: Blend topic affinity, freshness, engagement, creator quality, and business rules.
- News: Rank by reading history, topic interest, locality, freshness, and source diversity.
- Jobs: Match candidate preferences such as remote work, seniority, compensation, location, and tech stack against role attributes.
- Geo search: Treat distance as one normalized ranking term alongside relevance, quality, and preference.
- Video and audio: Combine embeddings, viewing history, metadata, freshness, and learned ranking models.
Different domains need different features. The architecture pattern is the same: retrieve candidates, rank with the signals that matter, update those signals as behavior changes, and keep the decision close to the data.
Scale doesn’t have to be the trade-off
The natural concern is that a more expressive ranking system must be slower. In practice, that does not have to be true.
Vespa was built for large-scale serving from the beginning: billions of documents, high query volume, and low-latency ranking. The reason this works is multi-stage ranking. The system does not run the most expensive logic across every possible result. Instead, it uses a fast first phase to narrow the candidate set, then applies more precise ranking to the smaller group that remains.
For example, a cheap first phase narrows a huge candidate set. Then, once the candidate set is smaller, Vespa can apply full-precision scoring, richer tensor operations, business logic, and model inference where they matter most.
The result is a practical balance: speed across the full corpus, accuracy in the final ranking, and enough flexibility to personalize each query without turning the serving stack into a chain of fragile services.
What’s next
Personalization is not failing because teams lack data. Most teams already have plenty of signals: query intent, clicks, product attributes, inventory, margin, freshness, location, and business priorities. The harder problem is that those signals often live in different systems, move at different speeds, and arrive too late to influence the final ranking decision.
That is why personalization should be treated as a ranking problem. When retrieval, ranking, personalization, and business logic are split across separate systems, the ranker is forced to work with stale or incomplete context. The user moves faster than the architecture can respond. Every new signal becomes another integration project.
A unified real-time ranking pipeline changes that. User behavior, item attributes, semantic similarity, lexical relevance, inventory, and business goals can all become parts of the same scoring function. Tensors make those signals directly comparable and usable at query time. Instead of bolting personalization onto the end of the system, personalization becomes part of the decision the engine makes for every query.
The goal is simple: rank each result with the best context available, at the moment the user asks. That is when personalization stops feeling like a feature and starts feeling like relevance.
The post Personalization is a ranking problem — architecture makes it work appeared first on The New Stack.
-
AI Infrastructure Archives - The New Stack
- Can prompt caching tame RAG costs without sacrificing accuracy?
Can prompt caching tame RAG costs without sacrificing accuracy?
The AI ecosystem is drowning in tutorials on how to build a retrieval-augmented generation (RAG) app in five minutes. The pitch is appealing but flawed: Chunk a document, run it through an embeddings API, load it into a vector database, and slap a UI on top. This setup works locally. It might even survive a beta test with friendly users. But launch it into a production-grade B2B SaaS environment, and the architecture collapses.
Enterprise applications don’t handle neat, static files. They ingest dynamic, unpredictable streams of live data bound by strict legal and compliance constraints. Treating vector search as a solved infrastructure problem at scale is a dangerous mistake.
“Treating vector search as a solved infrastructure problem at scale is a dangerous mistake.”
Here is exactly what breaks when a naive RAG setup hits production, and the architectural trade-offs needed to fix it.
Bottleneck 1: the synchronous ingestion trap
Synchronous data ingestion is the most prevalent architectural flaw in new AI products. A user uploads a 500-page compliance manual. The client makes a POST call to a web server, which parses the document, splits the text, iterates over a sequence of synchronous API calls to OpenAI or Cohere for vectorization, and writes those vectors to the database.
This approach introduces two critical failures:
- Timeouts: A 500-page document rarely finishes processing within standard HTTP timeouts (30 to 60 seconds) while waiting for the embedding API.
- Cascade failures: If the system hits rate limits or latency spikes, the entire ingestion operation fails, throwing a 500 error and losing the user’s document.
The fix: the batched fan-out pipeline
Production-grade AI pipelines require persistent events rather than simple HTTP calls. However, sending the whole 500-page document to be processed by one consumer from Kafka or RabbitMQ is a mistake. If a consumer spends 10 continuous minutes generating embeddings, it misses its broker heartbeat. Assuming the worker died, the broker kills the consumer and triggers a partition rebalance, creating an infinite loop of duplicated work and stalled processing.
“Synchronous data ingestion is the most prevalent architectural flaw in new AI products.”
Conversely, granular chunking, where every chunk becomes an individual Kafka message, launches a self-inflicted denial-of-service (DoS) attack on downstream services. A document with 1,500 chunks generates 1,500 individual messages. This instantly exceeds upstream requests per minute (RPM) limits and floods the pipeline with network overhead.
The engineering sweet spot is a batched fan-out approach:
- Asynchronous uploads: The web API stores the raw file in Amazon S3, triggers a document_uploaded event, and instantly returns a 202 Accepted status. This single, asynchronous path processes one-page invoices and 100-page SOC2 reports with equal reliability, eliminating the technical debt of maintaining separate “fast” and “slow” ingestion routes.
- Micro-batching: A lightweight “Spitter” consumer downloads the file, chunks it, and groups those chunks into optimized micro-batches (e.g., 64 chunks per batch).
- Controlled embedding: Embedding workers pull these batched events. To prevent concurrent workers from breaching upstream RPM limits, avoid fragile
sleep()delays. Instead, enforce a token bucket rate limiter at the consumer level or strictly cap the number of active message broker partitions.
Python
# Conceptual snippet for architectural illustration
def handle_document_upload(event):
try:
raw_text = download_from_s3(event.file_uri)
chunks = semantic_chunking(raw_text)
except Exception as e:
# Log failure and raise so the message broker routes this to a Dead Letter Queue (DLQ)
print(f"Failed to process document {event.file_uri}: {e}")
raise
# Batch size heavily depends on the downstream embedding model's context limits
batch_size = int(os.environ.get("EMBEDDING_BATCH_SIZE", 64))
for i in range(0, len(chunks), batch_size):
chunk_batch = chunks[i:i + batch_size]
kafka.publish("embedding_tasks", {
"tenant_id": event.tenant_id,
"document_id": getattr(event, "document_id", event.file_uri),
"chunks": chunk_batch
})
This keeps individual consumer tasks short, respects upstream rate limits by maximizing payload density, and allows horizontal scaling of embedding workers during traffic spikes.
Bottleneck 2: the multi-tenant nightmare
Developers often treat multi-tenancy as an afterthought. The simplest way to handle multiple B2B tenants using a single RAG system is logical segregation, where all vectors reside in a large index, and each entry has a tenant_id associated with its metadata. Upon retrieval, the application filters results by adding a clause to the metadata payload.
Flaws of the approach:
- Security vulnerabilities: Relying on application-level filtering creates an unacceptable risk. If an engineer omits or misconfigures a metadata filter, one client can access another’s confidential data. In highly regulated environments, this breaks compliance.
- The noisy neighbor problem: If one customer uploads 10 million vectors to the shared index, memory usage skyrockets during vector searches. This degrades performance across the entire system, even for tenants with a handful of documents.
The fix: serverless compute-storage decoupling
Echo-chamber thinking assumes that the only solution is to provide each tenant with its own dedicated database cluster. This is prohibitively expensive and practically impossible to manage in a modern-day SaaS offering.
The true gold standard here is using next-generation serverless vector databases like Pinecone Serverless or managed Qdrant implementations, which make a clear distinction between computing and storage.
| Isolation strategy | How it works | Trade-offs |
| Shared index (logical) | One index; application layer applies metadata filters. | High compliance risk; prone to noisy neighbor performance degradation. |
| Database per tenant (physical) | Client provisions a dedicated database cluster. | Maximum security, but introduces massive operational overhead and idle compute costs. |
| Serverless namespaces (standard) | Storage layer isolates vectors into namespaces; on-demand compute loads them only when queried. | Namespace-level access control prevents cross-tenant leaks. Zero idle compute costs. |
Engineering takeaway: stop building complex multi-tenant routing logic in your application code. Push the isolation boundary down to the infrastructure layer using serverless namespaces.
Bottleneck 3: the semantic caching trap
Once ingestion is asynchronous and tenants are segregated, inference costs become the final bottleneck. Hitting an LLM API for every individual query is economically unsustainable.
The industry defaults to semantic caching: embed the user’s prompt, calculate its cosine similarity against previous prompts, and return a pre-calculated LLM response if the score exceeds a set threshold (e.g., 0.95).
Why semantic caching fails
Embeddings capture overall semantic meaning, but they miss specific contexts and entities. The prompts “What was the holiday policy in 2023?” and “What is the holiday policy for 2024?” share a near-perfect cosine similarity score. The core semantics match, but returning a cached answer feeds the user incorrect or contradictory information.
The fix: hybrid verification vs. native prompt caching
To scale without compromising accuracy, there are only two choices to consider: application-layer validation or infrastructure-layer optimization.
Strategy A: combined lexical filtering and intent routing
When using an application-layer caching system (for instance, Redis), you need to layer the semantics search on top of extremely light guardrails.
- Exact-match filter: Apply a token-validation filter over vector similarity. In the case where the cached query is “2023”, and the current query is “2024,” throw out the cache hit right away.
- Intent routing: Before serving a cached answer, use an inexpensive, fast model as an intent match router.
Python
Query A: {incoming_query}
Query B: {cached_query}
Do these queries have the exact same intent and require the exact same factual answer?
Respond only with YES or NO.
Strategy B: infrastructure-level prompt caching
If the system cannot tolerate the added latency of an application-layer verification router, bypass custom caching entirely and offload the problem to the infrastructure.
Modern LLM providers natively support prompt caching. It is crucial to understand what is being cached here: it is not the user’s short question. When multiple users query the same corporate knowledge domain, the massive system instructions and the heavy retrieved context documents (often 10k+ tokens) are cached automatically at the provider’s inference layer.
“Stop approaching AI like a magic black box and instead approach it as a distributed systems problem.”
The application sends the full RAG query every time. The provider recognizes the repeated context block, slashes context token costs by up to 80%, and drops the time-to-first-token (TTFT) to milliseconds.
The infrastructure surrounding a foundation model separates a prototype from a production AI system. Stop approaching AI like a magic black box and instead approach it as a distributed systems problem, and things will fall into place. Batched fan-out asynchronous queuing solves timeout and rate-limiting issues. Serverless namespacing resolves compliance risks. Prompt caching and intent routing secure unit economics. Designing a native AI product today means engineering for inevitable API failures, cross-tenant data attacks, and runaway LLM costs.
The post Can prompt caching tame RAG costs without sacrificing accuracy? appeared first on The New Stack.