PostgreSQLプロトコル実装に必要な柔軟性

PostgreSQLプロトコル実装に必要な柔軟性

最近、PostgresSQL互換サーバーを実装する目的で、Go言語向けのPostgreSQL互換サーバー構築用フレームワークである「go-postgresql」と、それを活用したマルチプロトコル/マルチモデルをコンセプトとする「PuzzleDB」を開発する過程で、PostgreSQLのプロトコル実装を調査しました。 以前、RDBMSの代表的なPostgreSQL・MySQLの通信プロトコルの概要を調査[3]をしましたが、今回は、実装におけるポイントや、異なるクライアント実際についても考察し、PostgreSQLプロトコルの実装における柔軟性とその必要性について整理してみます。 メッセージの共通形式 PostgreSQL仕様書では、データベースのクライアント側をフロントエンド(Frontend)、サーバー側をバックエンド(Backend)とした用語の定義があります[3]。 PostgreSQLの通信プロトコルは、フロントエンドからの要求パケット、バックエンドからの応答パケットのいずれにおいても、基本的には、以下に示すパケット仕様に準じています[3]。 PostgreSQLの通信メッセージは、最初の1バイトはメッセージ種別(Cmd)、次の4バイト(Length of message)は自身を含むメッセージ部(Message body)長を示す共通ヘッダから始まります。また、後述するように、クライアントからのコネクション確立時においては、いくつかの例外があります。 メッセージ形式の特徴 特徴としては、MySQL[1]やMongoDB[2]のようにメッセージの識別子および応答の識別子となるメッセージ番号(ID)が含まれないことです。そのため、基本的にはフロントエンドからのメッセージ要求順に、バックエンドからのメッセージを順次応答する形式となります。 PostgreSQLプロトコルによる通信は、要求および応答共に複数のメッセージを合成的に組み合わせます。MySQLにおいては、その組み合わせは厳密に定義[7][8]されていますが、PostgreSQLでは厳密な定義はなく、順次要求されるメッセージおよび、それに対する応答メッセージは、各自の実装において、省略される場合があるのが特徴です。 メッセージの処理フロー PostgreSQLのサーバー側のコネクション確立以降の基本フローについては、公式ドキュメント[4]に概要の説明があります。この公式ドキュメントから今回実装してみた、正常系の主要メッセージを整理すると、以下のような状態マシン図となります。 基本的には後述するスタートアップ時の特殊通信メッセージによる認証および認可終了後は、バックエンド側は、クライアント側からの、静的な文字列である単純なクエリー(Simple Query)と拡張クエリー(Extended Query)などのメッセージ要求を処理をして、待機(Ready)状態への遷移繰り返えすのが基本フローとなります。 スタートアップ時の例外 メッセージの共通形式には例外があり、歴史的な経緯[3]から、クライアントからのコネクション確立時の最初のメッセージはからは、メッセージ種別(Cmd)が省略されています。その例外として、以下に示す、スタートアップメッセージ(StartupMessage)と、SSL要求メッセージ(SSLRequest)があります。 いずれも、共通形式と比較して、最初のメッセージ種別(Cmd)が省略されています。メッセージ種別(Cmd)はありませんが、スタートアップメッセージ(StartupMessage)はメッセージ内容とプロトコルバー順により可変ですが、SSL要求メッセージ(SSLRequest)は固定値であるため、SSL要求メッセージのヘッダ部で識別となります。 SSL要求メッセージの長さは8で固定、SSL要求(Request)部は80877103(=04D2162F)、つまり、1234(0x04D2)と5679(0x162F)の2つのマジックナンバーの固定値による識別となります。いずれにしろ、ネクション確立後のスタートアップは、このような例外的な処理となります。 拡張クエリーの処理フロー PostgreSQLでは、クライアントからの単純なクエリー(Simple Query)要求に加えて、拡張クエリー(Extended Query)が定義されています。拡張クエリー(Extended Query)は、SQL文を複数のステップに分割して送信するクエリー形式です。拡張クエリーにつき、公式ドキュメント[4]から今回実装してみた、正常系の主要メッセージを整理すると、以下のような状態マシン図となります。 拡張クエリプロトコルは上記図の一連のコマンドで実現されますが、フライアント側はバックエンド側の応答を待たずに、一連のクエリを送信するパイプライン処理を可能とします。また、パイプラインの目的としてはメッセージ往復回数が削減があり[4]、実際の実装でもバックエンド側からの応答が省略される場合があります。 プロトコル実装のポイント PostgreSQLプロトコルの実装においては、以下に示す「Frontend/Backend Protocol」公式資料を参照しながら作業を進めます。とは言え、MySQLのように厳密な定義[7][8]はなく、RFC形式のような必須・推奨・任意の定義もなく、実装においては、解釈の余地があります。…

Read more

Redis プロトコル実装調査 – RESPの課題と現状について

Redis プロトコル実装調査 – RESPの課題と現状について

Redisは、インメモリのキーバリューストアとして2009年に登場し、現在でも活発に機能拡張が継続されているプロダクトです。特に、2020年に登場したRedis 6系では、Rediの通信プロトコルであるRESP (REdis Serialization Protocol)[1]がRESP 3[2]として刷新され、大幅な機能拡張が実現されました。 本家のRedis自信も進化を続けていますが、最近ではRedisと互換性のあるRESPプロトコルに対応した大規模なRedis互換プロダクトが相次いで登場しています[4][5]。今回は、Redis互換プロダクトの互換性担保の基本ともなる、RESP (REdis Serialization Protocol)[1]の通信プロトコルを中心に、実装上の利点などや課題を踏まえて解説してみます。 RESP (REdis Serialization Protocol)の概要 RESPは「実装が簡単」「解析が高速」「ヒューマンリーダブル」を設計思想[1]とする通信プロトコル仕様です。通信パケットの起点となる共通ヘッダは、メッセージ種別を示す1バイトのみで、CRLFをメッセージの終端とする、非常に単純な仕様です。 項目 データ型 補足 メッセージ識別子 byte\<1>  ’+’, ‘-‘, ‘:’ など メッセージ部 –  メッセージ種別毎に規定 メッセージ終端 byte\<2> CR…

Read more

自己設計(Self-Design)と設計連続体(Design Continuums)の概念について

自己設計(Self-Design)と設計連続体(Design Continuums)の概念について

自己設計システムとは、システム内の選択可能なプリミティブから、データベースのワークロードとハードウェアに最適な組み合わせを自動的に生成するという概念のものです[1]。今回は、この自己設計システムのコンセプトの著者の、キーバリューストアを題材とした直近の論文を紹介してみます[2]。 自己設計によるカスタマイズ (Customization through Self-Design)とは? 自己設計(Self-Design)は中世の原子論的に、基礎となる設計要素を組み合わせ、最適な組み合わせとなるシステムを構成する概念のものです。選択可能なプリミティグには、ハードウェアだけではなく、分散システムのパーティショニング方法(例: ハッシュ分割、レンジ分割)やデータアクセス方法(例: スキャン、ソート済検索など)などのソフトウェア(アルゴリズム)も含まれます [3]。 自己設計による最適化手法には、学習コストモデルを用いた最適化や機械学習が用いられ、新しい組み合わせが未知のアルゴリズムやデータ構造を生み出すことで、パフォーマンスを大幅に向上させる可能性があります[1][3]。本論文では、具体的な評価対象システムとしてキーバリューストアを題材に、自己設計(Self-Design)における手法を提案しています。 キーバリューストアとは? キーバリューストアは、キーと値のペアを格納するデータ構造を実装したものです。キーバリューストアは、ソーシャルメディアのグラフ処理、アプリケーションデータのキャッシング、NoSQLストレージ、オンライントランザクション処理などの、多種多様なアプリケーションの基盤として活用されています[2]。 また、近年のリレーショナルデータベースであるGoogleのSpannerや、RockDBをMySQLのバックエンドストレージとしたMyRocks、FoundationDBをバックエンドストレージとしたデータウェアハウスのはSnowflakeなども、バックエンドにキーバリューストアが採用されています [2][5]。 主要なKey-Valueデータ構造 キーバリューストアのデータ構造については、読み取り・書き込み・メモリ消費量には常にトレードオフが存在し、この3つ全てを最適とするデータ構造は存在しません[2]。結論としては各目的に応じたデータ構造が必要となるのですが、現時点で代表的なデータ構造として、B+Tree、LSMツリーなどが存在しています。 B+Tree B+Treeは、RDBMSやファイルシステムでも採用されている主要なツリー構造で、合理的なメモリ消費量で読み取りと書き込み性能のバランスに優れ、現在はOracleの製品であるBerkeleyDBや、近年でもMongoDBの標準ストレージエンジンとなったWiredTiger、FoundationDBなどのキーバリューストアに採用されています。 LSMツリー LSMツリー(Logs-structured merge-tree)は、高速な書き込みと読み取り性能を共存させるデータ構造で、GoogleのLevelDBやBigTable、FacebookのRocksDB、Apache CassandraやHBase、さらにはMySQLやSQLiteなどのRDBMSの主なキー管理などにも採用されています。ただし、B+ツリー構造より書き込み性能は優れますが、読み込み性能は悪化する場合があります。 その他 (LSHテーブル、ハイブリッドな構成など) 近年はRiakのBitCaskやSpotifyのSparkeyに用いられているLSHテーブル(Log-Structured Hash-table)などの新しい構造もあります。また、アプリケーションに適切なパフォーマンスを提供するため、複数のデータ構造を排他的に提供する場合もあります。例えば、MongoDBのWireTigerでは、B+TreeとLSMツリー実装を排他的に個別にサポートしており、RocksDBでは部分的にハッシュテーブル構造を取り入れて最適化しています。 多くの最新のキーバリューストアは、相互に排他的なスワップ可能なデータレイアウト設計のセットで構成され、多様なパフォーマンス特性を提供します。たとえば、WiredTigerはBツリーとLSMツリーの実装を個別にサポートして読み取りまたは書き込みをそれぞれ最適化しますが、RocksDBファイルはソートされた文字列レイアウトまたはハッシュテーブルレイアウトをサポートして、範囲の読み取りまたはポイントをさらに最適化しますそれぞれ読み取ります。 自己設計(Self-Design)の目的 自己設計(Self-Design)の長期的な研究課題は、例えば前述のキーバリューストアの場合、対象の問題に最適なデータ構造を、簡単または自動的に選択できるかが重要な課題となります。自己設計は、対象となるドメイン知識となる原則を導き出すことを目的としており、今回の論文では、自己設計を実現するものとして、連続体(Design Continuums)の概念を提唱しています。…

Read more

MongoDB プロトコル実装調査 – v3.6以降の現状について

MongoDB プロトコル実装調査 – v3.6以降の現状について

MongoDBは、DB-Enginesの2019年8月のランキング[1]で第5位と、近年でも安定した人気を得ているドキュメントデータベースです。また、マイクロソフトのCosmosDBや、FoundationDBの「Document Layer」に代表されるように、既存のデータベースでもMongoDBインターフェースを互換APIで対応する動向も見受けられます[2][3]。 ただし、2019年8月現在、MongoDB公式の最新バージョンは4.0系であるにも関わらず、CosmosDBはv3.2ベース、Document Layerはv3.0ベースの実装に留まっています。 今回は、このようなMongoDB互換APIの実装背景を理解する上で必要となる、MongoDBの基本通信プロトコルであるWire Protocol[4]の概要と、MongoDB公式のv3.6以降の通信プロトコル実装状況についての調査結果をまとめてみます。 MongoDB Wire Protocolの概要 MongoDB Wire Protocolは、MongoDBの基礎となる要求応答の通信プロトコル仕様です。 MongoDBクライアントは、TCP/IPソケットでMongoDBサーバーと接続し、この基本プロトコルを用いて通信しています。 共通ヘッダ MongoDBの通信プロトコルのWire Protocloでは、通信メッセージの共通ヘッダが規定されています。共通ヘッダは、メッセージ総合計バイト数(Message Length)、メッセージ要求識別子(RequestID)、メッセージ応答識別子(ResponseTo)、メッセージ種別(OpCode)の4種類の整数(int32)から構成されます。 メッセージ総合計バイト数(Message Length)のデータ形がini32であるため、仕様的には2GB以上のデータの送受信にはメッセージの分割必要となります。MongoDB公式の標準的な実装としては46MB(48,000,000byte)[5]程度を上限としているようです。 なお、Wire Protocloの整数型は、TCP/IPヘッダのバイトオーダーとは異なり、リトルエンディアン順序での送受信となります。エンディアン選択については、BSON仕様[6]と同じ経緯で、実装効率メインで仕様が決定された感があります。 メッセージ種別 (OpCode) MongoDBのメッセージ種別(OpCode)は、廃止(Deprecated)されたものを含めると、現在まで以下の11種類の送受信メッセージが規定されています。 OpCoce 値 説明 OP_REPLY 1 クライアント要求応答…

Read more

RDBMSの問い合わせ(Query)プロトコルについて – PostgreSQL・MySQLを例に

RDBMSの問い合わせ(Query)プロトコルについて – PostgreSQL・MySQLを例に

最近、RDMBSの通信プロトコルに興味があり、代表的なPostgreSQL・MySQLの通信プロトコルの調査をしています。今回は、とくに興味があった問い合わせ(Query)とその応答の通信プロトコルについて整理してみます。 取っ掛りとなる調査ではありますが、その通信プロトコル仕様から、PostgreSQL・MySQLそれぞれの設計思想的なところも垣間見れ、興味深い調査となりました。端的に言えば、PostgreSQLは高速かつ合成的で合理的、対するMySQLの仕様は厳密で冗長的な設計の印象を持ちました。 RDBMSの質問(Query)操作とは RDBMSへ対する操作は、質問(query)と更新(update)に大別されます。RDBMSの基礎となるリレーショナル代数は、前者の質問操作を対象とするものであり、挿入(insert)や削除(delete)などは後者の更新処理に含まれます[1]。 リレーショナル代数の質問は再帰性(recursiveness)があり、その結果は再びリレーショナルとして表現されます。この結果を表すリレーションを、リレーショナル代数では結果リレーション(result relation)、SQLでは導出表(derived table)と呼ばれています[1]。 すなわち、質問(query)操作についてはリレーショナル代数の結果リレーション相当の情報が含まれる必要があります。 通信プロトコル PostgreSQL・MySQLともに通信プロトコルの基本となる通信パケットが定義されており、通信パケットに含まれるデータについても基本となるデータ型が定義されています。 通信パケット 通信パケットは、PostgreSQL・MySQLともに通信プロトコルの基本となるパケット形式で、全ての通信プロトコルは、このパケット仕様に準じています。 PostgreSQL・MySQLともに通信パケットのサイズを示すメッセージ(ペイロード)長が含まれています。以下の表に示す通り、相違点としては、PostgreSQLがメッセージ種別からパケットが開始されるのに対して、MySQLのメッセージ種別はペイロード部に含まれています。 項目 PostgreSQL MySQL メッセージ種別 byte\<1> – ペイロード長 int\<4> int\<3> シーケンス番号 – int\<1> ペイロード string\<var> or binary string…

Read more

学習によるデータベース最適化について – Sagedb: A learned database system

学習によるデータベース最適化について – Sagedb: A learned database system

昨年から今年に入り、マサチューセッツ工科大学とGoogleなどの共著で、SageDBなどの機械学習によるデータベース最適化の論文が発表されています[1][2]。現時点では研究レベルの論文ではありますが、将来のデータベース在り方について色々な示唆に富んでいる論文です。 今回は、この論文の主要コンセプトである「学習によるカスタマイズ」に関連する話題にふれ、今後のデータベース像について考えてみます。 データベース最適化の背景 データベース最適化の研究については長い歴史がありますが[1][5]、SageDBの論文[1]では、(データベースを含む)既存のデータ処理一般の最適化(カスタマイズ)手法を以下の3種類に分類しています。 設定によるカスタマイズ (Customization through Configuration) まずは、最も馴染み深いであろう、データーベースのコンフィグ設定やシステム設定変更による最適化です。具体的な例としては、各データベースのページサイズ、バッファプールサイズの調整などが該当します。広義的な意味では、インデックスやマテリアライズド・ビューの作成も含まれますが、基本的には静的なカスタマイズ手法と位置付けられています。 設定によるカスタマイズについては、データベースへのワークロードやデータ特性から設定値を自動的に最適化する先行研究が数多くあり、近年では機械学習をデータベースに応用した汎用的な最適化の研究もあります[4]。 アルゴリズム選択によるカスタマイズ (Customization through Algorithm Picking) 前述の静的な「設定によるカスタマイズ」に対して、動的な実行戦略の選択によるクエリー最適化の手法で、データベースの主要な研究の分野として長い歴史があります[1][5]。具体的な例としては、クエリー最適化は、オプティマイザにより最適な実行順序(例:述語プッシュダウン、結合順序など)を決定し、利用可能な一連のアルゴリズムから最良の実装を選択(例:ネステッドループ結合、ハッシュ結合など)するものです。 この最適化手法は、実行前にそのコストを推定するため、コストに基づく最適化(cost-based optimaization)とも呼ばれ、コスト推定には統計的な手法の基づくものが広く実装されています[5]。 自己設計によるカスタマイズ (Customization through Self-Design) 自己設計システムとは、システム内の選択可能なプリミティブから、データベースのワークロードとハードウェアに最適な組み合わせを自動的に生成するという概念のものです [3]。選択可能なプリミティグには、ハードウェアだけではなく、分散システムのパーティショニング方法(例: ハッシュ分割、レンジ分割)やデータアクセス方法(例: スキャン、ソート済検索など)などのソフトウェア(アルゴリズム)も含まれます [3]。 この最適化手法には、学習コストモデルを用いた最適化が用いられており、新しい組み合わせが未知のアルゴリズムやデータ構造を生み出すことで、パフォーマンスを大幅に向上させる可能性もあるとされ[3]、今回のSageDB[1]の論文と合わせて興味深い概念です。 SageDBとは? 現代のデータベース(原文:データ処理システム)は、多種多様なスキーマ、データ型、およびデータ分散を処理できるように汎用的に設計されています。その反面、その最適化については、前述の「アルゴリズム選択によるカスタマイズ」(=コストに基づく最適化)に基づくものが多く、特定のワークロードやデータ特性に対して適応できない可能性があります[1][2]。 SageDBでは「学習によるカスタマイズ」と名付けられた、既存のデータベース(原文:データ処理システム)のアルゴリズムとデータ構造に(機械学習の)モデルを深く埋め込むことによる最適化手法を提案しています。…

Read more