{"id":5432,"date":"2018-06-29T01:23:36","date_gmt":"2018-06-29T08:23:36","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=5432"},"modified":"2018-06-29T01:23:36","modified_gmt":"2018-06-29T08:23:36","slug":"inside-the-java-sdk-connection-management","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/es\/inside-the-java-sdk-connection-management\/","title":{"rendered":"Dentro del SDK de Java: Gesti\u00f3n de conexiones"},"content":{"rendered":"<p>En esta segunda entrega de \"Inside the Java SDK\" vamos a ver en profundidad c\u00f3mo el SDK gestiona y agrupa los sockets a los distintos nodos y servicios. Aunque en \u00faltima instancia no es necesario seguir, te recomiendo que eches un vistazo al primer post sobre <a href=\"https:\/\/www.couchbase.com\/blog\/es\/inside-the-java-sdk-bootstrap\/\">arranque\u00a0<\/a>tambi\u00e9n.<\/p>\n<p>Tenga en cuenta que este post fue escrito con las versiones Java SDK 2.5.9 \/ 2.6.0 en mente. Las cosas podr\u00edan cambiar con el tiempo, pero el enfoque general deber\u00eda seguir siendo el mismo.<\/p>\n<p>Siguiendo el esp\u00edritu de los modelos OSI y TCP, propongo un modelo de tres capas que represente la pila de conexiones del SDK:<\/p>\n<pre class=\"lang:default decode:true\">+-----------------+\r\n| Service Layer   |\r\n+-----------------+\r\n| Endpoint Layer  |\r\n+-----------------+\r\n| Channel Layer   |\r\n+-----------------+<\/pre>\n<p>Los niveles superiores se superponen a los inferiores, por lo que empezaremos por la capa Canal e iremos subiendo por la pila.<\/p>\n<h2>La capa de canales<\/h2>\n<p>La capa de canal es el nivel m\u00e1s bajo en el que el SDK se ocupa de las redes y est\u00e1 construida sobre la excelente biblioteca IO totalmente as\u00edncrona llamada <a href=\"https:\/\/netty.io\/\">Netty<\/a>\u00a0Hemos sido usuarios extensivos de Netty durante a\u00f1os y tambi\u00e9n hemos contribuido con parches as\u00ed como con el <a href=\"https:\/\/github.com\/netty\/netty\/tree\/4.1\/codec-memcache\">c\u00f3dec memcache<\/a> al proyecto.<\/p>\n<p>Cada Netty <a href=\"https:\/\/netty.io\/4.0\/api\/io\/netty\/channel\/Channel.html\">Canal<\/a> corresponde a un socket y se multiplexa sobre bucles de eventos. Cubriremos el modelo de hilos en una entrada posterior del blog, pero por ahora es importante saber que en lugar del modelo de \"un hilo por socket\" del IO de bloqueo tradicional, Netty toma todos los sockets abiertos y los distribuye a trav\u00e9s de un pu\u00f1ado de bucles de eventos. Hace esto de una manera muy eficiente, por lo que no es de extra\u00f1ar que Netty se utiliza <a href=\"https:\/\/netty.io\/wiki\/adopters.html\">en todo el sector<\/a>\u00a0para componentes de red de alto rendimiento y baja latencia.<\/p>\n<p>Dado que un canal s\u00f3lo se ocupa de los bytes que entran y salen, necesitamos una forma de codificar y decodificar las solicitudes a nivel de aplicaci\u00f3n (como una consulta N1QL o una solicitud de obtenci\u00f3n de clave\/valor) en su representaci\u00f3n binaria adecuada. En Netty esto se hace agregando <a href=\"https:\/\/netty.io\/4.0\/api\/io\/netty\/channel\/ChannelHandler.html\">manipuladores<\/a> a la <a href=\"https:\/\/netty.io\/4.0\/api\/io\/netty\/channel\/ChannelPipeline.html\">canalizaci\u00f3n<\/a>. Todas las operaciones de escritura de la red bajan por la tuber\u00eda y las respuestas del servidor vuelven a subir por la tuber\u00eda (tambi\u00e9n llamadas de entrada y salida en la terminolog\u00eda de Netty).<\/p>\n<p>Algunos manejadores se a\u00f1aden independientemente del servicio utilizado (como el registro o el cifrado) y otros dependen del tipo de servicio (por ejemplo, para una respuesta N1QL tenemos analizadores de flujo JSON personalizados para la estructura de la respuesta).<\/p>\n<p>Si alguna vez te has preguntado c\u00f3mo obtener una salida de registro a nivel de paquetes durante el desarrollo o depuraci\u00f3n (para producci\u00f3n usa tcpdump, wireshark o similar), todo lo que necesitas hacer es activar el nivel de registro TRACE en tu biblioteca de registro favorita y ver\u00e1s una salida como esta:<\/p>\n<pre class=\"lang:default decode:true\">[cb-io-1-1] 2018-06-28 14:03:34 TRACE LoggingHandler:94 - [id: 0x41407638, L:\/127.0.0.1:60923 - R:localhost\/127.0.0.1:11210] WRITE: 243B\r\n+-------------------------------------------------+\r\n| 0 1 2 3 4 5 6 7 8 9 a b c d e f |\r\n+--------+-------------------------------------------------+----------------+\r\n|00000000| 80 1f 00 db 00 00 00 00 00 00 00 e5 00 00 00 00 |................|\r\n|00000010| 00 00 00 00 00 00 00 00 7b 22 61 22 3a 22 63 6f |........{\"a\":\"co|\r\n|00000020| 75 63 68 62 61 73 65 2d 6a 61 76 61 2d 63 6c 69 |uchbase-java-cli|\r\n|00000030| 65 6e 74 2f 32 2e 36 2e 30 2d 53 4e 41 50 53 48 |ent\/2.6.0-SNAPSH|\r\n|00000040| 4f 54 20 28 67 69 74 3a 20 32 2e 36 2e 30 2d 62 |OT (git: 2.6.0-b|\r\n|00000050| 65 74 61 2d 31 36 2d 67 35 63 65 30 38 62 30 2c |eta-16-g5ce08b0,|\r\n|00000060| 20 63 6f 72 65 3a 20 31 2e 36 2e 30 2d 62 65 74 | core: 1.6.0-bet|\r\n|00000070| 61 2d 33 33 2d 67 31 62 33 65 36 66 62 29 20 28 |a-33-g1b3e6fb) (|\r\n|00000080| 4d 61 63 20 4f 53 20 58 2f 31 30 2e 31 33 2e 34 |Mac OS X\/10.13.4|\r\n|00000090| 20 78 38 36 5f 36 34 3b 20 4a 61 76 61 20 48 6f | x86_64; Java Ho|\r\n|000000a0| 74 53 70 6f 74 28 54 4d 29 20 36 34 2d 42 69 74 |tSpot(TM) 64-Bit|\r\n|000000b0| 20 53 65 72 76 65 72 20 56 4d 20 31 2e 38 2e 30 | Server VM 1.8.0|\r\n|000000c0| 5f 31 30 31 2d 62 31 33 29 22 2c 22 69 22 3a 22 |_101-b13)\",\"i\":\"|\r\n|000000d0| 30 43 34 37 35 41 43 41 35 46 33 38 30 41 32 31 |0C475ACA5F380A21|\r\n|000000e0| 2f 30 30 30 30 30 30 30 30 34 31 34 30 37 36 33 |\/000000004140763|\r\n|000000f0| 38 22 7d |8\"} |\r\n+--------+-------------------------------------------------+----------------+<\/pre>\n<p>Observe el peque\u00f1o <span class=\"lang:default decode:true crayon-inline\">LoggingHandler<\/span>\u00a0 ah\u00ed arriba? Esto se debe a que s\u00f3lo a\u00f1adimos el controlador de registro si el rastreo est\u00e1 habilitado para la tuber\u00eda, por lo que no est\u00e1 pagando la sobrecarga si no lo est\u00e1 utilizando (que es la mayor parte del tiempo):<\/p>\n<pre class=\"lang:java decode:true\">bootstrap = new BootstrapAdapter(new Bootstrap()\r\n  \/\/ *snip*\r\n  .option(ChannelOption.ALLOCATOR, allocator)\r\n  .option(ChannelOption.TCP_NODELAY, tcpNodelay)\r\n  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, env.socketConnectTimeout())\r\n  .handler(new ChannelInitializer&lt;Channel&gt;() {\r\n    @Override\r\n    protected void initChannel(Channel channel) throws Exception {\r\n      ChannelPipeline pipeline = channel.pipeline();\r\n      if (env.sslEnabled()) {\r\n        pipeline.addLast(new SslHandler(sslEngineFactory.get()));\r\n      }\r\n      if (LOGGER.isTraceEnabled()) {\r\n        pipeline.addLast(LOGGING_HANDLER_INSTANCE);\r\n      }\r\n    customEndpointHandlers(pipeline);\r\n  }\r\n}));<\/pre>\n<p>Tambi\u00e9n puedes ver que dependiendo de la configuraci\u00f3n del entorno hacemos otros ajustes como a\u00f1adir un handler SSL\/TLS al pipeline o configurar el nodelay TCP y los timeouts de los sockets.<\/p>\n<p>En <span class=\"lang:default decode:true crayon-inline\">customEndpointHandlers<\/span>\u00a0 se sobrescribe para cada servicio, aqu\u00ed est\u00e1 la tuber\u00eda para la capa KV (ligeramente simplificada):<\/p>\n<pre class=\"lang:java decode:true\">if (environment().keepAliveInterval() &gt; 0) {\r\n    pipeline.addLast(new IdleStateHandler(environment().keepAliveInterval(), 0, 0, TimeUnit.MILLISECONDS));\r\n}\r\n\r\npipeline\r\n    .addLast(new BinaryMemcacheClientCodec())\r\n    .addLast(new BinaryMemcacheObjectAggregator(Integer.MAX_VALUE));\r\n\r\npipeline\r\n    .addLast(new KeyValueFeatureHandler(context()))\r\n    .addLast(new KeyValueErrorMapHandler());\r\n\r\nif (!environment().certAuthEnabled()) {\r\n    pipeline.addLast(new KeyValueAuthHandler(username(), password(), environment().forceSaslPlain()));\r\n}\r\n\r\npipeline\r\n    .addLast(new KeyValueSelectBucketHandler(bucket()))\r\n    .addLast(new KeyValueHandler(this, responseBuffer(), false, true));<\/pre>\n<p>Aqu\u00ed pasan muchas cosas. Vayamos por partes:<\/p>\n<ul>\n<li>En <span class=\"lang:default decode:true crayon-inline\">IdleStateHandler<\/span>\u00a0 se utiliza para activar los keepalives a nivel de aplicaci\u00f3n.<\/li>\n<li>Los dos gestores siguientes <span class=\"lang:default decode:true crayon-inline\">BinaryMemcacheClientCodec<\/span>\u00a0 y <span class=\"lang:default decode:true crayon-inline\">BinaryMemcacheObjectAggregator<\/span>\u00a0 se encargan de codificar los objetos de solicitud y respuesta de memcache en sus representaciones en bytes y viceversa.<\/li>\n<li><span class=\"lang:default decode:true crayon-inline\">KeyValueFeatureHandler<\/span>\u00a0, <span class=\"lang:default decode:true crayon-inline\">KeyValueErrorMapHandler<\/span>\u00a0, <span class=\"lang:default decode:true crayon-inline\">KeyValueAuthHandler<\/span>\u00a0 y <span class=\"lang:default decode:true crayon-inline\">KeyValueSelectBucketHandler<\/span>\u00a0 todos realizan el handshaking, la autenticaci\u00f3n, la selecci\u00f3n de cubos, etc. durante la fase de conexi\u00f3n y se retiran de la cadena una vez completada.<\/li>\n<li>Por \u00faltimo, el <span class=\"lang:default decode:true crayon-inline\">KeyValueHandler<\/span>\u00a0 realiza la mayor parte del trabajo y \"conoce\" todos los tipos de solicitudes que entran y salen del sistema.<\/li>\n<\/ul>\n<p>Si quieres echar un vistazo a uno diferente, <a href=\"https:\/\/github.com\/couchbase\/couchbase-jvm-core\/blob\/master\/src\/main\/java\/com\/couchbase\/client\/core\/endpoint\/query\/QueryEndpoint.java#L48\">aqu\u00ed<\/a>\u00a0es el oleoducto N1QL, por ejemplo.<\/p>\n<p>Antes de subir una capa hay algo importante. El observable RxJava <a href=\"https:\/\/github.com\/couchbase\/couchbase-jvm-core\/blob\/master\/src\/main\/java\/com\/couchbase\/client\/core\/endpoint\/AbstractGenericHandler.java#L507\">finalizaci\u00f3n<\/a> tambi\u00e9n ocurre en esta capa. Una vez que se decodifica una respuesta, se completa en el bucle de eventos directamente o en un grupo de hilos (configurado por defecto).<\/p>\n<p>Es importante saber que una vez que un canal se cae (porque el socket subyacente se cierra) todo el estado a este nivel desaparece. En un intento de reconexi\u00f3n se crea un nuevo canal. \u00bfQui\u00e9n gestiona un canal? Subamos un nivel.<\/p>\n<h2>La capa final<\/h2>\n<p>En <span class=\"lang:default decode:true crayon-inline\">Punto final<\/span>\u00a0 se encarga de gestionar el ciclo de vida de un canal, incluyendo el arranque, la reconexi\u00f3n y la desconexi\u00f3n. Puede encontrar el c\u00f3digo <a href=\"https:\/\/github.com\/couchbase\/couchbase-jvm-core\/blob\/master\/src\/main\/java\/com\/couchbase\/client\/core\/endpoint\/AbstractEndpoint.java\">aqu\u00ed<\/a>.<\/p>\n<p>Siempre hay una relaci\u00f3n 1:1 entre el Endpoint y el canal que gestiona, pero si un canal desaparece y hay que reconectar un socket, el endpoint sigue siendo el mismo y recibe uno nuevo internamente. El endpoint es tambi\u00e9n el lugar donde se entrega la petici\u00f3n a los bucles de eventos (simplificado):<\/p>\n<pre class=\"lang:java decode:true\">@Override\r\npublic void send(final CouchbaseRequest request) {\r\n\tif (channel.isActive() &amp;&amp; channel.isWritable()) {\r\n\t    channel.write(request, channel.voidPromise());\r\n\t} else {\r\n\t    responseBuffer.publishEvent(ResponseHandler.RESPONSE_TRANSLATOR, request, request.observable());\r\n\t}\r\n}<\/pre>\n<p>Si nuestro canal est\u00e1 activo y se puede escribir en \u00e9l, escribiremos la solicitud en el canal, de lo contrario, se devuelve y se vuelve a poner en cola para otro intento.<\/p>\n<p>Aqu\u00ed hay un aspecto muy importante del endpoint a tener en cuenta: si un canal se cierra, el endpoint intentar\u00e1 reconectarse (con el backoff configurado) mientras se le diga expl\u00edcitamente que se detenga. Se detiene cuando el gestor del <span class=\"lang:default decode:true crayon-inline\">Punto final<\/span>\u00a0 llama a <span class=\"lang:default decode:true crayon-inline\">desconectar<\/span>\u00a0 que ocurrir\u00e1 en \u00faltima instancia cuando el servicio\/nodo respectivo ya no forme parte de la configuraci\u00f3n. As\u00ed que al final de un rebalanceo o durante un failover el cliente recibir\u00e1 una nueva configuraci\u00f3n de cluster de la cual infiere que este endpoint puede ser terminado y entonces lo hace en consecuencia. Si, por cualquier raz\u00f3n, hay un retraso entre la desconexi\u00f3n de un socket y la propagaci\u00f3n de esta informaci\u00f3n, es posible que se produzcan algunos intentos de reconexi\u00f3n que se detendr\u00e1n finalmente.<\/p>\n<p>Un punto final est\u00e1 muy bien, pero m\u00e1s siempre es mejor, \u00bfverdad? As\u00ed que subamos una capa m\u00e1s para averiguar c\u00f3mo se agrupan los puntos finales para crear sofisticados grupos de conexiones por nodo y servicio.<\/p>\n<h2>La capa de servicios<\/h2>\n<p>En <span class=\"lang:default decode:true crayon-inline\">Servicio<\/span>\u00a0 gestiona uno o m\u00e1s endpoints por nodo. Cada servicio s\u00f3lo es responsable de un nodo - as\u00ed, por ejemplo, si tienes un cluster Couchbase de 5 nodos con s\u00f3lo el servicio KV activado en cada uno de ellos, si inspeccionas un volcado de heap encontrar\u00e1s 5 instancias del servicio <span class=\"lang:default decode:true crayon-inline\">KeyValueService<\/span>\u00a0.<\/p>\n<p>En versiones anteriores del cliente s\u00f3lo se pod\u00eda configurar un n\u00famero fijo de puntos finales por servicio mediante m\u00e9todos como <span class=\"lang:default decode:true crayon-inline\">kvEndpoints<\/span>\u00a0, <span class=\"lang:default decode:true crayon-inline\">queryEndpoints<\/span>\u00a0 etc. Debido a requisitos m\u00e1s complejos, hemos obviado este enfoque \"fijo\" con una potente implementaci\u00f3n de pool de conexiones. Esta es la raz\u00f3n por la que en lugar de <span class=\"lang:default decode:true crayon-inline\">queryEndpoints<\/span>\u00a0 ahora debe utilizar <span class=\"lang:default decode:true crayon-inline\">queryServiceConfig<\/span>\u00a0 y equivalentes.<\/p>\n<p>Estos son los grupos por defecto por servicio en 2.5.9 y 2.6.0:<\/p>\n<ul>\n<li><span class=\"lang:default decode:true crayon-inline\">KeyValueService<\/span>\u00a0: 1 punto final por nodo, fijo.<\/li>\n<li><span class=\"lang:default decode:true crayon-inline\">Servicio de consulta<\/span>\u00a0de 0 a 12 puntos finales por nodo, din\u00e1mico.<\/li>\n<li><span class=\"lang:default decode:true crayon-inline\">VerServicio<\/span>\u00a0de 0 a 12 puntos finales por nodo, din\u00e1mico.<\/li>\n<li><span class=\"lang:default decode:true crayon-inline\">An\u00e1lisisServicio<\/span>\u00a0de 0 a 12 puntos finales por nodo, din\u00e1mico.<\/li>\n<li><span class=\"lang:default decode:true crayon-inline\">Servicio de b\u00fasqueda<\/span>\u00a0de 0 a 12 puntos finales por nodo, din\u00e1mico.<\/li>\n<\/ul>\n<p>La raz\u00f3n por la que la KV no se agrupa de forma predeterminada es que el establecimiento de conexiones es mucho m\u00e1s costoso (recuerde todos los gestores de la canalizaci\u00f3n) y el patr\u00f3n de tr\u00e1fico suele ser muy diferente del de los servicios basados en consultas m\u00e1s pesadas. La experiencia sobre el terreno ha demostrado que aumentar el n\u00famero de puntos finales de KV s\u00f3lo tiene sentido en escenarios de \"carga masiva\" y tr\u00e1fico muy irregular en los que la \"tuber\u00eda\" de un socket es demasiado peque\u00f1a. Si esto no se eval\u00faa adecuadamente, tambi\u00e9n podr\u00eda ser que a\u00f1adir m\u00e1s sockets a la capa KV puede degradar el rendimiento en lugar de mejorarlo - supongo que m\u00e1s no siempre es mejor.<\/p>\n<p>La l\u00f3gica de puesta en com\u00fan se encuentra en <a href=\"https:\/\/github.com\/couchbase\/couchbase-jvm-core\/blob\/master\/src\/main\/java\/com\/couchbase\/client\/core\/service\/PooledService.java\">aqu\u00ed<\/a> si tienes curiosidad, pero merece la pena examinar cierta sem\u00e1ntica que hay ah\u00ed.<\/p>\n<p>Durante la fase de conexi\u00f3n del servicio, se asegura de que se establece por adelantado el n\u00famero m\u00ednimo de puntos finales. Si el m\u00ednimo es igual al m\u00e1ximo, se desactiva la agrupaci\u00f3n din\u00e1mica y el c\u00f3digo elegir\u00e1 uno de los puntos finales para cada solicitud:<\/p>\n<pre class=\"lang:java decode:true\">synchronized (epMutex) {\r\n    int numToConnect = minEndpoints - endpoints.size();\r\n    if (numToConnect == 0) {\r\n        LOGGER.debug(\"No endpoints needed to connect, skipping.\");\r\n        return Observable.just(state());\r\n    }\r\n    for (int i = 0; i &lt; numToConnect; i++) {\r\n        Endpoint endpoint = endpointFactory.create(hostname, bucket, username, password, port, ctx);\r\n        endpoints.add(endpoint);\r\n        endpointStates.register(endpoint, endpoint);\r\n    }\r\n\r\n    LOGGER.debug(logIdent(hostname, PooledService.this)\r\n            + \"New number of endpoints is {}\", endpoints.size());\r\n}<\/pre>\n<p>Esto puede observarse en los registros inmediatamente durante el arranque:<\/p>\n<pre class=\"lang:default decode:true\">[cb-computations-5] 2018-06-28 14:03:34 DEBUG Service:257 - [localhost][KeyValueService]: New number of endpoints is 1\r\n[cb-computations-8] 2018-06-28 14:03:35 DEBUG Service:248 - [localhost][QueryService]: No endpoints needed to connect, skipping.<\/pre>\n<p>Cuando llega una solicitud, o bien se env\u00eda, o bien se crea otro endpoint (si a\u00fan queda espacio en el pool), que tambi\u00e9n se gestiona (de forma ligeramente simplificada):<\/p>\n<pre class=\"lang:java decode:true\">@Override\r\npublic void send(final CouchbaseRequest request) {\r\n    Endpoint endpoint = endpoints.size() &gt; 0 ? selectionStrategy.select(request, endpoints) : null;\r\n\r\n    if (endpoint == null) {\r\n        if (fixedEndpoints || (endpoints.size() &gt;= maxEndpoints)) {\r\n            RetryHelper.retryOrCancel(env, request, responseBuffer);\r\n        } else {\r\n            maybeOpenAndSend(request);\r\n        }\r\n    } else {\r\n        endpoint.send(request);\r\n    }\r\n}<\/pre>\n<p>Tenga en cuenta que si no podemos encontrar un endpoint adecuado y el pool es fijo o hemos alcanzado nuestro techo entonces la operaci\u00f3n se programa para reintento, muy similar a la l\u00f3gica del endpoint cuando no est\u00e1 activo o no se puede escribir.<\/p>\n<p>En los servicios basados en pool HTTP no queremos mantener esos sockets para siempre, as\u00ed que puedes configurar un tiempo de inactividad (que es de 300s por defecto). Cada pool ejecuta un temporizador de inactividad que examina regularmente los endpoints por si han estado inactivos durante m\u00e1s tiempo que el intervalo configurado y si es as\u00ed lo desconecta. N\u00f3tese que la l\u00f3gica siempre se asegura de no caer por debajo del n\u00famero m\u00ednimo.<\/p>\n<h2>Errores comunes relacionados con la conexi\u00f3n<\/h2>\n<p>Ahora que tienes una buena idea de c\u00f3mo el SDK maneja los sockets y los agrupa, vamos a hablar de un par de escenarios de error que pueden surgir.<\/p>\n<h3>Solicitud de anulaci\u00f3n<\/h3>\n<p>Hablemos del <span class=\"lang:default decode:true crayon-inline\">RequestCancelledException<\/span>\u00a0 primero.<\/p>\n<p>Si est\u00e1 realizando una operaci\u00f3n y falla con un <span class=\"lang:default decode:true crayon-inline\">RequestCancelledException<\/span>\u00a0 suele haber dos causas diferentes:<\/p>\n<ul>\n<li>La operaci\u00f3n dio vueltas dentro del cliente (sin enviarse por la red) durante m\u00e1s tiempo del configurado <span class=\"lang:default decode:true crayon-inline\">maxRequestLifetime<\/span>\u00a0.<\/li>\n<li>Se ha escrito una petici\u00f3n a la red, pero antes de obtener una respuesta se ha cerrado el canal subyacente.<\/li>\n<\/ul>\n<p>Hay otras razones menos comunes (por ejemplo, problemas durante la codificaci\u00f3n de una solicitud), pero para el prop\u00f3sito de este blog nos centraremos en la segunda causa.<\/p>\n<p>Entonces, \u00bfpor qu\u00e9 tenemos que cancelar la petici\u00f3n y no reintentarla en otro socket que todav\u00eda est\u00e9 activo? La raz\u00f3n es que no sabemos si la operaci\u00f3n ya ha causado un efecto secundario en el servidor (por ejemplo una mutaci\u00f3n aplicada). Si reintent\u00e1ramos operaciones no idempotentes se producir\u00edan efectos extra\u00f1os dif\u00edciles de diagnosticar en la pr\u00e1ctica. En lugar de eso, le decimos a quien llama que la petici\u00f3n ha fallado y entonces depende de la l\u00f3gica de la aplicaci\u00f3n averiguar qu\u00e9 hacer a continuaci\u00f3n. Si era una simple petici\u00f3n get y todav\u00eda est\u00e1s en tu presupuesto de tiempo de espera puedes reintentarlo por tu cuenta. Si se trata de una mutaci\u00f3n que usted necesita para poner un poco m\u00e1s de l\u00f3gica en su lugar para leer el documento y averiguar si se ha aplicado o usted sabe que puede ser enviado de nuevo de inmediato. Y luego siempre est\u00e1 la opci\u00f3n de propagar el error de nuevo a la persona que llama a su API. En cualquier caso es predecible desde el lado del SDK y no causar\u00e1 m\u00e1s da\u00f1o en segundo plano.<\/p>\n<h3>Problemas de Bootstrap<\/h3>\n<p>La otra fuente de errores que vale la pena conocer son los problemas durante la fase de conexi\u00f3n del socket. Normalmente encontrar\u00e1s errores descriptivos en los registros que te dir\u00e1n lo que est\u00e1 pasando (por ejemplo credenciales err\u00f3neas) pero hay dos que pueden ser un poco m\u00e1s dif\u00edciles de descifrar: El tiempo de espera de la salvaguarda de conexi\u00f3n y los errores de selecci\u00f3n de cubo durante el reequilibrio.<\/p>\n<p>Como has visto antes, el pipeline de KV contiene muchos handlers que trabajan con el servidor durante el bootstrap para averiguar todo tipo de configuraciones y negociar las caracter\u00edsticas soportadas. En el momento de escribir esto, cada operaci\u00f3n individual no tiene un tiempo de espera individual, sino que el tiempo de espera de salvaguarda de la conexi\u00f3n se activa si tarda m\u00e1s de lo que la fase de conexi\u00f3n tiene permitido en t\u00e9rminos de presupuesto total.<\/p>\n<p>As\u00ed que si ves el <span class=\"lang:default decode:true crayon-inline\">ConnectTimeoutException<\/span>\u00a0 en los registros con el mensaje <span class=\"lang:default decode:true crayon-inline\">Connect callback did not return, hit safeguarding timeout.<\/span>\u00a0 lo que significa es que una operaci\u00f3n o la suma de todas ellas ha tardado m\u00e1s de lo presupuestado y se realizar\u00e1 otro intento de reconexi\u00f3n. Esto no es perjudicial en general, ya que nos volveremos a conectar, pero es un buen indicio de que puede haber alguna lentitud en la red o en alg\u00fan otro lugar de la pila que deber\u00eda examinarse con m\u00e1s detenimiento. Un buen siguiente paso ser\u00eda iniciar <a href=\"https:\/\/www.wireshark.org\/\">wireshark<\/a> \/ <a href=\"https:\/\/www.tcpdump.org\/\">tcpdump<\/a> y registrar las fases de arranque para averiguar d\u00f3nde se gasta el tiempo y luego pivotar hacia el lado del cliente o del servidor en funci\u00f3n de los tiempos registrados. Por defecto, el tiempo de espera de salvaguardia est\u00e1 configurado como el <span class=\"lang:default decode:true crayon-inline\">socketConnectTimeout<\/span>\u00a0 m\u00e1s el <span class=\"lang:default decode:true crayon-inline\">connectCallbackGracePeriod<\/span>\u00a0 que est\u00e1 ajustado a 2 segundos y puede sintonizarse a trav\u00e9s de la tecla <span class=\"lang:default decode:true crayon-inline\">com.couchbase.connectCallbackGracePeriod<\/span>\u00a0 propiedad del sistema.<\/p>\n<p>Uno de los pasos durante el bootstrap desde que a\u00f1adimos soporte para RBAC (control de acceso basado en roles) se llama \"select bucket\" a trav\u00e9s de la funci\u00f3n <span class=\"lang:default decode:true crayon-inline\">KeyValueSelectBucketHandler<\/span>\u00a0. Dado que existe una desconexi\u00f3n entre la autenticaci\u00f3n y el acceso a un bucket, es posible que el cliente se conecte a un servicio de KV pero el propio motor de KV no est\u00e9 a\u00fan preparado para servirlo. El cliente manejar\u00e1 la situaci\u00f3n con elegancia y volver\u00e1 a intentarlo -y no se observa ning\u00fan impacto en la carga de trabajo real-, pero dado que la higiene de los registros tambi\u00e9n es una preocupaci\u00f3n, actualmente estamos mejorando el algoritmo del SDK en este punto. Si quieres puedes seguir el progreso en<a href=\"https:\/\/issues.couchbase.com\/browse\/JVMCBC-553\"> JVMCBC-553<\/a>.<\/p>\n<h2>Reflexiones finales<\/h2>\n<p>A estas alturas deber\u00edas tener una s\u00f3lida comprensi\u00f3n de c\u00f3mo el SDK gestiona sus sockets subyacentes y los agrupa en la capa de servicio. Si quieres profundizar en el c\u00f3digo base, empieza por <a href=\"https:\/\/github.com\/couchbase\/couchbase-jvm-core\/tree\/master\/src\/main\/java\/com\/couchbase\/client\/core\">aqu\u00ed<\/a>\u00a0y luego buscar en los respectivos espacios de nombres para <span class=\"lang:default decode:true crayon-inline\">servicio<\/span>\u00a0 y <span class=\"lang:default decode:true crayon-inline\">punto final<\/span>\u00a0. Todos los manejadores de canal de Netty est\u00e1n por debajo del <span class=\"lang:default decode:true crayon-inline\">punto final<\/span>\u00a0 tambi\u00e9n.<\/p>\n<p>Si tienes m\u00e1s preguntas, \u00a1comenta a continuaci\u00f3n! El pr\u00f3ximo post tratar\u00e1 sobre el modelo general de roscado del SDK.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this second instalment of &#8220;Inside the Java SDK&#8221; we are going to take an in-depth look at how the SDK manages and pools sockets to the various nodes and services. While not ultimately necessary to follow, I recommend you [&hellip;]<\/p>","protected":false},"author":19,"featured_media":13873,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1821,1818,2201],"tags":[],"ppma_author":[8987],"class_list":["post-5432","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-couchbase-architecture","category-java","category-tools-sdks"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.8 (Yoast SEO v25.8) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Inside the Java SDK: Connection Management - The Couchbase Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/es\/inside-the-java-sdk-connection-management\/\" \/>\n<meta property=\"og:locale\" content=\"es_MX\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Inside the Java SDK: Connection Management\" \/>\n<meta property=\"og:description\" content=\"In this second instalment of &#8220;Inside the Java SDK&#8221; we are going to take an in-depth look at how the SDK manages and pools sockets to the various nodes and services. While not ultimately necessary to follow, I recommend you [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/es\/inside-the-java-sdk-connection-management\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-06-29T08:23:36+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2022\/11\/couchbase-nosql-dbaas.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1800\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Michael Nitschinger\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@daschl\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Michael Nitschinger\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/\"},\"author\":{\"name\":\"Michael Nitschinger\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/e5d4d332756da6f361dd88c1576de61d\"},\"headline\":\"Inside the Java SDK: Connection Management\",\"datePublished\":\"2018-06-29T08:23:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/\"},\"wordCount\":2235,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"articleSection\":[\"Couchbase Architecture\",\"Java\",\"Tools &amp; SDKs\"],\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/\",\"name\":\"Inside the Java SDK: Connection Management - The Couchbase Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"datePublished\":\"2018-06-29T08:23:36+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png\",\"width\":1800,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Inside the Java SDK: Connection Management\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/e5d4d332756da6f361dd88c1576de61d\",\"name\":\"Michael Nitschinger\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/95e178617974d46e3b02dd1754a3f60b\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/dad99b5e02a74ca4bec14352e9da710160647a97290814b669babb3aac0ea675?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/dad99b5e02a74ca4bec14352e9da710160647a97290814b669babb3aac0ea675?s=96&d=mm&r=g\",\"caption\":\"Michael Nitschinger\"},\"description\":\"Michael Nitschinger works as a Principal Software Engineer at Couchbase. He is the architect and maintainer of the Couchbase Java SDK, one of the first completely reactive database drivers on the JVM. He also authored and maintains the Couchbase Spark Connector. Michael is active in the open source community, a contributor to various other projects like RxJava and Netty.\",\"sameAs\":[\"https:\/\/nitschinger.at\",\"https:\/\/x.com\/daschl\"],\"url\":\"https:\/\/www.couchbase.com\/blog\/es\/author\/michael-nitschinger\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Inside the Java SDK: Connection Management - The Couchbase Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/es\/inside-the-java-sdk-connection-management\/","og_locale":"es_MX","og_type":"article","og_title":"Inside the Java SDK: Connection Management","og_description":"In this second instalment of &#8220;Inside the Java SDK&#8221; we are going to take an in-depth look at how the SDK manages and pools sockets to the various nodes and services. While not ultimately necessary to follow, I recommend you [&hellip;]","og_url":"https:\/\/www.couchbase.com\/blog\/es\/inside-the-java-sdk-connection-management\/","og_site_name":"The Couchbase Blog","article_published_time":"2018-06-29T08:23:36+00:00","og_image":[{"width":1800,"height":630,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2022\/11\/couchbase-nosql-dbaas.png","type":"image\/png"}],"author":"Michael Nitschinger","twitter_card":"summary_large_image","twitter_creator":"@daschl","twitter_misc":{"Written by":"Michael Nitschinger","Est. reading time":"13 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/"},"author":{"name":"Michael Nitschinger","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/e5d4d332756da6f361dd88c1576de61d"},"headline":"Inside the Java SDK: Connection Management","datePublished":"2018-06-29T08:23:36+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/"},"wordCount":2235,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","articleSection":["Couchbase Architecture","Java","Tools &amp; SDKs"],"inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/","url":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/","name":"Inside the Java SDK: Connection Management - The Couchbase Blog","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","datePublished":"2018-06-29T08:23:36+00:00","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2022\/11\/couchbase-nosql-dbaas.png","width":1800,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/inside-the-java-sdk-connection-management\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Inside the Java SDK: Connection Management"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"El blog de Couchbase","description":"Couchbase, la base de datos NoSQL","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"El blog de Couchbase","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/e5d4d332756da6f361dd88c1576de61d","name":"Michael Nitschinger","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/95e178617974d46e3b02dd1754a3f60b","url":"https:\/\/secure.gravatar.com\/avatar\/dad99b5e02a74ca4bec14352e9da710160647a97290814b669babb3aac0ea675?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/dad99b5e02a74ca4bec14352e9da710160647a97290814b669babb3aac0ea675?s=96&d=mm&r=g","caption":"Michael Nitschinger"},"description":"Michael Nitschinger trabaja como Ingeniero de Software Principal en Couchbase. Es el arquitecto y mantenedor del SDK Java de Couchbase, uno de los primeros controladores de bases de datos completamente reactivos en la JVM. Tambi\u00e9n es autor y mantiene el conector Spark de Couchbase. Michael participa activamente en la comunidad de c\u00f3digo abierto, contribuyendo a otros proyectos como RxJava y Netty.","sameAs":["https:\/\/nitschinger.at","https:\/\/x.com\/daschl"],"url":"https:\/\/www.couchbase.com\/blog\/es\/author\/michael-nitschinger\/"}]}},"authors":[{"term_id":8987,"user_id":19,"is_guest":0,"slug":"michael-nitschinger","display_name":"Michael Nitschinger","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/dad99b5e02a74ca4bec14352e9da710160647a97290814b669babb3aac0ea675?s=96&d=mm&r=g","author_category":"","last_name":"Nitschinger, Principal Software Engineer, Couchbase","first_name":"Michael","job_title":"","user_url":"https:\/\/nitschinger.at","description":"Michael Nitschinger trabaja como Ingeniero de Software Principal en Couchbase. Es el arquitecto y mantenedor del SDK Java de Couchbase, uno de los primeros controladores de bases de datos completamente reactivos en la JVM. Tambi\u00e9n es autor y mantiene el conector Spark de Couchbase. Michael participa activamente en la comunidad de c\u00f3digo abierto, contribuyendo a otros proyectos como RxJava y Netty."}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/posts\/5432","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/users\/19"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/comments?post=5432"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/posts\/5432\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/media\/13873"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/media?parent=5432"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/categories?post=5432"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/tags?post=5432"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/es\/wp-json\/wp\/v2\/ppma_author?post=5432"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}