PostgreSQLをMisskeyクライアントにする
PostgreSQL Foreign Data Wrapper で psql を Misskey クライアントにする
PostgreSQLFDWC言語Misskeyみすてむずこれは みすてむず いず みすきーしすてむず Advent Calendar 2023 11日目の記事です。
前日は桃咲のあさんのマウス分解&清掃&再構築レポートでした。綺麗になったマウスと謎のネジ2本の今後が気になりますね。
みすてむずでのアカウント名はシアン化リチウムです。ネットで使う名前は一貫していないので液体のような蒸気のようなそんな感じでephemeralのIDの通り一時的な何かと捉えてください。ところでSNSで設定しているアカウント名/IDの発話中の呼びやすさ交流の積極さに相関がありそうですね。私はだいたい会話向けに使わない文字列を使っている気がします。人間のコミュニケーションは音声(または何かしら身体的動作で表現するもの)の方が有効だったりするのでしょうか。それゆえのテレワークから出社回帰だったりするんですかね。まあそれは記事に関係ないので考えないことにします。
PostgreSQLはDBサーバーでしょ
そうです。リレーショナルデータベース管理システム(RDBMS) です。
データをテーブル(表)で定義してSQLで問い合わせをして保存したり取り出したりします。
DBを使うときは奥で鎮座しているDBサーバーへクライアントプログラムがアクセスします。
クライアントプログラムは人が使う psql コマンドやその他のDBクライアントソフト、あるいは別のサーバープログラム(アプリケーションサーバー)だったりします。
PostgreSQLイメージ図
※ キャッシュや先行ログ、制御情報、冗長化などは無視した超絶シンプルな図です
論理的なテーブル構造はPostgreSQLによってストレージ上のデータディレクトリへテーブルファイルとして永続化されます。
みすてむずのサーバープログラムである現行の Misskey も PostgreSQL を使っています。
イメージ

何の変哲もない構成ですね。
FDW (Foreign Data Wrapper)
PostgreSQL には Foreign Data Wrapper という PostgreSQL の外のデータへアクセスできる仕組みが存在します。
これを使うと PostgreSQLを介して 別のデータソースを操作できます。
FDWイメージ

データソースとして別のPostgreSQLサーバーや他DBMSのOracle, MongoDB, Redisやクラスタ内のファイルを使うものがあります。
他DBMSへのアクセスということでネットワーク越しの操作も可能です。
というわけで
もしこれでMisskeyのAPIを叩いたとしたら
PostgreSQLに挟まれるMisskey鯖が見えてきたと思います。
イメージ

