Plus tôt cette année, nous avons lancé le plus grand projet de notre entreprise
Naturellement, nous avons choisi Timescale, notre plateforme cloud mature avec TimescaleDB en son cœur. Nous sommes habitués à travailler avec PostgreSQL et nous avons construit TimescaleDB pour rendre PostgreSQL plus rapide et plus évolutif : quoi de mieux que de vivre selon notre propre exemple ?
La façon la plus simple de décrire cette expérience de dogfooding consiste à utiliser les chiffres qui permettent de quantifier son ampleur. Pour créer Insights, nous devions collecter des informations sur les requêtes dans notre flotte de bases de données de production fonctionnant en continu. Nous avons rapidement collecté plus de 1 000 milliards d'enregistrements sur des requêtes individuelles (assainies) sur la plateforme.
Maintenant qu'Insights est en production, nous ingérons plus de 10 milliards de nouveaux enregistrements par jour . L'ensemble de données servi par un seul service Timescale augmente d'environ 3 To par jour et totalise actuellement plus de 350 To , et le même service de base de données alimente les tableaux de bord en temps réel pour tous nos clients.
Cet article de blog donne un aperçu des coulisses du processus de création d'Insights. Opérer à cette échelle signifiait repousser les limites d'un seul service Timescale et faire évoluer non seulement PostgreSQL mais également l'empathie de nos développeurs. Nous avons trouvé Timescale plus que à la hauteur, mais il y a aussi des domaines que nous souhaitons améliorer !
Pour concrétiser Insights, nous avons dû endosser notre casquette d'administrateur de base de données 🤠 et relever quelques défis techniques pour faire évoluer PostgreSQL vers plusieurs téraoctets de données. Nous souhaitions utiliser un service Timescale comme base de données centrale, hébergé sur notre plateforme sans infrastructure « particulière ». Cela signifiait ce qui suit :
Nous avons dû créer un pipeline capable d'ingérer des milliards d'enregistrements par jour dans un seul service Timescale. Timescale peut gérer des taux d’ingestion élevés, et il le fait régulièrement pour nos clients, mais ce niveau d’échelle sous les charges de requêtes de production fait toujours sourciller.
Nos clients devaient pouvoir interroger cette base de données avec la flexibilité nécessaire pour alimenter toutes les analyses proposées par Insights, et nous ne voulions pas leur faire attendre des minutes pour une réponse !
Nous devions stocker des centaines de To dans un seul service Timescale puisque chaque jour nous ajoutons plusieurs To. Les données plus anciennes (c'est-à-dire datant de plus de quelques semaines) devaient être accessibles, mais pas nécessairement rapides à interroger.
Du côté de la collecte de données , nous avons exploité l'architecture de la plateforme Timescale. Timescale fonctionne sur Kubernetes (k8s) et nous avons plusieurs clusters k8 exécutés dans différentes régions géographiques. Ces clusters comportent des nœuds qui contiennent un ou plusieurs services de base de données client. Pour collecter les exécutions de requêtes pour toutes ces bases de données, nous passons de cette base de données au niveau régional, puis disposons d'un rédacteur régional qui stocke les lots d'enregistrements dans le service de base de données Timescale qui alimente Insights.
Pardonnez l'agitation de la main qui évite certains détails sanglants de bas niveau, mais en termes généraux, voici comment les choses fonctionnent : chaque base de données exécutée sur la flotte est instrumentée pour créer un enregistrement (assaini pour la confidentialité et la sécurité) après chaque requête, y compris le l'interrogation elle-même et les statistiques qui nous intéressent.
Ces enregistrements sont collectés au niveau du nœud, étiquetés avec des étiquettes pour les conserver associés au service de base de données d'où ils proviennent, et regroupés pour être envoyés au rédacteur régional. Le service d'écriture régional est répliqué si nécessaire pour gérer la charge dans chaque région. Chaque rédacteur collecte des lots à partir des nœuds de chaque cluster et crée des lots encore plus volumineux.
Ces gros lots sont ensuite écrits en utilisant d'abord « COPY » dans une table temporaire (pas de journalisation en écriture anticipée = rapide). Les entrées de cette table temporaire sont ensuite utilisées pour mettre à jour les tables nécessaires (voir ci-dessous). La table temporaire nous permet d'utiliser `COPY` sans nous soucier des doublons, qui sont gérés par des opérations ultérieures supprimant les enregistrements de la table temporaire.
En résumé :
Zoomons sur la base de données qui alimente Insights. Nous exécutons Insights dans un service Timescale « prêt à l'emploi » avec un
La base de données qui alimente Insights comporte de nombreuses parties, mais nous essaierons de mettre en évidence les plus importantes.
Premièrement, nous avons deux tables PostgreSQL classiques qui servent de « tables de référence ». Ces tables contiennent des métadonnées de base de données d'informations et des métadonnées de chaîne de requête. Voici leurs (pseudo)schémas :
Métadonnées de base de données
Table "insights.cloud_db" Column | Type | Collation | Nullable | Default ---------------+--------------------------+-----------+----------+-------------------------------------- id | bigint | | not null | nextval('cloud_db_id_seq'::regclass) service_id | text | | not null | project_id | text | | not null | created | timestamp with time zone | | not null | now() Indexes: "cloud_db_pkey" PRIMARY KEY, btree (id) "cloud_db_project_id_service_id_key" UNIQUE CONSTRAINT, btree (project_id, service_id)
Métadonnées de requête
Table "insights.queries" Column | Type | Collation | Nullable | Default ---------------+--------------------------+-----------+----------+-------------------------------------- hash | text | | not null | normalized_query | text | | not null | created | timestamp with time zone | | not null | now() Indexes: "queries_pkey" PRIMARY KEY, btree (hash)
Chaque fois qu'une nouvelle base de données commence à recevoir des requêtes, elle est ajoutée à « insights.cloud_db ». Chaque fois qu'une nouvelle requête normalisée est exécutée, elle sera ajoutée à « insights.queries ».
(Qu'est-ce qu'une requête normalisée ? C'est une requête où toutes les constantes ont été remplacées par des espaces réservés : $1 pour la première, $2 pour la seconde, et ainsi de suite, nous ne voyons donc que la « forme » de la requête, pas ses valeurs. .)
Jusqu'à présent, nous utilisons simplement Postgres standard sans sauce secrète Timescale. Mais les autres objets importants de la base de données sont uniques à TimescaleDB, ce qui permet de faire passer PostgreSQL à un autre niveau. C'est là que la magie opère : les hypertables et les agrégats continus.
Les hypertables sont les tables automatiquement partitionnées de Timescale. Ils partitionnent automatiquement les données par dimension lors de leur ingestion, ce qui facilite grandement la mise à l'échelle des tables PostgreSQL à grande échelle. Les hypertables sont les éléments constitutifs de Timescale. Nous stockons les métriques de nos statistiques de requête dans une énorme hypertable, comme nous le verrons plus tard.
Les agrégats continus sont la version améliorée de Timescale des vues matérialisées PostgreSQL, permettant une matérialisation incrémentielle et automatique , ce qui s'est avéré très utile lors de la création d'Insights.
Voyons comment nous avons utilisé ces fonctionnalités pour permettre des requêtes analytiques rapides du côté des utilisateurs.
Comme nous le disions, nous utilisons une grande hypertable pour stocker des informations sur chaque exécution de requête. Cette hypertable est notre table principale, où résident les métriques brutes aseptisées. Il ressemble un peu à ce qui suit et est configuré pour utiliser sa colonne d'horodatage ( created
) pour partitionner automatiquement les données au fur et à mesure de leur ingestion.
Table "insights.records" Column | Type | Collation | Nullable | Default -----------------------------+--------------------------+-----------+----------+--------- cloud_db_id | bigint | | not null | query_hash | text | | | created | timestamp with time zone | | not null | total_time | bigint | | | rows | bigint | | | ...
Nous avons omis un certain nombre de statistiques pour cet exemple, mais vous voyez l'idée.
Nous devons désormais autoriser les requêtes rapides du côté utilisateur, mais ce tableau est énorme. Pour accélérer les choses, nous nous sommes fortement appuyés sur des agrégats continus (en utilisant
Les agrégats continus ont tout leur sens dans un produit offrant des analyses en temps réel destinées aux utilisateurs comme Insights. Pour fournir des informations exploitables aux utilisateurs, nous devons regrouper les métriques : nous ne montrons pas aux utilisateurs un journal de chaque requête qu'ils ont exécutée avec des statistiques à côté : certaines bases de données effectuent des milliers de requêtes par seconde, ce serait donc un cauchemar à trouver. quelque chose d'utile. Au lieu de cela, nous servons des agrégats d’utilisateurs.
Alors autant profiter du fait que nous ne montrons pas les enregistrements individuels bruts aux utilisateurs et conserver le résultat.
Nous aurions pu utiliser des vues matérialisées PostgreSQL, mais les agrégats continus de Timescale présentent plusieurs avantages qui nous ont été particulièrement utiles. Nous actualisons beaucoup les vues, et les agrégats continus ont des politiques intégrées pour les actualisations automatiques, et ils s'actualisent progressivement.
Nous actualisons les vues toutes les cinq minutes. Ainsi, au lieu de régénérer l'intégralité des informations matérialisées toutes les cinq minutes, des agrégats continus mettent à jour progressivement la vue en suivant les modifications dans la table d'origine. À l'échelle à laquelle nous opérons, nous ne pouvons tout simplement pas nous permettre d'analyser notre hypertable principale de haut en bas toutes les cinq minutes. Cette fonctionnalité d'agrégats continus a donc été pour nous un « déverrouillage » fondamental.
Dans ces agrégats continus qui alimentent Insights en coulisses, nous regroupons également la plupart des statistiques intéressantes dans un
Pourtant, à un moment donné, la base de données a commencé à faire beaucoup de travail pour insérer tous ces enregistrements bruts, puis les matérialiser pour les servir. Nous étions confrontés à certaines limites quant à la quantité que nous pouvions ingérer et suivre.
Pour augmenter encore notre taux d'ingestion au niveau dont nous avions besoin, nous avons déchargé la génération UDDSketch de la base de données vers les rédacteurs de la région. Désormais, nous stockons toujours une certaine quantité d'enregistrements sous forme d'enregistrements « bruts », mais nous plaçons également le reste dans des croquis pré-générés que nous stockons dans la base de données :
Table "insights.sketches" Column | Type | Collation | Nullable | Default -----------------------------+--------------------------+-----------+----------+--------- cloud_db_id | bigint | | not null | query_hash | text | | | created | timestamp with time zone | | not null | total_time_dist | uddsketch | | | rows_dist | uddsketch | | | ...
La meilleure partie d'UDDSketchs est qu'il est très facile de « regrouper » continuellement les esquisses pour prendre en charge des plages de temps plus larges. Grâce à un tel cumul, les esquisses qui couvrent des plages de temps plus étroites peuvent être regroupées en une esquisse qui couvre une large plage de temps, à la fois lors de la création d'un agrégat hiérarchique continu et au moment de la requête.
Un autre outil que nous avons utilisé pour garantir que l'ingestion rapide et les requêtes soient des réplicas en lecture. L'utilisation de la réplication est primordiale à la fois pour la haute disponibilité et les performances dans notre cas, étant donné qu'Insights alimente une fonctionnalité majeure destinée aux clients pour la plate-forme Timescale.
Notre instance de base de données principale est très occupée par le travail en masse, l'écriture de données, la matérialisation des agrégats continus, l'exécution de la compression, etc. (Plus d'informations sur la compression dans une minute.) Pour alléger une partie de sa charge, nous laissons le client du service de réplication lire les requêtes à partir de la console Insights.
Enfin, nous devions intégrer confortablement des centaines de To dans un seul service Timescale. La base de données Insights évolue rapidement : elle était de l'ordre de 100 To lorsque nous avons commencé, et elle dépasse désormais 350 To (et ce n'est pas fini).
Pour stocker efficacement autant de données, nous avons activé
Nous constatons des taux de compression supérieurs à 20x sur notre hypertable principale.
Un autre grand avantage lors de la gestion d’une très grande hypertable était la mutabilité du schéma des données compressées. Nous avons décrit notre schéma approximatif dans une section précédente, mais comme vous pouvez l'imaginer, nous le modifions souvent pour ajouter plus de statistiques, etc. C'est tellement utile de pouvoir le faire directement dans l'hypertable compressée.
Nous sommes également de gros utilisateurs de la hiérarchisation des données de Timescale. Cette fonctionnalité est entrée en accès anticipé plus tôt cette année (attendez bientôt les nouvelles de GA 🔥) et nous permet de garder des centaines de To accessibles via notre base de données Timescale. La hiérarchisation des données s'est également révélée très efficace : nous constatons ici également des taux de compression étonnants, avec 130 To réduits à 5 To, très économes en ressources.
Le processus de création d'Insights nous a montré jusqu'où notre produit peut réellement aller, mais le mieux était de marcher quelques kilomètres à la place de nos clients. Nous avons beaucoup appris sur l'expérience utilisateur liée à la mise à l'échelle de PostgreSQL avec Timescale, et nous avons ajouté certaines choses à notre liste de tâches en tant qu'ingénieurs derrière le produit.
Passons en revue tout cela : les bons et les médiocres.
Pardonnez notre impudeur, mais nous nous sentons parfois assez fiers de notre produit. Ingérer quotidiennement des dizaines de milliards d'enregistrements dans une seule base de données PostgreSQL comptant déjà des centaines de To n'est pas une raison pour dédaigner . Nous avons passé quelques semaines à régler la base de données lorsqu'elle a commencé à monter en puissance, mais maintenant elle fonctionne , sans baby-sitting ni surveillance constante. (Notez que c'est différent d'être non surveillé, c'est définitivement surveillé !)
Notre
La compression a si bien fonctionné pour nous. Comme nous l'avons partagé dans la section précédente, nous avons obtenu des taux de compression impressionnants (20x !) en utilisant une simple option « segmentby ». Pour nous, l’expérience de mise en place et d’ajustement de la politique n’a pas été difficile, même si, bien sûr, nous avons construit cette fonctionnalité… on pourrait dire que nous avons un léger avantage. 🙂 De plus, la possibilité d'ajouter de manière transparente de nouvelles colonnes dans des données compressées a encore amélioré la flexibilité et l'adaptabilité de notre base de données. Nous avons utilisé cette fonctionnalité sans complications.
Les agrégats continus ont simplifié la logique de construction de différentes périodes, rationalisant ainsi l'analyse et le traitement des données. Nous avons utilisé des tonnes d'agrégats continus hiérarchiques.
Les algorithmes d'approximation inclus dans les hyperfonctions de Timecale ont simplifié notre mise en œuvre et ont considérablement étendu notre analyse. La possibilité de regrouper facilement des esquisses était également essentielle pour prendre en charge efficacement différentes plages de temps et granularités de tranches de temps dans nos tableaux de bord Insights destinés aux clients.
Le stockage à chaud « infini » dont dispose une base de données Timescale via la hiérarchisation des données était essentiel pour passer à des centaines de To, avec une grande marge de croissance. Notre __ politique actuelle de hiérarchisation des données __ conserve trois semaines d'enregistrements dans un stockage à chaud.
Enfin, nous avons utilisé la possibilité de créer des tâches personnalisées pour améliorer l'observabilité (comme la surveillance de l'historique des tâches) et mettre en œuvre des stratégies d'actualisation expérimentales.
Après vous avoir raconté toutes les bonnes choses, il est temps de reconnaître les moins bonnes. Rien n'est parfait, y compris Timescale. Nous avons été confrontés à quelques défis lors de la mise en œuvre de notre pipeline, et nous ne les considérons pas comme des griefs :
L'observabilité des bases de données pourrait être améliorée dans la plateforme Timescale, notamment autour des jobs et des performances de matérialisation des agrégats continus.
TimescaleDB fournit principalement des vues basées sur des instantanés, ce qui rend difficile la compréhension des performances et des tendances au fil du temps. Par exemple, aucun tableau « historique des tâches » prêt à l'emploi n'est disponible. Dès le début, nous avons remarqué que la matérialisation incrémentielle de nos agrégats continus prenait de plus en plus de temps – conduisant finalement à la découverte d’un bug – mais nous n’avions aucun moyen d’en confirmer ou de quantifier l’ampleur.
Comme nous l'avons noté précédemment, la possibilité de définir des tâches personnalisées et de les exécuter dans le cadre de tâches de Timescale nous a permis d'en créer une version « assez bonne ». Nous interrogerions en permanence les vues que nous souhaitions surveiller au fil du temps et insérerions toutes les modifications dans une hypertable. Cela fonctionne pour Insights pour le moment, mais nous travaillons également à transformer certaines de ces choses en fonctionnalités intégrées, car nous pensons qu'elles sont cruciales une fois que vous faites évoluer Timescale au-delà du point où tout est rapide tout le temps. .
Les agrégats continus peuvent être difficiles à obtenir lorsque les données sous-jacentes sont volumineuses .
Utiliser l' option `__ SANS DONNÉES` lors de la création d'agrégats continus __est une bouée de sauvetage. Il est également important d'être judicieux avec vos compensations pour la politique d'actualisation, afin que la quantité de données que vous actualisez progressivement ne devienne pas accidentellement trop importante.
Même si vous suivez ce conseil, vous pourriez toujours vous retrouver avec un agrégat continu dont l'actualisation prend plus de temps que la quantité de données que vous essayez de matérialiser, par exemple prendre 30 minutes pour matérialiser 15 minutes de données. Cela se produit parce que, parfois, la tâche sous-jacente globale continue est trop volumineuse pour tenir en mémoire et se déverse sur le disque.
Nous avons rencontré ce problème, qui a été exacerbé en raison d'un bug ponctuel que nous avons trouvé (maintenant corrigé) et qui entraînait l'inclusion de morceaux supplémentaires dans le plan de requête même lorsqu'ils ne contribueraient finalement à aucune donnée à la matérialisation. La découverte de ce bug était en fait un cas de « dogfoodception » : nous avons découvert ce problème de performances en utilisant Insights pendant que nous le construisions 🤯. Les informations temporelles que nous avons vues dans Insights suggéraient que quelque chose n'allait pas ici, et nous avons découvert le problème en utilisant EXPLAIN et en examinant les plans. Nous pouvons donc vous dire que ça marche !
Pour accélérer la matérialisation, nous avons fini par créer une politique d'actualisation incrémentielle personnalisée qui limitait la taille des incréments à actualiser. Nous travaillons pour voir si c'est quelque chose que nous pouvons généraliser correctement dans TimescaleDB.
Le changement est difficile à grande échelle .
Une fois que vos données ont atteint une certaine taille, certaines opérations DDL (modification de schéma) dans TimescaleDB peuvent prendre plus de temps qu'idéal. Nous avons déjà vécu cela de plusieurs manières.
Par exemple, l’ajout de nouveaux index à de grandes hypertables devient un exercice de timing. Étant donné que TimescaleDB ne prend actuellement pas en charge l'utilisation de « CONCURRENTLY » avec « CREATE INDEX », la meilleure option suivante consiste à utiliser sa méthode intégrée pour créer l'index un morceau à la fois. Dans notre cas, nous devons le lancer juste après la création d'un nouveau morceau, donc le verrouillage sur le morceau « actif » est minime. Autrement dit, créer un index lorsqu'un morceau est nouveau signifie qu'il est (presque) vide et, par conséquent, peut se terminer rapidement et ne pas bloquer de nouvelles insertions.
Une autre raison pour laquelle le changement est difficile consiste à mettre à jour les agrégats continus pour ajouter de nouvelles mesures (colonnes). Les agrégats continus ne prennent actuellement pas en charge « ALTER ». Ainsi, lorsque nous voulons exposer une nouvelle métrique aux utilisateurs, nous créons une toute nouvelle « version » de l'agrégat continu, c'est-à-dire que pour l'agrégat continu « foo », nous aurions alors « foo_v2 », « foo_v3 », etc. loin d’être idéal mais fonctionne actuellement.
Enfin, la modification des paramètres de compression est assez difficile à grande échelle. En fait, cela n'est effectivement pas possible pour nous pour le moment, car cela nécessiterait de décompresser tous les morceaux compressés, de modifier les paramètres, puis de les recompresser, ce qui n'est pas réalisable à notre échelle actuelle.
Nous continuons de réfléchir avec nos collègues pour trouver des solutions viables à toutes ces choses. Pas seulement pour nous, mais pour tous les utilisateurs de Timescale.
C'était pas mal d'informations à rassembler dans un seul message. Mais si tu as besoin d'un
Building Insights a été une expérience profonde pour notre équipe. Nous avons pu constater par nous-mêmes jusqu'où nous pouvons pousser Timescale, en l'amenant à des échelles impressionnantes. Les problèmes que nous avons rencontrés tout au long du processus nous ont donné tellement d'empathie avec les clients : c'est la beauté du dogfooding.
L'année prochaine, j'espère écrire un autre article de blog sur la façon dont nous surveillons un nombre supplémentaire de bases de données d'un autre ordre de grandeur et sur la manière dont nous avons continué à améliorer l'expérience de travail avec Timescale à grande échelle.
À plus tard! 👋
Également publié ici.