etcd vs PostgreSQL

Jinhua Luo

March 17, 2023

Technology

Contexte historique

PostgreSQL

PostgreSQL a été initialement développé en 1986 sous la direction du professeur Michael Stonebraker à l'Université de Californie, Berkeley. Au fil de plusieurs décennies de développement, PostgreSQL est devenu le système de gestion de base de données relationnelle open-source le plus avancé disponible aujourd'hui. Sa licence permissive permet à quiconque d'utiliser, de modifier et de distribuer PostgreSQL librement, que ce soit à des fins privées, commerciales ou de recherche académique.

PostgreSQL offre un support robuste pour le traitement analytique en ligne (OLAP) et le traitement transactionnel en ligne (OLTP), avec des capacités de requête SQL puissantes et une large gamme d'extensions qui lui permettent de répondre à presque tous les besoins commerciaux. En conséquence, il a attiré une attention croissante ces dernières années. En effet, l'évolutivité et les hautes performances de PostgreSQL lui permettent de reproduire la fonctionnalité de pratiquement tout autre type de base de données.

architecture postgres Source de l'image (conformément à la licence CC 3.0 BY-SA) : https://en.wikibooks.org/wiki/PostgreSQL/Architecture

etcd

Comment etcd est-il né, et quel problème résout-il ?

En 2013, l'équipe de démarrage CoreOS a développé un produit appelé Container Linux. C'est un système d'exploitation open-source et léger qui priorise l'automatisation et le déploiement rapide des services d'application. Container Linux nécessite que les applications s'exécutent dans des conteneurs et fournit une solution de gestion de cluster, facilitant la gestion des services comme s'ils étaient sur une seule machine.

Pour garantir que les services des utilisateurs ne subissent pas d'interruption en cas de redémarrage d'un nœud, CoreOS devait exécuter plusieurs répliques. Mais comment coordonner plusieurs répliques et éviter que toutes les répliques ne deviennent indisponibles pendant les changements ?

Pour résoudre ce problème, l'équipe CoreOS avait besoin d'un service de coordination capable de stocker les informations de configuration des services et de fournir des fonctionnalités de verrouillage distribué, entre autres. Alors, quelle a été leur approche ? Ils ont d'abord analysé le scénario métier, les points de douleur et les objectifs principaux. Ensuite, ils ont sélectionné une solution alignée sur leurs objectifs, en évaluant s'ils devaient choisir une solution de la communauté open-source ou développer leur propre outil personnalisé. Cette approche est une méthode universelle de résolution de problèmes souvent utilisée face à des problèmes difficiles, et l'équipe CoreOS a suivi le même principe.

Un service de coordination idéal doit répondre aux cinq objectifs suivants :

  1. Haute disponibilité avec plusieurs répliques de données
  2. Cohérence des données avec vérification de version entre les répliques
  3. Capacité de stockage minimale : le service de coordination ne doit stocker que les informations de configuration de métadonnées critiques pour les services et les nœuds appartenant à la configuration du plan de contrôle, plutôt que les données utilisateur. Cette approche minimise le besoin de partitionnement des données pour le stockage et évite une conception excessive.
  4. Fonctionnalités CRUD (créer, lire, mettre à jour et supprimer), ainsi qu'un mécanisme pour écouter les changements de données. Il doit stocker les informations d'état des services, et lorsqu'il y a des changements ou des anomalies dans les services, il doit rapidement pousser l'événement de changement vers le plan de contrôle. Cela contribue à améliorer la disponibilité des services et à réduire les surcharges de performance inutiles pour le service de coordination.
  5. Simplicité opérationnelle : le service de coordination doit être facile à opérer, à maintenir et à dépanner. Une interface facile à utiliser peut réduire le risque d'erreurs, diminuer les coûts de maintenance et minimiser les temps d'arrêt.

Du point de vue du théorème CAP, etcd appartient au système CP (Cohérence & Tolérance aux partitions). architecture etcd

En tant que composant central d'un cluster Kubernetes, kube-apiserver utilise etcd comme stockage sous-jacent.

D'une part, etcd est utilisé pour la persistance lors de la création d'objets de ressources dans un cluster k8s. D'autre part, c'est le mécanisme de surveillance des données d'etcd qui entraîne le travail de l'Informer de l'ensemble du cluster, permettant une orchestration continue de conteneurs.

