Redis Best Practices, Partie 1

Dans une série de plusieurs articles, je présenterai ma traduction adaptée de la section Redis Best Practices du site officiel de Redis Labs.

Redis peut être utilisé de nombreuses façons, cependant, plusieurs modèles peuvent être utilisés pour résoudre des problèmes courants. Nous avons compilé une collection de modèles courants que nous considérons comme les meilleures pratiques pour résoudre ces problèmes. Cette collection n'est pas exhaustive et ne semble pas être un ensemble des seules façons d'utiliser Redis, mais nous espérons qu'elle servira de point de départ pour résoudre les problèmes à l'aide de Redis.

Nous avons divisé ce guide des meilleures pratiques en chapitres et sous-chapitres selon les besoins (Note du traducteur: certains sous-chapitres sont courts, je vais donc les combiner en un seul):

  • dans le chapitre «Modèles d'indexation», nous examinerons les moyens d'aller au-delà de l'accès aux valeurs-clés habituel avec Redis. Il comprend des moyens d'utiliser intelligemment les schémas clés en utilisant divers types de données Redis pour aider non seulement à trouver des données, mais aussi à réduire la complexité d'accès;
  • Le chapitre Modèles d'interaction se concentre sur les modèles Redis qui déplacent les données à travers l'infrastructure. Dans ce cas, Redis n'agit pas comme un référentiel, mais plutôt comme un guide pour les données;
  • le chapitre «Modèles de stockage des données» décrit les méthodes d'enregistrement des représentations complexes de données dans Redis. Nous calculerons des scripts complexes de documents qui peuvent être généralisés de manière simple et complexe;
  • les modèles concernant les données stockées temporairement sont décrits dans le chapitre «Modèles de séries temporelles»;
  • la limitation de vitesse est souvent utilisée à Redis. Dans le chapitre "Modèles de base de limitation de vitesse", nous allons passer aux principes de base de ses cas d'utilisation;
  • Le filtre Bloom a longtemps été vu dans Redis, et dans le chapitre «Patterns with a Bloom Filter» nous examinons les structures de données probabilistes et comment elles diffèrent de leurs analogues improbables;
  • le comptoir est une réception étonnamment profonde. Dans un chapitre distinct, nous explorons comment calculer l'activité et les éléments uniques de manière efficace par le calcul;
  • enfin, nous parlerons de la façon de tirer parti de Lua pour que Redis fasse plus avec moins.

Ce guide est incohérent, vous pouvez donc le démarrer avec n'importe quel chapitre. Vous pouvez également utiliser la navigation au début de chaque publication pour trouver quelque chose qui vous convient.

Modèles d'indexation


Conceptuellement, Redis est une base de données basée sur le paradigme clé / valeur, lorsque chaque élément de données est associé à une certaine clé. Si vous souhaitez obtenir des données pour autre chose qu'une clé, vous devrez implémenter un index qui utilise l'un des nombreux types de données disponibles dans Redis.

L'indexation dans Redis est assez différente de ce qui est présenté dans d'autres bases de données, donc vos propres scénarios d'utilisation et données détermineront la meilleure stratégie d'indexation. Dans ce chapitre, nous examinerons quelques stratégies générales de récupération de données en plus de la simple récupération de clé / valeur:

  • ensembles triés comme indices;
  • indices lexicographiques;
  • indices géospatiaux;
  • Géolocalisation IP
  • recherche plein texte;
  • index partitionnés.

Ensembles triés comme indices


Les ensembles triés (ZSET) sont un type de données standard dans Redis qui représente de nombreux objets uniques (les répétitions ne sont pas enregistrées), où chaque objet est affecté à un nombre (appelé un «décompte»), qui agit comme un mécanisme de tri naturel. Et bien que les objets ne puissent pas être répétés, quelques objets peuvent avoir le même nombre. Avec une complexité temporelle relativement faible pour ajouter, supprimer et obtenir une plage de valeurs (par rang ou nombre), les ensembles triés sont tout à fait appropriés pour être des index. Par exemple, prenons les pays du monde, classés par population:

> 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 

Obtenir les 5 meilleurs pays sera simple:

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

Et obtenir des pays avec des populations entre 10 000 000 et 1 000 000 000:

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

Vous pouvez créer plusieurs index pour illustrer différentes méthodes de tri des données. Dans notre exemple, nous pourrions utiliser les mêmes objets, mais au lieu du nombre de personnes, prendre la densité de population, la taille géographique, le nombre d'internautes, etc. Cela créera des index hautes performances pour divers aspects. De plus, en divisant le nom d'un objet par des données le concernant stockées soit dans Redis (dans Hash, par exemple) ou dans un autre magasin de données, le processus secondaire pourrait obtenir des informations supplémentaires sur chaque élément si nécessaire.

