etcd vs PostgreSQL

Jinhua Luo

March 17, 2023

Technology

歴史的背景

PostgreSQL

PostgreSQLは、1986年にカリフォルニア大学バークレー校のMichael Stonebraker教授の指導のもとで開発されました。数十年にわたる開発を経て、PostgreSQLは今日利用可能な主要なオープンソースのリレーショナルデータベース管理システムとして登場しました。その寛容なライセンスにより、誰でもPostgreSQLを自由に使用、変更、配布することができ、それが私的、商業的、または学術的研究目的であるかどうかに関係ありません。

PostgreSQLは、オンライン分析処理(OLAP)とオンライントランザクション処理(OLTP)の両方に対して強力なサポートを提供し、強力なSQLクエリ機能と幅広い拡張機能を備えているため、ほぼすべての商業ニーズを満たすことができます。その結果、近年ますます注目を集めています。実際、PostgreSQLの拡張性と高性能により、他のあらゆるタイプのデータベースの機能を再現することが可能です。

postgres architecture 画像ソース(CC 3.0 BY-SAライセンスに準拠): https://en.wikibooks.org/wiki/PostgreSQL/Architecture

etcd

etcdはどのようにして誕生し、どのような問題を解決するのでしょうか?

2013年、スタートアップチームのCoreOSは、Container Linuxという製品を開発しました。これは、アプリケーションサービスの自動化と迅速なデプロイを優先するオープンソースの軽量オペレーティングシステムです。Container Linuxは、アプリケーションをコンテナ内で実行することを要求し、クラスタ管理ソリューションを提供するため、ユーザーが単一のマシン上でサービスを管理するのと同じように便利に管理できます。

ユーザーサービスのダウンタイムを防ぐために、CoreOSは複数のレプリカを実行する必要がありました。しかし、複数のレプリカ間でどのように調整し、変更中にすべてのレプリカが利用不能になるのを避けるのでしょうか?

この問題を解決するために、CoreOSチームは、サービスの設定情報を保存し、分散ロック機能などを提供する調整サービスが必要でした。では、彼らはどのようにアプローチしたのでしょうか?彼らはまず、ビジネスシナリオ、課題、そして核心的な目標を分析しました。その後、目標に合致するソリューションを選択し、オープンソースコミュニティのソリューションを選ぶか、独自のカスタムツールを開発するかを評価しました。このアプローチは、難しい問題に直面した際にしばしば採用される普遍的な問題解決方法であり、CoreOSチームも同じ原則に従いました。

調整サービスは、理想的には以下の5つの目標を満たす必要があります:

  1. 複数のデータレプリカによる高可用性
  2. レプリカ間のバージョンチェックによるデータの一貫性
  3. 最小限のストレージ容量:調整サービスは、ユーザー関連のデータではなく、サービスとノードの制御プレーン設定に属する重要なメタデータ設定情報のみを保存するべきです。これにより、ストレージのためのデータシャーディングの必要性を最小限に抑え、過剰な設計を避けることができます。
  4. CRUD(作成、読み取り、更新、削除)機能と、データ変更を監視するメカニズム。サービスのステータス情報を保存し、サービスに変更や異常が発生した場合、変更イベントを迅速に制御プレーンにプッシュする必要があります。これにより、サービスの可用性が向上し、調整サービスの不要なパフォーマンスオーバーヘッドが削減されます。
  5. 操作の簡便性:調整サービスは、操作、保守、トラブルシューティングが容易であるべきです。使いやすいインターフェースは、エラーのリスクを減らし、保守コストを下げ、ダウンタイムを最小限に抑えることができます。

CAP定理の観点から、etcdはCP(一貫性とパーティション耐性)システムに属します。 etcd architecture

Kubernetesクラスタの中心的なコンポーネントであるkube-apiserverは、etcdをその基盤ストレージとして使用しています。

一方で、etcdはk8sクラスタ内のリソースオブジェクト作成の永続化に使用されます。もう一方で、etcdのデータ監視メカニズムがクラスタ全体のInformerの動作を駆動し、継続的なコンテナオーケストレーションを可能にしています。

