Melhores práticas do Redis, parte 1

Em uma série de vários artigos, apresentarei minha tradução adaptada da seção Redis Best Practices no site oficial do Redis Labs.

Os Redis podem ser usados ​​de inúmeras maneiras; no entanto, existem vários padrões que podem ser usados ​​para resolver problemas comuns. Compilamos uma coleção de padrões comuns que consideramos melhores práticas para resolver esses problemas. Essa coleção não é exaustiva e não parece ser um conjunto das únicas maneiras de usar o Redis, mas esperamos que sirva como ponto de partida para resolver problemas usando o Redis.

Dividimos este guia de práticas recomendadas em capítulos e subcapítulos, conforme necessário (Nota do tradutor: alguns subcapítulos são curtos, portanto os combinarei em um):

  • no capítulo “Padrões de indexação”, procuraremos maneiras de ir além do acesso usual de valores-chave com o Redis. Inclui maneiras de usar padrões de chave de maneira inteligente, usando vários tipos de dados Redis para ajudar não apenas a encontrar dados, mas também a reduzir a complexidade do acesso;
  • O capítulo Padrões de interação enfoca os padrões Redis que movem dados pela infraestrutura. Nesse caso, o Redis não atua como um repositório, mas como um guia para dados;
  • o capítulo “Padrões de armazenamento de dados” descreve métodos para salvar representações complexas de dados no Redis. Calcularemos scripts complexos de documentos que podem ser generalizados de maneiras simples e complexas;
  • padrões relacionados a dados armazenados temporariamente são descritos no capítulo “Padrões de séries temporais”;
  • O limite de velocidade é frequentemente usado no Redis. No capítulo "Padrões básicos de limitação de velocidade", passaremos ao básico de seus casos de uso;
  • O filtro Bloom é visto há muito tempo em Redis e, no capítulo “Padrões com filtro Bloom”, examinamos as estruturas de dados probabilísticas e como elas diferem de seus análogos incríveis;
  • o balcão é uma recepção surpreendentemente profunda. Em um capítulo separado, exploramos como calcular a atividade e elementos únicos de maneiras computacionais eficientes;
  • por fim, falaremos sobre como alavancar Lua para que os Redis façam mais com menos.

Este guia é inconsistente, portanto você pode iniciá-lo em qualquer capítulo. Você também pode usar a navegação no início de cada postagem para encontrar algo adequado.

Padrões de indexação


Conceitualmente, o Redis é um banco de dados baseado no paradigma de chave / valor, quando cada dado está associado a uma determinada chave. Se você deseja obter dados para algo que não seja uma chave, será necessário implementar um índice que use um dos muitos tipos de dados disponíveis no Redis.

A indexação no Redis é bem diferente da apresentada em outros bancos de dados, portanto, seus próprios cenários e dados de uso determinarão a melhor estratégia de indexação. Neste capítulo, examinaremos algumas estratégias gerais de recuperação de dados, além da simples recuperação de chave / valor:

  • conjuntos classificados como índices;
  • índices lexicográficos;
  • índices geoespaciais;
  • Geolocalização IP
  • pesquisa de texto completo;
  • índices particionados.

Conjuntos classificados como índices


Conjuntos classificados (ZSETs) são um tipo de dados padrão no Redis que representa muitos objetos exclusivos (as repetições não são salvas), onde cada objeto é atribuído a um número (chamado de "contagem"), que atua como um mecanismo de classificação natural. E embora os objetos não possam ser repetidos, poucos objetos podem ter a mesma contagem. Com uma complexidade de tempo relativamente baixa para adicionar, excluir e obter um intervalo de valores (por classificação ou contagem), os conjuntos classificados são bastante adequados para serem índices. Como exemplo, considere os países do mundo, classificados por população:

> ZADD countries-by-pop 1409517397 china > ZADD countries-by-pop 146573899 russia > ZADD countries-by-pop 81456724 germany > ZADD countries-by-pop 333016381 usa > ZADD countries-by-pop 1 mars > ZADD countries-by-pop 37290812 afghanistan > ZADD countries-by-pop 1388350202 india 