Indices lexicographiques


Les ensembles triés (ZSET) avec classement par nombre ont une propriété intéressante qui peut être utilisée pour créer un mécanisme de tri alphabétique approximatif. La propriété est que les objets avec le même score peuvent être retournés dans l'ordre lexicographique et par des valeurs limites. Prenez les données suivantes:

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

Cette commande ajoutera plusieurs animaux à la clé de la liste des animaux. Chaque objet a un score de 0. Après avoir exécuté la commande ZRANGE avec les arguments 0 et -1, nous voyons un ordre curieux:

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

Bien que les éléments n'aient pas été ajoutés par ordre alphabétique, ils ont été renvoyés triés par ordre alphabétique. Cet ordre est le résultat d'une comparaison de chaînes binaires, octet par bit. Cela signifie que les caractères ASCII seront retournés dans l'ordre alphabétique. Cela suggère ce qui suit:

  • les caractères en minuscule et en majuscule ne seront pas reconnus comme identiques;
  • les caractères multi-octets ne seront pas triés comme prévu.

Redis fournit également des fonctionnalités avancées pour affiner davantage les recherches lexicographiques. Par exemple, nous voulons renvoyer les animaux commençant par b et se terminant par e . Nous pouvons utiliser la commande suivante:

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

L'argument (f peut être un peu déroutant. C'est un point important, car Redis n'a aucune idée de la compréhension littérale des lettres de l'alphabet. Cela signifie que nous devons tenir compte du fait que tout ce qui commence par e restera toujours devant tout ce qui commence par f , quelles que soient les lettres suivantes Une autre remarque est que le crochet carré indique la recherche avec inclusion et le crochet rond indique la recherche sans inclusion. Dans notre cas, si nous interrogeons avec b , cela sera inclus dans la liste, tandis que f n'apparaîtra pas dans la sélection. Si vous avez besoin tous les éléments jusqu'à la fin, utilisez le dernier symbole codé (255 ou 0xFF):

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

Cette commande peut également être limitée, assurant ainsi la pagination:

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

Le seul écueil est que la complexité temporelle augmentera à mesure que l'indentation augmente (premier argument après LIMIT). Par conséquent, si vous avez 1 million d'objets et que vous essayez d'obtenir les deux derniers, cela nécessitera une analyse d'un million seulement.

Index géospatiaux


Redis possède plusieurs équipes d'indexation géospatiale (équipes GEO), mais contrairement à d'autres équipes, ces équipes n'ont pas leurs propres types de données. Ces commandes complètent en fait le type d'ensemble trié. Ceci est réalisé en encodant la latitude et la longitude dans le score de l'ensemble trié en utilisant l'algorithme geohash.
L'ajout d'éléments à un géo-index est facile. Supposons que vous suivez un groupe de voitures qui roulent le long d'une route. Nous appelons cet ensemble de voitures simplement des «voitures». Disons que votre machine particulière peut être identifiée comme un objet «ma voiture» (nous utilisons le terme «objet» parce que le géo-index est simplement une forme de l'ensemble). Pour ajouter une machine à l'ensemble, nous pouvons exécuter la commande:

 > GEOADD cars -115.17087 36.12306 my-car 

Le premier argument est l'ensemble auquel nous ajoutons, le second est la latitude, le troisième est la longitude et le quatrième est le nom de l'objet.

Pour mettre à jour l'emplacement de la machine, il vous suffit d'exécuter à nouveau la commande avec les nouvelles coordonnées. Cela fonctionne car un géo-index est simplement un ensemble où les éléments en double ne sont pas autorisés.

 > GEOADD cars -115.17172 36.12196 my-car 

Ajoutez une deuxième voiture aux «voitures». Cette fois, Volodya la conduit:

 > GEOADD cars -115.171971 36.120609 volodias-car 

En regardant les coordonnées, vous pouvez dire que ces voitures sont assez proches les unes des autres, mais combien? Vous pouvez le définir avec la commande GEODIST:

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

Cela signifie que deux véhicules sont distants d'environ 151 mètres. Vous pouvez également calculer dans d'autres unités:

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

Cela a renvoyé la même distance par étapes. Vous pouvez également utiliser des miles (ml) ou des kilomètres (km).

Voyons maintenant qui se trouve dans le rayon d'un certain point:

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

Cela a renvoyé tout le monde dans un rayon de 100 mètres autour du point spécifié. Vous pouvez également demander à tout le monde dans le rayon de n'importe quel objet de l'ensemble:

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

Nous pouvons également activer la distance en ajoutant l'argument optionnel WITHDIST (cela fonctionne pour GEORADIUS ou GEORADIUSBYMEMBER):

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

Un autre argument facultatif pour GEORADIUS et GEORADIUSBYMEMBER est WITHCOORD, qui renvoie les coordonnées de chaque objet. WITHDIST et WITHCOORD peuvent être utilisés ensemble ou séparément:

 > 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" 

Étant donné que les index géospatiaux ne sont qu'une alternative aux ensembles triés, certains opérateurs de ces derniers peuvent être utilisés. Si nous voulons supprimer "ma-voiture" de l'ensemble des "voitures", nous pouvons utiliser la commande de l'ensemble trié de ZREM:

 > ZREM cars my-car 

Redis fournit un riche ensemble d'outils pour travailler avec les données géospatiales, et dans cette section, nous n'avons examiné que les outils de base.

Géolocalisation IP


Trouver l'emplacement réel du service connecté peut être très utile. Les tables de géolocalisation IP sont généralement assez volumineuses et difficiles à gérer efficacement. Nous pouvons utiliser des ensembles triés pour implémenter des services de géolocalisation IP rapides et efficaces.

IPv4 est le plus souvent référencé en notation décimale (74.125.43.99, par exemple). Cependant, les services réseau voient cette même adresse comme un nombre 32 bits, chaque octet représentant l'un des quatre nombres sous forme décimale. L'exemple ci-dessus serait 0x4A7D2B63 en hexadécimal ou 1249717091 en décimal.

Les jeux de données de géolocalisation IP sont largement disponibles et prennent généralement la forme d'un simple tableau à trois colonnes (début, fin, emplacement). Le début et la fin sont la représentation décimale d'IPv4. Dans Redis, nous pouvons adapter des ensembles triés à ce format, car il n'y a pas de «trous» dans les plages IP, par conséquent, nous pouvons sans risque supposer que la fin d'une plage est le début d'une autre.

Pour un exemple simple, ajoutez quelques plages aux ensembles triés:

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

Le premier argument est la clé de notre ensemble, le second est la représentation décimale de la fin de la plage IP et le dernier est l'objet lui-même. Notez que l'objet set a un nombre après les deux points. C'est juste pour faciliter un exemple. Les vraies tables IP ont des identifiants uniques pour chaque plage (et plus d'informations supplémentaires que le nom du pays).

Pour interroger la table pour une adresse IP donnée, nous pouvons utiliser la commande ZRANGEBYSCORE avec quelques arguments supplémentaires. Prenez l'adresse IP et convertissez-la en décimale. Cela peut être fait en utilisant votre langage de programmation. Tout d'abord, utilisez l'adresse de l'exemple d'origine 74.125.43.99 (1249717091). Si nous prenons ce nombre comme point de référence et ne spécifions pas de maximum, puis limitons le résultat uniquement au premier objet, nous trouverons sa géolocalisation:

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

Le premier argument est la clé de notre ensemble trié, le second est la représentation décimale de l'adresse IP, le troisième (+ inf) indique à Redis de demander sans limite supérieure, et les trois derniers arguments indiquent simplement que nous voulons obtenir uniquement le tout premier résultat.

Recherche plein texte


Avant l'avènement des modules, la recherche en texte intégral était implémentée à l'aide des commandes natives de Redis. Le module RedisSearch est beaucoup plus productif que ce modèle, cependant, dans certains environnements, il n'est pas disponible. De plus, ce modèle est très intéressant et peut être généralisé à d'autres charges de travail dans lesquelles RedisSearch ne sera pas idéal.
Supposons que nous ayons plusieurs documents texte à rechercher. Ce n'est peut-être pas le cas d'utilisation évident pour Redis, car il fournit un accès clé, mais d'un autre côté, Redis peut être utilisé comme un moteur de recherche en texte intégral entièrement nouveau.

Tout d'abord, prenons quelques exemples de textes dans les documents:

"Redis est très rapide"
"Les guépards sont rapides"
"Les guépards ont des taches"

Nous les divisons en ensembles de mots, séparés par un espace de simplicité:

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

