Windows 10 の PostgreSQL 13 で Unix ドメイン ソケット【TCP? なにそれおいしいの？】

Windows 10 は April 2018 Update から Unix ドメイン ソケットをサポートしています。

- [AF_UNIX comes to Windows](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/)
- [Windows/WSL Interop with AF_UNIX](https://devblogs.microsoft.com/commandline/windowswsl-interop-with-af_unix/)

これを受けて PostgreSQL はバージョン 13 から Windows 10 でも Unix ドメイン ソケットをサポートするようになりました。

- [Release Notes - PostgreSQL 13.0](https://www.postgresql.org/docs/release/13.0/)

今回は、TCP/IP 接続ではない PostgreSQL 13 を Windows 10 20H2 で使ってみます。厳しいネットワーク管理により TCP/IP サーバーを立てられない想定、というかぶっちゃけ **TCP/IP サーバーを起動しない縛りプレイ**でお届けします。



# TCP の味を知らない子を Windows に立てる方法

## 配布ファイルの配置

さっそくインストールしてみましょう。インストーラーを使うと TCP サーバーが立ち上がってしまうため、zip ファイルからインストールしました。以下のようにファイルを準備します。

1. [Download PostgreSQL Binaries](https://www.enterprisedb.com/download-postgresql-binaries) から Windows 用最新版をダウンロード
2. zip ファイルを展開
3. pgsql フォルダを Program Files に移動



## データベース クラスターの作成

以降では、個人的な好みから PowerShell 7 を使っています[^b]。適宜、お好みのシェル[^f]にお読み替えください。

[18.2. Creating a Database Cluster](https://www.postgresql.org/docs/13/creating-cluster.html) を参考に、データベース クラスターを作成するため、`C:\Program Files\pgsql` で右クリックして、PowerShell 7 > Open here を選択し、PowerShell 7 の画面を開きます。

```
PS C:\Program Files\pgsql> .\bin\initdb.exe -D data --locale ja_JP.UTF-8
データベースシステム内のファイルの所有者はユーザ"hanohrs"となります。
このユーザをサーバプロセスの所有者とする必要があります。

データベースクラスタはロケール"ja_JP.UTF-8"で初期化されます。
デフォルトのデータベース符号化方式はそれに対応してUTF8に設定されました。
initdb: ロケール"ja_JP.UTF-8"用の適切なテキスト検索設定が見つかりませんでした
デフォルトのテキスト検索構成は simple に設定されます。

データベージのチェックサムは無効です。

ディレクトリdataを作成しています ... ok
サブディレクトリを作成しています ... ok
動的共有メモリの実装を選択しています ... windows
デフォルトのmax_connectionsを選択しています ... 100
デフォルトのshared_buffersを選択しています ... 128MB
デフォルトの時間帯を選択しています ... Asia/Tokyo
設定ファイルを作成しています ... ok
ブートストラップスクリプトを実行しています ... ok
ブートストラップ後の初期化を実行しています ... ok
データをディスクに同期しています ... ok

initdb: 警告: ローカル接続に対して"trust"認証を有効にします
pg_hba.confを編集する、もしくは、次回initdbを実行する時に -A オプション、
あるいは --auth-local および --auth-host オプションを使用することで変更する
ことがきます。

成功しました。以下のようにしてデータベースサーバを起動することができます:

    ^"C^:^/Program^ Files^/pgsql^/bin^/pg^_ctl^" -D data -l ログファイル start

```

最後の行の `^"C^:^/Program^ Files^/pgsql^/bin^/pg^_ctl^" -D data -l ログファイル start` は、PowerShell を使わない想定のメッセージですね。このエスケープは覚えられる気がしません。



## pg_hba.conf の編集

さて、data\pg_hba.conf を編集しましょう。ファイル末尾部分を以下のように編集し、Unix ドメイン ソケット以外の接続を無効化します。

```conf:pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
# host    all             all             127.0.0.1/32            trust
# IPv6 local connections:
# host    all             all             ::1/128                 trust
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     trust
# host    replication     all             127.0.0.1/32            trust
# host    replication     all             ::1/128                 trust
```



## postgresql.conf の編集

data\postgresql.conf も以下の箇所の設定を、以下の通りに変更します。`listen_addresses = ''` を忘れると TCP サーバーが起動してしまいます。[19.3.1. Connection Settings](https://www.postgresql.org/docs/13/runtime-config-connection.html#RUNTIME-CONFIG-CONNECTION-SETTINGS) を参考に、慎重に・・・。


```conf:postgresql.conf
listen_addresses = ''                   # what IP address(es) to listen on;
                                        # comma-separated list of addresses;
                                        # defaults to 'localhost'; use '*' for all
                                        # (change requires restart)
#port = 5432                            # (change requires restart)
max_connections = 100                   # (change requires restart)
#superuser_reserved_connections = 3     # (change requires restart)
unix_socket_directories = '/Users/hanohrs/AppData/Local/Temp'
                                        # comma-separated list of directories
                                        # (change requires restart)
```

unix_socket_directories の Unix 環境でのデフォルト値は `/tmp` です。これは、サーバーを強制終了した際に残ってしまう 0 バイトのファイルを削除しても構わないか、ユーザーが戸惑わないようにとの配慮からだと思います。Windows でも同様にしたほうが良いでしょう。


## サーバーの起動

いよいよサーバーを起動します。今回は [18.3. Starting the Database Server](https://www.postgresql.org/docs/13/server-start.html) の方法で起動します。TCP サーバーを起動させない遊びをしているのでなければ、Enterprise DB のインストーラーが作成したサービスを設定変更して使ったほうが良いと思います。

```
PS C:\Program Files\pgsql> .\bin\postgres.exe -D data
2021-02-23 21:55:45.323 JST [15120] LOG:  PostgreSQL 13.2, compiled by Visual C++ build 1914, 64-bit を起動しています
2021-02-23 21:55:45.330 JST [15120] LOG:  Unixソケット"/Users/hanohrs/AppData/Local/Temp/.s.PGSQL.5432"で待ち受けています
2021-02-23 21:55:45.408 JST [18076] LOG:  データベースシステムは 2021-02-23 21:51:24 JST にシャットダウンしました
2021-02-23 21:55:45.436 JST [15120] LOG:  データベースシステムの接続受け付け準備が整いました
```

おお！ ほんとに Unix ドメイン ソケットで起動しました！！

念のため、PowerShell 7 > Open here as Administrator を選択し、ポートが開いていないかコマンドで確認します。

```
PS C:\Program Files\pgsql> Get-NetTCPConnection -LocalPort 5432 -State Listen
Get-NetTCPConnection: No matching MSFT_NetTCPConnection objects found by CIM query for instances of the ROOT/StandardCimv2/MSFT_NetTCPConnection class on the  CIM server: SELECT * FROM MSFT_NetTCPConnection  WHERE ((LocalPort = 5432)) AND ((State = 2)). Verify query parameters and retry.
```

やりました！ 該当する接続がないため、エラー メッセージが出ました。

それにしてもエラー メッセージが嬉しいというのはなんだか変な気分です。


## psql からの接続

さて、いよいよ psql からの接続です。いつも使っている TCP 接続ではないので、使うべき引数がわかりません。[psql のマニュアル](https://www.postgresql.org/docs/13/app-psql.html) を参考に試してみます。

```
PS C:\Program Files\pgsql> .\bin\psql.exe -h /Users/hanohrs/AppData/Local/Temp -d postgres
psql (13.2)
"help"でヘルプを表示します。

postgres=# select 42 as "究極の疑問の答え";
 究極の疑問の答え
------------------
               42
(1 行)


postgres=# \q
```

クライアントからの接続もばっちりです！

## サーバーのシャットダウン

動作確認が取れましたので、サーバーは Ctrl-C で終了します。

```
2021-02-23 22:26:32.654 JST [15120] LOG:  高速シャットダウン要求を受け取りました
2021-02-23 22:26:32.660 JST [15120] LOG:  活動中の全トランザクションをアボートしています
2021-02-23 22:26:32.662 JST [16612] ERROR:  ユーザからの要求により文をキャンセルしています
2021-02-23 22:26:32.662 JST [14472] ERROR:  ユーザからの要求により文をキャンセルしています
2021-02-23 22:26:32.668 JST [15120] LOG:  バックグラウンドワーカ"logical replication launcher" (PID 16612)は終了コード1 で終了しました
2021-02-23 22:26:32.671 JST [4192] LOG:  シャットダウンしています
2021-02-23 22:26:32.702 JST [15120] LOG:  データベースシステムはシャットダウンしました
```


# 縛りプレイ以外に使い道はあるか

ここからは、TCP も有効化するなどしつつ、縛りプレイ以外の用途を探っていきたいと思います。Unix ドメイン ソケット接続には将来性はあると思いますが、果たして現時点での実用性はいかほどでしょうか？

## 対応しているのは psql コマンドくらい

今のところ psql くらいしか Windows でのこの接続方法に対応したクライアントがありません。pgAdmin でさえ未対応です。

Java 16 から標準ライブラリで Unix ドメイン ソケットが使えるようになりますので、今後 PostgreSQL の JDBC ドライバーでサポートされるといいなと思っています。

## `\copy` の速度は TCP と変わらない

Unix ドメイン ソケットは、TCP と異なりパケットの並び替えなどの処理がないので、Unix では高速に動作します。

psql で速度が向上すると嬉しい処理の例として、`\copy` の速度を測ってみます。環境は HP ENVY x360 13-ag0010AU で、CPU は 4C8T でベース 2GHz の AMD Ryzen、ストレージは NVMe SSD です。

まずはテストデータの準備です。10,000,000 行準備します。

```
PS C:\Program Files\pgsql> .\bin\psql.exe -h /Users/hanohrs/AppData/Local/Temp -d postgres -U lesser -c "create table jugem (t text);"
CREATE TABLE
PS C:\Program Files\pgsql> .\bin\psql.exe -h /Users/hanohrs/AppData/Local/Temp -d postgres -U lesser -c "insert into jugem select random()::text FROM generate_series(1, 10000000);"
INSERT 0 10000000
PS C:\Program Files\pgsql> .\bin\psql.exe -h /Users/hanohrs/AppData/Local/Temp -d postgres -c "copy jugem to E'C:\\Users\\hanohrs\\jugem';"
COPY 10000000
```

実験ではありますが、`\copy` を使うのは一般ユーザーだと思いますので、ユーザーを作成してから上記コマンドを実行しています。最後の行だけユーザーが違うのは、PowerShell から標準出力経由でファイルに送ろうとしたらメモリ消費が異常に増えてしまったためです[^s]。直接ファイルに出力するためにスーパー ユーザーを使う必要がありました。CMD よりハマりにくい印象のある PowerShell ですが、PowerShell は PowerShell でハマるんですよね。

さて、準備したファイルは 202,696,829 バイトになりました。これをロードしてみます。

```
PS C:\Program Files\pgsql> .\bin\psql.exe -h /Users/hanohrs/AppData/Local/Temp -d postgres -U lesser -c "truncate jugem;"
TRUNCATE TABLE
PS C:\Program Files\pgsql> Measure-Command { .\bin\psql.exe -h /Users/hanohrs/AppData/Local/Temp -d postgres -U lesser -c "\copy jugem from $home/jugem;" }

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 10
Milliseconds      : 393
Ticks             : 103936383
TotalDays         : 0.000120296739583333
TotalHours        : 0.00288712175
TotalMinutes      : 0.173227305
TotalSeconds      : 10.3936383
TotalMilliseconds : 10393.6383


PS C:\Program Files\pgsql> .\bin\psql.exe -h 127.0.0.1 -d postgres -U lesser -c "truncate jugem;"
TRUNCATE TABLE
PS C:\Program Files\pgsql> Measure-Command { .\bin\psql.exe -h 127.0.0.1 -d postgres -U lesser -c "\copy jugem from $home/jugem;" }

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 10
Milliseconds      : 879
Ticks             : 108795981
TotalDays         : 0.000125921274305556
TotalHours        : 0.00302211058333333
TotalMinutes      : 0.181326635
TotalSeconds      : 10.8795981
TotalMilliseconds : 10879.5981


```

速くなるかと思ったのに、変わりませんでした。

書き込みだとストレージがボトルネックになりやすいので、読み込みも試してみます。リダイレクトで性能が劣化しないように、コマンド プロンプトを使います。

```
C:\Program Files\pgsql>bin\psql.exe -h /Users/hanohrs/AppData/Local/Temp -d postgres -U lesser -c "\copy jugem to STDOUT" > NUL

C:\Program Files\pgsql>bin\psql.exe -h 127.0.0.1 -d postgres -U lesser -c "\copy jugem to STDOUT" > NUL
```

時間計測するコマンドが使えなかったため、Process Monitor から記録を抜粋して代用しました。使い方はここでは説明しませんので、関心のある方は『Windows Sysinternals徹底解説　改訂新版』などをご覧ください。

```
23:48:29.0196441	psql.exe	16708	Process Start
23:48:54.7395300	psql.exe	16708	Process Exit
23:49:44.0973261	psql.exe	1436	Process Start
23:50:09.5896252	psql.exe	1436	Process Exit
```

一番左がイベントの発生時刻ですが、差はどちらも25秒。読み込みもほとんど変わらないですね。

というわけで、Windows の Unix ドメイン ソケットは、PostgreSQL で使う限りでは IPv4 とほぼ同じ速度のようです。現状では速度面の利点はないといっていいでしょう。


## セキュリティ面では良いはず

セキュリティに関しては2つほど利点があります。

まず、ファイアーウォールを無効化しても Unix ドメイン ソケットには接続されないことが挙げられます。PostgreSQL は接続のために pg_hba.conf の設定が必要ですので、このファイルの設定変更を忘れるとファイアーウォールを無効化して試行錯誤してしまいがちです。Unix ドメイン ソケットを使えばファイアーウォールを疑う必要自体がありませんので、PostgreSQL 初心者がサーバーを立てるときでも安全です。

また、これは [Inside Java - JEP-380: Unix domain socket channels](https://inside.java/2021/02/03/jep380-unix-domain-sockets-channels/) の受け売りですが、ファイル システムを使っているため ACL で制御できます。たとえば Windows サーバーを複数人で使っていて、特定のグループにだけ PostgreSQL に接続させたいことはよくあると思います。今回は試していませんが、Unix ドメイン ソケットのパスにアクセスできるグループを制限すれば、pg_hba.conf でユーザー all に trust を指定したままでもアクセス制御を実現できるはずです。監査が不要な環境では実用的だと思います。ただしこのとき COPY コマンドを巧みに使うと psql を起動した OS ユーザーではなく、postgres プロセスの OS ユーザーの権限で好きなことができる点には注意が必要です[^h]。


## まとめ

Unix ドメイン ソケット だけを使う PostgreSQL 13 を Windows 10 に立てて遊んでみました。

インストールに難しいところはなく、スタンドアローンの環境を TCP 接続よりも安全に構築できました。

しかし、現時点では実用的とは言い難いです。psql こそ対応していますが、pgAdmin 4 や JDBC ドライバーの対応はこれからです。性能も TCP と変わりません。

なお、Unix ではだいぶ状況が異なり、マイナーな SocketFactory を使えば JDBC ドライバーで接続することもできます。既に実用的になっていると思います。


[^b]: 後述の通りハマりましたので、bash にしたほうが良かったかもしれません
[^f]: あるいは最も嫌いでないシェル
[^s]: 「`標準出力(StdOut)`へ出力されたデータは最終的に`String[]`型に変換されてStandard output streamに出力される様です。」([しばたテックブログ - PowerShellのストリームと標準入出力、リダイレクトとパイプラインの話](https://blog.shibata.tech/entry/2015/06/26/001944))。つまり、ストリームを配列にしているせい？
[^h]: [PostgreSQLでHello World!!](https://qiita.com/fujii_masao/items/067e8e40aec889ecf9fb)