Introducción
Couchbase es capaz de tasas de escritura muy altas, puede escalar rápidamente y añadir nodos fácilmente, pero un modelo de objetos pobre puede ser un obstáculo para estas cualidades. En algunas bases de datos, si tienes tasas de escritura muy altas, sacrificas las tasas de lectura, pero Couchbase tiene algunas capacidades bastante únicas en el espacio NoSQL para soportar ambas de una manera eficaz. En esta entrada del blog vamos a discutir lo que se necesita para diseñar un modelo de objetos que jugará a estas capacidades de registro y datos de eventos, pero también tienen fácil capacidad de búsqueda con N1QL también.
El ejemplo de caso de uso que me encontré en una conversación hace poco es utilizar Couchbase como base de datos operativa para recopilar varios tipos de eventos de sistemas externos como equipos de red, servidores o incluso datos de registro. A continuación, el servicio necesita la capacidad de ver muy rápidamente el número de eventos en la interfaz de usuario en una especie de rollup cada hora. La otra necesidad es poder hacer clic en ese número y profundizar en una lista de ese tipo de evento. Por ejemplo, mostrar todos los eventos RouterError del 22 de junio de 2015 para la hora de las 16:00.
Otra cosa que hay que recordar, esta entrada del blog es para mostrar un concepto más avanzado y cómo se puede aplicar. Obviamente no significa que sea la forma correcta para tu caso de uso, incluso si puede ser similar a lo que estoy hablando. Es para hacerte pensar sobre el modelado avanzado de objetos en Couchbase y cómo podrías utilizar su poder de manera más eficaz para sacar el máximo provecho de una manera que puede no ser obvia para todos.
Contador incremental para leer eventos y conteos por hora
Este enfoque permitiría leer fácilmente el último número N de eventos o un tipo de evento para una hora en lugar de hacer las cosas con una consulta de vista. O podemos leer todos los eventos de un día concreto. Está optimizado para tasas de escritura muy altas y permite una búsqueda fácil de datos dentro de un nivel decente de granularidad. Voy a explicar un alto nivel de esto y luego sumergirse en ejemplos específicos para explicar la idea más.
Para ello necesitaremos dos tipos de objetos en nuestro cubo.
- El objeto contador: es un objeto clave/valor que contiene un número entero. Este entero representa el extremo superior del número de objetos para ese tipo de evento y hora o dicho de otra manera, es el extremo superior de la matriz de eventos. También es el objeto que leerá para mostrar cuántos eventos hay para ese combo tipo/hora. Usaremos métodos especiales y específicos de los SDKs de Couchbase llamados Counter Operations. Cada SDK tiene su propia versión de estos métodos, pero aquí está la versión node.js de la misma como un ejemplo.
- El Objeto Evento - Este es un objeto documento JSON y tiene los datos reales sobre los eventos que queremos capturar.
El objeto contador
Se crea un objeto contador para cada combinación de tipo de evento y hora. Piense en ello como un objeto operativo sobre los documentos que vamos a crear. Esto puede sonar extraño, pero ten paciencia conmigo. Este objeto contador será un objeto clave/valor, no JSON, siendo el valor un entero. Hay una operación de contador en los SDKs de Couchbase específicamente para este tipo de objeto y es muy eficiente y ofrece capacidades rápidas de lectura-escritura para mantener la consistencia. Es una operación única y atómica en los SDKs de Couchbase por lo que es muy fácil y rápida de usar. He aquí un ejemplo de la versión node.js en la documentación. En nuestro caso, este contador se incrementará cada vez que añadamos un nuevo evento. Dado que cada tipo de evento y hora tiene su propio contador, podemos leer fácilmente cuántos eventos hay y ese número se convierte en el límite superior de un array si necesitáramos leer todos los eventos de ese tipo y hora.
La otra parte importante es la clave del objeto. Queremos elegir una clave para que la aplicación pueda construir fácilmente las claves necesarias y luego reunir los datos por clave para mostrar el número de eventos, pero también los eventos para el período de tiempo dado. La obtención de objetos por clave siempre será más rápida que la consulta. Es la diferencia entre conocer ya la respuesta y tener que hacer una pregunta para recuperar los datos que son la respuesta. Conociendo la clave, basta con decirle a la base de datos que recupere los datos. Simple, eficaz y muy rápido.
He aquí un ejemplo de clave/valor del contador:
Llave objeto:
Un ejemplo de ObjectID sería:
|
||||
| Valor: 293
Donde 293 es el valor del incremento más reciente del contador. |
Para la marca de tiempo en la clave, hice el año de cuatro dígitos, un mes de dos dígitos, el día, y luego la hora (en tiempo de 24 horas). No necesité bajar al nivel de minutos o segundos, pero se podría. También podría haber utilizado una marca de tiempo UNIX que también funcionaría, pero de nuevo eso era innecesariamente granular para este caso de uso particular.
En el ejemplo anterior, 2015 es el año, febrero es el mes, 20 es el día y 4pm para la hora. Así que si quisieras leer todos los contadores para un tipo de evento y un día específico, la aplicación podría reunir fácilmente los objectIDs para esos contadores y leerlos en bloque.
Otra cosa, yo utilizo dos puntos como delimitador, pero puedes utilizar lo que te parezca más lógico.
El objeto de evento
Para cada objeto de evento, el ID del objeto sería algo parecido a lo siguiente:
Donde 293 es el valor del incremento más reciente del objeto contador para esa hora |
||
|
Con este esquema, para que usted recupere el recuento de elementos para ese Tipo de Evento en una hora específica, sólo tiene que leer el objeto operativo uno y ahí lo tiene.
En pocas palabras, el valor del contador es el límite superior de los objetos de ese tipo de evento. Si quisieras los últimos 10 eventos de ese tipo de evento durante una hora, leerías ese contador, restarías 9 y luego harías una lectura masiva paralelizada en Couchbase para los siguientes objetos:
|
Así que puedes leer todos estos 10 eventos muy rápido y sin consultas, sin índices, sin vistas, sólo velocidad bruta vía lectura masiva paralelizada. Una lectura masiva de los objetos listados sería MUY rápida en Couchbase incluso si tuvieras más de 300 de ellos.
El único problema menor con este enfoque, es que es posible, aunque muy poco probable, que ese recuento sea inconsistente con el objeto contador real. Por ejemplo, alguien podría iterar el objeto contador, pero luego no crear un documento de evento con ese objeto. Dicho esto, si usted está utilizando operaciones masivas y solicita un objeto que no existe, simplemente recibirá un fallo y toda la operación no sufrirá por ello. En mi opinión, esto está muy bien teniendo en cuenta el rendimiento que puede alcanzar un modelo como este. Si se te ocurre una forma mejor, por favor publícala en los comentarios porque me encantaría conocerla.
El código de aplicación
Veamos cómo se podría diseñar el código de la aplicación para leer y escribir este modelo de objetos. Voy a usar pseudo-código para no entrar en un lenguaje en particular. Dejaré los detalles a tu elección de lenguaje y Couchbase SDK.
|
1 2 3 4 5 6 7 8 9 10 11 |
function writeNewEvent() { Read current date and time. Call iterate function on counter object with an initial value of 0 and read back the value. (You should read the value back because if it already exists you will get the most current value, if you just went with 0, you might cause a problem. If the counter object for this event and time period does not exist, it will be created by the SDK.) Create new event with the date/time and counter number as part of the key } |
|
1 2 3 4 5 6 7 8 |
readCounterDateObject() Do a loop to generate the list of keys you need to read based on the event type, date and time and the value of the counter object you received. (You could even say something like “just the last 10” and generate those keys in this. Your call.) Use that list to do a parallelized bulk read operation and bring back just the keys we want. } |
|
1 2 3 4 5 |
function readEventCount() { return the value of the event/date counter object. } |
Resumen
Utilizando la serie de técnicas de modelado de objetos descritas anteriormente, es posible estructurar los datos de forma que se maximicen el rendimiento y el rendimiento. Aunque en un principio pueda resultar contraintuitivo, el uso de búsquedas adicionales de clave/valor en lugar de consultas secundarias basadas en índices para la funcionalidad principal de la aplicación puede aportar ventajas significativas. En muchos sistemas, una búsqueda compleja basada en índices puede tardar un orden de magnitud más en completarse que las simples búsquedas clave-valor utilizadas en este diseño. En la arquitectura de sistema correcta, Couchbase puede proporcionar fácilmente un tiempo de respuesta consistente por debajo del milisegundo para estas búsquedas. Entonces, cuando necesites el poder de hacer consultas reales, usa N1QL. Puedes elegir dónde utilizar la potencia que te da Couchbase.
Además, gracias a la arquitectura de compartición automática de Couchbase, la carga de las consultas y la ingesta se repartirá uniformemente por todo el clúster. A medida que el uso de la aplicación y la demanda de operaciones aumenta con el tiempo, se pueden añadir nodos adicionales de Couchbase para escalar en una operación en línea, satisfaciendo la demanda sin ningún cambio en la capa de aplicación.
Posdata sobre la consulta
Una última cosa si has llegado hasta aquí. Puede que estés diciendo, pero todo esto y no estoy consultando la base de datos. ¿Por qué no estás usando N1QL? Yo no he dicho que en este caso de uso no estaría utilizando N1QL y nada nos impide utilizar N1QL en estos documentos. La forma en que veo N1QL es que es una herramienta más en la caja de herramientas para interactuar con Couchbase. El acceso clave/valor SIEMPRE será más rápido. Así es como son las cosas. Así que lo que promuevo a la gente es usar el poder y la flexibilidad que Couchbase ofrece para obtener el rendimiento y la funcionalidad que necesito donde y cuando lo necesito. Esto será una mezcla de clave/valor, vistas tradicionales de Couchbase, índices secundarios globales (GSI) y N1QL.
En este caso de uso específico, necesito poder escribir datos a una velocidad muy alta, tener una manera de buscar sólo algunos de los datos de una manera muy específica y escalar el nivel de datos linealmente para manejar esto con una complejidad mínima. Key/Value lo hizo por mí con el patrón de clave de objeto correcto. Nada me impide sacar N1QL para consultar estos eventos o registros de cómo hemos diseñado este esquema. Fíjate que ni siquiera he hablado mucho del modelado de objetos del documento JSON en sí. Para lo que estoy tratando de mostrar que no importaba y en cuanto a la consulta, simplemente no necesitaba esa herramienta de la bolsa de herramientas.
Dicho esto, en el próximo blog sobre este modelo de objetos me sumergiré para ver cómo podemos desmenuzar estos mismos eventos con N1QL y GSI donde tenga sentido.