Notez que nous mettons chaque ligne dans son propre ensemble. Il peut sembler que nous ajoutons simplement la ligne entière - SADD est variable et prend plusieurs éléments à la fois comme arguments. Nous avons également converti tous les mots en minuscules.
Ensuite, nous devons inverser cet index et montrer quel mot est contenu dans quel document. Pour ce faire, nous allons créer un ensemble pour chaque mot et mettre le nom du document en tant qu'objet:

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

Pour plus de clarté, nous avons divisé cela en différentes commandes, mais toutes les commandes sont généralement exécutées atomiquement dans le bloc MULTI / EXEC.

Pour interroger notre petit index de texte intégral, nous utilisons la commande SINTER (intersection d'ensembles). Trouvez des documents avec «très» et «rapide»:

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

Dans le cas où aucun document ne correspond à la demande, nous obtiendrons un résultat vide:

 > SINTER cheetahs redis (empty list or set) 

Par souci de cohérence, il est préférable d'utiliser SUNION au lieu de SINTER:

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

La suppression d'un objet de l'index est un peu plus compliquée. Tout d'abord, récupérez les mots indexés du document, puis supprimez l'identifiant du document de chaque mot:

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

Redis n'a pas d'opérateur séparé pour effectuer toutes ces étapes avec une seule commande, vous devez donc d'abord interroger avec la commande SMEMBERS, puis supprimer chaque objet séquentiellement à l'aide de SREM.

Bien sûr, il s'agit d'une recherche en texte intégral très simplifiée. Vous pouvez faire plus avancé en utilisant des ensembles triés au lieu des ensembles habituels. Dans ce cas, si un mot apparaît plusieurs fois dans un document, vous pouvez le classer plus haut que le document dans lequel il apparaît une fois. Les schémas décrits ci-dessus sont plus ou moins les mêmes, à l'exception des types d'ensembles utilisés.

Index partitionnés


Une seule instance (ou fragment) de Redis est très viable, mais il peut arriver que vous ayez besoin d'un index réparti sur plusieurs instances. Par exemple, pour augmenter le débit en parallélisant des index plus grands que l'espace libre d'une instance. Supposons que vous souhaitiez effectuer une opération sur plusieurs touches. Un moyen efficace de séparer (partitionner) ces clés est d'assurer une distribution uniforme des clés sur chaque partition, d'effectuer toutes les opérations en parallèle sur chaque partition, puis de combiner les résultats à la fin.

Pour obtenir une distribution uniforme des clés, nous utiliserons un algorithme de hachage non cryptographique. N'importe quelle fonction de hachage rapide fera l'affaire, mais nous utilisons le célèbre CRC-32 comme exemple. Dans la plupart des cas, ces algorithmes renvoient le résultat en hexadécimal (pour «my-cool-document» CRC-32 renverra F9FDB2C9). La représentation hexadécimale est plus simple pour la machine, mais ce n'est qu'une autre représentation d'entiers décimaux, ce qui signifie que vous pouvez effectuer des calculs sur ces valeurs.
Ensuite, vous devez déterminer le nombre de partitions - cela devrait être au moins x2 du nombre de copies. Cela contribue en outre à la mise à l'échelle.

Disons que nous avons 3 copies et 6 partitions. La partition à laquelle le document est envoyé peut être calculée par l'opération suivante:

hash function(identifier document) modnumber partitions



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

Dans Redis Enterprise, vous pouvez contrôler la partition à laquelle appartient la clé, en utilisant des expressions régulières prédéfinies ou en enveloppant une partie de la clé avec des accolades. Donc, pour notre exemple, nous pouvons définir la clé du document comme suit:

idx: my-cool-document {5}

Ensuite, nous avons un autre document qui émet une partition avec le numéro 3, par conséquent, la clé ressemblera à ceci:

idx: mon-autre-document {3}

Si vous avez des clés auxiliaires supplémentaires avec lesquelles vous devrez travailler et qui sont associées à ce document, vous devez être sur la même partition afin de pouvoir effectuer des opérations avec les deux clés en même temps sans rencontrer un tas d'erreurs. Pour ce faire, vous devez ajouter le même numéro de partition à la clé que le document.

En regardant vos données à distance, vous verrez que votre index est réparti assez uniformément sur les partitions. Vous pouvez paralléliser la tâche qui doit être effectuée pour chaque partition. Lorsque vous avez une tâche qui doit être effectuée à travers l'index entier, votre application devra effectuer la même logique pour chaque partition, retourner le résultat et combiner comme requis dans l'application.

Sur ce point, le premier article prend fin. La prochaine sera une traduction des sous-titres «Modèles d'interaction» et «Modèles de stockage de données».

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


All Articles