¿Qué base de datos es mejor para almacenar imágenes? A menudo me preguntan sobre el almacenamiento de imágenes, documentos, PDF u otros objetos binarios en Couchbase. O, casi con la misma frecuencia, ¿es Couchbase la mejor base de datos para almacenar imágenes?
Como arquitecto de servicios y bases de datos, suelo dar la misma respuesta: para este tipo de objetos, es una idea mediocre almacenar archivos en una base de datos. Cuando digo esto, hablo desde mi experiencia personal a lo largo de una larga carrera trabajando con muchas bases de datos relacionales y NoSQL.
Sé que desde el punto de vista del desarrollador es fácil y cómodo almacenar archivos en la base de datos. Incluso es lógico hacerlo. Tengo datos, necesito almacenarlos. Quiero tener información sobre esos datos para poder buscarlos y servirlos a mis usuarios. Todo esto también tiene sentido para mí.
Tengo dos argumentos de por qué es una idea mediocre almacenar este tipo de objetos en una base de datos (Couchbase u otra). También propondré una solución para utilizar mejor la base de datos de almacenamiento de imágenes Couchbase a un coste que sea eficiente y para servir mejor al usuario final. Al fin y al cabo, para eso estamos todos aquí de verdad, ¿no?
Gasto operativo y rendimiento
Al almacenar permanentemente objetos grandes en una base de datos, estará utilizando su capa más cara y la que debería ser una de las de mayor rendimiento para objetos que suelen ser estáticos y cambian con poca frecuencia en la mayoría de los casos de uso. Basta con pensar en el coste por GB de almacenamiento en una instancia EC2 en AWS en comparación con el almacenamiento de ese objeto en S3. Cuando utilicé la calculadora de AWS en el momento de escribir esto, el almacenamiento en S3 costaba al menos 1/5 del coste del almacenamiento más barato en EC2.
Digo al menos porque las configuraciones no son una comparación 1:1 de posibilidades, ya que realmente están pensadas para cosas diferentes. S3 está pensado específicamente para albergar montones de objetos estáticos con una tasa de durabilidad muy alta por un coste muy razonable y EC2 está pensado para tener almacenamiento operativo.
Ahora piense en el coste físico de almacenar imágenes de ese objeto en una base de datos y luego replicarlas, hacer copias de seguridad, etc. durante meses o años. Los costes y tiempos operativos de transportar ese volumen de datos se hacen evidentes. Con el tiempo, estos objetos se convierten en anclas alrededor del cuello del equipo de operaciones. Además, si no gestionas las cosas correctamente, podrías tener una base de datos dentro de dos años almacenando, haciendo copias de seguridad y replicando imágenes de un usuario que dejó de utilizar tu servicio hace 1,5 años mientras sigues pagando por cada KB utilizado varias veces.
Toda esa sobrecarga simplemente para facilitar el desarrollo. Simplemente no vale la pena o no es eficiente a largo plazo. De nuevo, esto no es exclusivo de Couchbase.
Utilizar cada herramienta para lo que es mejor
Couchbase puede servir objetos directamente desde la caché gestionada en RAM con un gran rendimiento si accedes a través del ID del objeto o si has identificado qué objeto quieres a partir de una consulta. ¿Puede Couchbase almacenar y servir esa imagen o binario más grande muy rápido desde RAM también? Por supuesto que puede, pero consumirá costosos recursos del servidor, no sólo de almacenamiento, para obtener ese rendimiento. Couchbase también tiene un límite de tamaño de objeto de 20MB. Incluso si tus objetos no se acercan a ese tamaño, todavía puede ser una mala idea almacenar este tipo de objetos en una base de datos de forma permanente. Como mencioné antes, Amazon S3 y HDFS son excelentes para almacenar y servir contenido estático como este. Esto es para lo que fueron diseñados. Ofrecen un gran rendimiento a un gran valor para ese tipo de carga de trabajo. Lo mejor es utilizar las herramientas adecuadas para los trabajos adecuados. Aunque podemos almacenar en la base de datos imágenes de gran tamaño, así como objetos estáticos, no es para lo que son mejores.
Entonces, ¿cómo puedo resolver esto para almacenar grandes datos en la base de datos? ¡Tengo tantas cosas que almacenar!
Para este ejemplo, hablaremos de un almacén de imágenes, ya que es el caso de uso más común que escucho, pero podría ser cualquier tipo de objeto estático grande. A un alto nivel para este caso de uso, deberías buscar almacenar sólo los datos en Couchbase que son requeridos por la aplicación cuando un usuario está buscando una imagen. Cuando planifiques tus patrones de acceso a datos y por tanto tu modelo de objetos de base de datos, hazte algunas preguntas.
- ¿Qué datos debe presentar la aplicación al usuario sobre cada imagen y en qué momento del flujo?
- ¿Qué búsquedas se harán sobre la imagen y cómo se presentarán los resultados? (Palabras clave, título, creador, fecha de creación, etc.) Si se ajusta a su caso de uso, podría incluso almacenar una pequeña miniatura de la imagen en la base de datos de almacenamiento para tener la opción de una entrega rápida.
- ¿En qué momento del flujo de la aplicación se necesitará cada dato?
- ¿Cuál será el patrón de ID de objeto para el objeto de metadatos de cada imagen? Para obtener más información sobre el modelado de objetos Couchbase y ejemplos de ID de objetos, lee las entradas de mi blog aquí y aquí para coger ideas.
Ahora que tienes los datos de cara al usuario en Couchbase, puedes hacer búsquedas rápidas de claves, vistas map reduce o consultas N1QL completas contra índices secundarios para llegar a los datos. Las imágenes grandes deben almacenarse en algo como AWS S3, HDFS, una red de distribución de contenidos (CDN), un servidor web, un servidor de archivos o cualquier otra cosa que sea ideal para servir objetos estáticos grandes, de acuerdo con tu caso de uso y presupuesto.
Profundicemos un poco más y hablemos de cómo lo conseguimos.
Ejemplo de modelo de objetos
Propongo dos objetos en Couchbase para cada imagen real de tu aplicación:
- Un objeto JSON que contiene los metadatos sobre la imagen. Estará en JSON para que podamos indexarla, consultarla con N1QL o Views, lo que necesite la aplicación. En este objeto también estará el puntero a la imagen principal en el otro sistema.
- Un objeto Key/Value que contiene datos para las imágenes en miniatura de la imagen principal y se almacena en un bucket separado de Couchbase. Estamos manteniendo la miniatura en Couchbase para tener una rápida presentación de la misma a un usuario. En teoría, podríamos tener esto como un valor en el documento JSON con los metadatos, pero las ventajas de tenerlos separados cuando se trata de la indexación y la utilización de recursos Couchbase compensar eso, especialmente si usted planea en una alta tasa de mutación de datos.
Ya que cada objeto en Couchbase tiene que tener un ID de objeto único (dentro de un bucket) y podemos tener 250 bytes en ese ID, usemos eso a nuestro favor y tengamos un patrón de ID de objeto estandarizado para una fácil y rápida recuperación de objetos. Un ID de objeto estandarizado nos ayudará a recuperar fácilmente una imagen y su contenido relacionado rápidamente desde el Servicio de Datos de Couchbase o cuando realicemos una consulta utilizando N1QL.
El patrón de ID de objeto para cada documento será el siguiente:
- objeto de metadatos: metadatos:: donde el identificador único es asignado a esa imagen por la aplicación. Dado que vamos a encontrar imágenes mediante consultas con N1QL, no voy con un ID de objeto más descriptivo.
- Objeto miniatura: miniatura:: donde el nombre es el que hemos utilizado para el objeto de metadatos. De este modo, establecemos una relación informal entre los objetos. Sabemos que cada metadatos tiene un objeto miniatura objeto. Así que si necesitamos obtener ambos, podemos hacerlo. Una vez que conocemos el UUID, podemos obtener la miniatura muy rápidamente o viceversa.
Para los propios objetos:
- El objeto de metadatos será un documento JSON y podría tener el siguiente aspecto:
{ "title" : "Lindo gatito y perrito",
"file-location" : "https://s3.amazonaws.com/kittypics/cutekittyanddoggy.jpg",
"thumbnail1" : "thumbnail::
“,
"dimensiones-px" : "50×50"
}
La parte de la miniatura donde dice .
sería sustituido por el ID de ese objeto, por supuesto. De esta forma, cuando obtengamos el objeto de metadatos, tendremos el ID del objeto miniatura y podremos cogerlo rápidamente. Esta es una de esas veces en las que probablemente es mejor hacer múltiples llamadas a Couchbase que como lo harías en otra base de datos donde sería mejor hacerlo en una sola llamada.
- El objeto miniatura será simplemente una clave/valor siendo el valor binario.
Configuración específica de Couchbase
Cubos
Propongo dos buckets Couchbase. Uno para los documentos JSON que contienen los metadatos sobre cada imagen y otro para la miniatura. Las dos razones específicas para esto son:
- Los cubos separados permiten ajustar la caché gestionada según las necesidades de los objetos. Por ejemplo, quizás quiero que los metadatos de cada imagen estén siempre disponibles lo más rápido posible. Dimensiono la cuota de RAM para el bucket de "metadatos" para tener todos esos objetos en la caché gestionada para obtener el mejor rendimiento. Cuando se trata del almacenamiento de datos de valores clave para las miniaturas, quizás queramos ahorrar un poco de dinero en el tamaño de nuestras instancias y no mantener tantas de ellas en la caché, porque si aparecen unos segundos más tarde, no pasa nada. Podríamos dimensionar la cuota de RAM para el cubo de metadatos a 300 GB en todo el clúster, pero las miniaturas a 50 GB en todo el clúster, a pesar de que las miniaturas podrían ser el conjunto de datos más grande en el disco.
- Nunca necesitaremos indexar o consultar los objetos miniatura. Siempre podemos cogerlos por el ID de objeto que obtuvimos del documento JSON de metadatos o por la aplicación que lo construye. Para profundizar un poco más en la razón por la que queremos estos objetos en dos buckets separados, cuando indexas en Couchbase, cada objeto de un bucket es interrogado en algún momento para ver si debe ser incluido en un índice. Esto lo hace el View Indexer si estás usando Views o el Projector si estás usando GSI (Global Secondary Indexing). Si tenemos estos dos tipos de datos en buckets separados, el indexador y el proyector, que necesitamos para consultar los documentos JSON nunca tendrán que molestarse con los objetos miniatura y desperdiciar ciclos o recursos ya que los índices son específicos de bucket. Otra ventaja es que, si usas vistas de Couchbase que se almacenan junto con los datos, los tiempos de reequilibrio del clúster deberían reducirse, ya que el indexador de vistas no tiene que consultar las miniaturas a medida que se mueven los datos. En general, esto significa que necesitas menos recursos de servidor, por lo que es más rentable.
A efectos de este ejemplo, llamaremos a los dos cubos algo críptico como "metadatos" y "miniaturas".
Valor Desalojar (por defecto) o Desalojar completamente
Lo más probable es que quieras evitar usar la función de desalojo completo de Couchbase para este caso de uso en particular. Es una gran característica, pero parte de la razón para almacenar estos objetos de metadatos de imagen en Couchbase es la funcionalidad, pero también el rendimiento que se obtiene de la caché gestionada. Lo más probable es que tu caso de uso requiera comprobar la existencia de un objeto en algún punto del flujo de la aplicación. Si ese es el caso, usar la evicción completa será malo ya que tendrás que ir al disco para comprobarlo. Si usas el "desalojo de valores" por defecto, entonces serás capaz de saber muy rápidamente si el objeto existe ya que los datos de Couchbase sobre cada objeto estarán en la caché gestionada en todo momento. Así que usa esta característica sabiamente y sólo activa la evicción completa si sabes exactamente lo que hará a tu aplicación y por qué.
Una excepción a la regla
Como siempre, hay excepciones que van en contra de las reglas. Hay un cliente de Couchbase que conozco que pone objetos binarios (archivos de audio para ser específicos) en Couchbase Server con un éxito asombroso. Lo hacen por una razón muy específica que utiliza Couchbase a su favor. Introducen grabaciones de audio en Couchbase, pero la clave es que su aplicación divide los archivos de audio en trozos más pequeños y los transmite a Couchbase a medida que llegan, junto con un documento de metadatos para esa grabación. Lo interesante es que no permanentemente almacenar el archivo de audio en la base de datos por las razones que ya he expuesto en este artículo. Transcurridos unos minutos, si no se ha accedido al archivo de audio, un proceso en segundo plano reconstruye cada archivo y lo traslada a Amazon S3 para almacenarlo durante más tiempo. A continuación, actualizan el documento JSON de metadatos del archivo de audio con un puntero al archivo en S3. Ingestión muy rápida y de alta velocidad con Couchbase y almacenamiento de objetos estáticos a largo plazo con S3. Es un gran ejemplo del uso de las mejores herramientas para lo que son mejores.
Resumen
No te hagas preguntas equivocadas. ¿Cuál es la mejor base de datos para imágenes? ¿Cuál es la mejor base de datos para almacenar archivos? Colocar permanentemente objetos grandes en una base de datos es una idea mediocre, en el mejor de los casos, independientemente de la plataforma de base de datos que utilices. Incluso si hay un sistema de archivos especial en la base de datos que dividirá los archivos binarios grandes en archivos más pequeños para almacenarlos en la base de datos y volver a montarlos automáticamente. Se aplican los mismos conceptos. Estás cambiando facilidad de desarrollo por una vida más cara y operacionalmente más difícil en el futuro. Esto le perseguirá más tarde.
Para obtener la mejor solución, utilice cada herramienta para lo que es mejor. Almacena en Couchbase un documento JSON de metadatos para cada objeto, tal vez una pequeña imagen en miniatura como mucho. En ese documento están los datos que necesitas sobre ese objeto en tu aplicación rápidamente, pero también un puntero a un almacén de objetos construido a propósito como S3, un sistema de archivos o HDFS. Obtendrá lo mejor de todos los mundos. Rendimiento, facilidad de operaciones y rentabilidad por no mucho trabajo extra.
¿No está de acuerdo? ¿Tienes otra excepción a la regla? Añádela a los comentarios y hablemos.