(A continuation of the previous post. If you want the full context, start there.)
Recently I discussed how Redis Stack became my deterministic retrieval and analysis layer. Today I want to explore what had the biggest practical impact on performance and cost: deciding how to model documents.
The topic seems simple: just put everything in JSON and index it. But when volume grows and the LLM starts consuming the result and getting lost, you discover that modeling decisions matter as much as the choice of database.
To avoid workplace ethics issues, I will continue using a domain I believe many developers have worked with: an e-commerce product catalog.
The concrete problem#
Imagine a conversational assistant answering questions such as:
"Are there men's sneakers up to R$ 300 with free shipping and available stock?" "Tell me more about the sneaker model you just showed me." "I want to see the 5 best-rated Bluetooth headphones."
Three different questions. Three radically different access patterns. And the LLM, despite being intelligent, hallucinates when you throw 80 fields into context that needed 5.
The three models I tested#
1. FAT - Store everything, index everything#
The initial temptation: one huge document with every field returned by the catalog API.
Enlarge ↗What happens:
RAM explodes: every document with more than 60 indexed fields = a heavy index
FT.SEARCH returns everything -> you pass the 800-token
descricao_completafield to the LLMThe model hallucinates because it gets lost in the noise
LLM costs rise without delivering better quality
Enlarge ↗2. SLIM - Only the essentials in retrieval#
The second attempt: index and return only what the search agent needs:
Enlarge ↗The problem: when the user asks "tell me this product's return policy" or "what is its weight for calculating shipping?", you do not have the fields. Then comes the temptation to expand SLIM, and you are back to FAT.
Enlarge ↗3. HYBRID - The pattern that stayed#
The insight: separate the search stage from the detail stage:
Enlarge ↗RediSearch filters and sorts using indexed fields. JSON.GET retrieves the complete document only when the user wants details of a specific item.
Enlarge ↗The schema supporting the hybrid#
Enlarge ↗
Enlarge ↗The trade-offs of each model:#
Enlarge ↗RediSearch has no native BOOL type. I learned the hard way: every boolean field becomes a TAG with the string "true"/"false".
Enlarge ↗It sounds trivial. But when you receive True, 1, "sim", "TRUE" from upstream (catalog APIs are full of these), without this converter the index becomes inconsistent and search returns zero results, without an explicit error.
What LangGraph gained from this#
Enlarge ↗Each tool knows exactly how much data the LLM needs for that specific intent. This is the pattern's core: the data model serves the intent, not the other way around.
What I learned#
Fat/Slim/Hybrid modeling in Redis is not a technical choice; it is a decision about the contract between retrieval and the LLM.
If you supply too much data, the model gets lost. Too little, and it hallucinates to fill the gaps. The optimal point is: the minimum necessary for the model to answer safely, with an escape hatch to full detail when the intent requires it.
In my case, it ended up like this:
Catalog search -> Slim (5 fields, N products, short context)
Product detail -> JSON.GET from Fat -> formatted as SlimDetail (20 fields, 1 product, medium context)
Aggregations -> FT.AGGREGATE directly, without passing any document to the LLM
Each pattern has its cost. Understanding which to use in each flow is what really changed the project's bill.
The next post will explain how DSPy entered this picture to classify intent and route to the right tool, without hardcoded rules, using supervised examples and automatic prompt optimization.