PostgreSQLをブラウザ上で実行する「Postgres-WASM」がオープンソースとして公開されました。
We've open sourced Postgres WASM
Hat tip to all the amazing engineers that made it possible for us to glue this together, including those at @supabase and @_snaplet
Join the discussion on HN! pic.twitter.com/u4tIceRF66
— Peter Cilliers-Pistorius 🇺🇦 (@appfactory) October 3, 2022
2022年8月にCrunchy DataがPOSTGRES PLAYGROUND としてブラウザ版PostgreSQLをサービス提供したことを受け、Supabase社とSnaplet社がオープンソース版としてリリースしたのが「Postgres-WASM」です。
クローズド・オープンソースという違いはあるものの、Crunchy DataとSupabase/Snapletがとったアプローチは同じです。
Crunchy's HN post provided some hints about the approach they took, which was to virtualize a machine in the browser. We pursued this strategy too, settling on v86 which emulates an x86-compatible CPU and hardware in the browser.
Postgres-WASM を少し触ってみたので、紹介します。
Postgres-WASMができること
Postgres-WASM では PostgreSQL 14が動作しています。もろもろのSQLを実行できるのはもちろんのこと、状態を保存・リストアできたり、ブラウザ外からの通信、例えば、psqlで接続したり、外部データベースとレプリケーションを組むこともできます。
触ってみる
デモページ
Supabase社がデモページにアクセスすると、すぐに体験できます。
数十MBのダウンロードを伴うので注意しましょう。
ローカルに構築
成果物はGitHubでOSSとして公開されているので、シュッと試せます。
$ git clone https://github.com/snaplet/postgres-wasm.git
$ cd postgres-wasm/packages/runtime && npx serve
┌─────────────────────────────────────────────────────┐
│ │
│ Serving! │
│ │
│ - Local: http://localhost:3000 │
│ - On Your Network: http://192.168.101.123:3000 │
│ │
│ Copied local address to clipboard! │
│ │
└─────────────────────────────────────────────────────┘
※素のOSS版
以降では、Postgres-WASM の 開発に携わった Supabase 社の Mark Burggraf 氏の詳細な解説記事をベースに技術面を確認し、機能・UIがリッチな同社の提供する次のデモサイトを前提に話を進めます。
WASM 向けにコンパイルされているのは何?
Postgres-WASM は名前からWASMを使っていることが伺えますが、PostgreSQL が WASM 向けにコンパイルされているわけではありません。 x86互換のCPU・ハードウェアをエミュレートする v86 が WASM ランタイム上で実行されています。
このv86 上で PostgreSQL がインストールされた x86 Linux の仮想マシンが動いているので、ブラウザ上でPostgreSQLが動いているように見えます。
デモページにアクセスすると、ブラウザの開発ツールから、370kBの v86.wasm をダウンロードしているのを確認できます。
CTRL-D で PostgreSQL のシェルインターフェースを抜けると、PostgreSQL をホストする Linux にアクセスできます。
postgres=# Ctrl-D
\q
# uname -a
Linux buildroot 5.17.15 #1 SMP PREEMPT Tue Sep 27 03:56:40 UTC 2022 i686 GNU/Linux
# cat /etc/os-release
NAME=Buildroot
VERSION=2022.08
ID=buildroot
VERSION_ID=2022.08
PRETTY_NAME="Buildroot 2022.08"
# ls -lh /usr/bin/postgres
-rwxr-xr-x 1 1000 1000 6.8M Sep 27 04:10 /usr/bin/postgres
# ps
PID USER COMMAND
1 root init
2 root [kthreadd]
3 root [rcu_gp]
4 root [rcu_par_gp]
5 root [netns]
6 root [kworker/0:0-eve]
7 root [kworker/0:0H-ev]
8 root [kworker/u2:0-ev]
9 root [kworker/0:1H-ev]
10 root [mm_percpu_wq]
11 root [rcu_tasks_kthre]
12 root [ksoftirqd/0]
13 root [rcu_preempt]
14 root [migration/0]
15 root [cpuhp/0]
16 root [kdevtmpfs]
17 root [inet_frag_wq]
18 root [kauditd]
19 root [kworker/0:1-eve]
20 root [oom_reaper]
21 root [writeback]
22 root [kcompactd0]
23 root [kblockd]
24 root [ata_sff]
25 root [md]
26 root [kworker/u2:1-ev]
27 root [rpciod]
28 root [xprtiod]
29 root [cfg80211]
30 root [kworker/u2:2]
31 root [kswapd0]
32 root [nfsiod]
34 root [kworker/0:2-eve]
35 root [mld]
36 root [ipv6_addrconf]
77 root /sbin/syslogd -n
81 root /sbin/klogd -n
128 root udhcpc -t1 -A3 -b -R -O search -p /var/run/udhcpc.eth0.pid -i eth0 -x hostname:buildroot
138 postgres /usr/bin/postgres -D /var/lib/pgsql
140 postgres postgres: checkpointer
141 postgres postgres: background writer
142 postgres postgres: walwriter
143 postgres postgres: autovacuum launcher
144 postgres postgres: stats collector
145 postgres postgres: logical replication launcher
147 root {exe} ash /usr/bin/watcher.sh
150 root -sh
192 root sleep 2
193 root ps
# netstat -l
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:postgres 0.0.0.0:* LISTEN
tcp 0 0 :::postgres :::* LISTEN
Active UNIX domain sockets (only servers)
Proto RefCnt Flags Type State I-Node Path
unix 2 [ ACC ] STREAM LISTENING 9097 /tmp/.s.PGSQL.5432
# dmesg
[ 15.592300] echo (148): drop_caches: 1
[ 21.444400] sh (173): drop_caches: 3
#
PostgreSQL を v86 に 移植するにあたり、v86 の JIT のバグで SEGV が起きたり、IPC のメモリ共有を Posix から System V に変えるなど、 v86 固有の問題を回避したそうです。
VM イメージサイズの削減
v86は Plan 9 で開発された 9p ファイルシステムをサポートしています。
A 9p filesystem is supported by the emulator, using a virtio transport. Using it, files can be exchanged with the guest OS
# mount
host9p on / type 9p (rw,dirsync,relatime,loose,access=client,trans=virtio)
devtmpfs on /dev type devtmpfs (rw,relatime,size=54360k,nr_inodes=13590,mode=755)
proc on /proc type proc (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=666)
tmpfs on /dev/shm type tmpfs (rw,relatime,mode=777)
tmpfs on /tmp type tmpfs (rw,relatime)
tmpfs on /run type tmpfs (rw,nosuid,nodev,relatime,mode=755)
sysfs on /sys type sysfs (rw,relatime)
# df -h
Filesystem Size Used Available Use% Mounted on
host9p 256.0G 73.1M 255.9G 0% /
devtmpfs 53.1M 0 53.1M 0% /dev
tmpfs 53.4M 0 53.4M 0% /dev/shm
tmpfs 53.4M 40.0K 53.4M 0% /tmp
tmpfs 53.4M 16.0K 53.4M 0% /run
VMイメージサイズが大きいと、初期化に時間がかかるため、ブートイメージを切り詰め、初期化に必須でないファイル群は、初期化後にオンデマンドで遅延ロードします。
これらの努力により、PostgreSQL のプログラム群を含んだ初期状態ファイルは30MBから12MBに圧縮されました。
デモ画面のトラフィックを確認すると、 https://wasm.supabase.com/state/state-128.bin.zst という 12.4MB の Zstandard (Zstd) 圧縮されたファイルをダウンロードしています。 このファイルが、初期状態ファイルと思われます。
ネットワーク
v86 はデフォルトではトラフィックが仮想イメージ内に閉じているため、VM外との通信には工夫が必要です。
# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 10.5.0.1 0.0.0.0 UG 0 0 0 eth0
10.5.0.0 * 255.255.0.0 U 0 0 0 eth0
# ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 00:22:15:80:1f:7f brd ff:ff:ff:ff:ff:ff
inet 10.5.6.146/16 brd 10.5.255.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::222:15ff:fe80:1f7f/64 scope link
valid_lft forever preferred_lft forever
# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: sendto: Network unreachable
v86はネットワークカード(ne2k-pci
)がエミュレートされており、WebSocketのプロキシーサーバー経由でVM外と通信できます。
# lspci -k
00:00.0 Class 0600: 8086:1237
00:12.0 Class 0300: 1234:1111
00:06.0 Class 0002: 1af4:1049 virtio-pci
00:05.0 Class 0200: 10ec:8029 ne2k-pci
00:01.0 Class 0601: 8086:7000
00:07.0 Class 0680: 8086:7113
v86/networking.md at master · copy/v86 · GitHub
ブラウザからVirtual Machineのネットワークを起動します。
仮想シェル下部にプロキシーサーバーのホスト・ポート情報が表示されます。
host:proxy.wasm.supabase.com port:6159
psql postgres://postgres@proxy.wasm.supabase.com:6159
0.0.0.0/0 へのルーティングを確認すると、ゲートウェイが wasm-proxy に変わりました。
# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default wasm-proxy.loca 0.0.0.0 UG 0 0 0 eth0
10.5.0.0 * 255.255.0.0 U 0 0 0 eth0
この状態で ping すると成功します。
# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=59 time=169.100 ms
64 bytes from 8.8.8.8: seq=1 ttl=59 time=166.600 ms
^C
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 166.600/167.850/169.100 ms
#
ただし、リファレンス実装のプロキシサーバー(GitHub - benjamincburns/websockproxy)は、フォワード・プロキシにしか対応していません。
psql でVM内のPostgreSQLに接続するなど、外からVM内に通信できるように、postgres-wasm ではリバース・プロキシに対応したフォーク版が同梱されています。
※ 引用元
VM側で postgres
ユーザーのパスワードを設定し(ALTER ROLE postgres WITH PASSWORD 'my_password';
)、VM外から psql で接続してみましょう。
$ psql postgres://postgres@proxy.wasm.supabase.com:6159
Password for user postgres:
psql (14.5)
Type "help" for help.
postgres=# select version();
version
------------------------------------------------------------------------------------------------------------------------------------
PostgreSQL 14.5 on i686-buildroot-linux-musl, compiled by i686-buildroot-linux-musl-gcc.br_real (Buildroot 2022.08) 12.1.0, 32-bit
(1 row)
postgres=# \x
Expanded display is on.
postgres=# select * from pg_stat_activity where usename = 'postgres';
-[ RECORD 1 ]----+-----------------------------------------------------------
datid |
datname |
pid | 145
leader_pid |
usesysid | 10
usename | postgres
application_name |
client_addr |
client_hostname |
client_port |
backend_start | 2022-09-27 12:18:18.7967+00
xact_start |
query_start |
state_change |
wait_event_type | Activity
wait_event | LogicalLauncherMain
state |
backend_xid |
backend_xmin |
query_id |
query |
backend_type | logical replication launcher
-[ RECORD 2 ]----+-----------------------------------------------------------
datid | 12971
datname | postgres
pid | 167
leader_pid |
usesysid | 10
usename | postgres
application_name | psql
client_addr |
client_hostname |
client_port | -1
backend_start | 2022-09-27 12:18:21.7741+00
xact_start |
query_start | 2022-09-27 12:18:40.004+00
state_change | 2022-09-27 12:18:41.292599+00
wait_event_type | Client
wait_event | ClientRead
state | idle
backend_xid |
backend_xmin |
query_id |
query | ALTER ROLE postgres WITH PASSWORD 'my_password';
backend_type | client backend
-[ RECORD 3 ]----+-----------------------------------------------------------
datid | 12971
datname | postgres
pid | 363
leader_pid |
usesysid | 10
usename | postgres
application_name | psql
client_addr | 10.5.0.1
client_hostname |
client_port | 49874
backend_start | 2022-09-27 12:19:57.039+00
xact_start | 2022-09-27 12:22:07.378699+00
query_start | 2022-09-27 12:22:07.378699+00
state_change | 2022-09-27 12:22:07.378699+00
wait_event_type |
wait_event |
state | active
backend_xid |
backend_xmin | 733
query_id |
query | select * from pg_stat_activity where usename = 'postgres';
backend_type | client backend
このネットワーク機能を有効にすると、ブラウザ上のPostgreSQLをソース・ターゲットとしてレプリケーションを組むこともできます。
※ 引用元
最後に
Supabase社とSnaplet社がオープンソース化したブラウザ版PostgreSQL「Postgres-WASM」を紹介しました。
2022年8月にCrunchy Dataがブラウザ版PostgreSQLを公開したあと、9月にはChrome開発チームがブラウザ版SQLiteの開発計画を発表するなど、データベース関連のWASM化がにわかに盛り上がっています。
出自が組み込み向けのSQLiteに比べ、クライアント・サーバー型のPostgreSQLを直接WASM化するのは、少しハードルが高そうです。
現状、Postgres-WASMは現時点の技術的な制約のために、v86上で仮想マシンを起動して、富豪的に動かしています。
今後、WASMのできることがリッチになったり、新しい技術的アイデアが出てくると、PostgreSを直接WASM向けにコンパイルして、いい感じに動くようになるのかなぁという印象を持ちました。
それでは。
参考
- Postgres WASM | Hacker News
- Postgres WASM by Snaplet and Supabase
- PostgreSQLをWebブラウザ上で動かす「Postgres-WASM」、オープンソースで公開。Webブラウザ上のx86仮想マシンで実行、レプリケーションも可能 - Publickey
- WebAssembly化したPostgreSQLをWebブラウザ上で実際に動かして学ぶ「Postgres playground」をCrunchy Dataが公開 - Publickey
- Chrome開発チームがSQLiteチームとWebAssembly版SQLiteを開発中。Webブラウザ上からのファイル書き込みで永続化も可能。Web SQL APIの代替として - Publickey
- Postgres Playground で pgbench を走らせよう!(第35回PostgreSQLアンカンファレンス@オンライン 発表資料)