Elegir un Almacén: Relacional vs Caché vs Documental
Nuestro servicio de valuación de vehículos escribe en tres bases de datos distintas a propósito — esta lección trata sobre la decisión, no sobre el recorrido de funciones.
Hasta este punto del curso, `VehicleValuationService` ha sido una historia sobre corrutinas, timeouts, reintentos y circuit breakers — toda la maquinaria que evita que tres proveedores externos poco confiables tumben nuestro hilo de request. A partir de este módulo damos un paso atrás para hacer una pregunta distinta: ¿dónde vive el dato una vez que lo tenemos? La respuesta honesta, para este servicio, es 'en tres lugares distintos', y eso no es un accidente histórico ni el síntoma de un equipo que no logró ponerse de acuerdo en una base de datos. Es una respuesta deliberada a tres patrones de acceso que no se parecen en nada entre sí.
Patrón uno: las reglas de precio y guardrail — las tablas que dicen 'un sedán 2021 con más de 80,000 millas recibe una deducción del 12%' o 'nunca ofrecer por encima del valor de libro en un vehículo con título de salvamento'. Estas reglas se leen en cada solicitud de valuación, hacen referencia a categorías de vehículos y ponderaciones de proveedores que viven en otras tablas, y cuando un analista actualiza un conjunto de reglas, varias filas deben cambiar juntas o ninguna. Ese es un patrón de acceso relacional: estructurado, con llaves foráneas, transaccional.
Patrón dos: la búsqueda de valuación por VIN. Las llamadas a Black Book, Carfax y S&P VIS son lentas y, en algunos niveles de precio, se cobran por llamada. Si dos concesionarios preguntan por el mismo VIN dentro de la misma hora, volver a llamar a los tres proveedores para el segundo concesionario es puro desperdicio — la respuesta no cambió, y no necesita estar perfectamente fresca. Ese es un patrón de acceso de caché: caliente, desechable, indexado por un solo campo, y tolerante a cierta obsolescencia a cambio de velocidad.
Patrón tres: el rastro de auditoría de cada valuación jamás calculada — qué proveedores respondieron, qué dijo cada uno, qué decidió el motor de reglas y por qué. Este dato se escribe muchísimo más de lo que se lee, su forma cambia a medida que agregamos campos (un proveedor nuevo, una bandera de regla nueva, un puntaje de fraude que no existía hace seis meses), y cuando se lee, casi siempre se lee por ID o por un puñado de filtros predecibles, no mediante joins contra otras seis tablas. Ese es un patrón de acceso documental: alto volumen de escritura, esquema flexible, de solo anexar (append-only).
La jugada tentadora — y la que la mayoría de los equipos hace por defecto — es meter los tres patrones en la única instancia de Postgres que el equipo ya sabe operar. Funciona, por un tiempo. Después las tablas de reglas, que necesitan escrituras transaccionales rápidas, empiezan a competir por I/O con una tabla de auditoría que crece un millón de filas al día. Las migraciones de esquema sobre la tabla de auditoría se vuelven aterradoras porque la tabla es enorme y cada proveedor agrega un conjunto de campos ligeramente distinto, así que la mitad de las columnas terminan siendo nullable y nadie recuerda por qué. Y las búsquedas por VIN, o terminan con su propia capa de caché añadida encima de todos modos — porque ningún equipo tolera la latencia de una lectura relacional en frío repetida por cada concesionario — o se desnormalizan dentro de las mismas filas de las que dependen las reglas transaccionales, acoplando dos cosas que no tienen ninguna razón para estar acopladas.
Así que la decisión que recorre este módulo no es 'cuál base de datos es la mejor' en abstracto. Es 'qué patrón de acceso tiene esta porción específica de datos, y qué modelo de almacenamiento fue construido para ese patrón'. Postgres, Redis y MongoDB no compiten aquí — son tres herramientas haciendo tres trabajos distintos dentro del mismo servicio, y `VehicleValuationService` llama a las tres sin que ninguno de sus consumidores necesite saberlo. El controlador pide una valuación; no tiene idea de que responderla implica una lectura transaccional en Postgres, una verificación de caché contra Redis y una escritura disparada hacia MongoDB.
Las siguientes tres lecciones toman cada almacén por turno — por qué Postgres es dueño de las reglas, por qué Redis se sienta delante de la búsqueda de valuación, y por qué el registro de auditoría dejó atrás un pipeline por lotes hacia Snowflake para pasar a MongoDB — y la lección posterior convierte esos tres casos en una lista de verificación que podrás aplicar a cualquier tabla nueva que tu propio servicio necesite, para preguntarte, con honestidad, a qué almacén pertenece.
class VehicleValuationService(private val ruleRepository: PricingRuleRepository, // Postgres: transactional, structuredprivate val valuationCache: ReactiveRedisTemplate<String, ValuationResult>, // Redis: hot, disposableprivate val eventLog: ValuationEventRepository, // MongoDB: append-only, schema-flexibleprivate val providerClients: List<ValuationProviderClient>,) {suspend fun valuate(vin: String): ValuationResult {// 1. Cache check (Redis) — is this VIN's valuation still fresh enough to reuse?valuationCache.opsForValue().get("valuation:$vin").awaitSingleOrNull()?.let { return it }// 2. Structured rules (Postgres) — guardrails and boosts for this vehicle's categoryval rules = ruleRepository.findActiveRulesForCategory(categoryOf(vin))// 3. Call providers, apply rules — the coroutine fan-out from Module 2val result = computeValuation(vin, rules, providerClients)// 4. Populate cache for the next dealer who asks about this VINvaluationCache.opsForValue().set("valuation:$vin", result, Duration.ofMinutes(30)).awaitSingle()// 5. Append-only audit write (MongoDB) — never blocks the response on a slow analytics storeeventLog.save(ValuationEvent.from(vin, rules, result))return result}}
This is illustrative only (runnable: false) — it sketches the three calls VehicleValuationService actually makes per request, one to each store, to make the access-pattern split concrete before the next three lessons dig into each one.
// Access pattern | Example in this service | Storage model that fits// --------------------------|------------------------------------|------------------------// Structured + transactional| pricing_rule, vehicle_category | Relational (Postgres)// Hot + disposable | valuation lookup by VIN | Cache (Redis)// High-volume + flexible | valuation_event audit log | Document (MongoDB)
This is illustrative only (runnable: false) — it names the three access patterns side by side, the way you'd sketch them on a whiteboard before picking a store, rather than a snippet meant to compile or run.
🧠 Comprueba tu comprensión
0/1 · 0/1 answered1. A teammate proposes storing the valuation cache as rows in the same Postgres database that holds pricing_rule, arguing 'one database is simpler to operate.' What is the strongest technical objection?