Saltar al contenido principal

7 publicaciones etiquetados con "Rendimiento"

Rendimiento, latencia y escalado en sistemas distribuidos.

Ver Todas las Etiquetas

Muchas claves, pocos viajes de ida y vuelta: cómo Zaris hace fan-out de una operación masiva

· 12 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

Haciendo fan-out de una operación masiva a sus nodos propietarios

Pídele a un almacén distribuido una clave y el costo lo domina un solo número: el viaje de ida y vuelta al nodo que la posee. La búsqueda en sí es nanosegundos; la red es microsegundos. Ahora pide mil claves. Si iteras y llamas a Get mil veces, pagas ese viaje de ida y vuelta mil veces, en serie, y el almacén gasta casi todo el tiempo de reloj sentado ocioso esperando paquetes. Las claves podrían estar repartidas sobre cuatro nodos que podrían haber respondido en paralelo —pero un bucle ingenuo nunca les da la oportunidad.

Zaris tiene tres APIs masivas —GetManyAsync, PutManyAsync, DeleteManyAsync— y un ExecuteBatchAsync mixto por debajo de ellas, y toda su razón de existir es convertir "mil claves" en "una solicitud por nodo propietario, emitidas en paralelo". Este artículo es la ruta de fan-out de principio a fin: cómo el cliente agrupa claves por propietario, por qué las cuatro APIs colapsan sobre un único motor, cómo un campo Index mantiene las respuestas en orden a través de dos capas de concurrencia, y por qué —a diferencia de una transacción multiclave— una operación masiva es por ítem y el éxito parcial es normal.

Un salto al propietario: cómo el cliente de Zaris enruta una clave

· 13 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

Cómo el cliente de Zaris enruta una clave a su propietario

Hay dos formas honestas de construir un cliente de caché distribuida. La primera es elegir cualquier nodo, enviarle la solicitud, y dejar que el clúster la reenvíe internamente a quien posea la clave —simple en el cliente, pero cada solicitud que aterriza en el nodo equivocado paga un salto de red adicional dentro del clúster. La segunda es hacer al cliente lo bastante listo como para saber qué nodo posee la clave antes de enviar nada, para que el primer socket que toque sea el que realmente puede responder. Zaris toma la segunda vía. Un GET sale de tu proceso ya dirigido al nodo que sostiene la clave —sin proxy, sin salto de reenvío, sin coordinador en el medio.

Este artículo es la vista interna de cómo funciona eso: la función que convierte una clave en un propietario, la tabla de búsqueda que el cliente mantiene en memoria, y —la parte que es fácil de equivocar— cómo el cliente se mantiene correcto cuando la propiedad se mueve mientras tu tráfico está en vuelo.

El piso de latencia: un microsegundo de trabajo dentro de diez de plomería

· 12 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

Un microsegundo de trabajo dentro de diez microsegundos de plomería

Aquí hay un número que reencuadra cómo piensas sobre un almacén clave/valor. Cuando aislamos el almacén en memoria de Zaris y le damos operaciones sin nada más en el camino —sin socket, sin serialización, sin enrutamiento— un solo hilo sirve lecturas a más de un millón por segundo, y escala limpiamente a más de ocho millones a lo largo de ocho hilos. El almacén, en otras palabras, hace el trabajo real de un GET en menos de dos microsegundos, y enfáticamente no es el cuello de botella.

Y sin embargo un GET completo sobre una conexión TCP real, en la misma máquina, se estanca en unos pocos cientos de miles por segundo. El almacén puede hacer el trabajo cinco veces más rápido de lo que el servidor puede entregarlo. Así que la pregunta de rendimiento interesante sobre Zaris no es "¿qué tan rápido es el almacén?". Es lo contrario: ¿a dónde va todo el tiempo entre el socket y el almacén? Este artículo es la respuesta, trazada operación por operación, porque la brecha entre 1,7 microsegundos de lógica y 10 a 13 microsegundos de CPU de servidor por operación no es ruido —es el piso de latencia, y casi todo él es plomería.

El techo del pool de workers: cuando un nodo se estanca al 25% de CPU

· 9 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

Un nodo clavado cerca del 25% de CPU con throughput plano porque el pool de workers de despacho era el techo

Aquí hay un resultado de benchmark que parece una contradicción. Un nodo de Zaris sirviendo una carga de trabajo intensiva en lecturas tope en poco menos de medio millón de ops/seg y se niega a trepar —añade más conexiones de cliente, más generadores de carga, y el número no se mueve. El diagnóstico obvio es "está saturado". Excepto que el gráfico de CPU dice otra cosa: el nodo está al cuarto de su CPU disponible, aproximadamente. Tres núcleos de cada cuatro están ociosos, y el throughput está plano.

