PostgreSQLをブラウザ上で実行するOSS「Postgres-WASM」を触ってみた

2022.10.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

PostgreSQLをブラウザ上で実行する「Postgres-WASM」がオープンソースとして公開されました。

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.

https://supabase.com/blog/postgres-wasm

Postgres-WASM を少し触ってみたので、紹介します。

Postgres-WASMができること

Postgres-WASM では PostgreSQL 14が動作しています。もろもろのSQLを実行できるのはもちろんのこと、状態を保存・リストアできたり、ブラウザ外からの通信、例えば、psqlで接続したり、外部データベースとレプリケーションを組むこともできます。

触ってみる

デモページ

Supabase社がデモページにアクセスすると、すぐに体験できます。

https://wasm.supabase.com/

数十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がリッチな同社の提供する次のデモサイトを前提に話を進めます。

https://wasm.supabase.com/

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

https://github.com/copy/v86/blob/master/docs/filesystem.md

# 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向けにコンパイルして、いい感じに動くようになるのかなぁという印象を持ちました。

それでは。

参考