HBase
 sql >> Base de Dados >  >> NoSQL >> HBase

Por dentro da arquitetura de ingestão de dados em tempo quase real do Santander (Parte 2)


Agradecemos a Pedro Boado e Abel Fernandez Alfonso, da equipe de engenharia do Santander, por sua colaboração neste post sobre como o Santander UK está usando o Apache HBase como um mecanismo de serviço quase em tempo real para impulsionar seu inovador aplicativo Spendlytics.

O aplicativo Spendlytics para iOS foi desenvolvido para ajudar os clientes de cartão de crédito e débito pessoal do Santander a controlar seus gastos, incluindo pagamentos feitos via Apple Pay. Ele usa dados de transações em tempo real para permitir que os clientes analisem seus gastos com cartão em períodos de tempo (semanal, mensal, anual), por categoria (viagens, supermercados, dinheiro etc.) e por varejista.

Em nossa postagem anterior, descrevemos como o Apache Flume e o Apache Kafka são usados ​​para transformar, enriquecer e transmitir transações no Apache HBase. Esta postagem continua descrevendo como as transações são organizadas no Apache HBase para otimizar o desempenho e como usamos coprocessadores para fornecer agregações de tendências de compra por cliente. O Santander e a Cloudera fizeram (e ainda estão) uma jornada HBase com o Spendlytics, que viu muitas iterações e otimizações de design de esquema e implementações de coprocessadores. Esperamos que essas lições aprendidas sejam os principais pontos desta postagem.

Esquema 1.0


Um bom design de esquema HBase é entender os padrões de acesso pretendidos. Faça certo e o HBase voará; se errar, você pode acabar com um desempenho abaixo do ideal devido a compensações de design, como pontos de acesso de região ou ter que realizar grandes varreduras em várias regiões. (Um ponto de acesso em uma tabela HBase é onde uma distribuição desigual de chave de linha pode fazer com que a maioria das solicitações seja roteada para uma única região, sobrecarregando o RegionServer e resultando em tempos de resposta lentos.)

O que sabíamos sobre os padrões de acesso pretendidos pela Spendlytics e como isso influenciou o design inicial do esquema:
  • Os clientes analisam apenas as transações em suas próprias contas:
    • Para um desempenho de varredura linear rápido, todas as transações do cliente devem ser armazenadas sequencialmente.
  • Os IDs de clientes estão aumentando monotonicamente:
    • Os IDs de clientes sequenciais aumentam a probabilidade de que os clientes mais novos sejam colocados na mesma região, criando potencialmente um ponto de acesso da região. Para evitar esse problema, os IDs de cliente devem ser saltados (prefixados) ou revertidos para distribuição uniforme entre regiões quando usados ​​no início da chave de linha.
  • Os clientes têm vários cartões
    • Para otimizar as verificações, as transações de um cliente devem ser agrupadas e classificadas por contrato de cartão, ou seja, o ID do contrato deve fazer parte da chave de linha.
  • As transações serão acessadas na íntegra, ou seja, atributos como varejista, comerciante, local, moeda e valor não precisam ser lidos separadamente
    • Armazenar atributos de transação em células separadas resultaria em uma tabela mais ampla e esparsa, o que aumentaria os tempos de busca. Como os atributos serão acessados ​​juntos, fazia sentido serializá-los juntos em um registro do Apache Avro. O Avro é compacto e nos fornece uma representação eficiente com capacidade de evolução de esquema.
  • As transações são acessadas individualmente, em lotes (por horário, categoria e varejista) e por agregado (por horário, categoria e varejista).
    • Adicionar um ID de transação exclusivo como qualificador de coluna permitirá a recuperação de transações individuais sem adicionar mais complexidade à chave de linha.
    • Para permitir a verificação rápida de transações em períodos de tempo variáveis, o carimbo de data/hora da transação deve fazer parte da chave de linha.
    • Adicionar categoria e varejista à chave de linha pode ser muito granular e resultar em uma tabela muito alta e estreita com uma chave de linha complexa. Alto e estreito é bom, já que a atomicidade não é um problema, mas tê-los como qualificadores de coluna ampliaria a tabela e ainda ofereceria suporte a agregações secundárias.
  • Os dados de tendências devem ser pré-computados o máximo possível para otimizar o desempenho de leitura.
    • Mais sobre isso mais tarde, mas por enquanto saiba que adicionamos um segundo grupo de colunas para armazenar as tendências.

    Com base no exposto, o design do esquema inicial é ilustrado da seguinte forma:


    Tendências de computação


    O aspecto do design inicial com o qual mais aprendemos foram as tendências de computação. O requisito era permitir que os clientes analisassem seus gastos por categoria e varejista até a hora. Os pontos de dados incluíam os menores e maiores valores de transação, valor total da transação e número de transações. Os tempos de resposta tinham que ser de 200 ms ou menos.

    As tendências de pré-computação nos dariam os tempos de resposta mais rápidos, então essa foi nossa primeira abordagem. As tendências não podiam atrasar as transações, então elas precisavam ser calculadas no caminho de gravação. Isso seria ótimo para o desempenho de leitura, mas nos apresentou alguns desafios:como organizar melhor as tendências no HBase e como calculá-las de maneira rápida e confiável sem afetar severamente o desempenho de gravação.

    Experimentamos diferentes designs de esquema e tentamos aproveitar alguns designs bem conhecidos sempre que possível (como o esquema do OpenTSDB). Após várias iterações, decidimos pelo design do esquema ilustrado acima. Armazenados na tabela de transações, em uma família de colunas separada, os valores de tendência são organizados juntos em uma única linha, com uma linha de tendência por cliente. Dando à chave de linha o mesmo prefixo das transações de um cliente (por exemplo, <reverse_customer_id>::<contract_id> ) garantiu que a linha de tendência seja classificada ao lado dos registros de transação do cliente correspondente. Com limites de região definidos e uma política de divisão de região personalizada em vigor, também podemos garantir que a linha de tendência sempre será colocada com os registros de transação de um cliente, permitindo que a agregação de tendências permaneça inteiramente no lado do servidor no coprocessador.

    Para pré-computar tendências, implementamos um coprocessador observador personalizado para se conectar ao caminho de gravação. (Coprocessadores observadores são semelhantes a gatilhos em um RDBMS, pois executam o código do usuário antes ou depois que um evento específico ocorre. Por exemplo, pré ou pós Put ou Get .)

    Em postPut o coprocessador executa as seguintes ações:
    1. Verifica o Put para um atributo de tendência (flag). O atributo é definido em novos registros de transação apenas para evitar chamadas recursivas ao atualizar o registro de tendência. Também permite que o coprocessador seja ignorado para Put s que não exigem que as tendências sejam atualizadas (por exemplo, acordos ).
    2. Obtenha o registro de tendências para o cliente. O registro de tendência de um cliente é colocado com suas transações (com base no prefixo da chave de linha) para que o coprocessador possa recuperá-lo diretamente da região atual. A linha de tendência deve ser bloqueada para evitar que vários threads do manipulador RegionServer tentem atualizar as tendências em paralelo.
    3. Atualizar pontos de dados:
    4. Atualize e desbloqueie a linha de tendência.

    A solução provou ser precisa durante os testes e, como esperado, o desempenho de leitura excedeu os requisitos. No entanto, houve algumas preocupações com esta abordagem. A primeira foi como lidar com falhas:as tendências são armazenadas em uma linha separada para que a atomicidade não possa ser garantida. A segunda foi como validar a precisão das tendências ao longo do tempo; ou seja, precisaríamos implementar um mecanismo para identificar e corrigir quaisquer imprecisões de tendências. Quando também consideramos os requisitos de HA e o fato de que precisaríamos executar duas instâncias ativas-ativas do HBase em data centers diferentes, isso poderia ser um problema maior. Não apenas a precisão da tendência pode diminuir ao longo do tempo, mas os dois clusters também podem derivar e precisam ser reconciliados, dependendo do método que usamos para sincronizá-los. Finalmente, corrigir bugs ou adicionar novos pontos de dados seria difícil porque possivelmente teríamos que retroceder e recalcular todas as tendências.

    Em seguida, houve o desempenho de gravação. Para cada nova transação, o observador tinha que buscar um registro de tendência, atualizar 32 pontos de dados e colocar o registro de tendência de volta. Apesar de tudo isso acontecer dentro dos limites de uma única região, descobrimos que a taxa de transferência foi reduzida de mais de 20.000 gravações por segundo para 1.000 gravações por segundo (por RegionServer). Esse desempenho foi aceitável no curto prazo, mas não seria dimensionado para suportar a carga prevista de longo prazo.

    Sabíamos que o desempenho de gravação era um risco, então tínhamos um plano de backup, que era um coprocessador de endpoint . Os coprocessadores de endpoint são semelhantes aos procedimentos armazenados em um RDBMS, pois permitem que você execute computação no lado do servidor — no RegionServer onde os dados estão localizados, em vez de no cliente. Os endpoints estendem efetivamente a API do HBase.

    Em vez de pré-computar tendências, o endpoint as calcula em tempo real, do lado do servidor. Como resultado, poderíamos eliminar a família de colunas de tendências do esquema e o risco de imprecisões e divergências o acompanhava. Afastar-se do observador resultou em um bom desempenho de gravação, mas as leituras seriam rápidas o suficiente? Em suma, sim. Com as transações de um cliente confinadas a uma única região e classificadas por cartão e carimbo de data/hora, o endpoint pode digitalizar e agregar rapidamente, dentro do objetivo de 200 ms da Spendlytics. Isso também significa que uma solicitação do cliente (da API Spendlytics neste caso) é roteada apenas para uma única instância do Endpoint (único RegionServer) e o cliente receberá uma única resposta de volta com um resultado completo, ou seja, nenhum lado do cliente o processamento é necessário para agregar resultados parciais de vários terminais, o que seria o caso se as transações de um cliente abrangessem várias regiões.

    Lições aprendidas


    O Spendlytics está ativo desde julho de 2015. Desde então, monitoramos de perto os padrões de acesso e procuramos maneiras de otimizar o desempenho. Queremos melhorar continuamente a experiência do usuário e fornecer aos clientes cada vez mais informações sobre seus gastos com cartão. O restante desta postagem descreve as lições que aprendemos ao executar o Spendlytics em produção e algumas das otimizações que foram implementadas.

    Após o lançamento inicial, identificamos vários pontos problemáticos nos quais queríamos nos concentrar em melhorar. A primeira foi como filtrar resultados por atributo de transação. Conforme mencionado anteriormente, os atributos de transação são codificados nos registros Avro, mas descobrimos que um número crescente de padrões de acesso queria filtrar por atributo e os usuários eram forçados a fazer isso no lado do cliente. A solução inicial foi implementar um ValueFilter personalizado do HBase que aceitaram nossas próprias expressões de filtro complexas, por exemplo:
    category='SUPERMARKETS' AND amount > 100 AND 
    (brand LIKE 'foo%' OR brand = 'bar')

    A expressão é avaliada para cada registro Avro, permitindo filtrar os resultados do lado do servidor e reduzir a quantidade de dados retornados ao cliente (economizando largura de banda da rede e processamento do lado do cliente). O filtro afeta o desempenho da varredura, mas os tempos de resposta permaneceram bem dentro do objetivo de 200 ms.

    Isso acabou sendo uma solução temporária devido a outras alterações necessárias para otimizar as gravações. Devido à forma como o processo de liquidação do cartão de crédito funciona, primeiro recebemos um autorizado transação desde o momento da venda (quase em tempo real) e, algum tempo depois, um resolvido transação da rede de cartão de crédito (em lote). Essas transações precisam ser reconciliadas, essencialmente pela fusão do liquidado transações com o autorizado transações já no HBase, ingressando no ID da transação. Como parte desse processo, os atributos da transação podem ser alterados e novos atributos podem ser adicionados. Isso provou ser doloroso devido à sobrecarga de ter que reescrever registros Avro inteiros, mesmo ao atualizar atributos únicos. Então, para tornar os atributos mais acessíveis para atualizações, nós os organizamos em colunas, substituindo a serialização do Avro.

    Também nos preocupamos apenas com a atomicidade no nível da transação, portanto, agrupar as transações por hora não nos deu nenhuma vantagem. Além disso, os resolvidos as transações que agora chegam em lote têm apenas granularidade de nível de dia, o que tornava difícil (custo) reconciliá-las com as autorizadas existentes transações armazenadas por hora. Para resolver esse problema, movemos o ID da transação para a chave de linha e reduzimos a granulação do carimbo de data/hora para dias, em vez de horas. O processo de reconciliação agora é muito mais fácil porque podemos simplesmente carregar as alterações em massa no HBase e deixar o acordo valores prevalecem.

    Resumindo:
    • Os coprocessadores observadores podem ser uma ferramenta valiosa, mas use-os com sabedoria.
    • Para alguns casos de uso, estender a API do HBase usando endpoints é uma boa alternativa.
    • Use filtros personalizados para melhorar o desempenho reduzindo os resultados do lado do servidor.
    • Os valores serializados fazem sentido para o caso de uso correto, mas aproveitam os pontos fortes do HBase ao favorecer o suporte nativo para campos e colunas.
    • Gerenciar resultados pré-computados é difícil; a latência adicional da computação em tempo real pode valer a pena.
    • Os padrões de acesso mudarão, portanto, seja ágil e aberto para fazer alterações no esquema do HBase para se adaptar e ficar à frente do jogo.

    Roteiro


    Uma otimização que estamos avaliando atualmente são os coprocessadores híbridos. O que queremos dizer com isso é a combinação de coprocessadores observadores e terminais para pré-computar tendências. No entanto, diferentemente de antes, não faríamos isso no caminho de gravação, mas em segundo plano, conectando-se às operações de compactação e limpeza do HBase. Um observador calculará as tendências durante os eventos de descarga e compactação com base no estabelecido transações disponíveis naquele momento. Em seguida, usaríamos um endpoint para combinar as tendências pré-computadas com agregações dinâmicas do delta de transações. Ao pré-computar as tendências dessa maneira, esperamos aumentar o desempenho das leituras, sem afetar o desempenho da gravação.

    Outra abordagem que estamos avaliando para agregação de tendências e para acesso ao HBase em geral é o Apache Phoenix. Phoenix é uma skin SQL para HBase que permite o acesso usando APIs JDBC padrão. Esperamos que o uso de SQL e JDBC simplifique o acesso ao HBase e reduza a quantidade de código que temos que escrever. Também podemos aproveitar os padrões de execução inteligentes do Phoenix e coprocessadores e filtros integrados para agregações rápidas. Phoenix foi considerado muito imaturo para uso em produção no início da Spendlytics, mas com casos de uso semelhantes sendo relatados por empresas como eBay e Salesforce, agora é a hora de reavaliar. (Um pacote Phoenix para CDH está disponível para instalação e avaliação, mas sem suporte, via Cloudera Labs.)

    O Santander anunciou recentemente que é o primeiro banco a lançar a tecnologia de banco de voz que permite que os clientes conversem com seu aplicativo SmartBank e perguntem sobre seus gastos com cartão. A plataforma por trás dessa tecnologia é a Cloudera, e a arquitetura do Spendlytics – conforme descrito neste conjunto de postagens – serviu como projeto de projeto.

    James Kinley é arquiteto de soluções principal da Cloudera.

    Ian Buss é arquiteto de soluções sênior na Cloudera.

    Pedro Boado é engenheiro de Hadoop no Santander (Isban) Reino Unido.

    Abel Fernandez Alfonso é engenheiro de Hadoop no Santander (Isban) Reino Unido.