Stairway to Azure 6: Storage de Azure a Fondo: Escalabilidad y Límites

Como vimos en este post (qué es recomendable leer primero), Windows Azure ofrece tres tipos básicos de almacenamiento. En este post profundizaremos en ellos y mostraremos un nuevo tipo de almacenamiento ofrecido por Azure. Los Drives.

Los tipos de almacenamiento convencionales:

Blobs:

Almacenamiento de archivos nombrados, junto con metadata.

Tables:

Almacenamiento escalable, masivo y estructurado. Una tabla es un conjunto de entidades. Cada una con un conjunto de propiedades. Las aplicaciones pueden manipular las entidades y consultar sus entidades.

Queues:

Proveen un robusto almacenamiento y distribución de mensajes para permitir la construcción de aplicaciones no acopladas y flujos de trabajo escalables entre las diferentes partes (roles) de las aplicaciones de Azure.

Drives:

Siendo este un tipo nuevo de almacenamiento, provee volúmenes de almacenamiento NTFS para ser usados por las aplicaciones de Windows Azure. De esta manera con los apis convencionales existentes de sistemas de archivos se puede acceder a drives de red mapeados. Cada drive está mapeado a una página de un Blob. Así que la escalabilidad de los drives, es la misma de los Blobs. De manera que sólo en mi siguiente post me estaré enfocando en los drives.

Veamos la jerarquía de almacenamiento en Azure:

clip_image002

Cómo se pueden acceder los datos?

Esencialmente*, los datos se acceden a través de REST. Cuando adquirimos una cuenta de storage, automáticamente tenemos tres servicios REST para poder ejecutar operaciones CRUD sobre cada uno de los tipos de almacenamiento (blob, table, queues).

Cada uno de estos servicios se accede con una URL de acuerdo a la siguiente estructura:

clip_image004

Observen que el nombre de la cuenta <account> se extrae del nombre que se registra cuando se adquiere la misma en el Developer Portal. Así que un DNS direcciona todos los requerimientos a la cuenta adecuada. Observemos también en los hostnames las palabras clave “blob”, “table” y “queue”: esto nos indica que podríamos tener en la misma cuenta un blob, una tabla y una cola llamadas por ejemplo “music”.

Una cuenta de almacenamiento de Windows Azure puede tener hasta 100TBs de almacenamiento. De resto, no hay limitaciones en la cantidad de contenedores, ni de blobs dentro de esos contenedores, ni de tablas ni entidades dentro de ellas, ni de colas ni mensajes dentro de ellas. Lo único, es que todo eso no debe sumar más de 100TBs.

Blobs

clip_image006

En este ejemplo, tengo una cuenta de storage llamada “warnov”. A la cual le he creado dos contenedores. Estos contenedores podrían equipararse con las carpetas del sistema de archivos. Los contenedores tienen una política de sharing pública o privada. La segunda requiere siempre autenticación mientras la primera puede ser accedida por todo el mundo. Los contenedores también tienen metadata asociada a ellos en la forma <nombre, valor> y puede tener hasta 8kb de tamaño por contenedor. Los blobs pertenecientes a un contenedor también tienen este tipo de metadata, con el mismo tamaño disponible.

Entonces para acceder a un blob determinado, usaríamos:

https://<account>.blob.core.windows.net/<container>/<blobname>

Básicamente tenemos dos tipos de blobs soportados:

  • Blobs de Bloque:
    • Son orientados a tareas de streaming
    • Cada blob es una lista secuencial de bloques
    • Cada bloque puede tener un tamaño máximo de 200GB
    • Las actualizaciones sobre los bloques requieren de una confirmación: Primero se suben todos los bloques a ser modificados, y luego se aceptan los cambios en una operación atómica.
  • Blobs de Página:
    • Son orientados a tareas de escritura aleatoria (manejo de archivos convencional)
    • El tamaño máximo de página es de 1TB
    • Cada blob contienen un índice no secuencial de páginas
    • No hay proceso de confirmación (al estilo sistema de archivos convencional y a diferencia de los bloques)
    • Precisamente sobre este tipo de blobs es que se montan los drives que mencioné al principio
    • También permiten ejecutar escriturar basadas en rango de un blob; o sea; tratar un blob como un archivo al cual solo se le modificarán ciertos bytes y no todo el archivo, lo que obviamente agiliza la operación. Además se puede obtener un permiso de acceso exclusivo en escenarios de concurrencia (por ejemplo cuando dos usuarios distintos tratan de modificar un mismo archivo)

Tablas:

clip_image008

Las entidades son comparables a filas en una tabla de base de datos. Contienen una serie de propiedades. Todas las tablas tienen dos propiedades. Una llave de partición y una llave de fila. Estas dos propiedades conforman la llave única de la entidad. Las llaves de partición (PartitionKey) son usadas por el sistema para distribuir automáticamente y balancear las cargas de las entidades de las tablas a través de varios servidores. Esto es, una tabla se divide físicamente en particiones que se distribuyen entre varios servidores. Ejemplos de particiones por ejemplo pueden ser las fechas en las que ocurren los registros.

Las llaves de fila (RowKey) identifican a cada registro dentro de una partición dada.

Estas dos llaves además representan un índice de búsqueda sobre las entidades. Así que si buscamos sobre estas dos propiedades, obtendremos resultados de una manera más eficiente. Los resultados además son emitidos ordenados primero por a PartitionKey y luego en RowKey.