したがって、技術的な観点から、Kubernetesがetcdを使用する核心的な理由は以下の通りです:

  • etcdはGo言語で書かれており、k8sの技術スタックと一致し、リソース消費が少なく、非常に簡単にデプロイできます。
  • etcdの強力な一貫性、監視、リースなどの機能は、k8sの核心的な依存関係です。

まとめると、etcdは設定管理と配布のために特別に設計された分散キーバリューデータベースです。クラウドネイティブソフトウェアとして、すぐに使える使いやすさと高性能を提供し、この特定のニーズにおいて従来のデータベースよりも優れています。

etcdとPostgreSQLという2つの異なるタイプのデータベースを客観的に比較するためには、同じ要件の文脈で評価することが重要です。したがって、この記事では、設定管理の要件を満たす能力に関して、両者の違いについてのみ議論します。

データモデル

異なるデータベースは、ユーザーに提示する異なるデータモデルを持っており、この要素がデータベースのさまざまなシナリオへの適合性を決定します。

キーバリュー vs SQL

キーバリューデータモデルは、NoSQLで人気のあるモデルであり、etcdもこのモデルを採用しています。このモデルはSQLと比較してどのような利点があるのでしょうか?

まず、SQLを見てみましょう。

リレーショナルデータベースは、データをテーブルで維持し、構造化された情報を効率的で直感的かつ柔軟に保存およびアクセスする方法を提供します。

テーブルリレーションとも呼ばれる)は、1つ以上のカテゴリのデータを含む列と、カテゴリを定義する一連のデータを含むテーブルレコードとも呼ばれる)で構成されます。アプリケーションは、「プロジェクト」操作で属性を識別し、「選択」操作でタプルを識別し、「結合」操作でリレーションを結合するクエリを使用してデータを取得します。データベース管理のためのリレーショナルモデルは、1970年にIBMのコンピュータ科学者であるEdgar Coddによって開発されました。

relational database

画像ソース(CC 3.0 BY-SAライセンスに準拠): https://en.wikipedia.org/wiki/Associative_entity

テーブル内のレコードには一意の識別子がありません。なぜなら、テーブルは複数の重複行を収容するように設計されているからです。キーバリュークエリを可能にするためには、テーブル内のキーとして機能するフィールドに一意のインデックスを追加する必要があります。PostgreSQLのデフォルトのインデックスはbtreeであり、etcdと同様に、キーに対して範囲クエリを実行できます。

構造化クエリ言語(SQL)は、リレーショナルデータベースに情報を保存および処理するためのプログラミング言語です。リレーショナルデータベースは、行と列が異なるデータ属性とデータ値間のさまざまな関係を表す表形式で情報を保存します。SQLステートメントを使用して、データベースから情報を保存、更新、削除、検索、および取得できます。また、SQLを使用してデータベースのパフォーマンスを維持および最適化することもできます。

PostgreSQLは、SQLに多数の拡張を加えており、それをチューリング完全な言語にしています。つまり、SQLは任意の複雑な操作を実行でき、データ処理ロジックを完全にサーバー側で実行することが容易になります。

一方、etcdは設定管理ツールとして設計されており、設定データは通常ハッシュテーブルとして表現されます。これが、そのデータモデルがキーバリュー形式で構造化されている理由であり、事実上1つの大きなグローバルテーブルを作成します。このテーブルに対してCRUD操作を実行でき、一意のキーとバージョン情報を持つ2つのフィールドのみを持ちます。その結果、クライアントはさらなる処理のために完全な値を取得する必要があります。

全体的に見て、etcdのキーバリュー構造はSQLを簡素化し、設定管理という特定のタスクに対してより便利で直感的です。

MVCC(マルチバージョン並行性制御)

MVCCは、設定管理におけるデータのバージョン管理に不可欠な機能です。これにより、以下のことが可能になります:

  • 履歴データのクエリ
  • バージョンを比較してデータの古さを判断
  • データの監視。増分通知を可能にするためにバージョン管理が必要

etcdとPostgreSQLの両方がMVCCを備えていますが、それらの違いは何でしょうか?

