sql >> Base de Datos >  >> NoSQL >> HBase

Dentro de la arquitectura de ingesta de datos casi en tiempo real de Santander (parte 2)

Gracias a Pedro Boado y Abel Fernández Alfonso del equipo de ingeniería de Santander por su colaboración en esta publicación sobre cómo Santander UK está utilizando Apache HBase como un motor de servicio casi en tiempo real para potenciar su innovadora aplicación Spendlytics.

La aplicación Spendlytics para iOS está diseñada para ayudar a los clientes de tarjetas de crédito y débito personales de Santander a controlar sus gastos, incluidos los pagos realizados a través de Apple Pay. Utiliza datos de transacciones en tiempo real para permitir a los clientes analizar el gasto de su tarjeta a través de períodos de tiempo (semanal, mensual, anual), por categoría (viajes, supermercados, efectivo, etc.) y por minorista.

En nuestra publicación anterior, describimos cómo se utilizan Apache Flume y Apache Kafka para transformar, enriquecer y transmitir transacciones en Apache HBase. Esta publicación continúa describiendo cómo se organizan las transacciones en Apache HBase para optimizar el rendimiento y cómo hacemos uso de los coprocesadores para proporcionar agregaciones de tendencias de compra por cliente. Santander y Cloudera emprendieron (y aún continúan) un viaje de HBase con Spendlytics, uno que ha visto muchas iteraciones y optimizaciones de diseño de esquemas e implementaciones de coprocesadores. Esperamos que estas lecciones aprendidas sean los puntos clave de esta publicación.

Esquema 1.0

Un buen diseño de esquema HBase consiste en comprender los patrones de acceso previstos. Hazlo bien y HBase volará; Si lo hace mal, podría terminar con un rendimiento subóptimo debido a las compensaciones de diseño, como los puntos de acceso de la región o la necesidad de realizar escaneos grandes en varias regiones. (Un punto de acceso en una tabla HBase es donde una distribución de clave de fila desigual puede hacer que la mayoría de las solicitudes se enruten a una sola región, lo que abruma al servidor regional y genera tiempos de respuesta lentos).

