web-dev-qa-db-fra.com

Mise en œuvre de la liste des notifications d'utilisateur à l'aide de l'aérospike

Je dois choisir le bon DB pour un système de notifications qui nécessite de gérer des milliards de notifications. La structure record est la suivante:

[user_id, item_type, item_id, created_at, other_data]

Les inserts vont être en bloc jusqu'à des centaines de milliers de pics. Et il doit soutenir des milliers de sélections par minute de ce type:

select * from user_notifications where user_id=12345 order by created_at limit 10
select * from user_notifications where user_id=12345 and item_type='comment' order by created_at limit 10
--- and for the pagination next page:
select * from user_notifications where user_id=12345 and item_type='comment' and created_at>'2020-11-01 10:50' order by created_at limit 10

Il devrait également permettre des mises à jour rapides et des suppresses et d'avoir idéalement TTL sur chaque enregistrement. C'est maintenant implémenté à l'aide de MySQL, nous n'avons que 400 millions de lignes et il est déjà lent comme l'enfer. Et le nettoyage en vrac est tout simplement impossible. .

Initialement, je pensais que Scylladb/Cassandra est idéal pour cela. Si je définis la clé principale pour être [user_id, item_type, item_id] (user_id étant la clé de partition) pour les insertions/mises à jour/suppresses et [user_id, item_type, created_at] comme indice secondaire. CQL semble simple dans ce cas et cela devrait fonctionner rapidement (corrigez-moi si je me trompe). Le problème est que nous sommes rubis-on-rails basés et il n'y a pas de bien Ruby Bibliothèque client pour cela. Celui qui figure dans la liste des clients Scyldb (- https: // github .Com/DataStax/Ruby-Driver ) est en mode de maintenance et je ne suis pas sûr qu'il sera mis à jour avec de nouveaux Ruby versions, etc.

Récemment, j'ai entendu parler de l'aérospike et de vos repères ont l'air vraiment cool, mais je ne pouvais pas comprendre comment mettre en œuvre les exigences ci-dessus à l'aide de l'architecture d'Aerospike. D'autant que leur indice secondaire semble être toujours en mémoire, ce qui rend impossible d'indexer des milliards de lignes.

Ce schéma de notifications me semble comme quelque chose de très courant, mais toujours, je n'ai pas pu trouver un bon article décrivant toutes les méthodes idéales de la mettre en œuvre. Toute suggestion est la bienvenue.

Merci

2
Kaplan Ilya

Étant donné que Scyllagreg met dans la bonne parole pour Scylla, je pensais que je représenterais pour l'aérospike, où je travaille. De toutes les variantes C * J'aime Scylladb le meilleur, mais je pense que dans le cas que vous décrivez, et en général pour les situations orientées à la ligne, la base de données Aerospike fonctionnera mieux que Scylladb en utilisant moins de matériel. J'ai écrit quelques articles moyens (@ rbotzer) Expliquer pourquoi, basé sur des cas d'utilisation réalistes.

Modélisation

Voici un moyen possible de modéliser cela en aérospike.

