Accueil Big Data Performances de recherche 4 fois plus rapides avec le cache Row Store de Rockset

Performances de recherche 4 fois plus rapides avec le cache Row Store de Rockset

0
Performances de recherche 4 fois plus rapides avec le cache Row Store de Rockset


En tant que base de données de recherche et d’analyse, Rockset alimente de nombreuses applications de personnalisation, de détection d’anomalies, d’IA et vectorielles qui nécessitent des requêtes rapides sur des données en temps réel. Rockset maintient des index inversés pour les données, ce qui lui permet d’exécuter efficacement des requêtes de recherche sans analyser toutes les données. Nous maintenons également des magasins de colonnes qui permettent des requêtes analytiques efficaces. Lire sur indexation convergée pour en savoir plus sur l’indexation dans Rockset.

Les index inversés sont le moyen le plus rapide de trouver les lignes correspondant à une requête sélective, mais une fois les lignes identifiées, Rockset doit récupérer les valeurs corrélées des autres colonnes. Cela peut constituer un goulot d’étranglement. Dans cet article de blog, nous expliquerons comment nous avons rendu cette étape beaucoup plus rapide, ce qui a permis d’accélérer 4 fois les requêtes de type recherche des clients.

Performances de recherche rapides pour les applications modernes

Pour de nombreuses applications en temps réel, la capacité d’exécuter des requêtes de recherche avec une latence de l’ordre de la milliseconde et des requêtes par seconde (QPS) élevées est essentielle. Par exemple, découvrez comment Étagère utilise Rockset comme backend pour la personnalisation en temps réel.

Ce blog présente comment nous avons amélioré les performances de l’utilisation du processeur et de la latence des requêtes de recherche en analysant les charges de travail et les modèles de requêtes liés à la recherche. Nous profitons du fait que, pour les charges de travail liées à la recherche, l’ensemble de travail tient généralement en mémoire et nous nous concentrons sur l’amélioration des performances des requêtes en mémoire.

Analyse des performances des requêtes de recherche dans Rockset

Supposons que nous construisions le backend pour les recommandations de produits en temps réel. Pour y parvenir, il faut récupérer une liste de produits, compte tenu d’une ville, pouvant être affichés sur le site par ordre décroissant de leur probabilité d’être cliqués. Pour y parvenir, nous pouvons exécuter l’exemple de requête suivant :

SELECT product_id, SUM(CAST(clicks as FLOAT)) / (SUM(CAST(impressions as FLOAT) + 1.0)) AS click_through_rate 
FROM product_clicks p
WHERE city = 'UNITED ST2' 
GROUP BY product_id
ORDER BY click_through_rate DESC

Certaines villes présentent un intérêt particulier. En supposant que les données des villes fréquemment consultées tiennent en mémoire, toutes les données d’indexation sont stockées dans RochesDB block cache, le cache intégré fourni par RocksDB. RocksDB est notre magasin de données pour tous les index.

Le product_clicks voir contient 600 millions de documents. Lorsque le filtre des villes est appliqué, environ 2 millions de documents sont émis, ce qui représente environ 0,3 pour cent du nombre total de documents. Il existe deux plans d’exécution possibles pour la requête.

  1. L’optimiseur basé sur les coûts (CBO) a la possibilité d’utiliser le magasin de colonnes pour lire les colonnes nécessaires et filtrer les lignes inutiles. Le graphique d’exécution à gauche de la figure 1 montre que la lecture des colonnes requises à partir du magasin de colonnes prend 5 secondes en raison de la grande taille de la collection de 600 millions de documents.


ligne-store-cache-fig1

Figure 1 : exécution de la requête à l’aide du magasin de colonnes à gauche. Exécution de la requête à l’aide de l’index inversé/de recherche à droite.

  1. Pour éviter de scanner la colonne entière, le CBO utilise l’index inversé. Cela permet de récupérer uniquement les documents 2M requis, puis de récupérer les valeurs de colonne requises pour ces documents. Le graphique d’exécution se trouve à droite de la figure 1.

Le plan d’exécution lors de l’utilisation de l’index inversé est plus efficace que celui lors de l’utilisation du magasin de colonnes. Le Cost-Based Optimizer (CBO) est suffisamment sophistiqué pour sélectionner automatiquement le plan d’exécution approprié.