etcdは、グローバルに増加する64ビットのバージョンカウンターを使用してMVCCシステムを管理します。オーバーフローを心配する必要はありません。このカウンターは、たとえ毎秒数百万回の更新が発生しても、膨大な数の更新を処理するように設計されています。キーバリューペアが作成または更新されるたびに、バージョン番号が割り当てられます。キーバリューペアが削除されると、バージョン番号が0にリセットされたトゥームストーンが作成されます。つまり、すべての変更が新しいバージョンを生成し、以前のバージョンを上書きしません。

さらに、etcdはキーバリューペアのすべてのバージョンを保持し、ユーザーに表示します。キーバリューデータは上書きされることはなく、新しいバージョンは既存のバージョンと一緒に保存されます。etcdのMVCC実装は、読み取りと書き込みの分離も提供し、ユーザーがロックせずにデータを読み取ることができるため、読み取り集中型のユースケースに適しています。

PostgreSQLのMVCC実装は、etcdとは異なり、増加するバージョン番号を提供することに焦点を当てるのではなく、トランザクションと異なる分離レベルをユーザーに透過的に実装することに焦点を当てています。MVCCは、並行更新を可能にする楽観的ロックメカニズムです。テーブル内の各行にはトランザクションIDレコードがあり、xminは行作成のトランザクションIDを表し、xmaxは行更新のトランザクションIDを表します。

  • トランザクションは、それ以前にコミットされたデータのみを読み取ることができます。
  • データを更新する際に、バージョン競合が発生した場合、PostgreSQLは更新を続行するかどうかを判断するためにマッチングメカニズムで再試行します。

例を参照するには、以下のリンクを参照してください: https://devcenter.heroku.com/articles/postgresql-concurrency

残念ながら、PostgreSQLで設定データのバージョン管理にトランザクションIDを使用することは、いくつかの理由で不可能です:

  • トランザクションIDは、同じトランザクションに関与するすべての行に割り当てられるため、行レベルでのバージョン管理は適用できません。
  • 履歴クエリを実行できず、行の最新バージョンにのみアクセスできます。
  • 32ビットカウンターの性質上、トランザクションIDはオーバーフローしやすく、バキューム中にリセットされる可能性があります。
  • トランザクションIDに基づいて監視機能を実装することはできません。

その結果、PostgreSQLでは、設定データのバージョン管理のために代替手段が必要です。なぜなら、組み込みのサポートが利用できないからです。

クライアントインターフェース

クライアントインターフェースの設計は、その使用に関連するコストとリソース消費を決定する上で重要な側面です。インターフェースの違いを分析することで、最も適したオプションを選択する際に情報に基づいた選択ができます。

etcdのkv/watch/lease APIは、設定管理に特に適していることが証明されています。しかし、これらのAPIをPostgreSQLでどのように実装できるのでしょうか?

残念ながら、PostgreSQLはこれらのAPIを組み込みでサポートしておらず、実装するためにはカプセル化が必要です。それらの実装を分析するために、私が開発したpg_watch_demoプロジェクトを検討します: pg_watch_demo

gRPC/HTTP vs TCP

PostgreSQLはマルチプロセスアーキテクチャを採用しており、各プロセスは一度に1つのTCP接続のみを処理します。カスタムプロトコルを使用してSQLクエリを介して機能を提供し、リクエスト-レスポンス相互作用モデル(HTTP/1.1と同様に、一度に1つのリクエストのみを処理し、複数のリクエストを同時に処理するためにパイプラインが必要)に従います。ただし、リソース消費が高く、比較的低効率であるため、特に高QPSのシナリオでは、接続プールプロキシ(pgbouncerなど)がパフォーマンスを向上させるために重要です。

一方、etcdはGolangのマルチコルーチンアーキテクチャに基づいて設計されており、gRPCとRESTfulの2つのユーザーフレンドリーなインターフェースを提供します。これらのインターフェースは統合が容易で、リソース消費の面でも効率的です。さらに、各gRPC接続は複数の同時クエリを処理できるため、最適なパフォーマンスが保証されます。

データの定義

etcd