Obter os 5 principais países será simples:

 > ZRANGE countries-by-pop 0 4 1) "mars" 2) "afghanistan" 3) "germany" 4) "russia" 5) "india" 

E obtendo países com populações entre 10.000.000 e 1.000.000.000:

 > ZRANGEBYSCORE countries-by-pop 10000000 1000000000 1) "afghanistan" 2) "germany" 3) "russia" 

Você pode criar vários índices para demonstrar maneiras diferentes de classificar dados. Em nosso exemplo, poderíamos usar os mesmos objetos, mas, em vez do número de pessoas, considerar a densidade populacional, o tamanho geográfico, o número de usuários da Internet etc. Isso criará índices de alto desempenho para vários aspectos. Além disso, dividindo o nome de um objeto com dados armazenados em Redis (no Hash, por exemplo) ou em outro repositório de dados, o processo secundário pode obter informações adicionais sobre cada elemento, conforme necessário.

Índices lexicográficos


Os conjuntos classificados (ZSETs) com classificação por contagem têm uma propriedade interessante que pode ser usada para criar um mecanismo para classificação alfabética aproximada. A propriedade é que objetos com a mesma pontuação podem ser retornados em ordem lexicográfica e por valores de limite. Pegue os seguintes dados:

 > ZADD animal-list 0 bison 0 boa 0 dog 0 emu 0 falcon 0 alligator 0 chipmunk 

Este comando adicionará vários animais à chave da lista de animais. Cada objeto tem uma pontuação igual a 0. Depois de executar o comando ZRANGE com os argumentos 0 e -1, vemos uma ordem curiosa:

 > ZRANGE animal-list 0 -1 1) "alligator" 2) "bison" 3) "boa" 4) "chipmunk" 5) "dog" 6) "emu" 7) "falcon" 

Embora os elementos não tenham sido adicionados em ordem alfabética, eles foram retornados em ordem alfabética. Essa ordem é o resultado da comparação de cadeias binárias, byte por bit. Isso significa que os caracteres ASCII serão retornados em ordem alfabética. Isso sugere o seguinte:

  • caracteres em maiúsculas e minúsculas não serão reconhecidos como os mesmos;
  • caracteres multibyte não serão classificados conforme o esperado.

O Redis também fornece alguns recursos avançados para restringir ainda mais as pesquisas lexicográficas. Por exemplo, queremos retornar animais começando com be terminando com e . Podemos usar o seguinte comando:

 > ZRANGEBYLEX animal-list [b (f 1) "bison" 2) "boa" 3) "chipmunk" 4) "dog" 5) "emu" 

O argumento (f pode ser um pouco confuso. Esse é um ponto importante, porque Redis não tem idéia do entendimento literal das letras do alfabeto. Isso significa que devemos levar em conta que tudo que começa com e sempre fica antes de tudo que começa com f , independentemente das letras subsequentes Outra observação é que o colchete indica pesquisa com inclusão e o colchete indica pesquisa sem inclusão.No nosso caso, se consultarmos com b , isso será incluído na lista, enquanto f não aparecerá na seleção. todos os elementos até o fim, use codificado o último símbolo (255 ou 0xFF):

 > ZRANGEBYLEX animal-list [c "[\xff" 1) "chipmunk" 2) "dog" 3) "emu" 4) "falcon" 

Este comando também pode ser limitado, garantindo a paginação:

 > ZRANGEBYLEX animal-list [b (f LIMIT 0 2 1) "bison" 2) "boa" > ZRANGEBYLEX animal-list [b (f LIMIT 2 2 1) "chipmunk" 2) "dog" 

A única armadilha é que a complexidade do tempo aumentará à medida que o recuo aumentar (primeiro argumento após LIMIT). Portanto, se você possui 1 milhão de objetos e está tentando obter os dois últimos, isso exigirá um rastreamento de apenas um milhão.

Índices geoespaciais