Qu’est-ce que prendre du temps ?

Examinons les goulots d’étranglement dans le plan d’exécution de l’index inversé présenté dans la figure 1 et identifions les opportunités d’optimisation. La requête s’exécute principalement en trois étapes :

  1. Récupérez les identifiants des documents à partir de l’index inversé.
  2. Obtenez les valeurs du document à l’aide des identifiants du Row Store. Le magasin de lignes est un index qui fait partie de l’index convergé, mappant un identifiant de document à la valeur du document.
  3. Récupérez les colonnes requises à partir des valeurs du document (c’est-à-dire product_id, clics, impressions).
  4. La combinaison des étapes 2 et 3 est appelée Add Fields operation.

Comme le montre le graphique d’exécution, l’opération Ajouter des champs est très gourmande en CPU et prend un temps disproportionné lors de l’exécution de la requête. Il représente 1,1 seconde du temps CPU total de 2 secondes pour la requête.

Pourquoi cela prend-il du temps ?

Utilisations de Rockset RochesDB pour toutes les stratégies d’indexation mentionnées ci-dessus. RocksDB utilise un cache en mémoire, appelé cache de blocs, pour stocker en mémoire les blocs les plus récemment consultés. Lorsque l’ensemble de travail tient en mémoire, les blocs correspondant au magasin de lignes sont également présents en mémoire. Ces blocs contiennent plusieurs paires clé-valeur. Dans le cas du row store, les couples prennent la forme de (identifiant du document, valeur du document). L’opération Ajouter des champs est chargée de récupérer les valeurs du document à partir d’un ensemble d’identifiants de document.

Récupérer une valeur de document à partir du cache de blocs en fonction de son identifiant de document est un processus gourmand en CPU. En effet, cela implique plusieurs étapes, principalement pour déterminer quel bloc rechercher. Ceci est accompli grâce à une recherche binaire sur un Index interne de RocksDB ou en effectuant plusieurs recherches avec un index interne RocksDB à plusieurs niveaux.

Nous avons observé qu’il existe une marge d’optimisation en introduisant un cache en mémoire complémentaire – une table de hachage qui mappe directement les identifiants des documents aux valeurs des documents. Nous appelons cette cache complémentaire la RowStoreCache.

RowStoreCache : compléter le cache de blocs RocksDB

Le RowStoreCache est un cache complémentaire interne à Rockset au cache de blocs RocksDB pour le magasin de lignes.

  1. Le RowStoreCache est un cache en mémoire qui utilise MVCC et agit comme une couche au-dessus du cache de blocs RocksDB.
  2. Le RowStoreCache stocke la valeur du document pour un identifiant de document lors du premier accès au document.
  3. L’entrée du cache est marquée pour suppression lorsque le document correspondant reçoit une mise à jour. Cependant, l’entrée n’est supprimée que lorsque toutes les requêtes précédentes y faisant référence ont fini de s’exécuter. Pour déterminer quand l’entrée du cache peut être supprimée, nous utilisons la construction de numéro de séquence fournie par RocksDB.
  4. Le numéro de séquence est une valeur croissante de façon monotone qui s’incrémente à chaque mise à jour de la base de données. Chaque requête lit les données à un numéro de séquence spécifié, que nous appelons un instantané de la base de données. Nous maintenons une structure de données en mémoire de tous les instantanés actuellement utilisés. Lorsque nous déterminons qu’un instantané n’est plus utilisé parce que toutes les requêtes le référençant ont été terminées, nous savons que les entrées de cache correspondantes au niveau de l’instantané peuvent être libérées.
  5. Nous appliquons une politique LRU sur RowStoreCache et utilisons des politiques basées sur le temps pour déterminer quand une entrée de cache doit être déplacée lors de l’accès dans la liste LRU ou supprimée de celle-ci.

Conception et mise en œuvre.

La figure 2 montre la disposition de la mémoire du pod feuille, qui est la principale unité d’exécution pour l’exécution de requêtes distribuées chez Rockset.


rangée-store-cache-fig2

Figure 2 : disposition de la mémoire du pod Leaf avec le cache de blocs RocksDB et les caches RowStore. (\RSC C1S1 : RowStoreCache pour la collection 1, fragment 1.)*

