Saltar al contenido principal

19 publicaciones etiquetados con ".NET"

Desarrollo de sistemas distribuidos e infraestructura en .NET.

Ver Todas las Etiquetas

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 de memoria: por qué un nodo de Zaris nunca crece sin límite

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

Un nodo de Zaris manteniendo el uso de memoria en una banda tranquila por debajo de su techo, expulsando al 80% hasta el 70% para que las escrituras nunca crucen el límite

Aquí hay una pregunta que suena simple y no lo es: ¿qué ocurre cuando un almacén en memoria se queda sin memoria?

La respuesta ingenua es "se cae" —el proceso trepa hasta que el sistema operativo lo mata. La respuesta un poco menos ingenua es "rechaza escrituras". Zaris no hace ninguna de las dos por defecto. Un nodo de Zaris es un almacén en memoria, pero deliberadamente no es uno ilimitado: cada nodo es un buffer acotado con un techo duro, y cuando los datos que posee se acercan a ese techo el nodo expulsa sus datos más fríos para hacer espacio. Las escrituras siguen teniendo éxito; el límite se sostiene; el proceso no se hincha. El nodo se comporta como una caché, porque por debajo, lo es.

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.

Caché lista para usar en ASP.NET Core: IDistributedCache y HybridCache sobre Zaris

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

Caché distribuida lista para usar en ASP.NET Core sobre Zaris

La mayoría de las razones para recurrir a una caché distribuida en una aplicación .NET nunca mencionan una caché por su nombre. Añades caché de salida (output caching) y quiere un IDistributedCache. Habilitas sesiones y el middleware quiere un almacén de respaldo. Activas el uso compartido de claves de protección de datos entre instancias y, de nuevo, quiere un lugar donde poner bytes que todos los nodos puedan ver. El código de la aplicación está escrito contra una abstracción; la única pregunta es qué implementación registras al arrancar.

Zaris responde a esa pregunta con tres pequeños paquetes NuGet, cada uno apuntando a una costura específica de ASP.NET Core: un IDistributedCache simple, un HybridCache de dos niveles y un almacén de sesiones. Registras una línea al arrancar y el resto de tu aplicación —incluidas las bibliotecas de terceros que solo conocen la abstracción— cachea en un clúster Zaris sin un solo cambio en el código que usa la caché. Esta entrada recorre los tres, y es deliberada sobre dónde se detiene honestamente cada uno.

El plano de control en tu shell: gestionar un clúster de Zaris desde PowerShell

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

Aprovisionar y ejecutar un clúster de Zaris desde PowerShell

La mayor parte de lo que lees sobre un almacén de datos trata del camino de datos: lecturas, escrituras, el protocolo de cable, lo rápido que va. Pero antes de que nada de eso importe, alguien tiene que poner la cosa en pie: decidir cuántas particiones tiene, cuántas copias de cada una, dónde viven los puertos, arrancarla, confirmar que levantó sana y vigilarla mientras corre. Eso es el plano de control, y en Zaris vive en PowerShell.

Este es un recorrido de operador de día uno. Crearemos un workspace, aprovisionaremos un almacén, lo arrancaremos y luego volveremos a leer su estado en vivo, todo desde el módulo AdminShell. Sin consola web, nada editado a mano. Y como el sentido de una historia de operador es la confianza, seremos explícitos sobre las decisiones que New-ZrStore pone ante ti y lo que cada una cuesta de verdad.

El mismo código, con red o sin ella: el modo embebido de Zaris

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

El modo embebido en proceso de Zaris

La mayoría de los almacenes de datos distribuidos te hacen elegir, pronto y de forma permanente, entre dos mundos. Está el almacén "de verdad": un clúster que despliegas, al que apuntas un cliente por la red y con el que hablas a través de un protocolo de cable. Y está el almacén "falso" al que recurres en las pruebas unitarias: un diccionario en memoria, un mock, un stub hecho a mano que implementa lo justo de la interfaz para que la prueba compile. Los dos no comparten nada. Tu prueba ejercita el comportamiento del stub, no el del almacén, y el hueco entre ellos es exactamente donde se esconden los bugs.

Zaris colapsa esa elección. El mismo cliente tipado de .NET que habla con un clúster distribuido sobre TLS también ejecuta el almacén embebido dentro de tu proceso, sin socket, sin mapa de particiones y sin despliegue, y es el mismo motor de almacén, no un sustituto. Seleccionas cuál obtienes con una única cadena de conexión. Este artículo trata de cómo funciona eso, los casos para los que es genuinamente bueno y la línea dura donde el modo embebido se detiene y empieza un clúster.

Reintentos que no escribes: sobrevivir a la rotación de propiedad en el cliente de Zaris

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

Reintento y reencaminamiento transparentes bajo rotación de propiedad

Aquí hay un hecho sobre cualquier almacén clave-valor particionado que los diagramas pulidos tienden a omitir: el nodo que posee tu clave se moverá. Un nodo falla y sus particiones conmutan por error a sus réplicas. Escalas horizontalmente y las particiones migran al nuevo miembro. Un primario preferido vuelve y la propiedad conmuta de vuelta a él. Cada uno de esos eventos significa que el nodo con el que tu cliente hablaba felizmente hace un milisegundo es, en este momento, el nodo equivocado para algún conjunto de claves.

La experiencia ingenua del cliente en ese momento es fea: un reinicio de conexión, un genérico "error de servidor" o —peor— una escritura que aterriza en un nodo que ya no es el dueño activo y silenciosamente no va a ninguna parte. El cliente .NET de Zaris está construido para que nada de eso llegue a tu código. Un cambio de propiedad se convierte en un estado tipado, el cliente reencamina al nuevo dueño, y el reintento está acotado tanto por un presupuesto de intentos como por una fecha límite de reloj de pared. En el caso común, tu PutAsync simplemente tarda unos milisegundos más y devuelve Ok. Esta publicación trata de la maquinaria que hace que eso sea cierto y —no menos importante— de los casos que deliberadamente no disimula.

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.