O Redis possui várias equipes de indexação geoespacial (equipes GEO), mas, diferentemente de outras equipes, essas equipes não têm seus próprios tipos de dados. Esses comandos realmente complementam o tipo de conjunto classificado. Isso é conseguido codificando a latitude e longitude na pontuação do conjunto classificado usando o algoritmo geohash.
É fácil adicionar elementos a um índice geográfico. Suponha que você esteja rastreando um grupo de carros que dirigem ao longo de uma estrada. Chamamos esse conjunto de carros simplesmente de "carros". Digamos que sua máquina específica possa ser identificada como um objeto "meu carro" (usamos o termo "objeto" porque o índice geográfico é simplesmente uma forma do conjunto). Para adicionar uma máquina ao conjunto, podemos executar o comando:

 > GEOADD cars -115.17087 36.12306 my-car 

O primeiro argumento é o conjunto ao qual adicionamos, o segundo é latitude, o terceiro é longitude e o quarto é o nome do objeto.

Para atualizar a localização da máquina, basta executar o comando novamente com as novas coordenadas. Isso funciona porque um índice geográfico é simplesmente um conjunto em que elementos duplicados não são permitidos.

 > GEOADD cars -115.17172 36.12196 my-car 

Adicione um segundo carro a "carros". Desta vez, Volodya a leva:

 > GEOADD cars -115.171971 36.120609 volodias-car 

Olhando as coordenadas, você pode dizer que esses carros são muito próximos um do outro, mas quanto? Você pode definir isso com o comando GEODIST:

 > GEODIST cars my-car volodias-car "151.9653" 

Isso significa que dois veículos estão a aproximadamente 151 metros de distância. Você também pode calcular em outras unidades:

 > GEODIST cars my-car robins-car ft "498.5737" 

Isso retornou a mesma distância em etapas. Você também pode usar milhas (ml) ou quilômetros (km).

Agora vamos ver quem está no raio de um certo ponto:

 > GEORADIUS cars -115.17258 36.11996 100 m 1) "volodias-car" 

Isso retornou todos dentro de um raio de 100 metros ao redor do ponto especificado. Você também pode solicitar a todos no raio de qualquer objeto do conjunto:

 > GEORADIUSBYMEMBER cars volodias-car 152 m 1) "volodias-car" 2) "my-car" 

Também podemos ativar a distância adicionando o argumento WITHDIST opcional (isso funciona para GEORADIUS ou GEORADIUSBYMEMBER):

 > GEORADIUSBYMEMBER cars volodias-car 152 m WITHDIST 1) 1) "volodias-car" 2) "0.0000" 2) 1) "my-car" 2) "151.9653" 

Outro argumento opcional para GEORADIUS e GEORADIUSBYMEMBER é WITHCOORD, que retorna as coordenadas de cada objeto. WITHDIST e WITHCOORD podem ser usados ​​juntos ou separadamente:

 > GEORADIUSBYMEMBER cars volodias-car 152 m WITHDIST WITHCOORD 1) 1) "volodias-car" 2) "0.0000" 3) 1) "-115.17197102308273315" 2) "36.12060917648089031" 2) 1) "my-car" 2) "151.9653" 3) 1) "-115.17171889543533325" 2) "36.12196018285882104" 

Como os índices geoespaciais são apenas uma alternativa aos conjuntos classificados, alguns operadores desses últimos podem ser usados. Se quisermos remover “my-car” do conjunto de “cars”, podemos usar o comando do conjunto classificado de ZREM:

 > ZREM cars my-car 

O Redis fornece um rico conjunto de ferramentas para trabalhar com geoespacial, e nesta seção examinamos apenas as básicas.

Geolocalização IP


Encontrar a localização real do serviço conectado pode ser muito útil. As tabelas de geolocalização de IP geralmente são muito grandes e difíceis de gerenciar de forma eficaz. Podemos usar conjuntos classificados para implementar serviços de geolocalização IP rápidos e eficientes.

O IPv4 é frequentemente referenciado em notação decimal (74.125.43.99, por exemplo). No entanto, os serviços de rede veem esse mesmo endereço como um número de 32 bits, com cada byte representando um dos quatro números na forma decimal. O exemplo acima seria 0x4A7D2B63 em hexadecimal ou 1249717091 em decimal.