Dans Rockset, chaque collection est divisée en N fragments. Chaque fragment est associé à une instance RocksDB responsable de tous les documents et index convergés correspondants au sein de ce fragment.

Nous avons implémenté RowStoreCache pour avoir une correspondance individuelle avec chaque fragment et une liste LRU globale pour appliquer les politiques LRU sur le pod feuille.

Chaque entrée dans RowStoreCache contient l’identifiant du document, la valeur du document, le numéro de séquence RocksDB auquel la valeur a été lue, le dernier numéro de séquence RocksDB auquel l’entrée a vu une mise à jour et un mutex pour protéger l’accès à l’entrée par plusieurs threads simultanément. Pour prendre en charge les opérations simultanées sur le cache, nous utilisons folly::ConcurrentHashMapSIMD.

Opérations sur RowStoreCache

  1. RowStoreCache :: Get (RowStoreCache, documentIdentifier, rocksDBSequenceNumber)

    Cette opération est simple. Nous vérifions si le documentIdentifier est présent dans le RowStoreCache.

    1. S’il est présent et que le document n’a reçu aucune mise à jour entre le numéro de séquence auquel il a été lu et le numéro de séquence actuel auquel il est interrogé, nous renvoyons la valeur correspondante. L’entrée est également déplacée en haut de la liste globale des entrées LRU afin qu’elle soit expulsée en dernier.
    2. S’il n’est pas présent, nous récupérons la valeur correspondant à l’identifiant du document depuis l’instance RocksDB et la définissons dans le RowStoreCache.
  2. RowStoreCache :: Set (RowStoreCache, documentIdentifier, documentValue, rocksDBSequenceNumber)

    1. Si l’opération get n’a pas trouvé le documentIdentifier dans le cache, nous essayons de définir la valeur dans le cache. Étant donné que plusieurs threads peuvent tenter d’insérer simultanément la valeur correspondant à un documentIdentifier, nous devons nous assurer que nous n’insérons la valeur qu’une seule fois.
    2. Si la valeur est déjà présente dans le cache, nous définissons la nouvelle valeur uniquement si l’entrée n’est pas marquée pour être supprimée et que l’entrée correspond à un numéro de séquence postérieur à celui déjà présent dans le cache.
  3. AssurerLruLimites

    1. Lorsqu’une entrée est ajoutée à la liste globale des entrées LRU et que nous devons récupérer de la mémoire, nous identifions l’entrée la moins récemment consultée et son RowStoreCache correspondant.
    2. Nous supprimons ensuite l’entrée du RowStoreCache correspondant et la dissocions de la liste LRU globale si les informations sur les mises à jour dans l’entrée du document ne sont pas pertinentes.

Améliorations des performances avec RowStoreCache

Améliorations de la latence

L’activation de RowStoreCache dans l’exemple de requête a entraîné une multiplication par 3 de la latence des requêtes, la réduisant de 2 secondes à 650 millisecondes.


rangée-store-cache-fig3

Figure 3 : exécution de la requête sans RowStoreCache à gauche. Exécution de la requête avec le RowStoreCache à droite.

La figure 3 montre que l’opération « Ajouter des champs » n’a pris que 276 millisecondes avec RowStoreCache, contre 1 seconde sans lui.

Améliorations du QPS

L’exécution de l’exemple de requête avec différents filtres pour la ville à un QPS élevé a montré une amélioration du QPS de 2 requêtes par seconde à 7 requêtes par seconde, en ligne avec la diminution de la latence par requête.

Cela représente une amélioration de 3 x du QPS pour l’exemple de requête.

La capacité de RowStoreCache peut être ajustée en fonction de la charge de travail pour des performances optimales.

Nous avons observé des améliorations de performances similaires allant jusqu’à 4 fois en termes de latence des requêtes et de QPS pour diverses requêtes de recherche provenant de plusieurs clients utilisant RowStoreCache.

Conclusion

Nous nous efforçons constamment d’améliorer notre stratégie de mise en cache pour obtenir les meilleures performances de requêtes. Le RowStoreCache est un nouvel ajout à notre pile de mise en cache, et les résultats ont montré qu’il est efficace pour améliorer les performances des requêtes de recherche, à la fois sur les métriques de latence et de QPS.


Auteurs du blog : Nithin Venkatesh et Nathan Bronson, ingénieurs logiciels chez Rockset.



LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici