本エントリーは、PostgreSQL Advent Calendar 2022の15日目の記事です。
昨日は、@kitayama_t さんによるPostgreSQL の拡張モジュールなどの周辺ツールに関する記事でした。多種多様な周辺ツールが存在していて、エコシステムも含めてのPostgreSQLと改めて実感しました。本記事で紹介するNeonについても、一部は拡張機能として実装されており、PostgreSQLの様々な領域への拡張性の高さも感じます。
PostgreSQLのクラウドネイティブ化を目指しているNeonとは
最近は、PostgreSQLも含めて、クラウド上でデータベースを動作させることが増えてきています。そういった背景を受け、よりクラウドに最適化し、コンピュート・ストレージを分離したアーキテクチャを採用した製品の開発が進められてきました。例えば、AWS Aurora、Azure SQL Database HyperScaleサービス、GCPのAlloyDB for PostgreSQLなどが有名かと思います。
このようなサービスに影響を受けて、OSSとしても実現しようとしている試みが進められています。それが本記事でご紹介したいNeonです。AWS Auroraなどを参考にし、コンピュート・ストレージを分離したアーキテクチャを採用し、サーバレスなPostgreSQLサービスを構築することを目指している Apache 2.0ライセンスのOSSです。
Google社内で開発が進められたBorgに影響を受けてKubernetesが開発されてきたのと同じように、AWS Auroraなどのクラウドベンダが開発を進めてきたソフトウェアを実現しようとしていると考えると、Neonが目指しているところなどが理解しやすいかと思います。
PostgreSQLコミッターの Heikki Linnakangas さんなどが創業した会社で、2021年3月から開発が進められており、PGCon 2022でも発表されています。ストレージレイヤをマルチテナントで共有することを想定している点や、よりメモリセーフなRustを用いて開発されている点も興味深いです。
Neonのアーキテクチャ
前述した通り、コンピュート・ストレージを分離したアーキテクチャを採用しています。
- Neonのアーキテクチャ図(Neon社のブログから引用)
大きく4つのコンポーネントから構築されており、ストレージレイヤ(②〜④)が分離されている点が、通常のPostgreSQLと異なります。
-
① Postgres Compute
- クライアントが接続するコンポーネントでクエリの実行などを担当するコンポーネント。ストレージレイヤとのやり取り部分など、一部修正しているが、大部分は通常のPostgreSQLを再利用している
-
② WAL Service
- WALの耐障害性を担保するコンポーネント。複数のSafekeeperと呼ばれるノードで構成されており、クォーラムベースのレプリケーションで一定数のノード障害でもWALを失わないようにしている
-
③ PageServer
- WALから実ページを生成し、永続化するコンポーネント。ページ生成は非同期で実施する。Computeノードからリクエストがあれば、ページ情報を返却する
-
④ Object Storage
- コールドデータや古いデータを永続化するコンポーネント
このようなアーキテクチャを採用することにより、通常のPostgreSQLと異なる、以下のようなメリットを得られます。
- データコピーなしで、複数コンピュートの実行が可能になる。また、コンピュートの高速な起動・停止が可能になる
- ストリーミングレプリケーションを採用したPostgresでStandbyを起動する場合、pg_basebackupなどで事前にデータコピーが必要になり、データ量が多いと起動までに時間がかかる。ストレージを切り出し、共有ディスク化することでその処理をなくせる
- 高速なリカバリが実現できるようになる
- 常に、非同期で、PageServer上では、WALからページを永続化するというCheckpoint相当の処理を実施しているため、ノード障害が発生しても、リカバリにかかる時間を短くできる
- アプリケーションへの影響なしで、バックアップやアーカイブなどの処理が実現できる
- ストレージレイヤを物理的に分離することで、CPUリソースの分離もでき、アプリケーションのクエリ実行への影響を限定的にできる
- CPUやDisk I/Oリソースを独立して、スケールさせることができる
- クラウドの特性を利用しやすいアーキテクチャであり、コストの最適化ができる
また、分離したストレージ層では、複数Computeノードでストレージを共有するマルチテナント機能や、ある時点から新規ブランチを作成するBranching機能、過去のある時点へのクエリ(Time Travel Query)など、Neon特有の機能も実装されています。
今回は、git
のブランチのように、データベースのデータをブランチとして管理することができるBranching機能が面白そうだったので少し動かしてみようと思います。
Branching機能
Branching機能は、データベースの状態をある時点から分岐させることを実現できる機能です。ブランチは、通常のPostgreSQLでのタイムラインと同じような概念ですが、Neonの場合は、複数ブランチのデータに同時にアクセスできます。
ユースケースとしては、開発者用に同一環境を用意したり、運用ミスなどが起きた場合に過去の時点に戻ってやり直したり、本番データを用いてデバック環境を作成するなどが考えられます。Copy-On-Writeでデータが複製されるため、低コストで新規ブランチを作成できます。
- ユースケース例:開発者用に複数環境を用意する(図はNeon社の[ドキュメント]
- mainブランチとして管理されているデータベース情報を元に各開発者用の環境を生成する
(https://neon.tech/docs/introduction/branching/)抜粋)
- mainブランチとして管理されているデータベース情報を元に各開発者用の環境を生成する
今回は、Neonのリポジトリで提供されている、ローカル環境でNeonを構築できるツールを利用して、動作確認をしてみようと思います。上記例の複数の開発メンバ用に同じ環境を提供する場合を想定してみます。
- 構築ツールをビルドする
# ビルド用の依存パッケージなどは、READMEを確認してインストールしておく
$ git clone --recursive https://github.com/neondatabase/neon
$ cd neon
$ make
- ローカル環境でNeon環境を構築する
# psqlなどのツールを利用できるよう環境変数を設定
$ export PATH=pg_install/v14/bin:$PATH
$ export LD_LIBRARY_PATH=pg_install/v14/lib
# Neonクラスタの初期化
$ ./target/debug/neon_local init
Starting pageserver at '127.0.0.1:64000' in '.neon'.
pageserver started, pid: 211033
Successfully initialized timeline c423bd785e73f5555c6a42a2d79e423a
Stopped pageserver 1 process with pid 211033
# pageserverとsafekeeperを起動
$ ./target/debug/neon_local start
Starting etcd broker using "/usr/bin/etcd"
etcd started, pid: 211458
Starting pageserver at '127.0.0.1:64000' in '.neon'.
pageserver started, pid: 211540
Starting safekeeper at '127.0.0.1:5454' in '.neon/safekeepers/sk1'.
safekeeper 1 started, pid: 211612
# compute nodeを起動
$ ./target/debug/neon_local pg start main
Starting new postgres (v14) main on timeline c423bd785e73f5555c6a42a2d79e423a ...
Extracting base backup to create postgres instance: path=.neon/pgdatadirs/tenants/6705596fa5ef3b3a6cd64c152487b221/main port=55432
Starting postgres node at 'host=127.0.0.1 port=55432 user=cloud_admin dbname=postgres'
# psqlで接続確認
$ psql -p 55432 -h 127.0.0.1 -U cloud_admin postgres -c "SELECT 1;"
?column?
----------
1
(1 row)
- Branch機能を試してみる
- ① 開発者用に渡す初期環境(mainブランチ)を作成する
- ② 開発者ごとに個別ブランチを作成する
- ③ johnユーザのブランチ上のデータを変更しても、mainブランチには影響がないことを確認する
# ① 開発者用に渡す初期環境(mainブランチ)を作成する
# 初期データを作成
$ psql -p 55432 -h 127.0.0.1 -U cloud_admin postgres
psql (14.6)
Type "help" for help.
postgres=# CREATE TABLE tbl (id int, str varchar(32));
CREATE TABLE
postgres=# INSERT INTO tbl VALUES (1, 'main');
INSERT 0 1
postgres=# SELECT * FROM tbl;
id | str
----+------
1 | main ★初期データは"main"のみ
(1 row)
postgres=# \q
# 今のcompute nodeを確認。main用のインスタンスのみ存在する
$ ./target/debug/neon_local pg list
NODE ADDRESS TIMELINE BRANCH NAME LSN STATUS
main 127.0.0.1:55432 c423bd785e73f5555c6a42a2d79e423a main 0/16CB3B0 running
# ② 開発者ごとに個別ブランチを作成する
# john, kate, emma用に新規ブランチを作成
$ ./target/debug/neon_local timeline branch --branch-name john
Created timeline '5e34d42086d898739e7e920f5802fc43' at Lsn 0/16CB3B0 for tenant: 6705596fa5ef3b3a6cd64c152487b221. Ancestor timeline: 'main'
$ ./target/debug/neon_local timeline branch --branch-name kate
Created timeline '6848194c9fe20ecfbf163e97f8b14eaa' at Lsn 0/16CB3B0 for tenant: 6705596fa5ef3b3a6cd64c152487b221. Ancestor timeline: 'main'
$ ./target/debug/neon_local timeline branch --branch-name emma
Created timeline '75920f92528decc0003c1ed98d6981ce' at Lsn 0/16CB3B0 for tenant: 6705596fa5ef3b3a6cd64c152487b221. Ancestor timeline: 'main'
# ブランチを確認すると、mainを親にして、3つの新規ブランチが作成されている
$ ./target/debug/neon_local timeline list
main [c423bd785e73f5555c6a42a2d79e423a]
┣━ @0/16CB3B0: john [5e34d42086d898739e7e920f5802fc43]
┣━ @0/16CB3B0: kate [6848194c9fe20ecfbf163e97f8b14eaa]
┗━ @0/16CB3B0: emma [75920f92528decc0003c1ed98d6981ce]
# john用のcompute nodeを起動する
$ ./target/debug/neon_local pg start john --branch-name john
Starting new postgres (v14) john on timeline 5e34d42086d898739e7e920f5802fc43 ...
Extracting base backup to create postgres instance: path=.neon/pgdatadirs/tenants/6705596fa5ef3b3a6cd64c152487b221/john port=55433
Starting postgres node at 'host=127.0.0.1 port=55433 user=cloud_admin dbname=postgres'
# john用のcompute nodeが 55433 というポートで起動されている
$ ./target/debug/neon_local pg list
NODE ADDRESS TIMELINE BRANCH NAME LSN STATUS
john 127.0.0.1:55433 5e34d42086d898739e7e920f5802fc43 john 0/16CB3E8 running
main 127.0.0.1:55432 c423bd785e73f5555c6a42a2d79e423a main 0/16CB3B0 running
# ③ johnユーザのブランチ上のデータを変更しても、mainブランチには影響がないことを確認する
# johnは、自分用のインスタンス(port=55433)を利用して開発
$ psql -p 55433 -h 127.0.0.1 -U cloud_admin postgres
psql (14.6)
Type "help" for help.
postgres=# UPDATE tbl SET str = 'john';
UPDATE 1
postgres=# SELECT * FROM tbl ;
id | str
----+------
1 | john ★データを"john"に書き換えたが...
(1 row)
# main(port=55432)は元のまま
$ psql -p 55432 -h 127.0.0.1 -U cloud_admin postgres -c "SELECT * FROM tbl;"
id | str
----+------
1 | main ★元のブランチのデータは"main"のまま
(1 row)
# kate, emma用についても、同様にcomputeを対象branchを指定して、新規にcompute nodeを起動すればOK
上記の通り、同じNeonクラスタ上に、論理的に複数ブランチを作成し、異なるブランチに対して、同時に複数のcompute nodeを起動することができます。このような仕組みにより、通常のPostgreSQLでは実現できないようなサーバレス機能やマルチテナント機能を実現しようとしていることが想像できます。
まとめ
Neonは、AWS Auroraなどと同じコンピュート・ストレージを分離したアーキテクチャを採用し、クラウドネイティブなPostgreSQLサービスの実現を目指しているOSSです。
全体のアーキテクチャについては、AuroraやAzure SQL Database Hyperscaleをかなり参考にしている印象を受けますが、過去の任意の時点に戻ったり、ブランチを作成したりする機能を実現するためのストレージエンジンを独自に実装していたりと工夫している点もあり、個人的に注目しています。もし、ご興味を持たれたら、ぜひ動かしてみてください。
明日は、@elhaltiさんによる「今年1年、alloyDBに魅せられた話」です。AlloyDBも同じアーキテクチャを採用しており、個人的にも気になっているので楽しみです!
参考
-
Architecture decisions in Neon
- Neonのアーキテクチャや開発モチベーションなど
-
Neon: Serverless PostgreSQL! (Heikki Linnakangas)
- Neonのストレージエンジンなど
-
Neon - building a Postgres storage system in Rust, Heikki Linnakangas | Rust Finland 9.5.2022
- Rustで開発することにした経緯や苦労話など
-
- NeonのSaaSサービス。無料で利用できるテクニカルプレビュー版など(2022年12月時点)