面白くないですか?
そうですか。
私はこの図の様が面白いと思いました。
そうして始まった物語です。
コード
https://gitlab.com/gnx2/misskey_note_fdw に置いています。
ここで力尽きたのでFDW実装については別記事で改めて理解してからまとめようと思います。
実装はみすてむず内でも賛否両論(賛2 否定8)を巻き起こすC言語です。
PostgreSQL拡張として登録するエントリポイントがC-ABIであればよいので実処理はC以外で書いてもよいです。
ビルド済みライブラリを使う時などはC言語限定というわけではないですからね。
ただし、メモリに関してPostgreSQL拡張特有のMemoryContextとpalloc/pfreeの使用によりメモリの確保元を切り替える処理のためC言語でないと扱いにくいせいなのか他FDW実装もCのみで書かれたものが多いです。1 2
最低限取得と投稿ができただけでさらに実装するところが多く残っています。
実行計画に寄与するコスト計算やパス情報、条件句のプッシュダウン、etc…
とりあえず本記事ではpsqlからの操作概観を示すとします。
動作
操作例です。みすてむずを接続先にしました。
定義
FDW登録からの処理はリポジトリ内の register.sql
にあります。
RDBはEverything is a table3なのでテーブル定義をする必要があります。
misskey_note_fdwではTL種別で定義を分けました。
CREATE foreign table home_tl (id text, content text, username text, userhost text, visibility text, created_at timestamptz) server misskey_server OPTIONS (type 'home');
CREATE foreign table local_tl (id text, content text, username text, userhost text, visibility text, created_at timestamptz) server misskey_server OPTIONS (type 'local');
CREATE foreign table social_tl (id text, content text, username text, userhost text, visibility text, created_at timestamptz) server misskey_server OPTIONS (type 'social');
foreign table の option として ‘home’, ‘local’, ‘social’ を渡してスキャン時に使用するMisskeyAPIエンドポイントを切り替えています。
結果を返すときに投稿IDを1番目、本文を2番目、、、という残念な実装をしているためカラム定義順は固定です。
取得
select で各TLから投稿を新しい順に取得できます。
※ 内部的に取得投稿数は100件を上限にしてます。
取得例 ローカルTLから公開投稿を最大14件取得してみました。
dev=# select * from local_tl where visibility = 'public' limit 14;
id | content | username | userhost | visibility | created_at
------------+--------------------------------------------------------------------------------------------------+---------------+----------+------------+----------------------------
9n3avnh3ym | ワンストップもオンラインでできるようになったの強いなぁ | tenderness330 | | public | 2023-12-10 17:05:42.039+00
9n3as7ypxu | Edgeから貼り付けたときだけの挙動っぽい +| cmt1910 | | public | 2023-12-10 17:03:01.969+00
| 謎 | | | |
9n3arhnbxe | 8番出口コンプリート +| 130cmwolf | | public | 2023-12-10 17:02:27.863+00
| 大体3周もすれば全部見つかるわね +| | | |
| それ以降はメタ読みで異変ポイントを:genbaneko:してしまうから +| | | |
| 大体すんなりクリアできそう | | | |
9n3am0l1vs | kaoismに細工されて絶対に調理されるアルゴリズム説 | CAT | | public | 2023-12-10 16:58:12.469+00
9n3allhevp | 今日のあなたは**明日のカリカリ**です。#はすきーのカリカリ +| CAT | | public | 2023-12-10 16:57:52.898+00
| https://misskey.systems/play/9ldb0jvjwu +| | | |
| +| | | |
| どうして... | | | |
9n3ajepdtm | :oyasumi: | gonk | | public | 2023-12-10 16:56:10.801+00
9n3aj1ijte | 今日のあなたは**なんとカリカリではない**です。#はすきーのカリカリ +| Walkure | | public | 2023-12-10 16:55:53.707+00
| https://misskey.systems/play/9ldb0jvjwu | | | |
9n3ai230ss | 今回も麦の匠にお世話になる | tenderness330 | | public | 2023-12-10 16:55:07.788+00
9n3ahsfnsn | 重い腰を上げてふるさと納税した | tenderness330 | | public | 2023-12-10 16:54:55.283+00
9n3af390qv | さつきちゃんも頭を抱えてるよ | Walkure | | public | 2023-12-10 16:52:49.332+00
9n3aegn5ql | そろそろ大掃除しないとなーと思い着手したんだけど、今週末は本当に手をつけただけで終わってしまった | Walkure | | public | 2023-12-10 16:52:20.033+00
9n3abko0q4 | ~月曜中止のお知らせ~ | den_shina | | public | 2023-12-10 16:50:05.28+00
9n3ab1i6po | チカレタにゃんね。いつの間にか月曜にゃ... | den_shina | | public | 2023-12-10 16:49:40.446+00
9n3a8pw7oe | 頑張るか… | cc | | public | 2023-12-10 16:47:52.087+00
ついにです。ついに psql がMisskeyのCLIクライアントになりました。これでDBメンテナンス中も投稿を見ることができますね!
投稿
本文とvisibilityのみ指定できます。
投稿例
dev=# insert into local_tl (content, visibility) values ('投稿テスト home ' || version(), 'home');
INSERT 0 1
dev=# select * from home_tl where username = 'ephemeral' limit 1;
id | content | username | userhost | visibility | createdat
------------+-------------------------------------------------------------------------------------------------------------------------------------+-----------+----------+------------+----------------------------
9n3a6d0fnu | 投稿テスト home PostgreSQL 16.1 (Debian 16.1-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit | ephemeral | | home | 2023-12-10 16:46:02.079+00
(1 row)
投稿結果 https://misskey.systems/notes/9n3a6d0fnu
思うところ
- 値取ってきて整形するだけかと思ったら闇に足を踏み入れていました
- 果たしてFDWは有用か
- 微妙
- DBから外のサーバーへ通信可能にする必要がある
- 他の拡張機能でもそうだがマネージドDBでは運営が採用しない限り基本使えない
- テーブルにするために非RDBへはちょっと曲芸的な取り扱いになりがちです
- PostgreSQLの内部実装に深く食い込んでいるので実装がメジャーバージョンごとに変わるところがあります
- 違いはプリプロセッサから PG_VERSION_NUM を参照して実装を切り替えます
- FDWではありませんが例えばpgaudit拡張はメジャーバージョン間で同一ソースにせず別ブランチで管理の方針を取っています
- 微妙
- PostgreSQLが内部でデータをどのように扱うかを知る(知らないといけない)ので必然的に理解の助けになります
- 実行計画を作るプランナの気持ちを1mmくらいわかった気がします(実際は1μメートル)
- システムカタログ (pg_hogehogeテーブル) と対になる構造体を見つけたときにこれかぁと少しテンションが上がります
参考
FDW実装に関するまとまった文書がPostgreSQL公式マニュアル以外に日本語であまり無く他FDW実装とGitLab AI4に助けられました。
- PostgreSQLライセンス (Slonik/Elephant Icon) https://www.postgresql.org/about/licence/
- Writing A Foreign Data Wrapper https://wiki.postgresql.org/images/6/67/Pg-fdw.pdf
- postgresqlinternals https://www.postgresqlinternals.org/
- PostgreSQL のメモリ管理関数の解説 https://www.nminoru.jp/~nminoru/postgresql/pg-memory-management.html
- file_fdw https://github.com/postgres/postgres/tree/REL_16_0/contrib/file_fdw
- postgres_fdw https://github.com/postgres/postgres/tree/REL_16_0/contrib/postgres_fdw
- redis_fdw https://github.com/pg-redis-fdw/redis_fdw/tree/REL_16_STABLE
- jdbc_fdw https://github.com/pgspider/jdbc_fdw/tree/v0.3.1
- mongo_fdw https://github.com/EnterpriseDB/mongo_fdw/tree/REL-5_5_1
- oracle_fdw https://github.com/laurenz/oracle_fdw/tree/ORACLE_FDW_2_6_0
アドベントカレンダー
アドベントカレンダーというものは初めてですが期限駆動で書けるのは良いですね。
当日0時には書き終わりたかったですが意思が弱かったので来年は鍛えます。
なんとみすてむずアドベントカレンダーは4+1枚あります。(個人, etc を加えるともっとあります。)
カレンダー1 次の回は中村さんの Vueどーじょー一期生の日記を書きます
です!道場、なにやら強くなっていそうですね。
脚注
-
国産RDBMS TsurugiのFDWはC++を使用しています (https://github.com/project-tsurugi/ogawayama/tree/v2.0) ↩
-
pallocによって確保した領域は解放をしない(忘れた)ときでもトランザクション終了時に解放され確保量の上限もあるので安全です。これを使わずmallocでも動きはします。このため外部ライブラリ内で確保されるなどした領域については確実に解放処理が必要になります ↩
-
今考えました ↩
-
今現在ベータ版で無料で試用できていたので使っています ↩