Par conséquent, d'un point de vue technique, les raisons principales pour lesquelles Kubernetes utilise etcd sont :

  • etcd est écrit en Go, ce qui est cohérent avec la pile technologique de k8s, a une faible consommation de ressources et est extrêmement facile à déployer.
  • La forte cohérence, la surveillance, le bail et d'autres fonctionnalités d'etcd sont des dépendances essentielles de k8s.

En résumé, etcd est une base de données clé-valeur distribuée conçue spécifiquement pour la gestion et la distribution de configurations. En tant que logiciel cloud-native, il offre une facilité d'utilisation et des performances élevées, le rendant supérieur aux bases de données traditionnelles dans ce domaine spécifique.

Pour comparer objectivement etcd et PostgreSQL, qui sont deux types de bases de données différents, il est important de les évaluer dans le contexte de la même exigence. Par conséquent, cet article ne discutera que des différences entre les deux en termes de capacité à répondre aux exigences de gestion de configuration.

Modèle de données

Différentes bases de données ont différents modèles de données qu'elles présentent aux utilisateurs, et ce facteur détermine l'adéquation de la base de données à divers scénarios.

Clé-valeur vs SQL

Le modèle de données clé-valeur est un modèle populaire dans NoSQL, également adopté par etcd. Comment ce modèle se compare-t-il à SQL et quels sont ses avantages ?

Tout d'abord, examinons SQL.

Les bases de données relationnelles maintiennent les données dans des tables et fournissent un moyen efficace, intuitif et flexible de stocker et d'accéder à des informations structurées.

Une table, également appelée relation, est composée de colonnes qui contiennent une ou plusieurs catégories de données, et de lignes, également appelées enregistrements de table, qui incluent un ensemble de données définissant les catégories. Les applications récupèrent les données en utilisant des requêtes qui emploient des opérations telles que "projection" pour identifier les attributs, "sélection" pour identifier les tuples, et "jointure" pour combiner les relations. Le modèle relationnel pour la gestion des bases de données a été développé en 1970 par Edgar Codd, un informaticien chez IBM.

base de données relationnelle

Source de l'image (conformément à la licence CC 3.0 BY-SA) : https://en.wikipedia.org/wiki/Associative_entity

Les enregistrements dans une table n'ont pas d'identifiants uniques car les tables sont conçues pour accueillir plusieurs lignes en double. Pour permettre les requêtes clé-valeur, un index unique doit être ajouté au champ qui sert de clé dans la table. L'index par défaut de PostgreSQL est btree, qui, comme etcd, peut effectuer des requêtes de plage sur les clés.

Le langage de requête structuré (SQL) est un langage de programmation pour stocker et traiter des informations dans une base de données relationnelle. Une base de données relationnelle stocke des informations sous forme de tableau, avec des lignes et des colonnes représentant différents attributs de données et les diverses relations entre les valeurs de données. Vous pouvez utiliser des instructions SQL pour stocker, mettre à jour, supprimer, rechercher et récupérer des informations de la base de données. Vous pouvez également utiliser SQL pour maintenir et optimiser les performances de la base de données.

PostgreSQL a étendu SQL avec de nombreuses extensions, le rendant un langage Turing-complet. Cela signifie que SQL peut effectuer toute opération complexe, facilitant l'exécution de la logique de traitement des données entièrement côté serveur.

En comparaison, etcd est conçu comme un outil de gestion de configuration, avec des données de configuration généralement représentées sous forme de table de hachage. C'est pourquoi son modèle de données est structuré sous forme de clé-valeur, créant effectivement une seule grande table globale. Des opérations CRUD peuvent être effectuées sur cette table, qui n'a que deux champs : une clé unique avec des informations de version, et une valeur non typée. Par conséquent, les clients doivent récupérer la valeur complète pour un traitement ultérieur.

Globalement, la structure clé-valeur d'etcd simplifie SQL et est plus pratique et intuitive pour la tâche spécifique de gestion de configuration.

MVCC (Contrôle de concurrence multi-version)

MVCC est une fonctionnalité essentielle pour le versionnage des données dans la gestion de configuration. Il permet :

  • De consulter les données historiques
  • De déterminer l'âge des données en comparant les versions
  • De surveiller les données, ce qui nécessite un versionnage pour permettre des notifications incrémentielles