message KeyValue {
  bytes key = 1;
  // キーが作成されたときのリビジョン番号
  int64 create_revision = 2;
  // キーが最後に変更されたときのリビジョン番号
  int64 mod_revision = 3;
  // キーが更新されるたびに増加するインクリメントカウンター。
  // このカウンターは、キーが削除されるとゼロにリセットされ、トゥームストーンとして使用されます。
  int64 version = 4;
  bytes value = 5;
  // TTLのためにキーが使用するリースオブジェクト。値が0の場合、TTLはありません。
  int64 lease = 6;
}

PostgreSQL

PostgreSQLは、etcdのグローバルデータスペースをシミュレートするためにテーブルを使用する必要があります:

CREATE TABLE IF NOT EXISTS config (
  key text,
  value text,
  -- `create_revision`と`mod_revision`に相当
  -- ここでは、リビジョンをシミュレートするために大きな整数のインクリメントシーケンスタイプを使用
  revision bigserial,
  -- トゥームストーン
  tombstone boolean NOT NULL DEFAULT false,
  -- 複合インデックス、最初にキーで検索し、次にリビジョンで検索
  primary key(key, revision)
);

get

etcd

etcdのget APIは、幅広いパラメータを持っています:

  • 範囲クエリ。例えば、key/abcrange_end/abdに設定すると、/abcをプレフィックスとするすべてのキーバリューペアが取得されます。
  • 履歴クエリ。revisionまたはmod_revisionの範囲を指定します。
  • 返される結果の数と並べ替え。
message RangeRequest {
  ...
  bytes key = 1;
  // 範囲クエリ
  bytes range_end = 2;
  int64 limit = 3;
  // 履歴クエリ
  int64 revision = 4;
  // 並べ替え
  SortOrder sort_order = 5;
  SortTarget sort_target = 6;
  bool serializable = 7;
  bool keys_only = 8;
  bool count_only = 9;
  // 履歴クエリ
  int64 min_mod_revision = 10;
  int64 max_mod_revision = 11;
  int64 min_create_revision = 12;
  int64 max_create_revision = 13;
}

PostgreSQL

PostgreSQLは、SQLを介してetcdのget機能を実行でき、さらに複雑な機能を提供することもできます。SQL自体が言語であり、固定パラメータのインターフェースではないため、非常に汎用性が高いです。ここでは、最新のキーバリューペアを取得する簡単な例を示します。主キーが複合インデックスであるため、範囲で迅速に検索でき、高速な検索が可能です。

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;
  // この`Put`リクエストによる更新前のキーバリューペアデータで応答するかどうか。
  bool prev_kv = 4;
  bool ignore_value = 5;
  bool ignore_lease = 6;
}

PostgreSQL

etcdと同様に、PostgreSQLは変更をその場で実行しません。代わりに、新しい行が挿入され、新しいリビジョンが割り当てられます。

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

etcdと同様に、PostgreSQLでの削除はデータをその場で変更しません。代わりに、トゥームストーンフィールドがtrueに設定された新しい行が挿入され、それがトゥームストーンであることを示します。

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;
  // 監視するキーの範囲を指定
  bytes range_end = 2;
  // 監視の開始リビジョン
  int64 start_revision = 3;
  ...
}

message WatchResponse {
  ResponseHeader header = 1;
  ...
  // 効率のために、複数のイベントを返すことができます
  repeated mvccpb.Event events = 11;
}

PostgreSQL

PostgreSQLには組み込みのwatch機能はなく、代わりにトリガーとチャネルの組み合わせを使用して同様の機能を実現する必要があります。pg_notifyを使用して、特定のチャネルを監視しているすべてのアプリケーションにデータを送信できます。

-- 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
    -- JSONを使用してエンコード
    data = row_to_json(NEW);
    -- キーから配布用のチャネル名を抽出
    channel = (SELECT SUBSTRING(NEW.key, '/(.*)/'));
    -- チャネルを監視しているアプリケーションがある場合、イベントを送信
    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; -- 結果は無視されます。これはAFTERトリガーです
END;
$$ LANGUAGE plpgsql;

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