Os conjuntos de dados de localização geográfica de IP estão amplamente disponíveis e geralmente assumem a forma de uma tabela simples com três colunas (início, fim, localização). O começo e o fim são a representação decimal do IPv4. No Redis, podemos adaptar conjuntos classificados para esse formato, porque não há "buracos" nos intervalos de IP; portanto, podemos assumir com segurança que o final de um intervalo é o início de outro.

Para um exemplo simples, adicione alguns intervalos aos conjuntos classificados:

 > ZADD ip-loc 1249716479 us:1 > ZADD ip-loc 1249716735 taiwan:1 > ZADD ip-loc 1249717759 us:2 > ZADD ip-loc 1249718015 finland:1 

O primeiro argumento é a chave do nosso conjunto, o segundo é a representação decimal do final do intervalo de IPs e o último é o próprio objeto. Observe que o objeto definido possui um número após os dois pontos. Isso é apenas para facilitar um exemplo. As tabelas IP reais têm identificadores exclusivos para cada intervalo (e mais informações adicionais do que apenas o nome do país).

Para consultar a tabela para um determinado endereço IP, podemos usar o comando ZRANGEBYSCORE com alguns argumentos adicionais. Pegue o endereço IP e converta-o em decimal. Isso pode ser feito usando sua linguagem de programação. Primeiro, use o endereço do exemplo original 74.125.43.99 (1249717091). Se tomarmos esse número como um ponto de referência e não especificarmos um máximo, e depois limitarmos o resultado apenas ao primeiro objeto, encontraremos sua localização:

 > ZRANGEBYSCORE ip-loc 1249717091 +inf LIMIT 0 1 1) "us:2" 

O primeiro argumento é a chave do nosso conjunto classificado, o segundo é a representação decimal do endereço IP, o terceiro (+ inf) diz ao Redis para solicitar sem um limite superior, e os três últimos argumentos simplesmente indicam que queremos obter apenas o primeiro resultado.

Pesquisa de texto completo


Antes do advento dos módulos, a pesquisa de texto completo era implementada usando comandos nativos do Redis. O módulo RedisSearch é muito mais produtivo que esse padrão, no entanto, em alguns ambientes, ele não está disponível. Além disso, esse padrão é muito interessante e pode ser generalizado para outras cargas de trabalho nas quais o RedisSearch não será o ideal.
Suponha que tenhamos vários documentos de texto que precisam ser pesquisados. Esse pode não ser o caso de uso óbvio para o Redis, pois fornece acesso chave, mas, por outro lado, o Redis pode ser usado como um mecanismo de pesquisa de texto completo completamente novo.

Primeiro, vamos dar alguns exemplos de textos nos documentos:

"Redis é muito rápido"
“Chitas são rápidas”
"Chitas têm manchas"

Nós os dividimos em conjuntos de palavras, separados por um espaço para simplificar:

 > SADD ex1 redis is very fast > SADD ex2 cheetahs are very fast > SADD ex3 cheetahs have spots 

Observe que colocamos cada linha em seu próprio conjunto. Pode parecer que estamos apenas adicionando a linha inteira - SADD é variável e aceita vários elementos ao mesmo tempo como argumentos. Também convertemos todas as palavras para minúsculas.
Então, precisamos inverter esse índice e mostrar qual palavra está contida em qual documento. Para fazer isso, criaremos um conjunto para cada palavra e colocaremos o nome do documento como um objeto:

 > SADD redis ex1 > SADD is ex1 > SADD very ex1 ex2 > SADD fast ex1 ex2 > SADD cheetahs ex2 ex3 > SADD have ex3 > SADD spots ex3 

Para maior clareza, dividimos isso em diferentes comandos, mas todos os comandos geralmente são executados atomicamente no bloco MULTI / EXEC.

Para consultar nosso pequeno índice de texto completo, usamos o comando SINTER (interseção de conjuntos). Encontre documentos com "muito" e "rápido":

 > SINTER very fast 1) "ex2" 2) "ex1" 

