
PostgreSQLをブラウザ上で実行するOSS「Postgres-WASM」を触ってみた
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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アンカンファレンス@オンライン 発表資料)

