Lo que sabíamos sobre los patrones de acceso previstos de Spendlytics y cómo influyó en el diseño del esquema inicial:

  • Los clientes solo analizan transacciones en sus propias cuentas:
    • Para obtener un rendimiento de escaneo lineal rápido, todas las transacciones de los clientes deben almacenarse secuencialmente.
  • Los ID de clientes aumentan de forma monótona:
    • Los ID de clientes secuenciales aumentan la probabilidad de que los clientes más nuevos se ubiquen dentro de la misma región, lo que podría crear un punto de acceso de la región. Para evitar este problema, los ID de los clientes se deben saltear (prefijar) o invertir para distribuirlos uniformemente entre regiones cuando se usan al principio de la clave de fila.
  • Los clientes tienen varias tarjetas
    • Para optimizar los escaneos, las transacciones de un cliente deben agruparse y ordenarse por contrato de tarjeta, es decir, la identificación del contrato debe formar parte de la clave de fila.
  • Se accederá a las transacciones en su totalidad, es decir, los atributos como minorista, comerciante, ubicación, moneda y cantidad no necesitan leerse por separado
    • Almacenar los atributos de transacción en celdas separadas daría como resultado una tabla más amplia y escasa, lo que aumentará los tiempos de búsqueda. Como se accederá a los atributos juntos, tiene sentido serializarlos juntos en un registro de Apache Avro. Avro es compacto y nos proporciona una representación eficiente con capacidad de evolución de esquemas.
  • Se accede a las transacciones individualmente, en lotes (por hora, categoría y minorista) y por agregado (por hora, categoría y minorista).
    • Agregar una ID de transacción única como calificador de columna permitirá la recuperación de transacciones individuales sin agregar más complejidad a la clave de fila.
    • Para habilitar el escaneo rápido de transacciones en períodos de tiempo variables, la marca de tiempo de la transacción debe formar parte de la clave de fila.
    • Agregar categoría y minorista a la clave de fila podría ser demasiado granular y daría como resultado una tabla muy alta y angosta con una clave de fila compleja. Alto y angosto está bien dado que la atomicidad no es un problema, pero tenerlos como calificadores de columna ampliaría la tabla sin dejar de admitir agregaciones secundarias.
  • Los datos de tendencia deben calcularse previamente tanto como sea posible para optimizar el rendimiento de lectura.
    • Más sobre esto más adelante, pero por ahora sepa que agregamos una segunda familia de columnas para almacenar las tendencias.

    Con base en lo anterior, el diseño del esquema inicial se ilustra de la siguiente manera:

    Tendencias informáticas

    El aspecto del diseño inicial del que más aprendimos fue la informática de tendencias. El requisito era permitir que los clientes analizaran sus gastos por categoría y minorista hasta la hora. Los puntos de datos incluyeron los valores de transacción más pequeños y más grandes, el valor total de transacción y el número de transacciones. Los tiempos de respuesta tenían que ser de 200 ms o menos.

    Precomputar las tendencias nos daría los tiempos de respuesta más rápidos, por lo que este fue nuestro primer enfoque. Las tendencias no podían retrasar las transacciones, por lo que tenían que calcularse en la ruta de escritura. Esto sería excelente para el rendimiento de lectura, pero nos planteó un par de desafíos:cómo organizar mejor las tendencias en HBase y cómo calcularlas de manera rápida y confiable sin afectar gravemente el rendimiento de escritura.

    Experimentamos con diferentes diseños de esquemas y tratamos de aprovechar algunos diseños bien conocidos cuando fue posible (como el esquema de OpenTSDB). Después de varias iteraciones, nos decidimos por el diseño del esquema ilustrado arriba. Almacenados en la tabla de transacciones, en una familia de columnas separada, los valores de tendencia se organizan juntos en una sola fila, con una fila de tendencia por cliente. Dando a la clave de fila el mismo prefijo que las transacciones de un cliente (por ejemplo, <reverse_customer_id>::<contract_id> ) aseguró que la fila de tendencias se ordenará junto con los registros de transacciones del cliente correspondiente. Con límites de región definidos y una política de división de región personalizada, también podemos garantizar que la fila de tendencias siempre se ubicará junto con los registros de transacciones de un cliente, lo que permite que la agregación de tendencias permanezca completamente del lado del servidor en el coprocesador.

    Para calcular previamente las tendencias, implementamos un coprocesador observador personalizado para enganchar en la ruta de escritura. (Los coprocesadores del observador son similares a los disparadores en un RDBMS en el sentido de que ejecutan el código de usuario antes o después de que ocurra un evento específico. Por ejemplo, antes o después de Put o Get .)

    En postPut el coprocesador realiza las siguientes acciones:

    1. Comprueba el Put para un atributo de tendencia (bandera). El atributo se establece en nuevos registros de transacciones solo para evitar llamadas recursivas al actualizar el registro de tendencias. También permite omitir el coprocesador para Put s que no requieren que se actualicen las tendencias (por ejemplo, asentamientos ).
    2. Obtenga un registro de tendencias para el cliente. El registro de tendencia de un cliente se ubica con sus transacciones (según el prefijo de la clave de fila) para que el coprocesador pueda recuperarlo directamente de la región actual. La fila de tendencias debe bloquearse para evitar que varios subprocesos del controlador RegionServer intenten actualizar las tendencias en paralelo.
    3. Actualizar puntos de datos:
    4. Actualice y desbloquee la fila de tendencia.

    La solución demostró ser precisa durante las pruebas y, como se esperaba, el rendimiento de lectura superó los requisitos. Sin embargo, hubo algunas preocupaciones con este enfoque. El primero fue cómo manejar las fallas:las tendencias se almacenan en una fila separada, por lo que no se puede garantizar la atomicidad. El segundo fue cómo validar la precisión de las tendencias a lo largo del tiempo; es decir, necesitaríamos implementar un mecanismo para identificar y remediar cualquier inexactitud de tendencia. Cuando también consideramos los requisitos de HA y el hecho de que necesitaríamos ejecutar dos instancias activas-activas de HBase en diferentes centros de datos, esto podría ser un problema mayor. No solo podría disminuir la precisión de la tendencia con el tiempo, sino que los dos grupos también podrían desviarse y tener que reconciliarse según el método que usamos para sincronizarlos. Finalmente, corregir errores o agregar nuevos puntos de datos sería difícil porque posiblemente tendríamos que retroceder y volver a calcular todas las tendencias.

    Luego estaba el rendimiento de escritura. Por cada nueva transacción, el observador tenía que obtener un registro de tendencias, actualizar 32 puntos de datos y volver a colocar el registro de tendencias. A pesar de que todo esto sucedió dentro de los límites de una sola región, descubrimos que el rendimiento se redujo de más de 20 000 escrituras por segundo a 1000 escrituras por segundo (por RegionServer). Este rendimiento fue aceptable a corto plazo, pero no escalaría para admitir la carga prevista a largo plazo.

    Sabíamos que el rendimiento de escritura era un riesgo, por lo que teníamos un plan de respaldo, y ese era un coprocesador de punto final . Los coprocesadores de punto final son similares a los procedimientos almacenados en un RDBMS en el sentido de que le permiten realizar cálculos del lado del servidor, en el RegionServer donde se encuentran los datos, en lugar de en el cliente. Los puntos finales extienden efectivamente la API de HBase.

    En lugar de calcular previamente las tendencias, el punto final las calcula sobre la marcha, en el lado del servidor. Como resultado, pudimos eliminar la familia de columnas de tendencias del esquema y el riesgo de inexactitudes y divergencias se fue con ello. Alejarse del observador resultó en un buen rendimiento de escritura, pero ¿las lecturas serían lo suficientemente rápidas? En resumen, sí. Con las transacciones de un cliente limitadas a una sola región y clasificadas por tarjeta y marca de tiempo, el punto final puede escanear y agregar rápidamente, dentro del objetivo de 200 ms de Spendlytics. Esto también significa que la solicitud de un cliente (desde la API de Spendlytics en este caso) solo se enruta a una sola instancia de Endpoint (único RegionServer) y el cliente obtendrá una respuesta única con un resultado completo, es decir, sin el lado del cliente. se requiere procesamiento para agregar resultados parciales de múltiples puntos finales, que sería el caso si las transacciones de un cliente abarcaran varias regiones.

    Lecciones aprendidas

    Spendlytics está disponible desde julio de 2015. Desde entonces, hemos monitoreado de cerca los patrones de acceso y buscado formas de optimizar el rendimiento. Queremos mejorar continuamente la experiencia del usuario y brindar a los clientes más y más información sobre sus gastos con tarjeta. El resto de esta publicación describe las lecciones que aprendimos al ejecutar Spendlytics en producción y algunas de las optimizaciones que se implementaron.

    Después del lanzamiento inicial, identificamos una serie de puntos débiles en los que queríamos centrarnos para mejorar. El primero fue cómo filtrar los resultados por atributo de transacción. Como se mencionó anteriormente, los atributos de transacción están codificados en los registros de Avro, pero descubrimos que un número cada vez mayor de patrones de acceso querían filtrar por atributo y los usuarios se vieron obligados a hacerlo del lado del cliente. La solución inicial fue implementar un HBase personalizado ValueFilter que aceptaba nuestras propias expresiones de filtro complejas, por ejemplo:

    category='SUPERMARKETS' AND amount > 100 AND 
    (brand LIKE 'foo%' OR brand = 'bar')

    La expresión se evalúa para cada registro de Avro, lo que nos permite filtrar los resultados del lado del servidor y reducir la cantidad de datos que se devuelven al cliente (ahorrando ancho de banda de red y procesamiento del lado del cliente). El filtro afecta el rendimiento del escaneo, pero los tiempos de respuesta se mantuvieron dentro del objetivo de 200 ms.

    Esto terminó siendo una solución temporal debido a los cambios adicionales que se requerían para optimizar las escrituras. Debido a la forma en que funciona el proceso de liquidación de tarjetas de crédito, primero recibimos una autorizada transacción desde el momento de la venta (casi en tiempo real) y luego, algún tiempo después, una liquidada transacción de la red de tarjetas de crédito (en lote). Estas transacciones deben conciliarse, esencialmente fusionando las liquidadas transacciones con los autorizados transacciones que ya están en HBase, uniéndose al ID de transacción. Como parte de este proceso, los atributos de la transacción pueden cambiar y se pueden agregar nuevos atributos. Esto resultó ser doloroso debido a la sobrecarga de tener que volver a escribir registros completos de Avro, incluso cuando se actualizan atributos individuales. Entonces, para que los atributos fueran más accesibles para las actualizaciones, los organizamos en columnas, reemplazando la serialización de Avro.

    Además, solo nos preocupamos por la atomicidad a nivel de transacción, por lo que agrupar las transacciones por hora no nos dio ninguna ventaja. Además, los establecidos las transacciones que ahora llegan en lotes solo tienen una granularidad a nivel de día, lo que dificultó (costó) conciliarlas con las autorizadas existentes. transacciones almacenadas por hora. Para resolver este problema, movimos la identificación de la transacción a la clave de fila y redujimos la granularidad de la marca de tiempo a días, en lugar de horas. El proceso de reconciliación ahora es mucho más fácil porque simplemente podemos cargar los cambios de forma masiva en HBase y dejar que la liquidación los valores tienen prioridad.

    En resumen:

    • Los coprocesadores de Observer pueden ser una herramienta valiosa, pero utilícelos sabiamente.
    • Para algunos casos de uso, una buena alternativa es ampliar la API de HBase utilizando puntos finales.
    • Utilice filtros personalizados para mejorar el rendimiento recortando los resultados del lado del servidor.
    • Los valores serializados tienen sentido para el caso de uso correcto, pero aprovechan las fortalezas de HBase favoreciendo el soporte nativo para campos y columnas.
    • Administrar resultados precalculados es difícil; la latencia adicional de la computación sobre la marcha puede valer la pena.
    • Los patrones de acceso cambiarán, así que sea ágil y esté abierto a realizar cambios en el esquema de HBase para adaptarse y mantenerse a la vanguardia.

    Hoja de ruta

    Una optimización que actualmente estamos evaluando son los coprocesadores híbridos. Lo que queremos decir con esto es la combinación de coprocesadores de punto final y de observador para calcular previamente las tendencias. Sin embargo, a diferencia de antes, no haríamos esto en la ruta de escritura sino en segundo plano al conectarnos a las operaciones de compactación y vaciado de HBase. Un observador calculará las tendencias durante los eventos de descarga y compactación en función de los asentados transacciones disponibles en ese momento. Luego usaríamos un punto final para combinar las tendencias precalculadas con agregaciones sobre la marcha del delta de transacciones. Al calcular previamente las tendencias de esta manera, esperamos dar un impulso al rendimiento de las lecturas, sin afectar el rendimiento de la escritura.

    Otro enfoque que estamos evaluando para la agregación de tendencias y para el acceso a HBase en general es Apache Phoenix. Phoenix es una máscara de SQL para HBase que permite el acceso mediante las API de JDBC estándar. Esperamos que el uso de SQL y JDBC simplifique el acceso a HBase y reduzca la cantidad de código que tenemos que escribir. También podemos aprovechar los patrones de ejecución inteligente de Phoenix y los coprocesadores y filtros incorporados para agregaciones rápidas. Se consideró que Phoenix era demasiado inmaduro para el uso de producción al inicio de Spendlytics, pero con casos de uso similares informados por eBay y Salesforce, ahora es el momento de reevaluar. (Hay disponible un paquete de Phoenix para CDH para su instalación y evaluación, pero sin soporte, a través de Cloudera Labs).

    Santander anunció recientemente que es el primer banco en lanzar tecnología de banca por voz que permite a los clientes hablar con su aplicación SmartBank y preguntar sobre los gastos de sus tarjetas. La plataforma detrás de esta tecnología es Cloudera, y la arquitectura de Spendlytics, como se describe en este conjunto de publicaciones, sirvió como diseño del modelo.

    James Kinley es arquitecto principal de soluciones en Cloudera.

    Ian Buss es arquitecto sénior de soluciones en Cloudera.

    Pedro Boado es ingeniero de Hadoop en Santander (Isban) Reino Unido.

    Abel Fernández Alfonso es ingeniero de Hadoop en Santander (Isban) Reino Unido.