El próximo libcouchbase incluirá actualizaciones mejoradas de los clústeres y muchas otras mejoras de estabilidad y rendimiento. La mayor parte del nuevo trabajo gira en torno al Configuración del clúster Publicación portadora o CCCP para abreviar.
Couchbase es un escalable, elástica clúster. Parte de este conjunto de características significa que los nodos del clúster pueden añadirse o eliminarse libremente sin causar tiempo de inactividad a nivel de aplicación. Intercambiar todos los nodos de un clúster y reemplazarlos es totalmente compatible y algo normalmente transparente para un servidor de aplicaciones que utiliza el SDK.
El propio SDK debe asegurarse de que está hablando con un clúster saludable y es consciente de los distintos nodos que son miembros del clúster. En concreto, esto implica saber dos cosas:
- Qué nodos forman parte del clúster
- Qué nodo es responsable de una clave determinada
Esta información se transfiere a través de un objeto JSON denominado "Mapa de configuración" o "Mapa de conglomerados". Puede verlo usted mismo navegando a una URL como
Este objeto JSON contiene las listas de los nodos, así como un gran "Mapa de vBucket"que básicamente indica al cliente qué nodo es responsable de cada vBucket, y el cliente compara las claves con este mapa.
Bootstrapping
Para ver cómo funciona el SDK (en concreto, libcouchbase) coloca todo esto junto, podemos examinar el strace salida de cbc
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 6 connect(6, {sa_family=AF_INET, sin_port=htons(8091), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) connect(6, {sa_family=AF_INET, sin_port=htons(8091), sin_addr=inet_addr("127.0.0.1")}, 16) = 0 sendmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"GET /pools/default/bucketsStream"..., 145}], msg_controllen=0, msg_flags=0}, 0) = 145 recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"HTTP/1.1 200 OKrnTransfer-Encodi"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 200 recvmsg(6, 0x7fff7b7e9bb0, 0) = -1 EAGAIN (Resource temporarily unavailable) recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"2223rn{"name":"protected","bucke"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 8756 recvmsg(6, 0x7fff7b7e9bb0, 0) = -1 EAGAIN (Resource temporarily unavailable) socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 7 connect(7, {sa_family=AF_INET, sin_port=htons(11210), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) connect(7, {sa_family=AF_INET, sin_port=htons(11210), sin_addr=inet_addr("127.0.0.1")}, 16) = 0 getsockname(7, {sa_family=AF_INET, sin_port=htons(34432), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0 getpeername(7, {sa_family=AF_INET, sin_port=htons(11210), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0 sendmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"200 ", 24}], msg_controllen=0, msg_flags=0}, 0) = 24 recvmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"201 16CRAM-MD5"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 38 recvmsg(7, 0x7fff7b7e9b70, 0) = -1 EAGAIN (Resource temporarily unavailable) sendmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"200!1010CRAM-MD5", 32}], msg_controllen=0, msg_flags=0}, 0) = 32 recvmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"201!!36<9003993"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 54 recvmsg(7, 0x7fff7b7e9b70, 0) = -1 EAGAIN (Resource temporarily unavailable) sendmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"200"10002CRAM-MD5"..., 74}], msg_controllen=0, msg_flags=0}, 0) = 74 recvmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"201"rAuthenti"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 37 recvmsg(7, 0x7fff7b7e9b70, 0) = -1 EAGAIN (Resource temporarily unavailable) sendmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"2003s31foo", 27}], msg_controllen=0, msg_flags=0}, 0) = 27 recvmsg(7, {msg_name(0)=NULL, msg_iov(1)=[{"20142111~$@327-17Hell"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 41 recvmsg(7, 0x7fff7b7e9b70, 0) = -1 EAGAIN (Resource temporarily unavailable) "foo" Size:13 Flags:0 CAS:f2dd740247e0100 Hello World! |
Arriba puedes ver que la librería primero hace una petición HTTP al puerto 8091 para recuperar el mapa del cluster. Una vez recuperado el mapa del cluster, se conecta al nodo que es el vBucket Master para la clave "foo" en el puerto 11210, realiza la autenticación SASL y, por último, emite un memcached solicitud de la llave.
Con CCCP, el puerto memcached en 11210 también puede albergar la información de configuración. Así, si observamos una traza similar de la próxima DP versión del cbc herramienta, veremos esto:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
connect(6, {sa_family=AF_INET, sin_port=htons(11210), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) connect(6, {sa_family=AF_INET, sin_port=htons(11210), sin_addr=inet_addr("127.0.0.1")}, 16) = 0 getsockname(6, {sa_family=AF_INET, sin_port=htons(34412), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0 getpeername(6, {sa_family=AF_INET, sin_port=htons(11210), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0 sendmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"200 ", 24}], msg_controllen=0, msg_flags=0}, 0) = 24 recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"201 16CRAM-MD5"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 38 recvmsg(6, 0x7fff8206e770, 0) = -1 EAGAIN (Resource temporarily unavailable) sendmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"200!1010CRAM-MD5", 32}], msg_controllen=0, msg_flags=0}, 0) = 32 recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"201!!36<9014490"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 54 recvmsg(6, 0x7fff8206e770, 0) = -1 EAGAIN (Resource temporarily unavailable) sendmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"200"10002CRAM-MD5"..., 74}], msg_controllen=0, msg_flags=0}, 0) = 74 recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"201"rAuthenti"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 37 recvmsg(6, 0x7fff8206e770, 0) = -1 EAGAIN (Resource temporarily unavailable) sendmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"200265r360", 24}], msg_controllen=0, msg_flags=0}, 0) = 24 recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"20126536{r360{"rev":1"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 7827 recvmsg(6, 0x7fff8206e770, 0) = -1 EAGAIN (Resource temporarily unavailable) sendmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"2003s31foo", 27}], msg_controllen=0, msg_flags=0}, 0) = 27 recvmsg(6, {msg_name(0)=NULL, msg_iov(1)=[{"20142111~$@327-17Hell"..., 65536}], msg_controllen=0, msg_flags=0}, 0) = 41 recvmsg(6, 0x7fff8206e730, 0) = -1 EAGAIN (Resource temporarily unavailable) "foo" Size:13 Flags:0 CAS:f2dd740247e0100 Hello World! |
Aquí reducimos una conexión TCP adicional al obtener la configuración directamente del puerto de datos.
Incluso contra un clúster localhost, el segundo ejemplo se ejecuta aproximadamente el doble de rápido que el primero, debido a la reducción de la conexión HTTP inicial.
Actualizaciones de la configuración
Las actualizaciones de configuración son mapas de clúster nuevos y actualizados que se reciben cuando cambia la topología del clúster. Esto permite al cliente saber que se han añadido o eliminado nodos y, por consiguiente, que el mapa de vBucket ha cambiado.
El comportamiento anterior de libcouchbase era conectarse a la base de datos streaming REST API para el bucket en cuestión con el fin de recibir actualizaciones de configuración. Esto requería que la biblioteca mantuviera una conexión TCP casi siempre inactiva a la que se enviaría información de configuración desde el clúster. Este enfoque tenía dos desventajas principales:
- Colocaría la biblioteca en un estado de espera/sondeo largo, en el que el socket que estaba sirviendo la configuración asumiría que estaba conectado a un nodo en funcionamiento que pulse la información de configuración. Esta suposición, por supuesto, fallaría si el nodo al que el cliente estaba conectado a través del punto final de streaming fuera el que fallara por sí mismo (y peor aún, nunca entregara un TCP RST). Dado que la semántica estaba basada en push, el cliente no podría establecer un tiempo de espera para asegurarse de que la conexión seguía funcionando correctamente.
- Era necesaria una conexión TCP adicional en todo momento para mantener actualizada la información de configuración. Como los puntos finales de la API REST se diseñaron para la administración, no para el uso rutinario, no están optimizados para el uso de memoria. En concreto, cada conexión TCP a estos incurre en una importante penalización de recursos en el lado del servidor.
Confmon - Administrador de configuración
La nueva 2.3 cambia el modelo y el enfoque de cómo se recupera la configuración. Un conjunto interno de APIs conocidas colectivamente como confmon/clconfig en la base de código. En lugar de tener un modelo intrusivo basado en push donde la configuración se impondría sobre un socket abierto, confmon está basado en pull y se activa sólo cuando es necesario. Así, por defecto, el cliente no mantendrá sockets abiertos sólo para la configuración, sino que asumirá una configuración válida hasta que alcance un determinado umbral de error o reciba una orden explícita de NOT_MY_VBUCKET error de uno de los nodos del cluster (indicando que el mapa del cliente se ha quedado sin datos).
En concreto, con el nuevo CCCP mejoras, cada NOT_MY_VBUCKET respuesta en sí ya contiene el mapa de clúster actualizado, eliminando así la necesidad de volver a obtener la configuración en primer lugar.
Las ventajas también se obtienen con los clústeres más antiguos, ya que el nuevo modelo sólo abre conexiones REST API a la carta - en lugar de mantenerlos abiertos indefinidamente (de hecho, hemos hecho una pequeña optimización en la que hacemos un sondeo largo durante un corto periodo de tiempo, ya que las actualizaciones de configuración tienden a suceder sucesivamente durante los cambios de topología, como los reequilibrios).
Registro
Se han añadido ganchos de registro a la biblioteca. Este modelo le permite activar la opción predeterminada registrador de consola estableciendo el LCB_LOGLEVEL o instale sus propios ganchos de registro implementando la variable de entorno lcb_logprocs e informando a la instancia sobre sus ganchos de registro.
Se ha añadido el registro de eventos notables pero no intensivos en CPU, como tiempos de espera, conexiones de sockets, destrucción de sockets, actualizaciones de configuración, etc.
Ten en cuenta que hay más cosas que nos gustaría registrar y que este no es el final de toda la instrumentación y ayudas al diagnóstico que tenemos previsto añadir.
Gestión de conexiones
También hemos mejorado la forma en que gestionamos las nuevas conexiones a los nodos de "datos" de memcached. Anteriormente, las conexiones se asignaban a los objetos que las creaban. Esto significaba que el lcb_server_t abriría y cerraría una conexión.
En 2.3, hemos añadido una función connmgr que funciona de forma muy parecida a una agrupación de sockets (y se utilizará en el futuro para soportar la agrupación de sockets para cosas como las consultas de vistas). En lugar de tener subsistemas abra y cerrar conexiones del sistema de E/S directamente, ahora solicitar y liberar (o descarte) desde y hacia el connmgr instancia
Ahora las estructuras de servidor no cerrarán incondicionalmente sus conexiones TCP, sino que comprobarán si hay datos pendientes en ellas; si hay datos entonces el socket es desechado al pool (es decir, se libera el socket y cualquier recurso del pool asociado a él) porque consideramos que el socket se encuentra en un estado no válido (ya que las respuestas del servidor serán probablemente más NOT_MY_VBUCKET respuestas). Si no hay datos pendientes el socket se publicado de nuevo en el pool, quedando disponible para una solicitud posterior de una nueva conexión. En nuestras pruebas, esto ha supuesto una reducción de hasta 6 veces en la creación de nuevas conexiones TCP durante los cambios de topología del clúster.
Obtener el código
Puede descargarse un archivo tar con el código fuente de esta versión en
https://packages.couchbase.com/clients/c/snapshots/libcouchbase-2.3.0_dp2.tar.gz
¿Existe alguna referencia de cómo construir drivers personalizados para couchbase? No hay ningún controlador Erlang nativo para couchbase todavía y me pregunto si hay algún documento coherente que podría ayudar a desarrollarlo.