watch機能がカプセル化されているため、クライアントアプリケーションも対応するロジックを実装する必要があります。Golangを例にとると、以下の手順を実行する必要があります:

  1. 監視を開始:監視が開始されると、すべての通知データがPostgreSQLとGolangのチャネルレベルでキャッシュされます。
  2. get_all(key_prefix, revision)を使用してすべてのデータを取得:この関数は、指定されたリビジョンから始まるすべての既存データを読み取ります。各キーについて、最新のリビジョンデータのみが返され、削除されたデータは自動的に削除されます。リビジョンが指定されていない場合、指定されたkey_prefixを持つすべてのキーの最新データが返されます。
  3. 新しいデータを監視。最初と2番目のステップの間にキャッシュされた通知を含め、この時間枠中に発生する新しいデータを見逃さないようにします。2番目のステップで既に読み取ったリビジョンは無視します。
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)
   // 再接続時、切断前のリビジョンに基づいて送信を再開
                str := fmt.Sprintf(`select * from get_all_from_rev_with_stale('/routes/', %d)`, latestRev)
                rows, err := db.Query(str)
                ...
                continue
            }
            ...
            // 受信した最新のリビジョンを記録する状態を維持
            updateRoute(cfg)
        case <-time.After(15 * time.Second):
            log.Println("Received no events for 15 seconds, checking connection")
            go func() {
                // 長時間イベントを受信しない場合、接続の健全性を確認
                if err := l.Ping(); err != nil {
                    log.Println("listener ping error: ", err)
                }
            }()
        }
    }
}

log.Println("get all routes...")
// 初期化時、アプリケーションは現在のすべてのキーバリューペアを取得し、その後watchを通じて増分的に更新を監視
rows, err := db.Query(`select * from get_all('/routes/')`)
...
go watch(listener)

transaction

etcd

etcdのトランザクションは、条件チェック付きの複数の操作の集合であり、トランザクションによる変更はアトミックにコミットされます。

message TxnRequest {
  // トランザクション実行条件を指定
  repeated Compare compare = 1;
  // 条件が満たされた場合に実行する操作
  repeated RequestOp success = 2;
  // 条件が満たされない場合に実行する操作
  repeated RequestOp failure = 3;
}

PostgreSQL

PostgreSQLのDOコマンドは、ストアドプロシージャを含む任意のコマンドを実行できます。PL/pgSQLやPythonなどの組み込み言語を含む複数の言語をサポートしています。これらの言語を使用して、任意の条件判断、ループ、その他の制御ロジックを実装できるため、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

etcdでは、アプリケーションが定期的に更新する必要があるリースオブジェクトを作成できます。各キーバリューペアはリースオブジェクトにリンクでき、リースオブジェクトが期限切れになると、関連するすべてのキーバリューペアも期限切れになり、自動的に削除されます。

message LeaseGrantRequest {
  // リースのTTL
  int64 TTL = 1;
  int64 ID = 2;
}

// リース更新
message LeaseKeepAliveRequest {
  int64 ID = 1;
}

message PutRequest {
  bytes key = 1;
  bytes value = 2;
  // リースID、TTLを実装するために使用
  int64 lease = 3;
  ...
}

PostgreSQL

  • PostgreSQLでは、外部キーを使用してリースを維持できます。クエリ時に、関連するリースオブジェクトが期限切れの場合、トゥームストーンと見なされます。
  • キープアライブリクエストは、リーステーブルのlast_keepaliveタイムスタンプを更新します。
CREATE TABLE IF NOT EXISTS config (
  key text,
  value text,
  ...
  -- 外部キーを使用して関連するリースオブジェクトを指定
  lease int64 references lease(id),
);

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

パフォーマンス比較

PostgreSQLは、etcdのさまざまなAPIをカプセル化してシミュレートする必要があります。では、そのパフォーマンスはどうでしょうか?以下は簡単なテストの結果です:https://github.com/kingluo/pg_watch_demo#benchmark

etcd_vs_postgres

結果は、読み取りと書き込みのパフォーマンスがほぼ同じであり、PostgreSQLがetcdを上回ることもあることを示しています。さらに、更新が発生してからアプリケーションがイベントを受信するまでのレイテンシは、更新の配布効率を決定し、PostgreSQLとetcdは同様のパフォーマンスを示します。クライアントとサーバーが同じマシン上でテストされた場合、watchのレイテンシは1ミリ秒未満でした。