etcd et PostgreSQL ont tous deux MVCC, mais quelles sont les différences entre eux ?

etcd utilise un compteur de version 64 bits globalement incrémenté pour gérer son système MVCC. Il n'y a pas besoin de s'inquiéter d'un débordement. Le compteur est conçu pour gérer un grand nombre de mises à jour, même à un rythme de millions par seconde. Chaque fois qu'une paire clé-valeur est créée ou mise à jour, elle se voit attribuer un numéro de version. Lorsqu'une paire clé-valeur est supprimée, une tombe est créée avec un numéro de version réinitialisé à 0. Cela signifie que chaque changement produit une nouvelle version, plutôt que d'écraser la précédente.

De plus, etcd conserve toutes les versions d'une paire clé-valeur et les rend visibles aux utilisateurs. Les données clé-valeur ne sont jamais écrasées, et les nouvelles versions sont stockées aux côtés des versions existantes. L'implémentation MVCC dans etcd fournit également une séparation lecture-écriture, ce qui permet aux utilisateurs de lire les données sans verrouillage, ce qui est adapté aux cas d'utilisation intensifs en lecture.

L'implémentation MVCC de PostgreSQL diffère de celle d'etcd en ce qu'elle ne se concentre pas sur la fourniture de numéros de version incrémentés, mais plutôt sur la mise en œuvre de transactions et de différents niveaux d'isolation de manière transparente pour l'utilisateur. MVCC est un mécanisme de verrouillage optimiste qui permet des mises à jour concurrentes. Chaque ligne dans une table a un enregistrement d'ID de transaction, avec xmin représentant l'ID de transaction de la création de la ligne et xmax représentant l'ID de transaction de la mise à jour de la ligne.

  • Les transactions ne peuvent lire que les données déjà validées avant elles.
  • Lors de la mise à jour des données, si un conflit de version est rencontré, PostgreSQL réessayera avec un mécanisme de correspondance pour déterminer si la mise à jour doit se poursuivre.

Pour voir un exemple, veuillez consulter le lien suivant : https://devcenter.heroku.com/articles/postgresql-concurrency

Malheureusement, l'utilisation des ID de transaction pour le contrôle de version des données de configuration dans PostgreSQL n'est pas possible pour plusieurs raisons :

  • Les ID de transaction sont attribués à toutes les lignes impliquées dans la même transaction, ce qui signifie que le contrôle de version ne peut pas être appliqué au niveau de la ligne.
  • Les requêtes historiques ne peuvent pas être effectuées, et seule la dernière version d'une ligne peut être consultée.
  • En raison de leur nature de compteur 32 bits, les ID de transaction sont sujets à un débordement et à une réinitialisation lors du nettoyage.
  • Il n'est pas possible de mettre en œuvre une fonctionnalité de surveillance basée sur les ID de transaction.

Par conséquent, PostgreSQL nécessite des méthodes alternatives pour le contrôle de version des données de configuration, car le support intégré n'est pas disponible.

Interface client

La conception d'une interface client est un aspect critique lorsqu'il s'agit de déterminer le coût et la consommation de ressources associés à son utilisation. En analysant les différences entre les interfaces, on peut faire des choix éclairés lors de la sélection de l'option la plus appropriée.

Les API kv/watch/lease d'etcd se sont avérées particulièrement adaptées à la gestion des configurations. Cependant, comment peut-on implémenter ces API dans PostgreSQL ?

Malheureusement, PostgreSQL ne fournit pas de support intégré pour ces API, et une encapsulation est nécessaire pour les implémenter. Pour analyser leur implémentation, nous examinerons le projet pg_watch_demo développé par moi-même : pg_watch_demo.

gRPC/HTTP vs TCP

PostgreSQL suit une architecture multi-processus, où chaque processus ne gère qu'une seule connexion TCP à la fois. Il utilise un protocole personnalisé pour fournir des fonctionnalités via des requêtes SQL et suit un modèle d'interaction demande-réponse (similaire à HTTP/1.1, qui ne gère qu'une seule demande à la fois et nécessite un pipelining pour traiter plusieurs demandes simultanément). Cependant, étant donné la consommation élevée de ressources et l'efficacité relativement faible, un proxy de pool de connexions (comme pgbouncer) est crucial pour améliorer les performances, en particulier dans les scénarios à haut QPS.

D'autre part, etcd est conçu sur une architecture multi-coroutine en Golang et offre deux interfaces conviviales : gRPC et RESTful. Ces interfaces sont faciles à intégrer et sont efficaces en termes de consommation de ressources. De plus, chaque connexion gRPC peut gérer plusieurs requêtes concurrentes, ce qui garantit des performances optimales.

Définition des données

etcd

message KeyValue {
  bytes key = 1;
  // Numéro de révision lorsque la clé a été créée
  int64 create_revision = 2;
  // Numéro de révision lorsque la clé a été modifiée pour la dernière fois
  int64 mod_revision = 3;
  // Compteur incrémentiel qui augmente à chaque mise à jour de la clé.
  // Ce compteur est réinitialisé à zéro lorsque la clé est supprimée, et est utilisé comme une tombe.
  int64 version = 4;
  bytes value = 5;
  // L'objet de bail utilisé par la clé pour TTL. Si la valeur est 0, alors il n'y a pas de TTL.
  int64 lease = 6;
}

PostgreSQL

PostgreSQL doit utiliser une table pour simuler l'espace de données global d'etcd :

CREATE TABLE IF NOT EXISTS config (
  key text,
  value text,
  -- Équivalent à `create_revision` et `mod_revision`
  -- Ici, un type de séquence incrémentielle de grand entier est utilisé pour simuler la révision
  revision bigserial,
  -- Tombe
  tombstone boolean NOT NULL DEFAULT false,
  -- Index composite, recherche par clé d'abord, puis par révision
  primary key(key, revision)
);

get

etcd

L'API get d'etcd a une large gamme de paramètres :

  • Requêtes de plage, par exemple, définir key comme /abc et range_end comme /abd récupérera toutes les paires clé-valeur avec /abc comme préfixe.
  • Requêtes historiques, spécifiant revision ou une plage de mod_revision.
  • Tri et limitation du nombre de résultats retournés.
message RangeRequest {
  ...
  bytes key = 1;
  // Requêtes de plage
  bytes range_end = 2;
  int64 limit = 3;
  // Requêtes historiques
  int64 revision = 4;
  // Tri
  SortOrder sort_order = 5;
  SortTarget sort_target = 6;
  bool serializable = 7;
  bool keys_only = 8;
  bool count_only = 9;
  // Requêtes historiques
  int64 min_mod_revision = 10;
  int64 max_mod_revision = 11;
  int64 min_create_revision = 12;
  int64 max_create_revision = 13;
}

PostgreSQL

PostgreSQL peut effectuer la fonction get d'etcd via SQL, et même fournir des fonctionnalités plus complexes. Comme SQL lui-même est un langage plutôt qu'une interface à paramètres fixes, il est très polyvalent. Ici, nous montrons un exemple simple de récupération de la dernière paire clé-valeur. Comme la clé primaire est un index composite, elle peut être rapidement recherchée par plage, ce qui permet une récupération à grande vitesse.

CREATE FUNCTION get1(kk text)
RETURNS table(r bigint, k text, v text, c bigint) AS $$
    SELECT revision, key, value, create_time
    FROM config
    where key = kk and tombstone = false
    ORDER BY key, revision desc
    limit 1
$$ LANGUAGE sql;

put

etcd

message PutRequest {
  bytes key = 1;
  bytes value = 2;
  int64 lease = 3;
  // si l'on doit répondre avec les données de la paire clé-valeur avant la mise à jour de cette requête `Put`.
  bool prev_kv = 4;
  bool ignore_value = 5;
  bool ignore_lease = 6;
}

PostgreSQL

Comme dans etcd, PostgreSQL n'exécute pas les changements sur place. Au lieu de cela, une nouvelle ligne est insérée, et une nouvelle révision lui est attribuée.

CREATE FUNCTION set(k text, v text) RETURNS bigint AS $$
  insert into config(key, value) values(k, v) returning revision;
$$ LANGUAGE SQL;

delete

etcd

message DeleteRangeRequest {
  bytes key = 1;
  bytes range_end = 2;
  bool prev_kv = 3;
}

PostgreSQL

Comme dans etcd, la suppression dans PostgreSQL ne modifie pas les données sur place. Au lieu de cela, une nouvelle ligne est insérée avec le champ tombstone défini sur true pour indiquer qu'il s'agit d'une tombe.

CREATE FUNCTION del(k text) RETURNS bigint AS $$
  insert into config(key, tombstone) values(k, true) returning revision;
$$ LANGUAGE SQL;

watch

etcd

message WatchCreateRequest {
  bytes key = 1;
  // Spécifie la plage de clés à surveiller
  bytes range_end = 2;
  // Révision de départ pour la surveillance
  int64 start_revision = 3;
  ...
}

message WatchResponse {
  ResponseHeader header = 1;
  ...
  // Pour l'efficacité, plusieurs événements peuvent être retournés
  repeated mvccpb.Event events = 11;
}

PostgreSQL

PostgreSQL ne dispose pas d'une fonction watch intégrée, et nécessite plutôt une combinaison de déclencheurs et de canaux pour obtenir une fonctionnalité similaire. En utilisant pg_notify, les données peuvent être envoyées à toutes les applications qui écoutent un canal spécifique.

-- fonction de déclencheur pour distribuer les événements put/delete
CREATE FUNCTION notify_config_change() RETURNS TRIGGER AS $$
DECLARE
  data json;
  channel text;
  is_channel_exist boolean;
BEGIN
  IF (TG_OP = 'INSERT') THEN
    -- utiliser JSON pour encoder
    data = row_to_json(NEW);
    -- Extraire le nom du canal pour la distribution à partir de la clé
    channel = (SELECT SUBSTRING(NEW.key, '/(.*)/'));
    -- Si une application surveille le canal, envoyer un événement via celui-ci
    is_channel_exist = NOT pg_try_advisory_lock(9080);
    IF is_channel_exist THEN
        PERFORM pg_notify(channel, data::text);
    ELSE
        PERFORM pg_advisory_unlock(9080);
    END IF;
  END IF;
  RETURN NULL; -- Le résultat est ignoré car il s'agit d'un déclencheur AFTER
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER notify_config_change
AFTER INSERT ON config
FOR EACH ROW EXECUTE FUNCTION notify_config_change();

Comme la fonctionnalité watch est encapsulée, les applications clientes doivent également implémenter une logique correspondante. En utilisant Golang comme exemple, les étapes suivantes doivent être suivies :

  1. Démarrer l'écoute : Lorsque l'écoute commence, toutes les données notify seront mises en cache à la fois au niveau de PostgreSQL et au niveau du canal Golang.
  2. Récupérer toutes les données en utilisant get_all(key_prefix, revision) : Cette fonction lit toutes les données existantes à partir de la révision spécifiée. Pour chaque clé, seule la dernière révision des données sera retournée, avec toute donnée supprimée automatiquement supprimée. Si la révision n'est pas spécifiée, elle retourne les dernières données pour toutes les clés avec le key_prefix donné.
  3. Surveiller les nouvelles données, y compris les notifications qui auraient pu être mises en cache entre les première et deuxième étapes, pour éviter de manquer de nouvelles données qui pourraient survenir pendant cette fenêtre de temps. Ignorer les révisions déjà lues dans la deuxième étape.
func watch(l *pq.Listener) {
    for {
        select {
        case n := <-l.Notify:
            if n == nil {
                log.Println("listener reconnected")
                log.Printf("get all routes from rev %d including tombstones...\n", latestRev)
   // Lors de la reconnexion, reprendre la transmission en fonction de la révision avant la déconnexion.
                str := fmt.Sprintf(`select * from get_all_from_rev_with_stale('/routes/', %d)`, latestRev)
                rows, err := db.Query(str)
                ...
                continue
            }
            ...
            // maintenir un état qui enregistre la dernière révision qu'il a reçue
            updateRoute(cfg)
        case <-time.After(15 * time.Second):
            log.Println("Received no events for 15 seconds, checking connection")
            go func() {
                // Si aucun événement n'est reçu pendant une période prolongée, vérifier la santé de la connexion
                if err := l.Ping(); err != nil {
                    log.Println("listener ping error: ", err)
                }
            }()
        }
    }
}

log.Println("get all routes...")
// Lors de l'initialisation, l'application doit obtenir toutes les paires clé-valeur actuelles, puis surveiller les mises à jour de manière incrémentielle via watch
rows, err := db.Query(`select * from get_all('/routes/')`)
...
go watch(listener)

transaction

etcd

Les transactions d'etcd sont une collection de plusieurs opérations avec des vérifications conditionnelles, et les modifications apportées par la transaction sont validées de manière atomique.

message TxnRequest {
  // Spécifier la condition d'exécution de la transaction
  repeated Compare compare = 1;
  // Opérations à exécuter si la condition est remplie
  repeated RequestOp success = 2;
  // Opérations à exécuter si la condition n'est pas remplie
  repeated RequestOp failure = 3;
}

PostgreSQL

La commande DO dans PostgreSQL permet d'exécuter n'importe quelle commande, y compris les procédures stockées. Elle supporte plusieurs langages, y compris les langages intégrés comme PL/pgSQL et Python. Avec ces langages, toute logique de contrôle, comme les conditions et les boucles, peut être implémentée, ce qui la rend plus polyvalente qu'etcd.

DO LANGUAGE plpgsql $$
DECLARE
     n_plugins int;
BEGIN
    SELECT COUNT(1) INTO n_plugins FROM get_all('/plugins/');
    IF n_plugins = 0 THEN
       perform set('/routes/1', 'foobar');
       perform set('/upstream/1', 'foobar');
       ...
    ELSE
       ...
    END IF;
END;
$$;

lease

etcd

Dans etcd, il est possible de créer un objet de bail que les applications doivent renouveler périodiquement pour éviter qu'il n'expire. Chaque paire clé-valeur peut être liée à un objet de bail, et lorsque l'objet de bail expire, toutes les paires clé-valeur associées expirent également, les supprimant automatiquement.

message LeaseGrantRequest {
  // TTL du bail
  int64 TTL = 1;
  int64 ID = 2;
}

// Renouvellement du bail
message LeaseKeepAliveRequest {
  int64 ID = 1;
}

message PutRequest {
  bytes key = 1;
  bytes value = 2;
  // ID du bail, utilisé pour implémenter TTL
  int64 lease = 3;
  ...
}

PostgreSQL

  • Dans PostgreSQL, un bail peut être maintenu via une clé étrangère. Lors de la requête, s'il y a un objet de bail associé qui a expiré, il est considéré comme une tombe.
  • Les requêtes de keepalive mettent à jour l'horodatage last_keepalive dans la table de bail.
CREATE TABLE IF NOT EXISTS config (
  key text,
  value text,
  ...
  -- Utiliser une clé étrangère pour spécifier l'objet de bail associé.
  lease int64 references lease(id),
);

CREATE TABLE IF NOT EXISTS lease (
  id text,
  ttl int,
  last_keepalive timestamp;
);

Comparaison des performances

PostgreSQL doit simuler diverses API d'etcd via l'encapsulation. Alors, comment se comporte-t-il en termes de performances ? Voici les résultats d'un test simple : https://github.com/kingluo/pg_watch_demo#benchmark.

etcd_vs_postgres

Les résultats montrent que les performances de lecture et d'écriture sont presque identiques, avec PostgreSQL surpassant même etcd. De plus, la latence entre la survenue d'une mise à jour et la réception de l'événement par l'application détermine l'efficacité de la distribution des mises à jour, et PostgreSQL et etcd se comportent de manière similaire. Lorsqu'ils sont testés sur la même machine pour le client et le serveur, la latence de watch était inférieure à 1 milliseconde.

PostgreSQL, cependant, présente quelques inconvénients à mentionner :

  • Le journal WAL pour chaque mise à jour est plus volumineux, ce qui entraîne deux fois plus d'I/O disque par rapport à etcd.
  • Il consomme plus de CPU par rapport à etcd.
  • Notify basé sur les canaux est un concept au niveau de la transaction. Lors de la mise à jour du même type de ressource, la mise à jour est envoyée au même canal, et les demandes de mise à jour se disputent les verrous d'exclusion mutuelle, ce qui entraîne des demandes sérialisées. En d'autres termes, l'utilisation de canaux pour implémenter watch affecte le parallélisme des opérations put.

Cela met en évidence que pour atteindre les mêmes exigences, nous devons investir davantage dans l'apprentissage et l'optimisation de PostgreSQL.

Stockage

Les performances sont déterminées par le stockage sous-jacent, et la manière dont les données sont stockées détermine les besoins en mémoire, disque et autres ressources de la base de données.

etcd

Diagramme d'architecture du stockage d'etcd :

stockage etcd

etcd écrit d'abord les mises à jour dans le journal d'écriture anticipée (WAL) et les vide sur le disque pour s'assurer que les mises à jour ne sont pas perdues. Une fois que le journal est écrit avec succès et confirmé par une majorité de nœuds, les résultats peuvent être retournés au client. etcd met également à jour de manière asynchrone TreeIndex et BoltDB.

Pour éviter que le journal ne grossisse indéfiniment, etcd prend périodiquement un instantané du stockage, et les journaux antérieurs à l'instantané peuvent être supprimés.

etcd indexe toutes les clés en mémoire (TreeIndex), enregistrant les informations de version de chaque clé, mais ne garde qu'un pointeur vers BoltDB (révision) pour la valeur.

La valeur correspondant à la clé est stockée sur le disque et maintenue à l'aide de BoltDB.

TreeIndex et BoltDB utilisent tous deux la structure de données btree, connue pour son efficacité dans les recherches et les recherches de plage.

Diagramme de structure TreeIndex :

treeindex etcd

(Source de l'image : https://blog.csdn.net/H_L_S/article/details/112691481, sous licence CC 4.0 BY-SA)

Chaque clé est divisée en différentes générations, chaque suppression marquant la fin d'une génération.

Le pointeur vers la valeur est composé de deux entiers. Le premier entier main est l'ID de transaction d'etcd, tandis que le second entier sub représente l'ID de mise à jour de cette clé dans cette transaction.

Boltdb supporte les transactions et les instantanés, et stocke la valeur correspondant à la révision.

boltdb etcd

(Source de l'image : https://blog.csdn.net/H_L_S/article/details/112691481, sous licence CC 4.0 BY-SA)

Exemple d'écriture de données :

Écrire key="key1", revision=(12,1), value="keyvalue5". Notez les changements dans les parties rouges de treeIndex et BoltDB :

put etcd

(Source de l'image : https://blog.csdn.net/H_L_S/article/details/112691481, sous licence CC 4.0 BY-SA)

Supprimer key="key", revision=(13,1) crée une nouvelle génération vide dans treeIndex et génère une valeur vide dans BoltDB avec key="13_1t".

Ici, le t signifie "tombstone". Cela implique que vous ne pouvez pas lire la tombe car le pointeur dans treeIndex est (13,1), mais dans BoltDB, c'est 13_1t, qui ne peut pas être correspondu.

delete etcd

(Source de l'image : https://blog.csdn.net/H_L_S/article/details/112691481, sous licence CC 4.0 BY-SA)

Il est à noter qu'etcd planifie à la fois les lectures et les écritures vers BoltDB en utilisant une seule goroutine pour réduire les I/O disque aléatoires et améliorer les performances I/O.

PostgreSQL

Diagramme d'architecture du stockage de PostgreSQL :

stockage postgres

Comme etcd, PostgreSQL ajoute d'abord les mises à jour à un fichier journal, et attend que le journal soit vidé avec succès sur le disque avant de considérer la transaction terminée. Pendant ce temps, les mises à jour sont écrites dans la mémoire shared_buffer.

Le shared_buffer est une zone de mémoire partagée par toutes les tables et index dans PostgreSQL, et sert de mappage pour ces objets.

Dans PostgreSQL, chaque table est composée de plusieurs pages, chaque page ayant une taille de 8 Ko et contenant plusieurs lignes.

En plus des tables, les index (comme les index btree) sont également composés de pages de table dans le même format. Cependant, ces pages sont spéciales et sont interconnectées pour former une structure arborescente.

PostgreSQL est équipé d'un processus de point de contrôle qui vide périodiquement toutes les pages de table et d'index modifiées sur le disque. Avant chaque point de contrôle, les fichiers journaux peuvent être supprimés et recyclés pour éviter que le journal ne grossisse indéfiniment.

Structure de page :

![page postgres](https://static.api7.ai/uploads/2023/02/23/CZ4aNJyn_220578965-23b2dfdd-286c-4593-86a5-498379ed8c

Tags: