Una persona revisa en una pantalla de oficina un diagrama de transacciones y conflictos de concurrencia mientras anota decisiones en una libreta.
Volver al blog

Serializable: menos miedo, menos bugs

Serializable isolation level suele sonar caro, pero ayuda a evitar bugs sutiles de concurrencia en apps reales. Aquí ves qué garantiza, qué anomalías bloquea y por qué muchas bases sacrifican aislamiento para no romper compatibilidad en equipos de LatAm.

Si alguna vez te dijeron que “serializable” es lento, complicado o demasiado estricto, no estás solo. Mucha gente lo escucha y piensa en una base de datos peleada con el rendimiento, como si elegir ese nivel de aislamiento fuera pedir problemas. Pero la pregunta de fondo es otra: ¿de verdad le temes más a serializable que a los bugs sutiles que aparecen cuando dos transacciones se pisan sin que nadie lo note?

Ese miedo no sale de la nada. En sistemas reales, las anomalías de concurrencia no siempre rompen todo de golpe. A veces dejan una reserva duplicada, un saldo mal calculado o una fila perdida entre dos escrituras válidas. El sistema sigue respondiendo, las métricas no se disparan y el bug aparece solo cuando ya afectó dinero, inventario o datos de usuarios. Por eso vale la pena aterrizar qué promete serializable, qué problemas evita y por qué tantas bases de datos prefieren relajar garantías antes que romper compatibilidad con aplicaciones existentes.

Qué significa realmente serializable

Serializable es el nivel de aislamiento más fuerte que suelen ofrecer las bases de datos transaccionales. La idea es simple de explicar y difícil de implementar bien: el resultado de ejecutar varias transacciones concurrentes debe ser equivalente a ejecutarlas una por una, en algún orden. No significa que corran literalmente en serie, sino que el efecto final no debe diferir de una ejecución secuencial.

Eso importa porque, en la práctica, muchas aplicaciones no programan pensando en carreras entre transacciones. Tú escribes lógica que parece obvia: leer saldo, validar stock, insertar orden, actualizar contador. Si otra transacción se mete en medio, el resultado puede seguir siendo “válido” a nivel de SQL, pero incorrecto a nivel de negocio. Serializable apunta justo a cerrar esa brecha.

Un ejemplo simple: dos compras al mismo tiempo

Imagina una tienda con 1 unidad de un producto. Dos usuarios hacen checkout al mismo tiempo. Ambos leen stock = 1, ambos pasan la validación y ambos insertan una orden. Si la base permite una anomalía de concurrencia como write skew o una lectura no protegida, puedes terminar con 2 órdenes confirmadas para 1 unidad disponible.

Ese bug no siempre aparece con carga baja. A veces solo emerge en picos, cuando dos clicks coinciden en la misma ventana de milisegundos. Y como cada transacción, vista por separado, parece correcta, el problema puede tardar semanas en detectarse.

Serialización no es lo mismo que bloquear todo

Hay una confusión común: pensar que serializable obliga a poner un candado global y sacrificar toda concurrencia. No necesariamente. Una implementación puede usar bloqueos, validación de conflictos, MVCC o una mezcla de técnicas para garantizar el mismo resultado lógico sin detener el sistema completo. El punto no es el mecanismo, sino la garantía.

En otras palabras, serializable no significa “más lento por definición”. Significa que la base asume la responsabilidad de impedir combinaciones de operaciones que romperían la consistencia lógica. Eso puede costar más coordinación interna, sí, pero también evita que tú tengas que reconstruir esa lógica a mano en cada endpoint.

Las anomalías que te conviene reconocer

Cuando hablamos de aislamiento, no basta con decir “hay bugs”. Conviene nombrar el tipo de bug, porque no todos se ven igual. Las anomalías de concurrencia son patrones concretos donde dos o más transacciones interactúan de una forma que produce un resultado inesperado.

Las más conocidas son dirty read, non-repeatable read, phantom read y write skew. No todas aparecen en todos los niveles de aislamiento, y no todas se corrigen igual. El problema es que mucha gente cree que “READ COMMITTED” o “REPEATABLE READ” ya cubren todo, cuando en realidad dejan huecos importantes según el motor.

Tabla rápida de anomalías comunes

AnomalíaQué pasaEjemplo realRiesgo típico
Dirty readLees datos no confirmadosVes un saldo que luego se revierteDecisiones basadas en datos falsos
Non-repeatable readLees dos veces y cambia el valorEl saldo cambia entre lecturasValidaciones inconsistentes
Phantom readAparecen o desaparecen filas entre lecturasCambia el número de pedidos pendientesReportes y límites mal calculados
Write skewDos transacciones válidas juntas rompen una reglaDos doctores se salen de guardia y dejan el turno sin coberturaInvariante de negocio rota

No necesitas memorizar la tabla como si fuera examen. Lo útil es entender que hay bugs que no son de sintaxis ni de lógica obvia, sino de interacción entre transacciones. Esos bugs suelen sobrevivir pruebas unitarias y aparecer recién en integración o producción.

Por qué write skew da tanto miedo

Write skew es especialmente traicionero porque cada transacción puede leer un estado consistente y tomar una decisión correcta según su propia vista. El problema aparece cuando dos decisiones correctas, tomadas al mismo tiempo, dejan el sistema en un estado inválido.

Un ejemplo clásico es el de guardias médicos: dos doctores deben estar de turno, y la regla dice que al menos uno debe permanecer disponible. Si ambos leen que el otro sigue de guardia y cada uno se desmarca, el resultado final viola la regla. Ninguna transacción hizo algo “mal” de forma aislada, pero juntas rompieron la invariantes.

Por qué tantas bases no activan serializable por defecto

Aquí está la parte incómoda: si serializable reduce bugs, ¿por qué no viene siempre activado? La respuesta corta es compatibilidad. La respuesta larga incluye rendimiento, expectativas históricas y el costo de cambiar el comportamiento de aplicaciones ya desplegadas.

Muchas bases crecieron con niveles de aislamiento más débiles porque eran más fáciles de implementar y más predecibles para ciertos patrones de uso. Con el tiempo, miles de aplicaciones se construyeron asumiendo ese comportamiento. Cambiar el default a serializable puede romper flujos que hoy “funcionan” aunque estén apoyados en coincidencias frágiles.

Compatibilidad primero, garantías después

Cuando un motor de base cambia su nivel por defecto, no solo cambia la teoría. Cambia el resultado de consultas concurrentes, la frecuencia de abortos, la latencia de transacciones y hasta el comportamiento de ORMs que nunca fueron diseñados para reintentar automáticamente.

Por eso muchas bases prefieren sacrificar garantías antes que romper compatibilidad. Si una aplicación ya depende de ciertos patrones de lectura-escritura, subir el aislamiento por defecto puede convertir un sistema estable en uno que falla más, aunque esos fallos sean técnicamente más correctos. En producción, eso duele.

El costo real no siempre es CPU

Mucha gente piensa que el costo de serializable es solo rendimiento. Sí, hay sobrecosto de coordinación, validación o bloqueos. Pero también hay costo operativo: más abortos que reintentar, más complejidad en clientes, más observabilidad para entender por qué una transacción falló y más disciplina en diseño.

Si tu equipo no maneja reintentos idempotentes, serializable puede sentirse agresivo. Pero ese problema no lo inventa el aislamiento fuerte. Solo hace visibles defectos que ya estaban ahí y que otros niveles de aislamiento escondían.

Cuándo te conviene subir el aislamiento

No todo sistema necesita serializable en todas partes. Si tienes lecturas analíticas, catálogos públicos o flujos donde una pequeña inconsistencia temporal no rompe nada, un nivel más débil puede ser suficiente. El punto no es usar lo más estricto siempre, sino saber dónde sí vale la pena pagar el costo.

En cambio, hay casos donde serializable te ahorra semanas de debugging y parches:

  1. pagos y saldos
  2. inventario y reservas
  3. cupos, límites y cuotas
  4. asignación de recursos compartidos
  5. reglas de negocio que deben cumplirse siempre

Si tu sistema tiene una invariante que no puede romperse ni por un segundo, ahí serializable deja de ser un lujo y se vuelve una herramienta práctica.

Qué mirar antes de decidir

Antes de subir aislamiento, revisa tres cosas: si tu aplicación reintenta transacciones, si tus operaciones son idempotentes y si tu equipo entiende qué anomalías tolera el negocio. No necesitas una teoría completa de bases distribuidas para empezar, pero sí una definición clara de qué error sería inaceptable.

También conviene medir. No te quedes con la intuición de que “seguro será lento”. Prueba con carga realista, latencias reales y patrones de acceso reales. Un sistema con pocas contenciones puede vivir muy bien en serializable. Otro, con hotspots claros, puede necesitar rediseño antes que un simple cambio de configuración.

Cómo aterrizarlo sin romper la app

La mejor forma de adoptar serializable no suele ser cambiar todo de golpe. Funciona mejor identificar operaciones críticas, revisar sus invariantes y probarlas bajo concurrencia real. Si tu base soporta serializable de forma robusta, puedes empezar por los flujos donde el costo de un bug es alto.

Un enfoque práctico sería este:

  1. identifica las transacciones que escriben sobre el mismo recurso
  2. define la regla de negocio que no se puede romper
  3. simula concurrencia con 20, 50 o 100 solicitudes paralelas
  4. observa abortos, retries y tiempos de respuesta
  5. agrega reintentos con backoff donde tenga sentido
  6. valida que el cliente no duplique efectos secundarios como emails o cobros

Ese último punto es clave. Una transacción puede reintentarse sin problema, pero un webhook o un cargo a tarjeta no deberían ejecutarse dos veces porque el backend decidió repetir la operación.

Reintentos y idempotencia

Serializable suele venir acompañado de abortos por conflicto. Eso no es un fallo del sistema, es parte del contrato. Tú necesitas que el cliente o la capa de aplicación reintente con seguridad, y eso implica diseñar operaciones idempotentes cuando haya efectos externos.

Por ejemplo, si generas órdenes, usa un idempotency key. Si envías emails, separa la confirmación de negocio del envío asíncrono. Si registras pagos, guarda un identificador único del proveedor. Así, un retry no duplica el efecto aunque la transacción original haya sido abortada.

Herramientas y documentación que sí vale la pena leer

Si quieres profundizar con fuentes oficiales, estas referencias ayudan bastante:

No necesitas adoptar una base específica para aprender el concepto. Pero sí conviene leer cómo cada motor implementa la garantía, porque serializable no se comporta exactamente igual en todos.

Qué gana tu equipo cuando deja de temerle

Cuando tú y tu equipo entienden serializable, dejan de ver la base como una caja negra que “a veces falla” y empiezan a tratar la concurrencia como parte normal del diseño. Eso cambia la conversación interna. En vez de parchear bugs después de producción, puedes definir invariantes antes de escribir el endpoint.

También cambia la forma de revisar código. Ya no preguntas solo si una consulta es correcta, sino si dos ejecuciones simultáneas pueden dejar el sistema en un estado inválido. Esa pregunta es mucho más útil cuando manejas dinero, inventario o cualquier dato que no tolera inconsistencias.

Y sí, serializable puede ser más exigente. Pero muchas veces el costo de no usarlo aparece en forma de incidentes raros, tickets difíciles de reproducir y horas perdidas buscando una condición de carrera que solo ocurre a las 3 de la mañana. Si tu negocio depende de datos correctos, ese costo oculto suele ser más alto que el de unas cuantas transacciones abortadas.

Tabla resumen

Pregunta cortaRespuesta corta
¿Qué garantiza serializable?Que el resultado equivale a ejecutar las transacciones una por una.
¿Qué bug ayuda a evitar?Anomalías de concurrencia como write skew y lecturas inconsistentes.
¿Por qué no es el default en todas las bases?Por compatibilidad, rendimiento y aplicaciones que dependen de otros niveles.
¿Cuándo sí conviene?En pagos, inventario, cuotas y reglas de negocio críticas.
¿Qué necesitas para usarlo bien?Reintentos, idempotencia y pruebas de concurrencia reales.
¿Es igual en todas las bases?No, cada motor lo implementa con matices distintos.

Al final, la pregunta no es si serializable da respeto. Claro que lo da. La pregunta útil es si prefieres ese respeto o prefieres perseguir bugs sutiles que solo aparecen cuando ya afectaron a usuarios reales. En sistemas donde una inconsistencia cuesta dinero o confianza, la respuesta suele ser bastante clara.

Preguntas frecuentes

¿Serializable siempre es la mejor opción?
No siempre. Si tu sistema tolera inconsistencias temporales y prioriza throughput, un nivel más bajo puede ser suficiente. Pero si hay invariantes críticas, serializable suele darte una protección más sólida contra bugs de concurrencia.
¿Serializable significa que la base bloquea todo?
No necesariamente. Algunas bases usan validación de conflictos, MVCC o bloqueos selectivos para lograr la misma garantía lógica. Lo importante es el resultado final, no el mecanismo interno.
¿Qué es write skew en palabras simples?
Es cuando dos transacciones, cada una correcta por separado, juntas rompen una regla del negocio. Pasa mucho en casos donde ambas leen el mismo estado y luego escriben decisiones compatibles entre sí, pero inválidas en conjunto.
¿Qué tengo que cambiar en mi app para usar serializable?
Normalmente necesitas manejar reintentos, hacer operaciones idempotentes y revisar efectos externos como emails o cobros. También conviene probar con carga concurrente real para ver dónde aparecen abortos.
¿Serializable afecta mucho el rendimiento?
Depende del patrón de acceso y del motor. En sistemas con pocos conflictos puede ir muy bien, pero en hotspots con muchas escrituras concurrentes sí puede aumentar abortos o latencia. Por eso conviene medir antes de asumir.
¿Por qué algunas bases no lo activan por defecto?
Porque cambiar el default puede romper aplicaciones que ya dependen de comportamientos más débiles. Muchas bases priorizan compatibilidad hacia atrás para no afectar sistemas existentes, aunque eso implique menos garantías.
¿Cómo detecto si tengo una anomalía de concurrencia?
Busca bugs que solo aparecen con varias solicitudes simultáneas, sobre todo en flujos de saldo, stock o cupos. Si el error desaparece cuando pruebas una sola transacción a la vez, es una señal fuerte de carrera.

Azirgo

¿Listo para construir tu Producto Digital?

Sitios web, apps móviles, software a medida y soluciones blockchain. Cuéntanos qué tienes en mente y armamos un plan claro contigo.

  • Cotización clara en 48 horas
  • Equipo en Ecuador, atención en español
  • Desde un MVP hasta un producto en producción