Eso no es un nodo limitado por núcleos, y —si lo has descartado con cuidado— tampoco es un benchmark limitado por el cliente. Es una tercera cosa, y es un clásico: el pool de workers de despacho de solicitudes era más estrecho de lo que el trabajo quería ser, así que las solicitudes se encolaban mientras los núcleos estaban de brazos cruzados.

Rutas calientes sin asignaciones: cómo el cliente .NET de Zaris usa ValueTask

· 7 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

El cliente .NET de Zaris usa ValueTask en sus operaciones de ruta caliente

La mayor parte del tiempo, Task<T> es exactamente el tipo de retorno correcto para un método asíncrono, y no deberías pensártelo dos veces. Pero hay un lugar específico donde te cuesta en silencio: una ruta caliente que se completa de forma síncrona, llamada millones de veces.

Aquí está el mecanismo. Task<T> es un tipo de referencia. Cada llamada que devuelve uno asigna un objeto Task en el heap — incluso cuando la operación se completa de forma síncrona. Un hit de caché servido directamente desde un búfer local, un awaitable que ya está completado: nada de eso requiere asincronía real, pero aún pagas por un objeto en el heap para transportar el resultado. Una asignación no es nada. A la tasa de llamadas de un cliente de alto rendimiento — un bucle apretado haciendo millones de pequeños Gets y Puts — eso es un flujo constante de pequeñas asignaciones en el heap, y ese flujo alimenta al recolector de basura. Más recolecciones de gen-0, más CPU gastada en GC en lugar de tu trabajo, más jitter en tus latencias de cola.

El cliente .NET de Zaris ataca esto exactamente donde importa y en ningún otro sitio. Este post trata de esa decisión: qué métodos cambiaron, por qué, las reglas que tienes que seguir para usarlos correctamente, y — deliberadamente — qué métodos dejamos intactos.

Valores grandes, pausas pequeñas: fragmentación de objetos grandes en Zaris

· 7 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

La fragmentación de objetos grandes mantiene los valores grandes fuera del LOH

Hay un número que todo ingeniero de rendimiento de .NET acaba memorizando: 85.000 bytes. Cualquier asignación individual en o por encima de aproximadamente ese tamaño no va al heap normal de objetos pequeños — va al Large Object Heap (LOH). El LOH es un animal diferente. Solo se recolecta durante una recolección de basura de gen-2 (completa), y por defecto no se compacta. Esa combinación está bien para un búfer grande que asignas una vez y conservas. No está bien para un almacén en memoria de baja latencia que escribe y sobrescribe repetidamente valores grandes.

Imagina la carga de trabajo: blobs serializados grandes, documentos JSON grandes, imágenes, payloads de protocolo — almacenados, actualizados, reemplazados, una y otra vez. Cada una de esas escrituras grandes deposita un objeto nuevo en el LOH. Como el LOH no se compacta, los huecos liberados no se fusionan de vuelta en espacio contiguo; se fragmentan. Y como recuperar algo de él implica una recolección completa de gen-2, las pausas se alargan y aterrizan con más frecuencia justo a medida que crece el working set. Para un almacén cuyo argumento entero es baja latencia, esa es la clase equivocada de sorpresa.

Zaris incluye una función opcional para esquivar el LOH por completo para estos valores. Está desactivada por defecto, y este post trata de lo que hace, cómo funciona, y — igual de importante — cuándo deberías y cuándo no deberías activarla.

Lecturas que escalan con tus núcleos

· 6 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

El rendimiento de GET de Zaris escala con los núcleos

Redis es rápido. También es, por diseño, monohilo en la ruta de datos: un bucle de eventos, un núcleo, un comando a la vez. Ese es un diseño genuinamente bueno: hace que Redis sea fácil de razonar y elimina toda una categoría de errores de concurrencia. Pero tiene un techo del que no puedes salir a base de dinero. Dale a un motor de un solo bucle de eventos una máquina de 4 núcleos, una de 8 núcleos, una de 32 núcleos, y el rendimiento de lectura acaba en el mismo lugar. Los núcleos extra permanecen inactivos.

Zaris toma el otro camino. Las claves están particionadas y cada partición tiene un propietario, de modo que las lecturas de claves diferentes aterrizan en núcleos diferentes y se ejecutan al mismo tiempo. El resultado es un rendimiento que se mueve cuando añades núcleos: en lugar de una línea plana, una pendiente.