MongoDB para Registros de Eventos: La Historia de Snowflake a Mongo
El rastro de auditoría solía llegar a Snowflake con un día de retraso mediante un job por lotes; moverlo a MongoDB cambió algo de poder analítico por un registro consultable en el instante en que ocurre el evento.
Cada valuación que este servicio calcula produce un registro de auditoría: qué proveedores fueron llamados, qué devolvió cada uno, qué reglas se activaron, cuál fue la decisión final, y — cada vez más — metadatos que nadie anticipó cuando se diseñó por primera vez la forma del registro, como un puntaje de riesgo de fraude agregado el trimestre pasado o un campo `promotion_code` agregado el trimestre anterior a ese. Durante mucho tiempo, este dato siguió un camino familiar en esta organización: las escrituras se acumulaban en algún lugar transaccional, un job por lotes nocturno las extraía, las transformaba a la forma esperada por el warehouse, y las cargaba en Snowflake, donde finalmente analistas y dashboards podían verlas.
Ese pipeline es un diseño completamente razonable para el problema para el que fue construido — consultas analíticas pesadas y ad hoc a través de años de historial, el tipo de cosa en la que Snowflake es genuinamente excelente. Pero tiene dos costos que importaron cada vez más a medida que este servicio creció, y vale la pena enunciarlos con precisión en lugar de despacharlos como 'el batch es lento'. Primero: el retraso de visibilidad. Si una valuación ocurre a las 9am y el job por lotes corre a las 2am del día siguiente, un ingeniero de soporte investigando la queja de un concesionario a las 11am de esa misma mañana todavía no puede ver el registro en el warehouse — está depurando a ciegas, o recurriendo a buscar en logs de aplicación, durante hasta un día.
Segundo, y más corrosivo con el tiempo: la rigidez de esquema como contrato entre equipos. La ingesta de Snowflake esperaba una forma definida y acordada. Agregar un campo nuevo al evento de auditoría — el puntaje de fraude, el código de promoción, eventualmente un campo para qué estado de circuit breaker observó la solicitud — significaba coordinar un cambio de esquema con el equipo del data warehouse, actualizar la transformación del ETL, y a menudo esperar una ventana de release que no tenía nada que ver con el propio ritmo de releases de este servicio. El payload del evento, que evoluciona naturalmente a medida que el negocio hace preguntas nuevas, estaba encadenado al ritmo de un equipo y un pipeline que tenían toda la razón para ser conservadores respecto a cambiar un contrato compartido.
Mover el rastro de auditoría a una colección de MongoDB llamada `valuation_event`, escrita directamente por `VehicleValuationService` en el momento en que una valuación se completa, ataca ambos costos en su raíz. El retraso de visibilidad desaparece porque la escritura ocurre en el mismo camino de la solicitud que calculó la valuación — el registro existe en Mongo en cuestión de milisegundos, no en la siguiente ventana de batch del día siguiente. La rigidez de esquema desaparece porque MongoDB no impone una forma fija a través de los documentos de una colección: los documentos de este trimestre pueden traer `fraudRiskScore`, los del trimestre pasado no, y ni la colección ni ningún otro consumidor necesita ser avisado de la adición con anticipación. Un campo nuevo es simplemente un campo nuevo en el siguiente documento escrito; nada tiene que migrar.
Esto no es una mejora gratuita, y vale la pena ser honestos sobre lo que se cedió a cambio. El almacenamiento columnar de Snowflake y su SQL analítico maduro están construidos exactamente para las consultas que un almacén documental maneja mal — agregar una métrica a través de 400 millones de eventos, unirla contra otras tres tablas, y hacerlo en segundos. El modelo documental de MongoDB está construido para lo opuesto: obtener este evento por ID, o por un puñado de filtros predecibles (VIN, rango de fechas, proveedor), rápido, a la escala de la escritura. Pídele que haga agregación analítica pesada entre colecciones al volumen que este registro de auditoría alcanza, y lo hará — pero notablemente peor que un warehouse construido específicamente para ese trabajo.
La resolución no es 'elegir uno para siempre' — es desacoplar el camino de escritura del camino analítico. `VehicleValuationService` escribe a MongoDB de forma síncrona (o, como mostrará el Módulo 4, de forma asíncrona mediante una corrutina de disparar-y-olvidar, lo cual elimina incluso el pequeño costo de latencia de esa escritura de la respuesta) porque esa es la necesidad operativa de baja latencia. Un job de ETL río abajo puede seguir sincronizando MongoDB hacia un warehouse para los analistas que necesitan las consultas de agregación pesada — pero esa sincronización ocurre en su propio horario, completamente desacoplada de si un concesionario está esperando una respuesta de valuación, y un cambio en el proceso de ingesta del warehouse ya no bloquea ni ralentiza la evolución propia de este servicio.
La forma de esta decisión se generaliza más allá de este servicio en particular: cuando el trabajo de un camino de escritura es 'hacer este hecho durable y consultable ahora mismo, en una forma que seguirá cambiando', y una preocupación completamente separada es 'dejar que los analistas corran consultas históricas pesadas sobre todo lo que ha ocurrido', esos dos trabajos merecen dos almacenes distintos conectados por un pipeline explícito y asíncrono — no un solo almacén esforzándose por hacer ambos.
// A document in the valuation_event collection.// Note fraudRiskScore and promotionCode: fields added in later quarters// that simply did not exist on older documents — no migration required.{"_id": ObjectId("64f1c2a1e4b0f5a1d2c3b4a5"),"vin": "1HGCM82633A004352","requestedAt": ISODate("2026-08-22T14:03:11Z"),"providerResponses": [{ "provider": "BLACK_BOOK", "value": 18250, "latencyMs": 340 },{ "provider": "CARFAX", "value": 17980, "latencyMs": 610 },{ "provider": "SP_VIS", "value": null, "error": "TIMEOUT" }],"rulesApplied": ["GUARDRAIL_BRANDED_TITLE", "BOOST_LOW_MILEAGE"],"finalOffer": 17500,"fraudRiskScore": 0.12,"promotionCode": "SUMMER26"}
This is illustrative only (runnable: false) — the valuation_event document shape written directly to MongoDB, showing fields from different eras of the schema coexisting without a migration.
@Document(collection = "valuation_event")data class ValuationEvent(@Id val id: String? = null,val vin: String,val requestedAt: Instant,val providerResponses: List<ProviderResponse>,val rulesApplied: List<String>,val finalOffer: BigDecimal,val fraudRiskScore: Double? = null, // added later; older docs simply omit itval promotionCode: String? = null, // added later too)interface ValuationEventRepository : MongoRepository<ValuationEvent, String> {fun findByVinOrderByRequestedAtDesc(vin: String): List<ValuationEvent>}// Written directly in the request path — no nightly batch job in between.eventLog.save(ValuationEvent.from(vin, rulesApplied = firedRules, finalOffer = result.offer))
This is illustrative only (runnable: false) — a Spring Data MongoDB repository and document class for valuation_event, showing how the service writes the audit record directly instead of batching it toward a warehouse.
🧠 Comprueba tu comprensión
0/1 · 0/1 answered1. The team decides that heavy historical analytics (e.g., 'average offer accuracy across all branded-title trucks in the last two years') is still important to the business. Given the Mongo migration, what is the most consistent way to keep supporting that need?