No caso em que não há documentos correspondentes à solicitação, obtemos um resultado vazio:

 > SINTER cheetahs redis (empty list or set) 

Para maior consistência, é melhor usar SUNION em vez de SINTER:

 > SUNION cheetahs redis 1) "ex2" 2) "ex1" 3) "ex3" 

Remover um objeto do índice é um pouco mais complicado. Primeiro, obtenha palavras indexadas do documento e exclua o identificador do documento de cada palavra:

 > SMEMBERS ex3 1) "spots" 2) "have" 3) "cheetahs" > SREM have ex3 > SREM cheetahs ex3 > SREM spots ex3 

O Redis não possui um operador separado para executar todas essas etapas com um único comando; portanto, primeiro é necessário consultar o comando SMEMBERS e excluir cada objeto seqüencialmente usando o SREM.

Obviamente, esta é uma pesquisa de texto completo muito simplificada. Você pode fazer mais avançado usando conjuntos classificados em vez dos habituais. Nesse caso, se uma palavra aparecer mais de uma vez em um documento, você poderá classificá-la mais alta que o documento em que ocorre uma vez. Os padrões descritos acima são mais ou menos os mesmos, com exceção dos tipos de conjuntos usados.

Índices Particionados


Uma única instância (ou shard) do Redis é muito viável, mas há circunstâncias em que você pode precisar de um índice distribuído por várias instâncias. Por exemplo, para aumentar a taxa de transferência paralelizando índices maiores que o espaço livre de uma instância. Digamos que você queira executar uma operação em várias teclas. Uma maneira eficaz de separar (partição) essas chaves é garantir a distribuição uniforme das chaves em cada partição, executar quaisquer operações em cada partição em paralelo e depois combinar os resultados no final.

Para obter uma distribuição uniforme de chaves, usaremos um algoritmo de hash não criptográfico. Qualquer função de hash rápido serve, mas usamos o famoso CRC-32 como exemplo. Na maioria dos casos, esses algoritmos retornam o resultado em hexadecimal (para "meu documento legal", o CRC-32 retornará F9FDB2C9). A representação hexadecimal é mais simples para a máquina, mas é apenas outra representação de números decimais, o que significa que você pode executar cálculos com esses valores.
Em seguida, você precisa determinar o número de partições - isso deve ser pelo menos x2 do número de cópias. Isso contribui ainda mais para o dimensionamento.

Digamos que temos 3 cópias e 6 partições. A partição para a qual o documento é enviado pode ser calculada pela seguinte operação:

hash function(identificador document) modnumber partitions



 CRC32(“my-cool-document”) = F9FDB2C9 (16)  4194153161 (10) 4194153161 mod 6 = 5 

No Redis Enterprise, você pode controlar a qual partição a chave pertence, usando expressões regulares predefinidas ou quebrando parte da chave com chaves. Portanto, para o nosso exemplo, podemos definir a chave do documento da seguinte maneira:

idx: meu-documento-legal {5}

Depois, temos outro documento que emite uma partição com o número 3; portanto, a chave ficará assim:

idx: meu-outro-documento {3}

Se você tiver chaves auxiliares adicionais com as quais precisará trabalhar e associadas a este documento, precisará estar na mesma partição para poder executar operações com as duas chaves ao mesmo tempo, sem encontrar muitos erros. Para fazer isso, você precisa adicionar o mesmo número de partição à chave do documento.

Observando seus dados remotamente, você verá que seu índice está distribuído de maneira bastante uniforme entre as partições. Você pode paralelizar a tarefa que precisa ser executada para cada partição. Quando você tem uma tarefa que precisa ser executada por todo o índice, seu aplicativo precisará executar a mesma lógica para cada partição, retornar o resultado e combinar conforme necessário no aplicativo.

Sobre isso, o primeiro artigo chega ao fim. A seguir, será apresentada uma tradução dos subtítulos "Padrões de interação" e "Padrões de armazenamento de dados".

Source: https://habr.com/ru/post/pt485672/


All Articles