ただし、PostgreSQLにはいくつかの欠点があります:

  • 各更新のWALログが大きく、etcdと比較してディスクI/Oが2倍になります。
  • etcdと比較してCPU消費量が多い。
  • チャネルに基づくNotifyはトランザクションレベルの概念です。同じタイプのリソースを更新する場合、更新は同じチャネルに送信され、更新リクエストは相互排他ロックを競合し、リクエストがシリアライズされます。つまり、チャネルを使用してwatchを実装すると、put操作の並列性に影響を与えます。

これは、同じ要件を達成するために、PostgreSQLの学習と最適化により多くの投資が必要であることを示しています。

ストレージ

パフォーマンスは基盤となるストレージによって決定され、データの保存方法がデータベースのメモリ、ディスク、その他のリソースに対する要件を決定します。

etcd

etcdストレージのアーキテクチャ図:

etcd storage

etcdは、まず更新を先行書き込みログ(WAL)に書き込み、ディスクにフラッシュして更新が失われないようにします。ログが正常に書き込まれ、大多数のノードによって確認されると、結果をクライアントに返すことができます。etcdはまた、TreeIndexとBoltDBを非同期に更新します。

ログが無限に成長するのを防ぐために、etcdは定期的にストレージのスナップショットを取得し、スナップショット以前のログを削除できます。

etcdは、すべてのキーをメモリ内にインデックス化し(TreeIndex)、各キーのバージョン情報を記録しますが、値についてはBoltDBへのポインタ(リビジョン)のみを保持します。

キーに対応する値はディスク上に保存され、BoltDBを使用して維持されます。

TreeIndexとBoltDBの両方がbtreeデータ構造を使用しており、検索と範囲検索の効率が高いことで知られています。

TreeIndex構造図:

etcd treeindex

(画像ソース: https://blog.csdn.net/H_L_S/article/details/112691481, CC 4.0 BY-SAライセンスに準拠)

各キーは異なる世代に分割され、各削除は世代の終わりを示します。

値へのポインタは2つの整数で構成されます。最初の整数mainはetcdのトランザクションIDであり、2番目の整数subはそのトランザクション内でのこのキーの更新IDを表します。

Boltdbはトランザクションとスナップショットをサポートし、リビジョンに対応する値を保存します。

etcd boltdb

(画像ソース: https://blog.csdn.net/H_L_S/article/details/112691481, CC 4.0 BY-SAライセンスに準拠)

データ書き込みの例:

key="key1", revision=(12,1), value="keyvalue5"を書き込みます。treeIndexとBoltDBの赤い部分の変化に注意してください:

etcd put

(画像ソース: https://blog.csdn.net/H_L_S/article/details/112691481, CC 4.0 BY-SAライセンスに準拠)

key="key", revision=(13,1)を削除すると、treeIndexに新しい空の世代が作成され、BoltDBにkey="13_1t"の空の値が生成されます。

ここで、tは「トゥームストーン」を表します。これは、treeIndexのポインタが(13,1)であるため、トゥームストーンを読み取ることができないことを意味しますが、BoltDBでは13_1tであり、一致しません。

etcd delete

(画像ソース: https://blog.csdn.net/H_L_S/article/details/112691481, CC 4.0 BY-SAライセンスに準拠)

etcdは、BoltDBへの読み取りと書き込みの両方を単一のゴルーチンでスケジュールし、ランダムディスクI/Oを減らし、I/Oパフォーマンスを向上させます。

PostgreSQL

PostgreSQLストレージのアーキテクチャ図:

postgres storage

etcdと同様に、PostgreSQLはまず更新をログファイルに追加し、ログがディスクに正常にフラッシュされるのを待ってからトランザクションを完了と見なします。同時に、更新はshared_bufferメモリに書き込まれます。

shared_bufferは、PostgreSQL内のすべてのテーブルとインデックスによって共有されるメモリ領域であり、これらのオブジェクトのマッピングとして機能します。

PostgreSQLでは、各テーブ

Tags: