Feeds:
Entradas
Comentarios

Archive for May 2010


Como no puedo atender temporalmente este blog como es debido, os dejo una encuesta para que os vayáis entreteniendo.

Por supuesto dejad los comentarios que queráis, yo vuelvo pronto.

Read Full Post »


Aquí estoy de nuevo, esta vez con un post tecnológico. Voy a tratar de explicar en pocas palabras (a ver si no me sale un nuevo truño-post) lo que son los DVCS, que ventajas tienen y si nuestra alma corre algún peligro al usarlos.

Un Distributed Version Control System (DVCS) o en castellano, sistema de control de versiones distribuido, es la última moda en control de versiones de código, y al contrario de lo que yo pensaba en un principio, de distribuido tienen poco, de hecho es un nombre engañoso. Un DVCS no es más que un repositorio de código que puede sincronizarse con otros repositorios, pudiendo actuar tanto como cliente, tanto como servidor de otros repositorios. Lo más sencillo es comparar los DVCS con los tradicionales repositorios de código centralizados. Mantendré la discusión en este post a un nivel sencillo, de forma que cualquier usuario de repositorios tradicionales pueda entenderlo, para ello haré algunas simplificaciones, por favor, gurus del Git, no me crucifiqueis.

Tradicionalmente hemos usado repositorios centralizados. Un servidor central al que conectábamos nuestros entornos de trabajo. Cuando queríamos traer código desde el repositorio central a nuestro entorno de trabajo local, hacíamos un check out o update. Cuando queríamos mandar nuestros cambios al repositorio remoto hacíamos un check in o commit. Si queremos añadir nuevos ficheros o proyectos al repositorio central hacemos un share o add ¿Cómo hacemos todo ésto en nuestro nuevo y flamante DVCS? Pues exactamente igual: add, commit y update. Sí señor, en esto los DVCS son como cualquier otro repositorio.

En cuanto a los repositorios centralizados, los había principalmente de dos tipos:

  • Bloqueantes. Para poder trabajar con un fichero necesitas abrirlo en edición. Esto bloquea el fichero e impide que otro compañero, uy, quise decir usuario, pueda abrirlo en edición y trabajar con él. Cuando has terminado haces commit y el fichero se desbloquea. También puedes descartar cambios y desbloquear. La lógica detrás de este tipo de sistemas se basa en intentar disminuir la cantidad de conflictos y en la idea de que ciertos artefactos de código pertenecen a ciertos desarrolladores y nadie debería poder tocarlos (private code ownership). Con estos sistemas tienes menos conflictos, aunque también puede aparecer alguno de vez en cuando. También te lo pasas pipa cuando alguien se deja el fichero bloqueado y cae enfermo, se da de baja, está de vacaciones o en una reunión. Como veis este tipo de sistemas no promueven el agilismo.
  • No bloqueantes. Los más famosos son el CVS y el SVN. Simplemente te actualizas el fichero, trabajas sobre él, y haces commit de los cambios. Si tienes suerte no hay conflictos. Si no, tienes un conflicto, alguien modificó el fichero y lo subió antes que tu. Normalmente los conflictos se resuelven con un merge automático, pero a veces necesitas hacer un merge manual. Si usas integración continua no tendrás mucho problema, ya que al integrar cambios más frecuentemente, la cantidad de código sobre los que pueden haber conflictos es pequeña, con lo cual la probabilidad de éstos disminuye y la facilidad de resolverlos aumenta. De hecho la probabilidad de un conflicto y la facilidad para hacer el merge aumentan exponencialmente con la cantidad de código a integrar, lo que hace que si no usas integración continua seas hombre muerto. Si no usas integración continua, es mejor que uses un sistema bloqueante, y por lo tanto no podrás ser ágil.

Los DVCS son no bloqueantes (al menos todos los que yo conozco). Como veis los DVCS ese parecen mucho a un SVN o CVS, ¿dónde está la diferencia? Muy sencillo, los DVCS introducen dos nuevas operaciones:

  • La operación push. De la misma manera que podemos enviar cambios con un commit desde nuestra área de trabajo al repositorio, podemos enviar los cambios que hay en un repositorio hacia otro repositorio, si tenemos permisos claro. Esto puede generar que en el repositorio destino se tengan conflictos, es decir, distintos cambios sobre el mismo artefacto provenientes de distintos repositorios.
  • La operación pull. De la misma manera que actualizamos nuestro entorno de trabajo local con el contenido del repositorio central, podemos traer cambios desde otro repositorio hacia el repositorio origen del pull. Es la operación simétrica al push, y puede generar conflictos en el repositorio origen del pull.

Las operaciones push y pull pueden tener un commit en el destino implícito o no, dependiendo del DVCS que usemos y del comando exacto que lancemos. En caso de que no tengan commit implícito, debemos hacer commit manualmente si queremos aceptar los cambios.

Como hemos visto tanto el push como el pull pueden generar conflictos, que deben ser resueltos con merge. En los DVCS los algoritmos de merge están optimizados y suelen resolver automáticamente más conflictos que en un SVN o CVS, y tardan menos en ejecutarse.

Armados con estos conceptos podemos entender como se trabaja con un DVCS. Lo que se suele hacer es que cada desarrollador tenga uno o más repositorios instalados en su máquina local. Esto permite a cada miembro del equipo trabajar en local, con las consiguientes ventajas:

  • Se puede trabajar sin necesidad de conectividad, ya que el repositorio está en local.
  • Mayor rendimiento, ya que no hay necesidad de tráfico de red.
  • Mayor escalabilidad. No hay un servidor central que deba soportar la carga concurrente de distintos usuarios.
  • Sencillez en la gestión de los permisos. Cada desarrollador local sólo necesita dar permisos a los compañeros en los que confía.
  • No hay miedo a hacer commit, ya que el repositorio es local al desarrollador. Este miedo es sustituido por miedo a hacer pull o push, ya lo veremos en un momento.

¿Y lo de distribuido? Pues simplemente cuando dos desarrolladores quieren compartir código o integrar trabajo, hacen pull o push entre ellos.

¿No hay un repositorio central? ¿Quién controla el código del producto? Bueno, puedes tener un repositorio central si quieres, al que todos los repositorios locales entreguen código con pull o push. Pero puedes tener la topología o arquitectura de repositorios que quieras. De hecho es muy importante elegir la arquitectura de repositorios adecuada a implantar en tu proyecto. Yo recomiendo seguir la misma estructura que tienes en tu equipo de trabajo:

  • Si tienes un equipo plano, típico de Scrum, puedes usar una topología peer to peer. Todos los repositorios intercambian código entre si. Adicionalmente puedes añadir un repositorio principal de equipo y otro para las releases estables.
  • Si tienes un equipo jerárquico usa una topología jerárquica. Existe un repositorio central bendito (blessed) controlado por un jefe de proyecto. Este jefe de proyecto tiene lugartenientes con un repositorio cada uno. El jefe sólo incluye código en el repositorio central desde los repositorios de sus lugartenientes, y éstos desde los repositorios de sus subordinados.
  • Si tienes un equipo grande de Scrum, dividido en varios feature teams, cada feature team tendrá su topología de repositorios peer to peer, con un repositorio central por equipo. Los distintos repositorios centrales de los distintos equipos pueden conectarse a su vez en topología peer to peer o en modo jerárquico contra un repositorio principal de proyecto.

En general doy el mismo consejo que con la elección de estructura de equipo, si tienes varios repositorios, en una red rápida, y que deban intercambiar información frecuentemente, conéctalos en peer to peer para facilitar el intercambio de información y no tener intermediarios. Si el número de repositorios ya es grande, puedes añadir un nivel de jerarquía. A mi, en cualquier caso, me gusta tener siempre un repositorio central para cada equipo, y otro por proyecto si hubieran varios equipos.

¿Y como hacemos backup si no hay repositorio central? En esta forma de trabajar, el código importante e interesante, suele estar distribuido entre distintos repositorios. Esto hace que el sistema sea naturalmente tolerante a fallos. Esto no impide que hagas backup de repositorios que consideres especiales o que cada desarrollador haga backup del suyo. La política de backup depende de la topología escogida para los repositorios. Si usáis un repositorio por equipo y/o un repositorio por proyecto podéis hacer backup formal de éstos.

Seguro que todo esto tiene alguna pega… Cierto, al trabajar cada desarrollador en un repositorio diferente, se produce de forma implícita un branching agresivo del código. Es como si usáramos un repositorio centralizado, y cada desarrollador tuviera una rama diferente. Pensad en los posibles conflictos que pueden producirse cuando múltiples desarrolladores hagan push de sus cambios al repositorio del equipo o proyecto. Psicológicamente, el desarrollador, al trabajar contra su repositorio local, puede tener una falsa sensación de seguridad al tener un control de versiones respaldando sus cambios, y olvidarse de hacer push de sus cambios a sus compañeros o al repositorio del equipo. Por otro lado va a tener miedo, y ser renuente, a hacer pull de los cambios de los demás, no vaya a ser que le provoquen conflictos y le rompan su repositorio. Bien, no nos desesperemos, la solución es sencilla, entregar los cambios al repositorio de equipo de forma frecuente, es decir usar integración continua. Si usamos DVCS y no usamos integración continua estamos perdidos.

¿Espera un momento, integración continua dices, pero si no tenemos un repositorio central? Exacto, lo que realmente hace falta es… integración continua distribuida (o incremental) Este invento consiste en poner un demonio de integración continua en cada repositorio. Cada vez que se produce un commit, pull o push sobre el repositorio, el demonio se despierta y mira si hay conflictos. En caso de que los hubiera, intenta hacer un merge automático. Si el merge automático falla, el build se considera roto. Si funciona o no hubiera conflictos, se ejecuta un proceso de integración continua normal (compilación, tests, etc). Si el build es exitoso, entonces se envía una petición a un repositorio de más alto nivel, para que acepte los cambios. Este repositorio de más alto nivel suele ser el de equipo o el del proyecto. Si se aceptan los cambios (de forma manual o mediante una regla de decisión automática), se hace un pull para recibir los cambios, lo que vuelve a disparar el proceso de integración continua sobre éste repositorio. El proceso se repite recursivamente hasta que el build falla o se hace un build con éxito en el repositorio central de proyecto. Una idea interesante es mantener los builds de los repositorios de nivel de desarrollador sencillos y veloces, por ejemplo, sólo compilación y tests unitarios. El nivel de calidad exigido en los builds va creciendo conforme se va subiendo a repositorios de mayor nivel. Por ejemplo el repositorio de equipo puede exigir tests de integración para todas las historias de usuario. El repositorio de proyecto puede pasar una suite exhaustiva incluyendo despliegue en un servidor y tests de aceptación contra una infraestructura más real. De esta manera el build en local se mantiene ágil, y a su vez los cambios que van recibiendo los repositorios de mayor nivel tienen una mayor calidad, ya que han pasado los procesos de build continuo de los niveles anteriores.

Una etapa de Integración continua distribuida

Una etapa de Integración continua distribuida

Algunos estaréis pensando que este proceso de integración continua distribuida es demasiado complejo para ser implementado. Otros quizás penséis que es imposible poner un demonio para cada repositorio. Yo simplemente os digo que hace tiempo, cuando explicaba la integración continua simple, esto es, asociado a un repositorio centralizado, muchas veces me miraban con incredulidad y se solían escuchar las misma protestas: «demasiado complejo…», «demasiado para nuestros servidores…», «eso se va a caer…», «se va a formar un pollo de narices…», etc.

Otro punto a tener en cuenta, es que la integración continua simple, está siendo sustituida por integración continua multietapa, en organizaciones grandes, con repositorios centralizados. En la integración continua multietapa se realizan más de un proceso de integración continua de forma secuencial, donde cada etapa implica un nivel de calidad más estricto que el anterior. El objetivo es aumentar la escalabilidad del sistema de integración continua, ya que no se lanzan los procesos de integración continua más estrictos, y por lo tanto más pesados, sin tener un mínimo de seguridad de que vayan a tener éxito. Este mínimo de seguridad se consigue ejecutando antes procesos de integración menos estrictos y más ligeros. Si lo pensais es algo muy similar al proceso de integración continua distribuida que os comento anteriormente, pero adaptado a un entorno de desarrollo con repositorio centralizado.

Yo veo varias ventajas en la aplicación de un proceso de integración continua distribuida en un entorno de DVCS:

  • El desarrollador pierde el miedo a hacer pull y/o push de otros repositorios al suyo. La integración continua distribuida baja aun más la probabilidad de conflicto por las mismas razones que la IC convencional.
  • Mayor escalabilidad. La integración continua deja de ser un proceso centralizado, ya no hay un servidor centralizado de integración continua, sino una red de procesos distribuida. Podemos tener equipos más grandes trabajando sin necesitar un super servidor.
  • Mayor tolerancia a fallos. Por las mismas razones que antes, no hay dependencia de un servidor central, lo que hace que podamos seguir integrando código.
  • Si el build se rompe, esto es, que no pasa los test, no compila o simplemente no cumple una nivel de calidad mínimo, el resto del equipo puede seguir integrando en otro repositorio mientras se arregla el build. En la integración continua tradicional si el build no pasa, el repositorio central se bloquea para evitar que alguien suba y fusione cambios con una base de código que no funciona. El equipo que había cometido el fallo debe arreglar el build cuanto antes, pero el resto de los desarrolladores ya no pueden seguir integrando. Esto puede generar miedo a subir cambios al repositorio central y también disminuir la frecuencia efectiva de integración. En un sistema distribuido esto no pasa, ya que se puede seguir integrando contra otro repositorio.

Para alcanzar el máximo de estas ventajas hay que montar una topología de repositorios donde no haya repositorios especiales o blessed.

Por último comentaros que hay dos DVCS distintos que pegan fuerte, que son:

  • Git. Escrito en C, es el que tiene más rendimiento y el más flexible de todos. Sin embargo para la gente que viene de SVN puede ser un poco complejo. Destaca que mediante una hash criptográfica, Git, permite detectar si el contenido del repositorio se ha corrompido o ha sido víctima de un ataque malicioso. Se ha usado con éxito para guardar el código del kernel de Linux.
  • Mercurial (Hg). Más sencillo que Git y escrito en Python, es el que tiene de momento, mejor integración con Windows y con herramientas JAVA. No es tan eficiente como Git y no tiene el concepto de ramas. En vez de ello, puedes clonar repositorios, que actúan como ramas. El clonado no implica una copia física del repositorio original, sino que es por referencia. Las operaciones de push y pull actúan por defecto contra el repositorio original del clon.

Conclusión, si queremos ser ágiles la integración continua no nos la quita nadie. De hecho es más importante decidir usar integración continua, que el hecho de elegir entre DVCS o repositorios centralizados no bloqueantes (SVN, CVS…). En cualquier caso a mi esto de los DVCS me parece un paso adelante y algo muy interesante (más flexibilidad, escalabilidad, trabajo offline y adecuar arquitectura de repositorios a la estructura de equipo).

Bueno, espero que os haya sido interesante. Yo me despido por tres o cuatro semanas. Por motivos personales no podré seguir actualizando este blog durante este tiempo, pero no os preocupéis, volveré. Por cierto, os espero por la conferencia de agilismo que se va a producir en Madrid, la CAS2010, donde tengo el honor de dar una charla junto con mi compañero Antonio David, ¡nos vemos y no falteis!

Ups, lo he vuelto ha hacer, más de dos mil palabras…

Read Full Post »


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:

  1. 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.
  2. 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.
  3. 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 !

Read Full Post »


¡ Oh no ! Otro post infumable sobre equipos de desarrollo… Si señores y señoras vuelvo a la carga con este tema que considero de importancia vital, no os preocupéis creo que es el último de la serie. Considero importante este tema porque si no tenemos un equipo bien estructurado lo tendremos muy difícil para tener éxito en nuestros proyectos.

En mi anterior post sobre equipos comenté que una estructuración plana y democrática era la más ágil y óptima para el desarrollo de software. Sin embargo al crecer en tamaño dicha estructura se colapsa debido a la sobrecarga de información de cada miembro del equipo. Además para explotar las ventajas de tal estructura de equipo lo mejor es que estén todos colocados en la misma sala. Esto nos lleva a un problema, ¿qué hacemos si necesitamos un equipo muy grande (más de 9)?¿Qué hacemos si no tenemos a todos lo miembros del equipo colocado? En estos casos necesitamos estructurar el equipo de forma diferente. Veamos alternativas.

La primera tentación es montar una jerarquía. Ciertamente las jerarquías disminuyen la cantidad de lineas de información que llegan a una persona, lo que permite que esta se sature menos si el equipo es grande. Desde este punto de vista los equipos organizados en jerarquías escalan bien con el tamaño. De hecho el número de lineas de comunicación crece mucho más despacio (logarítmicamente)  que con equipos planos (crecimiento lineal). Otra ventaja es que la toma de decisiones la toma el jefe, lo que permite mayor velocidad de decisión que la basada en consenso. Es la forma tradicional de organizar los equipos. Todo parece bonito pero… esta estructura ¡no es ágil! y además ¡ es frágil ! Veamos:

  • Frágil. La estructura depende de las personas que son jefes de equipo. Con suerte puedes tener un jefe que sepa lo que hace y sea competente, si no tienes tanta suerte te toca un jefe que es una nulidad. Cuanto más arriba en la jerarquía esté la persona, más vital es. Un equipo plano es más robusto ya que no depende de una sola persona.
  • No ágil. Cada miembro del equipo no tiene una linea de comunicación con sus compañeros sino con su jefe. Si una información necesita pasar desde una persona a otra, no puede hacerlo directamente, sino que debe pasar por al menos un jefe o más. Por cuántos intermediarios pase depende de la «distancia» entre las dos personas, si están en el mismo equipo pasan por un sólo jefe, si están en equipos organizatívamente lejanos pasa por varios intermediarios. De hecho el número máximo de intermediarios crece logarítmicamente con el tamaño del equipo mientras que en un equipo plano se mantiene constante en cero. Paradójicamente, esta estructura hace que los jefes más importantes, los que deben tomar decisiones vitales, reciben la información más anticuada y desvirtuada ya que son los que tienen siempre más intermediarios con la realidad.
  • No favorece el espíritu de equipo y colaboración sino la competencia entre jefes y la «política».

Evidentemente este modelo radical de jerarquía no suele aplicarse. Es más común el tener pequeños grupos planos que están coordinados por un jefe. Esto disminuye un poco el problema pero no lo elimina por completo, sobre todo si tenemos en cuenta que los equipos planos están en la base, y que es más raro encontrar equipos planos de jefes.

Otra forma típica de estructurar un equipo es alrededor de especialistas. Se suelen tener grupos de especialistas, donde cada grupo es experto en un componente del sistema o en una tarea o actividad. La idea detrás de esto es que los especialistas son muy eficientes en realizar unas actividades en concreto, por lo tanto la forma más eficiente de organizar un equipo es que cada individuo o grupo haga lo que mejor sabe hacer, su especialidad. De esta forma el rendimiento del equipo de desarrollo será óptimo, ¿verdad? Pues no, ¡ FAIL ! Veamos el siguiente dibujo ilustrando un equipo basado en especialistas:

Equipo organizado en grupos de especialistas

Especialistas

Como se ve desde que una historia de usuario se extrae del product backlog hasta que se completa y reporta un valor al cliente (y dinero para nosotros), ésta pasa por… ¡un montón de intermediarios! Cada intermediario (especialista) de nuevo añade un posible punto de encolamiento («el otro equipo está tardando mucho en darme trabajo, yo no tengo la culpa….») y de malentendidos («pero suponíamos que la interfaz entre el componente A y el componente B era así, no nos la explicásteis bien»). El hecho de que cada grupo sólo entienda de su especialidad agrava los problemas, ya que provoca entre otras cosas:

  • Cada uno intenta optimizar el proceso global y el código desde el punto de vista de su especialidad sin tener en cuenta el objetivo global. Esto produce optimizaciones locales que no son eficientes. Por ejemplo un desarrollador puede decidir escribir código sin pruebas rigurosas, lo que optimiza su trabajo (acaba antes) pero genera un problema en el equipo de control de calidad.
  • Crea rivalidades y evita el espíritu de equipo. Cada grupo tiende a ver a los demás como «tontos» que no entienden su trabajo (especialidad).
  • No se produce una diseminación del conocimiento entre los distintos empleados. Cada uno se queda «encasillado» en su especialidad para siempre.
  • No hay un feedback rápido ante problemas. Un problema introducido por un equipo puede no ser detectado hasta que llega a otro equipo, potencialmente alejado.
  • Como normalmente los equipos de especialistas no colaboran entre si, se necesita la figura de un coordinador.
  • Normalmente llega a una utilización ineficiente de las personas. No todas las «historias de usuario» tienen que necesitar de todos los especialistas. Si en un momento dado sólo hay historias de usuario que necesitan a los especialistas A, C y D, el especialista B se quedará desasignado. Esto es una consecuencia obvia de usar especialistas, los especialistas no son siempre asignables y pueden llevar a una utilización ineficiente de los recursos.
  • El efecto «patata caliente» y «tu la llevas». Se suele echar las culpas a los demás equipos y pasarle el marrón al siguiente. Esto es debido a que cada grupo de especialistas no comparte una meta común con los otros grupos y además son incapaces de ver el proyecto en su conjunto, con tal de que su trozo esté terminado no les importa que el producto en global esté mal (la culpa será de los otros, mi parte está bien).

Como hemos visto la jerarquía y la organización en grupos de especialistas genera intermediarios y colas que son fuente de múltiples problemas. Por cada intermediario se produce un retraso, con lo que la información llegará más anticuada al receptor. Por cada intermediario hay una cierta de corrupción de la información y malentendidos, con lo que la información se perderá por el camino y lo que llegue al receptor no puede no ser lo suficientemente veraz o preciso. Si encima cada intermediario tarda una cantidad diferente y no predecible de tiempo en procesar la información se producirán encolamientos. Si quieres un equipo ágil, evita intermediarios. Las colas son también fuentes de ineficiencias. Por cada cola tenemos trabajo a medio hacer, parado que no genera valor. Por cada cola se retrasa el feedback, haciendo que un montón de posibles defectos y problemas estén escondidos, a la espera de aparecer en el momento más inoportuno y cuando todo el mundo está ya «en otra cosa». Si quieres ser lean debes evitar las colas.

Lo más eficiente es organizar los equipos grandes de forma que se minimicen el número de intermediarios y colas. Para esto tenemos dos armas: la autogestión y los equipos multidisciplinares. Veamos algunos consejos generales en este sentido:

  • Organiza en equipos planos colocados en la misma sala a toda la gente que necesite comunicarse con mucha frecuencia y responder ágilmente. Equipos que no deban interaccionar tan a menudo pueden estar en otras salas o en centros remotos.
  • Cada equipo debe ser multidisciplinar, debe tener varios especialistas. Además estos especialistas irán enseñando sus tareas a los demás miembros del equipo. Esto genera una transición gradual de especialistas a técnicos multidisciplinares y un mejor espíritu de equipo.
  • Cada equipo debe tender a la autogestión, tanto de sus cometidos como a la interacción con otros equipos. Esto evita la figura del coordinador y aunque tengamos un coordinador su actuación no sea tan vital.
  • Para coordinar varios equipos cada equipo puede nombrar a uno de sus miembros como representante. Este nombramiento puede rotar con el tiempo. Todos los representantes de todos los grupos se organizan de forma democrática y se reúnen periódicamente para discutir temas de organización.
  • Cada equipo es responsable por entero de la entrega de la tarea que tiene encomendada. No se debe responsabilizar a individuos. Esto genera una meta común y une al equipo.
  • De la misma manera todos los equipos son responsables del éxito o fracaso del proyecto en su conjunto.
  • Mantén los miembros de un mismo equipo juntos durante mucho tiempo, de esta forma se conocerán entre si y su coordinación crecerá.
  • De vez en cuando ofrece la posibilidad a los miembros de un equipo para cambiarse a otro equipo. Asi no se quemarán ni se cansarán del mismo ambiente y tendrás un nivel adicional de diseminación de información.

Si implementamos estos consejos podemos montar un equipo estructurado en base a «feature teams». En este tipo de estructura cada grupo o «feature team» se encarga de realizar una historia de usuario. Veamos el siguiente dibujo:

Proyecto organizador en equipos por historias de usuario

Feature Teams

Como vemos eliminamos las colas. Cada equipo es multidisciplinar y tiene su propio Product Owner. De esta forma es capaz de coger una historia de usuario e implementarla sin necesidad de pasar por otros grupos. Como cada equipo es plano, éste se comporta de forma ágil y rápida. En la figura hay cuatro equipos, cada uno de ellos en su propia sala. Los equipo que pertenecen a «Color Features» están en el mismo edificio porque cogen historias de usuario que tienen bastante relación entre si, son funcionalmente próximas, por lo tanto los dos equipos necesitan comunicación ágil y por lo tanto proximidad. De la misma manera «Alpha Features» está en otro centro de trabajo y se estructura de forma similar. El uso de autogestión y representantes de equipos disminuye mucho la necesidad de coordinadores. Sin embargo siempre es bueno es tener varios scrum masters itinerantes o coachers que se dediquen a resolver dudas,  aconsejen a los equipos, desatasquen potenciales problemas globales o arbitren en disputas «irresolubles». Estos «coachers» no serían coordinadores sino facilitadores y maestros. Tampoco es mala práctica que además del representante de equipo se reúnan periódicamente los product owners de cada grupo para mantener una linea de coherencia estratégica del producto.

Un hecho interesante es que como cada historia de usuario requiere de varias tareas o componentes para ser completadas, cada grupo debe trabajar sobre distintas áreas de código. Esto hace que todos los grupos tengan una responsabilidad compartida sobre todos los componentes de código de la aplicación. De esta forma a todos los grupos le convienen que todos los componentes de código estén hechos con una calidad y mantenibilidad decentes, porque te puede tocar usarlos o modificarlos para la siguiente historia de usuario. Contrasten este efecto con lo que ocurría con los especialistas, cada especialista sólo se preocupa de su componente y no de los demás. El hecho de que varios grupos modifiquen simultáneamente las mismas zonas de código nos genera un problema de gestión. Este problema se soluciona con varias prácticas del agilismo:

  • Pair programming. Para aumentar la calidad del código (revisión continua) y la diseminación de la información (enseñanza mutua entre los miembros de la pareja).
  • Repositorio de código con control de versiones y concurrencia optimista. Es vital que el sistema de control de versiones de código no bloquee los ficheros. Si los bloqueara se producirían encolamientos entre dos grupos que necesitan modificar el mismo componente de código.
  • TDD. Para mantener una suite de pruebas que te permita tocar el código de otro sin miedo a romperlo y no darte cuenta.
  • Integración continua. Para integrar de forma continua los cambios de los distintos grupos, disminuir la probabilidad de conflictos, y agilizar la fusión de versiones en caso de que los hubiera.

Finalmente, todo esto funciona porque los miembros de tu proyecto que necesitan una comunicación más rápida y ágil son los que están implementando una misma historia de usuario. Dos personas implementando dos historias de usuario distintas no tienen tanto acoplamiento y necesidad de intercomunicación como dos personas que están trabajando sobre la misma historia de usuario. De hecho el acoplamiento entre dos «feature teams» se produce sobre todo a través de la base de  código compartido. En este caso dicho acoplamiento se puede optimizar mediante TDD e integración continua con lo cual no es necesario una intercomunicación tan frecuente. Dicho esto aprovecho para introducir una práctica muy necesaria en este escenario, pero tenida por no ágil: la documentación. Como vemos la comunicación se realizará de forma más intensa por código entre dos «feature teams» distintos, por lo que además del TDD y la integración continua es necesario reforzar el grado de legibilidad del código, y eso se hace precisamente con una buena documentación. Recordad que el agilismo no prohíbe la documentación, sólo dice que documentes lo justo y necesario. En proyectos grandes, la cantidad de documentación justa y necesaria es mucho mayor que en un proyecto pequeño. Si no vas a implementar TDD, integración continua y no vas a mantener una buena documentación, tus «feature teams» no podrán coordinarse con efectividad y tu proyecto fracasará.

