sábado, 12 de noviembre de 2011

Los niveles de aislamiento en PostgreSQL (no son 4)



"No. No lo intentes. Hazlo, o no lo hagas, pero no lo intentes."

Cuando ví el Episodio IV de la Guerra de las Galaxias ("El Imperio Contraataca"), aún era un niño impresionable, y me llamó la atención la reprenda que hizo Yoda a Luke y que cito aquí. Era uno de ésos consejos marciales que se enuncian solemnes como un consejo vital.

Con el tiempo te das cuenta que como consejo está bien, y puede ser incluso un buen objetivo... pero en ciertas facetas de la vida no es válido. Por ejemplo, en el mundo científico y en nuestro mundo profesional el consejo es justamente el contrario: inténtalo, pruébalo y compruébalo. Y sólo cuando lo hayas hecho y consigas un resultado repetible, ponlo en práctica, esto es, súbelo a producción.

Gracias a no seguir el consejo de Yoda fui consciente de una (grave) carencia de PostgreSQL con respecto a los niveles de aislamiento. Como ya hice una introducción al aislamiento en otro artículo, no voy a extenderme más en el tema y voy al grano. El caso que me ocupaba es que tenía que hacer una importación transaccional de datos masiva en un sistema en OLTP de producción en el que se están modificando constantemente datos relacionados con los que voy a importar. El resultado de la importación es que la transacción gigante bloquea datos e impide que se efectúen modificaciones en datos relacionados paralizando en la práctica buena parte de las operaciones de producción produciéndose un efecto "bola de nieve" al sumarse cada vez más transacciones en cola a la transacción gigante pendiente. Sin embargo, para lo único que quiero la transacción es para tener atomicidad en la operación, es decir, para que no se quede a medias en caso de algún problema, pero no me importa que se puedan producir eventualmente casos de dirty read, que no afectarían a los usuarios del sistema de producción en su trabajo. Vale, no hay problema. La transacción de inserción la podemos realizar con el nivel de aislamiento más bajo (READ_UNCOMMITED) y problema solucionado. ¿No? No obstante, no hagamos caso a Yoda, y probémoslo en pre-producción. Resultado: exactamente el mismo que si no hubiera especificado nada del nivel de aislamiento.

¿Qué ha pasado?

En la documentación de SET TRANSACTION, en efecto compruebo que PostgreSQL admite los cuatro niveles de aislamiento como la mayoría de las bases de datos (de hecho, la sentencia no dio error)... pero sólo sintácticamente: no están todos implementados. Si contiúas leyendo te das cuenta que no son 4, sino 2 los niveles implementados:
"The SQL standard defines two additional levels, READ UNCOMMITTED and REPEATABLE READ. In PostgreSQL READ UNCOMMITTED is treated as READ COMMITTED, while REPEATABLE READ is treated as SERIALIZABLE."
Lo que viene a decir: "¿Pensabas que eran cuatro, no? Pues no. Son dos". De hecho, ahora (con la versión 9.1) ya son 3. Pero sigue sin estar soportada la lectura sucia (dirty read). En la documentación sobre aislamiento también es bastante claro:
"In PostgreSQL, you can request any of the four standard transaction isolation levels. But internally, there are only two distinct isolation levels, which correspond to the levels Read Committed and Serializable. When you select the level Read Uncommitted you really get Read Committed, and when you select Repeatable Read you really get Serializable, so the actual isolation level might be stricter than what you select."

Un verdadero jarro de agua a mis pretensiones, ya que no puedo conseguir atomicidad sin bloqueo (aunque sea realizando lecturas de transacciones no confirmadas) en PostgreSQL, al menos con la versión actual 9.1.


Referencias y más información:

    4 comentarios :

    1. Es dificil entender que necesites READ UNCOMMITED y que a la vez requieras un traspaso transaccional "fiable" en un entorno OLTP. Salvo que lo que quieras es saber cual es el "progreso" del traspaso. Y para esto hay otras formas de hacerlo.

      Quizá si explicas mejor qué es exactamente lo que quieres hacer, haya alternativas. Seguro que las hay.

      Salu2.

      ResponderEliminar
    2. Gracias por tu comentario.
      Lo que pretendo es realizar una transacción de alta de información de una gran cantidad de registros (varias decenas de miles) que se realice de forma atómica (es decir, o se hacen todas las altas, o ninguna). Imagina, por ejemplo que estoy añadiendo datos a una base de datos de libros, por ejemplo, libros de autores existentes, autores nuevos y sus libros, direcciones de autores nuevos y existentes, etc... No quiero que los autores existentes afectados por altas de nuevos libros queden bloqueados en la transacción y no me importa que se puedan producir lecturas sucias de nuevos libros de autores existentes sin confirmar. ¿Qué alternativas propones?

      ResponderEliminar
    3. En el ejemplo que pones y considerando que los libros son una tabla dependiente de la de autores, solo veo problema a la modificación de información (direcciones) de los autores existentes. Esta es la "transacción" que bloquea, el resto (inserción de autores nuevos y libros nuevos) no debería ofrecer ningún problema de bloqueos.

      Siendo este el caso, además de buscar un espacio temporal donde no se esté usando la base de datos, que supongo que ya lo has intentado y no es posible, tienes un proceso en 2 pasos:

      1.- Realizar la transacción problematica -bloqueante- (actualización de direcciones de los autores existentes) fuera de una transacción atómica. Es decir, fila a fila, buscar el autor, si existe UPDATEas su dirección y COMMIT... otra... etc...

      2.- Realizar la inserción de autores nuevos y sus libros. Además, insertar los libros de los autores existentes. Esta no debería bloquear a ninguno de los procesos que están leyendo la información YA existente.

      Con esos dos pasos, no creo que tengas problemas.

      Si el ejemplo no se ajusta a lo que necesitas, tendrás que definir que es lo que realmente necesitas. Puesto que la solución va a estar muy "atada" a tu modelo de datos y lo que con ellos quieres hacer.

      Un saludo y suerte.

      ResponderEliminar
    4. Si. En efecto estoy de acuerdo contigo en que esas son las mejores alternativas. Esas u otras intentadas todas tienen en común la reununcia a la atomicidad de la operación de alguna u otra forma, y asumir el riesgo de quedarme a medias en la carga en caso de error. Es lo que comentaba: no puedo tener atomicidad sin bloqueo. En fin, gracias por tu interés y tus aportaciones, muy útiles.

      Gracias por pasarte por el blog. Espero que siga despertando tu interés en adelante.

      ResponderEliminar

    Related Posts Plugin for WordPress, Blogger...
    cookieassistant.com