Replicación

Este capítulo introduce el sistema de replicación "de primera clase" de CouchDB. Este sistema sincroniza dos copias de la misma base de datos, permitiendo al usuario tener acceso de baja latencia donde quiera que esté. Las bases de datos pueden estar alojadas en el mismo servidor o en servidores distintos-CouchDB no hace distinción. Si cambia una copia de la base de datos, el proceso de replicación envía los cambios a la otra copia.

La replicación es una operación puntual y aislada: mandamos una petición HTTP a CouchDB incluyendo una base de datos de origen (source) y una de destino (target), y CouchDB manda todos los cambios del origen al destino. Eso es todo. Aunque decir que algo es "de primera clase" y después sólo necesitar una frase para explicarlo suena un poco raro. Dicho esto, veremos que esta simplicidad es uno de los factores que dan al sistema de replicación tanta potencia.

Veamos un ejemplo de replicación:

POST /_replicate HTTP/1.1
{"source":"database","target":"http://example.org/database"}

Esta petición manda todos los documentos de la base de datos local database a la base de datos remota http://example.org/database. Una base de datos se considera “local” cuando está en la misma instancia de CouchDB a la que le mandamos la petición HTTP POST /_replicate. Cualquier otra instancia de CouchDB se considera “remota”.

Si quieres sincronizar en la otra dirección, desde la base de datos remota a la local, simplemente cambiamos el origen (source) por el destino (target). Y ya está!.

POST /_replicate HTTP/1.1
{"source":"http://example.org/database","target":"database"}

Las bases de datos remotas se identifican por la misma URL que usamos para hacer consultas. La replicación de CouchDB funciona por HTTP usando los mismos mecanismos a tu alcance. Este ejemplo muestra que la replicación es un proceso unidireccional. Los documentos se copian de una base datos a otra pero esto no implica que se copie nada en la otra dirección. Si quieres replicación bidireccional, tienes que iniciar dos replicaciones con el origen (source) y destino (target) intercambiados.

La Magia

Cuando le pedimos a CouchDB que replique una base de datos en otra, éste va y compara las dos bases de datos para averiguar qué documentos del origen son distintos del destino y después envía tandas de documentos cambiados hasta que todos los cambios se han transferido. Los cambios incluyen documentos nuevos, documentos cambiados, y documentos borrados. Los documentos que ya existan en el destino y estén en la misma revisión no se transfieren.

En CouchDB las bases de datos tienen un número de secuencia (sequence number) que se incrementa cada vez que la base datos cambia. CouchDB se acuerda de qué cambios se hicieron en cada número de secuencia. De esta manera, CouchDB puede responder a preguntas como, “¿Qué ha cambiado en la base de datos A entre el número de secuencia 212 y ahora?” y presentarnos con una lista de documentos nuevos y documentos cambiados. Ésto nos permite encontrar diferencias entra bases de datos de manera eficiente. Además, contribuye a la robustez del sistema de replicación.

Las vistas de CouchDB usan el mismo mecanismo para determinar cuándo una vista necesita actualizarse y qúe documentos replicar. Ésto es algo que también podemos usar para construir nuestras propias soluciones.

La replicación se puede usar en una sola instancia de CouchDB para crear "snapshots" (instantáneas) de tus bases y así poder testear código sin poner en riesgo los datos, o para poder referirnos a estados previos de nuestra bases datos. Pero la replicación se pone interesante de verdad cuando usamos dos o más computadoras, potencialmente separadas geográficamente.

Con varios servidores, potencialmente separados por cientos o miles de kilómetros, sabemos que van ocurrir problemas. Los servidores se cuelgan, las conexiónes de red se caen, siempre pasan cosas. Cuando un proceso de replicación se interrumpe, las bases de datos se quedan en un estado inconsistente. Después, cuando los problemas desaparecen e iniciamos la replicación otra vez, continúa donde se quedó.

Replicación Simple usando la Interfaz de Administración

La replicación se puede ejecutar desde un navegador web usando Futon, la interfaz de administración que viene con CouchDB. Arranca CouchDB, y en tu navegador abre http://127.0.0.1:5984/_utils/. En el lado derecho de la pantalla verás una lista de cosas que ver en Futon. Haz click en “Replication”.

Futon nos muestra una interfaz para iniciar la replicación. Podemos especificar el origen y el destino usando la lista de bases de datos locales o directamente con la URL a una base datos remota.

Haz click en el botón que dice Replicate, espera un poco, y echa un vistazo a la mitad inferior de la pantalla donde CouchDB nos muestra algunas estadísticas sobre la replicación o, si ocurrieron errores, un mensaje explicatorio.

Enhorabuena-acabas de ejecutar tu primera replicación.

Replicación al Detalle

Por ahora hemos omitido los resultados de las peticiones de replicación. Éste es un buen momento para ver una respuesta en detalle. Ahí va un ejemplo formateado bonito:

{
  "ok": true,
  "source_last_seq": 10,
  "session_id": "c7a2bbbf9e4af774de3049eb86eaa447",
  "history": [
    {
      "session_id": "c7a2bbbf9e4af774de3049eb86eaa447",
      "start_time": "Mon, 24 Aug 2009 09:36:46 GMT",
      "end_time": "Mon, 24 Aug 2009 09:36:47 GMT",
      "start_last_seq": 0,
      "end_last_seq": 1,
      "recorded_seq": 1,
      "missing_checked": 0,
      "missing_found": 1,
      "docs_read": 1,
      "docs_written": 1,
      "doc_write_failures": 0,
    }
  ]
}

La parte que dice "ok": true, al igual que en otras respuestas, nos dice que todo ha ido bien. source_last_seq incluye la secuencia de modificación (update_seq) del origen que se ha tenido en cuenta en esta replicación. A cada petición de replicación se le asigna un identificador de sesión (session_id), que es simplemente un UUID; también podemos hacer referencia a una sesión de replicación identificada por este ID.

La siguiente parte es la historia de replicación. CouchDB mantiene una lista de historias de sesiones para poder hacer referencia en el futuro. Esta array de historias está limitada a 50 elementos por el momento. Cada objeto de replicación único (el string JSON que contiene las bases de datos de origen y destino así como otras opciones) recibe su propia historia. Veamos de qué se trata esto de las historias.

El ID de sesión (session_id) aparece aquí otra vez por comodidad. Podemos ver cuándo se inició y cuándo terminó. _last_seq hace referencia a las secuencias de modificación (update_seq) que eran válidas en el momento de iniciar y terminar la sesión. recorded_seq es la secuencia de modificación (update_seq) del destino otra vez. Es diferente a end_last_seq si la replicación se interrumpe y se reinicia después. missing_checked es el número de documentos en el destino que ya están ahí y no necesitan ser replicados. missing_found es el número de documentos que faltan en el origen.

Las tres últimas propiedades-docs_read, docs_written, y doc_write_failures—muestran cuántos documentos se han leído del origen, cuántos se han escrito en el destino, y cuántos han fallado. Si todo va bien, _read y _written deberían ser idénticos y doc_write_failures es 0. Si no, sabemos que algo no ha ido bien en la replicación. Posibles errores pueden ser servidores colgándose, conexiones caídas o una función validate_doc_update rechazando la escritura de un documento.

Un caso común es iniciar replicación en nodos donde se han activado cuentas de administración. Solamente los administradores pueden crear documentos de diseño (design documents), y si la replicación se inicia sin credenciales de administración, los documentos de diseño fallarán y se reportarán en doc_write_failures. Si tienes administradores, asegúrate de incluir los credenciales en la petición de replicación:

> curl -X POST http://127.0.0.1:5984/_replicate  -d '{"source":"http://example.org/database", "target":"http://admin:password@127.0.0.1:5984/database"}'

Replicación Continua

Ahora que ya sabes cómo funciona la replicación, compartimos un pequeño y elegante truquito. Cuando añadimos "continuous":true a la petición de replicación, CouchDB no se detiene cuando termina de replicar los documentos del origen que faltan en el destino, si no que continúa y escucha los _changes que emite la API de CouchDB (ver Chapter 20, Change Notifications) y replica cualquier documento nuevo o modificado según van apareciendo. De hecho, no se replican inmediatamente; hay un algoritmo complejo que decide cuándo es el momento ideal para replicar y obtener el mejor rendimiento. El algoritmo es complejo y se actualiza de vez en cuando, y documentarlo aquí no tendría mucho sentido.

> curl -X POST http://127.0.0.1:5984/_replicate -d '{"source":"db", "target":"db-replica", "continuous":true}'

Al momento de escribir esto, CouchDB no se acuerda de replicaciones al reiniciar. Por el momento, hay que reiniciarlas cuando volvamos a arrancar CouchDB. En el futuro, CouchDB permitirá definir replicaciones continuas permanentes que sobreviven a un reinicio del servidor sin tener que hacer nada.

En CouchDB 1.1.0 se introdujo la base de datos _replicator que se encarga precisamente de acordarse de las tareas de replicación y reiniciarlas en caso de que el servidor se reinicie. Más info aquí (en inglés).

¿Eso es todo?

La replicación son los cimientos sobre los que se apoyan los siguientes capítulos. Asegúrate de entender bien este capítulo. Si no estás seguro, léelo otra vez y juega un poco con la replicación en la interfaz de Futon.

Todavía no te hemos contado todo sobre la replicación. Los próximos capítulos describen cómo manejar conflictos (ver Chapter 17, Conflict Management), cómo configurar un grupo de instancias de CouchDB sincronizadas para balance de carga (ver Chapter 18, Load Balancing), y cómo construir un clúster de CouchDBs que pueda responder a más peticiones de datos o escritura que un nodo sólo (ver Chapter 19, Clustering).