Estas dos llaves siempre tienen que ser de tipo String. El resto de propiedades pueden ser: Binary, Bool, DateTime, Double, GUID, Int, Int64, String.

Las tablas no tienen esquema fijo. Lo único que almacenan son propiedades y cada propiedad se compone de un nombre y un valor. De esta manera, puede darse que en una misma tabla, puedan haber entidades muy diferentes en estructura. O que Dos entidades tengan propiedades llamadas igual pero con distinto tipo de valor. La única restricción es que en una sola entidad no se puede repetir el nombre de las propiedades.

Una entidad puede tener hasta 255 propiedades. Cada entidad puede ser máximo de 1MB de tamaño. Estas propiedades son Case Sensitive y se pueden equiparar con columnas en una DB.

Para manejar una concurrencia optimista cada entidad es marcada un una versión mantenida por el sistema a través de un TimeStamp interno (que en últimas es otra propiedad).

Colas:

clip_image010

En las colas generalmente guardamos mensajes que representan los requerimientos de nuestros clientes. Este sistema nos permite almacenar todos los requerimientos para que de manera escalable, los podamos atender sin que se pierdan.

Las colas pueden tener metadata asociada a ellas en la forma de <llave, valor> con un tamaño máximo de 8KB. Las colas almacenan los mensajes.-

Un mensaje se almacena máximo por una semana. Los mensajes más viejos de una semana son eliminados por un garbage collector del sistema.

Los mensajes pueden ser hasta de 8KB de tamaño. Para almacenar dartos más grandes, recurriremos pues al blob storage o table storage y en el mensaje almacenamos la referencia al lugar en que hemos guardado el resto de la información.

Finalmente, a pesar de que podemos poner mensajes en la cola de manera binaria, cuando los traemos nuevamente, la respuesta se da en formato XML y codificado con base64.

Particiones

No solo las tablas usan particiones.

Todos los objetos de almacenamiento de Azure lo hacen. Las particiones son usadas para localizar los elementos requeridos y para hacer balanceo de carga y partición de objetos a través de los servidores para satisfacer las necesidades de tráfico.

Todos los objetos de una misma partición se agrupan en el mismo grupo físico. Esto permite básicamente:

  • Ejecutar operaciones atómicas fácilmente en la misma partición
  • Ejecutar un cacheo local para mejorar el performance

Cuál es la partición para los distintos objetos?

  • Blobs: ContainerName + BlobName
  • Entities: TableName + PartitionKey
  • Messages: QueueName

Más de límites

Como lo mencioné anteriormente, cada cuenta de Almacenamiento en Windows Azure tiene hasta 100TB de espacio.

Una suscripción de Windows Azure, brinda hasta 5 cuentas de almacenamiento.

Si en nuestra aplicación llegáramos a necesitar Petabytes de información, se puede contactar al soporte al cliente para solicitar más cuentas aparte de las 5 obtenidas.

Entonces es bueno tener en mente que no vamos a crear una cuenta por cada cliente que tengamos, sino por ejemplo representar cada cliente en un container.

En cuanto a transacciones, en una sola cuenta se pueden tener hasta 5000 requests por segundo y en ocupación de ancho de banda hasta 500Mbps.

Ahora, hablando de límites por partición tenemos:

Transacciones por segundo permitidas para una partición: 500. Esto quiere decir, por ejemplo que una cola puede ser solicitada 500 veces en un segundo (recordemos que cada cola ocupa su propia partición).

Pero en una tabla, donde podemos tener varias particiones, esos 500 requests por segundo podrían escalarse a varios miles!

En cuanto a blobs, dado que cada blob tiene su propia partición, tenemos que el único límite sería el ancho de banda para cada blob que en este caso es de 60Mbps

Qué pasa si se superan los límites de trabajo para una partición en particular? Se empezaran a producir errores “503 Server Busy”. Cuando se detectan estos errores, una buena práctica es comenzar a usar un algoritmo de “exponential backoff” para disminuir la carga de la partición. Pero si esto continúa muy frecuentemente, la aplicación debería tratar de mejorar su partición de datos y tasa de transferencia de la siguiente manera:

Blobs: Considerar usar mejor el Windows Azure Content Delivery Network (CDN) para permitir acceso anónimo a blobs que tienen mucho acceso. Una solución de Windows Azure que provee a los desarrolladores una solución global para distribuir contenido de alto ancho de banda. Esperen un post al respecto.

Tablas: Usar una PartitionKey más granular que permita a Azure distribuir las particiones entre más servidores.

Colas: Hacer batch de varios work ítems en un solo mensaje, para aumentar la cantidad de trabajo resuleto en cada uno de ellos, o usar múltiples colas.

Para finalizar, en este post de Brad Calder (basado en el cual está este mío), encontré un comentario donde una persona decía que 500 transacciones por segundo para una cola era algo muy pequeño. Y que en realidad esperaba que fuera al menos 100 veces mejor, para justificar el cuento de la nube y poder desarrollar algo como twitter.

Esto tiene mucho sentido desde cierto punto de vista. Pero si tenemos en cuenta que Windows Azure no nos impone límite sobre el número de colas que puedo crear y si además mandamos varias requisiciones para un solo mensaje, si yo empaquetase 10 requerimientos por mensaje y tuviera 10 colas, fácilmente lograría 100 veces más rendimiento que un solo mensaje y una sola cola. Con esta misma metodología podría lograr rendimientos aún mayores!