Como conclusión: es posible gestionar proyectos grandes usando agilismo aunque nuestro equipo se muy grande o esté distribuido. Ciertamente no es el caso óptimo pero la realidad manda. Para afrontar esto hay que estructurar tu proyecto en múltiples equipos pequeños, colocados en la misma sala, multidisciplinares y autogestionados, donde cada equipo implementa una historia completa de usuario detrás de otra. Como sacrificio podemos hacer que cada equipo pueda estar en su propio centro de trabajo, pero la menor interacción entre dos «feature teams» hacen que el TDD, la integración continua y la documentación sean vitales. Como prácticas a evitar están la de estructurar en grupos de especialistas y minimizar el grado de jerarquía.

Read Full Post »


Hago un alto con la serie de posts sobre la estructuración de los equipos para hacer una introducción a un tema candente, las bases de datos no relacionales y el movimiento noSQL.
Algunos pensarán que esto del noSQL es una moda más, pero lo curioso es que se enfrenta a otra moda mucho más poderosa e influyente, el SQL y las base de datos relacionales. En muchos sitios me enerva una práctica muy común que consiste en que al diseñar la aplicación y analizar los requisitos lo primero que se hace es… ¡el modelo de tablas relacional! Muchos consideran que la aplicación está casi hecha en cuanto tenemos un esquema de base de datos y unas sentencias SQL, y acoplan todo su código a esto. Esta forma de diseñar aplicaciones está muy bien cuando trabajas con COBOL pero desperdicia por completo las características de cualquier lenguaje de programación OO moderno. La cosa llega a tal extremo que muchas personas sólo piensan en aplicaciones CRUD y de hecho hay frameworks como Grails o Rails que te proporcionan una aplicación CRUD muy rápidamente si no te importa acoplarte con la BBDD.
Mi filosofía es un poco diferente, usar Acceptance TDD y DDD y DSL (a.k.a. API Sexy) para definir un core de negocio orientado a objetos que sea fácil de probar, fácil de usar y modele el dominio de la aplicación. Es decir, como uso OO, lo que hago es realmente modelar el negocio mediante un modelo de objetos, que refleja la realidad del problema que queremos resolver y contiene la lógica de negocio. Sobre este modelo monto un DSL para poder trabajar con el modelo escribiendo código que parezca lenguaje natural o al menos sea legible por el analista funcional. En esta filosofía de trabajo la interfaz de usuario y la capa de persistencia son periféricas a este core de negocio. De hecho se puede diseñar el sistema para que tenga distintas interfaces de usuario y puedas cambiar el mecanismo de persistencia de forma sencilla.

Desde este punto de vista, si yo diseño mi sistema con un core OO de negocio robusto, y necesito almacenar los objetos de forma persistente, entonces debo usar una capa de persistencia, y no me importa especialmente cual sea mientras cumpla mis requisitos de la forma más sencilla posible. Desde este punto de vista analicemos lo que nos ofrece una base de datos relacional:

  • Persistencia. Ok, es lo que busco.
  • Lógica de negocio, en forma de procedimientos almacenados y triggers. Pues no, esto me sobra, lo tengo en el core de negocio.
  • Transacciones ACID. Esto depende, si estoy haciendo operaciones críticas que deben realizarse de forma instantánea desde el punto de vista de negocio entonces sí. Si estoy escribiendo una aplicación de redes sociales y quiero escribir una actualización de estado, no me importa mucho si falla al grabarse, o tengo lecturas fantasma. Otro ejemplo de operación no ACID es el de una transferencia monetaria bancaria. Ciertamente das de alta la transferencia de dinero de forma atómica, pero la consolidación de los balances entre la cuenta destino y origen puede tardar días, teniendo mientras tanto un estado «inconsistente».
  • SQL. Un lenguaje de query complejo, que me permite hacer casi cualquier consulta y que para optimizarlo tengo que ser un experto en BBDD. No gracias, las consultas complejas las hago en mi core de negocio, en mi lenguaje de programación favorito, y por que no, con mi DSL. Yo sólo quiero grabar, leer, y hacer unas cuantas consultas muy concretas, no cualquiera. Además, ¿cómo puede una BBDD ser más óptima en la ejecución de la consulta que mi código, hecho específicamente para mi problema? Es el típico caso de que una solución genérica a un problema no puede ser más eficiente que una solución específica para un problema específico. En este caso SQL es un lenguaje de query genérico, y no conoce los detalles específicos y posibles optimizaciones de mi aplicación, sólo puede usar técnicas generales.
  • Esquema. Estructura rígida y tipada de almacenamiento en forma de tablas con columnas ¿Para que quiero eso, si la estructura y el modelo y los tipos y validaciones del sistema está en el core OO? Realmente esto es un dolor, todos habréis sufrido con problemas de conversión de tipos entre columnas de tablas y campos de objetos, o entre objetos y tablas. Tal es el problema que nuestra aplicación termina haciendo uso de algún framework de mapeo objeto relacional, como Hibernate o iBatis o JPA, que añade otro grado de complejidad y configuración innecesario. Es innecesario porque la estructura de la información ya está en el modelo de objetos, no es necesaria replicarla en la capa de persistencia.

Si somos prácticos y aplicamos el principio KISS, vemos que una BBDD relacional puede ser, en muchos casos, algo que nos añade una complejidad innecesaria. Nos añade funcionalidades duplicadas con el core OO o simplemente innecesarias en algunos casos. Lo que ocurre es que el típico escenario de aplicación, el típico caso de uso, está cambiando respecto a lo que era antes. Cada vez tenemos más aplicaciones que tienen alguna o todas de las siguientes características:

  • Deben soportar grandes volúmenes de datos. Pensad en cualquier red social o en aplicaciones de Google, como el google maps.
  • Deben soportar altas cargas de transacciones online, debido a que el número de usuarios en la web puede ser enorme. De hecho cuantos más usuarios tengamos, los señores de negocio estarán más contentos.
  • El rendimiento no debe degradarse debido a los dos puntos anteriores.
  • ¡Que no se caiga! Hoy en día la caída e indisponibilidad de una aplicación tiene consecuencias monetarias muy fuertes.
  • En aplicaciones como las redes sociales, la consistencia no es crítica, con lo que las transacciones ACID no son necesarias.
  • ¡Fácil de usar! No quiero sufrir para simplemente grabar datos y hacer cuatro consultas básicas. No quiero saber nada de configuraciones de Hibernate o explains de Oracle.
  • ¡Fácil de integrar con un core OO de negocio!

Como vemos en muchos tipos de aplicaciones, las bases de datos relacionales te ofrecen capacidades que no te interesan a cambio de complicarte la vida. Por otro lado puedes tener muchos problemas con los requisitos de rendimiento, escalabilidad y disponibilidad. Para solucionar esos problemas vas a necesitar expertos, máquinas y productos normalmente caros. Lo peor que es que dichos expertos pueden coger una posición de poder y empezar a imponer sus reglas. Lo ideal es que las reglas las ponga el cliente, no un grupo de sacerdotes de una oscura tecnología.

Hasta ahora por tradición y por moda (o por imposición) cada vez que se nos planteaba la necesidad de persistir información elegíamos una BBDD relacional de forma automática y sin pensar. Como la integración entre un core OO y la BBDD relacionales es complicada, la gente llegó a varias soluciones (no necesariamente excluyentes):

  • No usar un core OO de negocio. Me hago una aplicación igual que la que hacía en COBOL pero traducida literalmente a JAVA.
  • Uso un Grails o un Rails, me acoplo al esquema de la BBDD y ya está.
  • Uso mi framework de mapeo objeto relacional (Hibernate, iBatis, etc),  e invierto esfuerzo en configurarlo y optimizarlo.

Esto es un razonamiento torticero, como tengo que usar BBDD relacionales entonces me complico la vida o paso de la OO. Es decir, me creo problemas «artificiales», que no existían originalmente, porque elijo no solucionar el problema original (la persistencia) de forma óptima, sino hacer según manda la tradición y la moda. Lo lógico es analizar que tiene que hacer tu sistema de persistencia y después decidir qué usas. Puedes terminar usando una BBDD relacional si realmente la necesitas, pero en la mayoría de los casos os daréis cuenta que no os hace falta. Este es precisamente el punto de partida del movimiento noSQL.

Resumiendo, el movimiento noSQL, entre otras cosas, te propone:

  • No tienes porque usar una BBDD relacional, tienes alternativas para la persistencia de tu modelo de objetos.
  • No te hace falta un lenguaje de triggers ni de consultas complejas ya que la lógica de negocio está en la aplicación, no en el sistema de persistencia.
  • No necesitas un esquema rígido en tu sistema de persistencia, ya tienes tu modelo de objetos. Puede que incluso trabajes con un lenguaje de programación de tipado dinámico (js, ruby, etc). Simplemente quieres persistir objetos enteros o documentos. La estructura exacta de lo que guardes no debe ser de la incumbencia del sistema de persistencia, sino de tu modelo de negocio.
  • Te vale una API sencilla y un modelo de consulta sencillo. Uno muy popular es el clave valor, o tabla hash persistente. Guardas objetos o documentos y los asocias a una clave para poder recuperarlos.
  • Puedes prescindir de ACID para conseguir alta disponibilidad, escalabilidad y bajos tiempos de respuesta.
  • No odies SQL, simplemente debes saber cuando usarlo y cuando no. Hay casos en los que es la mejor solución, pero no en todos, y creo que cada vez en menos.

En siguientes posts hablaré sobre que sistemas de persistencia noSQL son más usados, su paradigma de desarrollo y como podemos eliminar, en muchos casos, la necesidad de transacciones ACID y el 2PC, con el objetivo de aumentar la disponibilidad y escalabilidad. Iré mezclando posts de noSQL con los de estructuración de equipo para que haya variedad.

Read Full Post »