Je suppose que vous souhaitez toujours accéder aux notifications d'un utilisateur spécifique. Pour cette raison, la clé d'un enregistrement (une ligne dans l'aérospike-parler) serait le user_id. Le reste des données s'adapte dans une seule poubelle contenant le fichier clé suivant map map Structure:

{ Epoch: [ item_type, item_id, other_data ] }

Epoch est un moyen de simplifier le créé_at DateTime dans un entier représentant des minutes, des secondes ou des millisecondes depuis une époque arbitraire, telle que la date de votre application en direct (ex: minutes depuis 2020-10-01 00:00). Vous pouvez choisir la résolution qui a du sens pour vous afin d'éviter une écrasement. Dans les deux cas, Aerospike utilise le message d'accès à Serialize sur cette carte cartographique et MessagePack a l'effet secondaire Nice de l'autorisation de valeurs numériques dans une taille de stockage minimale.

Interrogation

Maintenant, pour interroger ces données, nous utiliserons l'API Carte Operations Operations Operations API :

  • L'ajout de toutes les informations est une carte simple put ou put_items, qui optimiste les données dans la carte.
  • Pour obtenir toutes les notifications d'un utilisateur spécifique, vous venez de simplement get() l'enregistrement par user_id.
  • Pour paginer via les notifications d'un utilisateur spécifique, vous utilisez la carte get_by_index_range(KeyValue, i, 10) avec i = 0, 10, 20, ...
  • Pour obtenir toutes les notifications entre deux points à temps (2 octobre), vous utiliseriez la carte get_by_key_interval(KeyValue, 1440, 2880)
  • Pour obtenir toutes les notifications de type 'Commentaire', vous utiliseriez la carte get_all_by_value(KeyValue, ['comment', *])
  • Pour effacer toutes les notifications en octobre 2020, vous utiliseriez la carte remove_by_key_interval(Count, 0, 44639)

Vous pouvez voir qu'il y a de actuellement Pas de moyen de et une requête pour une plage de date et une requête pour le type d'élément. Cependant, l'aérospike est extrêmement rapide, les données étant récupérées après avoir examiné une seule clé et fonctionnant sur les données enregistrées, qui sont stockées contiguës. Vous pouvez facilement obtenir un peu plus de données (tout dans une plage de date) et filtrer pour le item_type du côté de l'application.

Notez que cette approche de modélisation nécessite zéro index secondaire. Il utilise simplement la puissance de la distribution même des données d'Aerospike, des recherches d'index primaires extrêmement rapides et une seule IO lit de stockage.

Future pour interrogation

Aerospike Base de données 5.2 Ajouté Expressions , qui peut déjà faire des choses comme chaîne une carte une carte get_by_index_range => get_all_by_value => get_by_index_range, mais uniquement dans le contexte d'un filtre. Cela signifie que vous pouvez utiliser une expression complexe comme condition conditionnelle pour pouvoir appliquer une opération à une requête, numérisation, lecture de lectures de lot ou unique.

Dans un proche avenir, vous pourrez appliquer une expression à l'opération, ce qui résoudra la limitation et la limitation. Si vous vouliez trouver toutes les notifications dans une plage de temps, de type spécifique et qui les paginez également, vous le feriez

exp(get_by_key_interval(KeyValue, 1440, 2880), get_all_by_value(KeyValue, ['comment', *]), get_by_index_range(KeyValue, 0, 10))

Modélisation alternative

Comme je l'ai décrit dans aérospike modélisation: iot capteurs , vous pouvez choisir de partitionner les notifications utilisateur par jour, avec la touche étant userID:YYYYMMDD.

Cela vous permettrait de conserver la taille des enregistrements raisonnables si vous attendez trop de notifications. Il rend également pratique de purger les données devant un certain âge avec des suppressions.

Cependant, je soupçonne que vous ne rencontrerez pas ce problème. Supposons qu'une notification est de 40 octets, puis 256 notifications prennent au plus 10kib. En réalité, ceux-ci sont probablement plus petits et, comme je le mentionne dans l'article, MessagePack permettra d'intégrer certains de ces types de données. Si vous utilisez (la fonctionnalité EE de l'aérospike), il sera encore plus petit. Même s'il s'agit de 40 octets, le bloc d'écriture de 1MIB par défaut peut contenir 26 000 notifications pour un seul utilisateur et vous pouvez couper les personnes quotidiennes avec un remove_by_value_range.

Une autre chose agréable à propos de la partition de jour est que un seul enregistrement est jamais écrit à (aujourd'hui ').


Maintenant, pour répondre à votre commentaire sur les index secondaires en aérospike, il est vrai qu'ils sont actuellement stockés en mémoire, mais le coût de la mémoire n'est pas aussi mauvais que vous attendez.

Premièrement, Aerospike ne tamponnez pas les données dans la mémoire comme toutes les variantes C *. Dans le déploiement classique de la base de données aérospike, vous Store Tous Les données sur SSD, et seuls les index consomment la mémoire. Si vous lisez mes messages médiums, j'entre dans la raison pour laquelle Aerospike est plus rapide que Scylladb pour récupérer un enregistrement aléatoire à partir d'une pile d'enregistrements arbitrairement importante.

Chaque enregistrement a une entrée de 64 octets dans l'indice principal. En ce qui concerne les index secondaires, l'absolu le pire cas Si vous avez une seule clé par valeur distincte indexée (et que vous ne devez jamais utiliser un indice secondaire dans une telle situation) Coût Vous 85 octets supplémentaires.

Un calcul rapide pour 1 milliard d'enregistrements est de 10 ^ 9 * (64 + 85) = 139Gib. Si vous avez un cluster de 3 nœuds aérospike, le coût de la mémoire est de 46Gib par nœud. Considérant que vous n'avez pas besoin de mémoire pour les données, et il y a très peu de surcharge sur cela, vous vous retrouvez avec un assez abordable RAM Consommation qui convient à un petit groupe.

À l'avenir, un peu de changement viendra dans des indices secondaires à Aerospike, mais je suggère toujours de modéliser une manière similaire à celle décrite ci-dessus. L'utilisation des opérations de carte et de liste sur un seul enregistrement sera plus rapide et consommez moins de ressources qu'une requête utilisant des index secondaires dans une base de données C *.

1
Ronen Botzer

Divulgation complète - Je travaille sur Scylladb, je suis donc honnêtement biaisé vers le logiciel que je pense est assez génial.

Si "Scylladb/Cassandra est idéal pour cela", pourquoi pas simplement utiliser Scylla?

0
ScyllaGreg