Continúo con el tema del noSQL. Técnicamente, no se si realmente el uso de transacciones no ACID, entra en el movimiento noSQL, pero la verdad es que normalmente van de la mano. Además hay una identificación muy estrecha entre transacciones ACID y las bases de datos tradicionales (aunque esto no es necesariamente cierto).
Todos hemos aprendido desde chiquititos lo que son las transacciones ACID y lo bondadosas que son. Para refrescar un poco:
- A de Atomic. Las transacciones se ejecutan correctamente o no. Si fallan es como si ni siquiera se hubieran intentado ejecutar.
- D de Durability. El resultado de las transacciones es un cambio en el estado del sistema persistente. Si apagamos la máquina y la arrancamos de nuevo el cambio producido por la transacción aun está, persiste.
- C de Consistencia. Un sistema consistente es uno que garantiza que cada vez que se hace un cambio en el estado del sistema, a partir de ese momento, éste reportará el nuevo valor del estado a todos los clientes que lo soliciten, mientras no se vuelva a cambiar explícitamente el dato.
- I de Isolation. Cuando varias transacciones se ejecutan en paralelo, cada una de ellas ve el sistema de la misma manera a como lo vería si se ejecutaran de forma aislada o secuencial.
Esto es algo básico que se enseña en todas las universidades. Sin embargo no suelen enseñar en la facultad el teorema CAP (al menos a mi no). El teorema CAP viene a decir, de forma resumida, sólo puedes tener dos de las siguientes tres propiedades, pero no las tres a la vez:
- Consistencia fuerte (la C en ACID y CAP). En el sentido de la transacciones ACID. Todos los clientes ven la misma versión de los datos, incluso en presencia de actualizaciones, de forma consistente.
- Disponibilidad o Availability (la A de CAP pero no en ACID). El sistema está disponible y responde en un tiempo adecuado a todos los clientes. Este concepto está muy relacionado con el tiempo de respuesta. Si el tiempo de respuesta excede un umbral, el sistema se considera no disponible. Este umbral puede ser el timeout de un socket, pero también puede ser la paciencia del usuario.
- Tolerancia a fallos (la P en CAP, en inglés Partition Tolerance). Incluso en presencia de fallos en el servidor, todos los clientes pueden tener servicio y poder acceder a los datos. Un sistema tolerante a fallos sigue funcionando aunque uno de sus servidores se caiga o se corten algunas de las conexiones de red entre servidores. Un sistema completamente tolerante a fallos sólo puede dejar de funcionar si caen todos los servidores o se pierde el contacto con todos ellos.
El teorema CAP nos dice que no hay nada bueno, bonito y barato en el mundo de la persistencia, y que debemos reflexionar sobre que propiedades queremos escoger. Hay que enfatizar que no tiene que ser una decisión de todo o nada. Podemos sacrificar algo de consistencia en vez de toda la consistencia. También podemos sacrificar un poco de disponibilidad, que en la práctica es permitir que se degrade el tiempo de respuesta e incluso que algunos clientes pierdan disponibilidad por un tiempo limitado. O bien se puede decidir perder algo de tolerancia a fallos, que en la práctica significa tolerar una cantidad limitada determinada de fallos pero nada por encima de un límite.
La forma en que podemos conseguir tolerancia a fallos es tener muchos nodos de forma que si cae uno haya otro capaz de tomar el relevo. Si queremos un sistema tolerante a fallos y que además tenga consistencia absoluta, debemos replicar los datos entre los nodos cada vez que hagamos una escritura. Al escribir, todos los nodos deben replicarse y debemos esperar hasta que este hecho suceda para poder dar por terminada la operación. Esto como es normal hace que la operación sea tanto más lenta cuanto más nodos tengamos, lo que degrada el tiempo de respuesta y por lo tanto la disponibilidad. Otro efecto negativo es que aumenta la probabilidad de que la operación no se produzca con éxito, ya que debemos escribir en todos los nodos, y alguno puede estar caído, y para que la operación sea exitosa debe escribir en todos los nodos. La probabilidad de que al menos un nodo esté caído aumenta con el número de nodos totales del sistema, con lo que cuanto más nivel de replicación tenemos, más probabilidad de fallar en la operación y por lo tanto de falta de disponibilidad. Esto va en contra de la disponibilidad del sistema. Como contrapartida sólo necesitamos leer de un nodo, con lo que en lectura tenemos gran rendimiento, disponibilidad y tolerancia a fallos. Las bases de datos tradicionales suelen usar el enfoque anteriormente expuesto como mecanismo de tolerancia a fallos.
Si queremos aumentar la disponibilidad, la operación debe terminar antes, lo que implica que no podemos esperar a que se repliquen todos los nodos, sino sólo uno o quizás unos cuantos pero no todos. El hecho de que la operación termine antes de que todos los nodos se repliquen aumenta la disponibilidad pero a su vez degrada la consistencia al no tener todos los nodos la misma información. Así si un cliente lee de un nodo que pudo ser replicado no tendrá problema, pero si lee de un nodo que aún no ha sido replicado se producirá una lectura inconsistente con la escritura anterior. Normalmente los sistemas que permiten sacrificar consistencia por disponibilidad, tiempo de respuesta y tolerancia a fallos implementan un modelo de consistencia llamado eventually consistency (consistencia eventual). En sistemas con consistencia eventual, si bien al terminar una operación el sistema puede estar inconsistente, se garantiza que al cabo de un tiempo el sistema quedará en un estado consistente, es decir, que eventualmente se conseguirá un estado consistente. Cuanto más tiempo pase entre la escritura y la lectura más probabilidad habrá de que el sistema sea consistente. No se puede especificar un tiempo mínimo para que se produzca esta consistencia ya que el sistema puede sufrir algún fallo (congestión de red, caída de un nodo, etc).
El hecho de que podamos tener inconsistencia complica la lógica de los clientes ¿Cómo podemos detectar y resolver problemas de consistencia? Para explicarlo os pongo un escenario:
- Un cliente escribe un dato con un grado de consistencia intermedio. Cuando acaba la operación está garantizado que la mitad de los nodos más uno están actualizados.
- Otro cliente lee la misma entidad que el anterior modificó. Si leyera con consistencia baja, es decir de sólo un nodo, es probable que lea un dato antiguo. Esto en algunos sistemas donde la consistencia no es importante no es problemático. Si necesitamos un nivel intermedio de consistencia sí es un problema ya que no somos capaces de detectar que la lectura es inconsistente.
- Otro cliente más hace una lectura de dicha entidad, pero esta vez con consistencia intermedia (mitad nodos más uno). De esta manera se asegura que leerá de al menos un nodo actualizado, recordad que la escritura garantizó que se escribió en la mitad de los nodos más uno, con lo que en el peor de los casos el conjunto de los nodos de los que se lee y en los que se escribió tendrán un nodo en común. Todo esto implica que el cliente va a recibir dos versiones del mismo dato, la antigua obtenida de los nodos no actualizados, y la nueva obtenida de los nodos actualizados. Es decir, al contrario que en un sistema de persistencia tradicional, el sistema nos puede devolver más de una versión del mismo dato. Si esto ocurre el cliente se da cuenta de que se ha producido una inconsistencia, ya que si no se hubiera producido sólo nos devolvería una versión (a condición de que la escritura se haya hecho con consistencia intermedia).
Aunque en el escenario me centro en la lectura, la inconsistencia también puede presentarse en la escritura, en el caso en el que intentásemos escribir en varios nodos, cada uno con una versión diferente. Como vemos, en sistemas con grados de consistencia no estricto, el cliente tiene la posibilidad de detectar inconsistencias, pero por contra le queda la papeleta de resolver el conflicto entre varias versiones. A algunos les parecerá esto una torpeza, ¡ pero a mi me parece genial ! Esto quiere decir que la resolución de conflictos queda en mano de reglas de negocio en vez de en algoritmos genéricos (que es lo que en realidad hacen las bases de datos tradicionales de forma encubierta). Tal vez nos dé más trabajo programar estas reglas de negocio, pero como son específicas para cada situación de nuestro sistema, seguro que son más eficientes. Algunos algoritmos típicos:
- Ante conflictos de consistencia fallar siempre. Regla sencilla pero matona.
- En caso de escritura y conflicto, sobrescribir siempre (éxito asegurado).
- Si la consistencia me da igual cojo una versión al azar. Si este es el caso es mejor realizar la operación con un grado de consistencia mínimo (un solo nodo) que es más eficiente.
- Escojo la versión más reciente. Normalmente los sistemas basados en consistencia eventual te proporcionan un timestamp de cual es el dato más reciente. Si no a podemos implementarlo manualmente, poniendo en cada dato una fecha de cambio.
- No resuelvo y le paso todas las versiones al cliente en un formato amigable para el usuario. Esto se va pareciendo a un sistema de control de versiones…
- Fusiono (merge) las varias versiones usando alguna regla de negocio. Lo dicho, un sistema de control de versiones, Enrique, que se te va la olla.
Bueno, espero que el cuerpo se os haya quedado bien.
Otro hecho interesante de este tipo de sistemas es que podemos especificar un grado de consistencia diferente a las operaciones de lectura y escritura. Esto nos permite optimizar el rendimiento de las escrituras o de las lecturas en función de lo que nos interese.
Obviamente podemos tener una vía alternativa: sacrificar la tolerancia a fallos y tener sólo un nodo. De esta forma sólo se necesita escribir y leer del único nodo del sistema, con lo que conseguiremos consistencia y disponibilidad mientras el nodo no se caiga. Ni que decir tiene que estos sistemas tienen tolerancia a fallos cero. Realmente este es el enfoque clásico de una base de datos tradicional sin tolerancia a fallos. Es interesante que este enfoque no está totalmente libre de la degradación del tiempo de respuesta o de la disponibilidad aunque no se haya producido ningún fallo. Pensad en lo que ocurre si dos clientes quieren ejecutar dos transacciones concurrentes que afectan al mismo dato. En este momento se produce un conflicto. Si forzamos ACID estricto, podemos invocar la propiedad de aislamiento transaccional y decidir serializar las transacciones ya que intentan acceder al mismo dato (en BBDD relacionales, la misma fila). Para ello se suele implementar un mecanismo de bloqueo a nivel de fila, aunque también es típico bloqueo a nivel de tabla. Bien este mecanismo se suele llamar concurrencia pesimista, y puede llevar a la degradación del tiempo de respuesta o incluso a la indisponibilidad del sistema con alta carga de transacciones concurrentes. Recordad que esto está pasando en un sistema con un único nodo, es decir con tolerancia a fallos cero. Los fabricantes de base de datos lo saben y por ello te permiten relajar la consistencia mediante una disminución en el grado de aislamiento de las transacciones. Sin entrar en detalles, diré que si relajamos el grado de aislamiento, podemos usar concurrencia optimista, que consiste en no bloquear filas, y al escribir comprobar si la versión que está en la BBDD es la misma que la versión que leímos al principio de la transacción. En caso de que sea así no hay conflicto. En caso de que sean distintas versiones se produce un conflicto y hay que solucionarlo. Los algoritmos para solucionarlos son exactamente los mismos que los expuestos anteriormente: fallar, sobrescribir, fusionar automáticamente o fusión manual por parte del usuario. Como veis si tenéis un sistema de persistencia tradicional, no tolerante a fallos, y queréis escalar, tenéis que bajar el aislamiento transaccional y usar concurrencia optimista, lo que os lleva también a la necesidad de gestionar conflictos e inconsistencias en la aplicación.
El objetivo de diseño de un sistema tradicional siempre ha sido favorecer la consistencia sobre las otras propiedades del sistema. Esto nos lleva al dilema de o bien sacrificar la disponibilidad o la tolerancia a fallos. Es decir si queremos un sistema disponible y consistente, que responda a todos los clientes en un tiempo razonable, no puede ser tolerante a fallos y por lo tanto no se puede caer ningún servidor o línea de comunicación. Si tenemos un sistema tolerantes a fallos y consistente, y se produjera un fallo de red o la caída de una máquina, el sistema seguiría funcionando, pero o bien el tiempo de respuesta se degradaría o algunos clientes perderían el servicio temporalmente. Estos compromisos entre disponibilidad y tolerancia a fallos pueden ser aceptables en aquellas aplicaciones donde la consistencia es crítica, y el coste de un fallo en la consistencia es mayor que el coste de estar un tiempo sin servicio o que el sistema responda muy lentamente.
Sin embargo en algunos escenarios esto no es aceptable. Por ejemplo en una tienda online, la perdida de servicio es desastrosa, ya que equivale a cerrar completamente el negocio y es mucho más costoso que una inconsistencia puntual de vez en cuando. También es costoso que el usuario de una tienda online perciba un tiempo de respuesta muy lento o que no pueda entrar de vez en cuando. En estos sistemas se requiere una tolerancia a fallos y disponibilidad a toda costa, con lo que se relajan los requisitos de consistencia. Es una pura decisión práctica guiada por las fuerzas de mercado.
En un sistema que deba ofrecer información en tiempo real, la degradación del tiempo de respuesta es catastrófica ya que hace que el sistema deje de ser útil. Por lo tanto o se sacrifica la consistencia o la tolerancia a fallos. En el caso de que el sistema sea crítico se sacrifica la consistencia.
Normalmente en el negocio de los sistemas web a gran escala lo que realmente se necesita es disponibilidad, bajo tiempo de respuesta y tolerancia a fallos. Google, Amazon o eBay no pueden permitirse estar caídos. Twitter, Facebook o LinkedIn pueden tolerar grandes grados de inconsistencia pero no una caída o una degradación del rendimiento. No es de extrañar que éstas empresas hayan abandonado total o parcialmente el modelo de bases de datos tradicionales con transacciones ACID, y hayan investigado en sistemas de persistencia alternativos.
En este apartado merecen una mención especial Dynamo de Amazon y Cassandra (de Apache y facebook). Este último está basado en el propio Dynamo mezclado con BigTable (Google). En Cassandra puedes especificar al leer o escribir el nivel de consistencia que quieres tener en la operación. El nivel de consistencia más bajo es 1, que indica que al leer o escribir te basta con completar la operación en uno de los nodos. También puedes realizar operaciones con máxima consistencia, donde todos los nodos deben replicarse al escribir o bien responder en la lectura. Por último tiene un modo intermedio de consistencia llamado de quorum, donde se busca mayoría simple (la mitad más uno) para considerar una operación completada. Además tanto Dynamo como Cassandra implementan consistencia eventual y tolerancia a fallos mediante una arquitectura distribuida que no necesita de máquinas especiales. Basta con poner una buena cantidad de PCs para tener un sistema muy robusto. Ellos claman que no se necesitan procedimientos de instalación y explotación complejos pero dada mi falta de experiencia con estos productos en este sentido no os lo puedo asegurar.
Así que ya sabéis, si necesitáis un sistema a prueba de bombas, que esté siempre disponible con tiempos de respuesta decentes, y que escale a volúmenes de carga grande (decenas o cientos de miles de usuarios), id pensando en sacrificar consistencia y pasaros a la consistencia eventual, pero, ¡ apretaos los cinturones de seguridad !
Muy interesante el post.
Justo estoy empezando (muy lentamente) a investigar un poco en el tema y pensaba probar con MongoDB, que en principio mantiene propiedades ACID, aunque algunas las implementa a su manera como la D(urability), viniendo a decir que es recomendable hacer snapshots y montar un cluster, lo que puede ser una opción más o menos lógica si a cambio te garantiza las otras propiedades.
http://nosql.mypopescu.com/post/392868405/mongodb-durability-a-tradeoff-to-be-aware-of
Otra base de datos del estilo en cuanto a que más o menos mantiene ACID parece que es CouchDB: http://www.mikeperham.com/2009/09/01/comparing-document-oriented-databases/
Todo esto está casi empezando pero creo que habrá proyectos en los que sea muy útil mantener estas propiedades pudiendo combinarlo con el almacenamiento documental, y otros con necesidades brutales de rendimiento que hagan que sea necesario algo como Dynamo o Cassandra, y que al final todas las bases de datos que se engloban en NoSQL se irán diviendo en varios segmentos.
Ánimo con el blog!
Buenas 😉
Lo primero felicitarte por el artículo, me ha parecido genial. Todos nacimos en el mismo universo entidad-relación y solemos ser reacios a otros paradigmas pero la distribución de máquinas junto con la evolución de la velocidad de interconexión hace cada vez más viables soliciones como las que hablas. Tengo que profundizar mucho más en el tema y tus artículos sobre noSql me parecen una buena referencia. Sigue así! 😉
Un saludo
¡Gracias por los ánimos! A ver si mantengo el nivel. Sobre noSQL pienso escribir un par de posts más, ya algo más concreto. Tal vez sobre el modelo de datos de Cassandra, o sobre como usar map/reduce y CouchDB
Just want to say what a great blog you got here!
I’ve been around for quite a lot of time, but finally decided to show my appreciation of your work!
Thumbs up, and keep it going!
Cheers
Christian
For NoSQL databases to survive, they will have to offer the same stability and reliability as their RDBMS counterparts. You are not going to convince a major e-commerce shop to use a database that is guaranteed to lose data.
While Twitter, Facebook and LinkedIn can afford to drop records. The Home Shopping network cannot. A hospital cannot. The business world requires reliability, proven technologies and proven methodologies. «Educating» developers about the acceptability of under-engineered, ineffective technologies does a disservice to the developer community.
RDBMS like MS SQL or Oracle have extremely complex architectures for a reason. While NoSQL looks extremely promising, the technologies are still immature for many use-cases.
Hi Darryl, your last paragraph is precisely the key point in my discussion. You must use the persistence system that best fits your application scenario. Sometimes a traditional RDBMS system is perfect, but sometimes not. Using always a SQL RDBMS system is not a good practice. Perhaps the hospital scenario is best suited to a SQL based system. The online shopping example is not so clear. Think that Amazon, one of the most successful online shops, invested money to create its own noSQL system, called Dynamo, which is one of the references in the noSQL movement. Clearly the Amazon guys thought that mySQL or an Oracle system was not the solution for them.
One of the points in noSQL is trading consistency for availability and partition tolerance. Depending on your concrete business requirements one trade off is better than others. Sometimes SQL systems do not offer the desirable trade off. Keep in mind that relaxing consistency does not necessarily mean losing data. It often means that a write in the system is not instantaneously seen by all clients performing reads, but it takes some amount of time to make the changes visible to every client. This behaviour is called «eventual consistency» and do not lose data.
The reliability and stability of a system, do not depend on if we are using noSQL or a traditional approach, but on the implementation quality of the persistence system used. I really cannot see how systems used by Google or Amazon are unreliable or unstable. Of course some vendors could think the noSQL movement is against their interest, and make a marketing campaign against this. Other vendors will simply embrace the noSQL approach and launch products of their own based in this approach. We’ll see.
To sum up, engineering must not be a tradition guided discipline, choose the persistence system best suited to your problem. I think that «educating» developer in «flexible thinking», «design the most simple solution that can work» and «don’t use always the same tool but the best one for your problem» is not a disservice to the developer community. I always encourage people to think for their own and give intelligent solutions to the problems.
[…] to your data set John P. Wood NoSQL posts Databases: relational vs object vs graph vs document NoSQL: no necesitas ACID NoSQL and SQL anti-patterns Learning NoSQL from Twitters […]
Por lo que veo las BD NoSQL son herramientas de persistencia que sólo son útiles a gran escala.
¿Podría ser Cassandra una opción para el sistema de un hotel mediano? ¿Podría mongoDB funcionar en mi BD para una clínica pequeña?
¿Puede funcionar Cassandra/mongoDB/CouchDB en un sistema con un solo nodo?
Desde mi punto de vista noSQL es simplemente una alternativa a los sistemas de persistencia tradicional. No todos los sistemas noSQL están orientado a la escalabilidad, muchos sí, pero otros se orientan a la sencillez de uso (comparado con un sistema SQL). En cualquier caso los que se orientan a la escalabilidad, tienden a emplear un enfoque de escalabilidad «elástica», es decir, pueden correr en un único nodo sin problemas, probablemente consumiendo menos que una BBDD tradicional (aunque esto hay que comprobarlo), y se les puede ir añadiendo nodos adicionales en caliente para aumentar la cantidad de peticiones que puedan procesar.
Por lo tanto yo te diría que efectivamente sí puedes usar un mongoDB o un CouchDB o un Cassandra en un sistema con un solo nodo. Su rendimiento no va a ser peor que un sistema SQL tradicional, y tal vez sea hasta mejor. Cassandra es el más complejo de utilizar, te da menos herramientas como programador, y es un poco de bajo nivel, pero es sin duda el más elástico y robusto. MongoDB es muy sencillo y fácil de empezar a usar, pero bastante menos escalable que Cassandra. CouchDB es algo más intermedio. Tu elijes.
[…] entre los datos reales del sistema, y los ofrecidos por las consultas. De nuevo estamos ante el teorema CAP. Veámoslo a […]
Tremendo artículo. Excelente la forma directa y clara de encarar los temas, me parece que así se debe escribir.
Gracias
IVM
Hola chicos!
Cuando hablamos de consistencia fuerte, en el contexto de replicación, nos referimos
a que siempre obtenemos valores consistentes (los mismos valores) de la base de
datos, aunque algunas réplicas puedan almacenar valores inconsistentes?