前書き
お久しぶりでございます。Scalaでバックエンドを開発しているaxtstar(@axtstart)です。
ずいぶん昔になりますが、OracleDBでアプリ開発をしていたころ、DBの中でデータが(なぜか)バイナリで格納されていて、そのビット演算を行いながら検索するみたいな案件をやったことがあります。はじめはアプリサイドからSQLで頑張って演算していたのですが、あまりにも遅くて、結局OCI(Oracle Call Interface)を利用して、C++でバイナリを扱うように書き換えたら激的に速度が改善したことがありました。
今回ふと思い立ち、Postgresqlってそういうことができるのかしら?と思い至り今回のブログ担当を利用して検証してみることにしました。
なので、(確実に)プロダクション環境で実施するのははばかられるような内容ですのでご注意下さい。
使用できる言語は...
ちなみに、今回対象としたPostgresqlはDockerHubから持ってきたpostgresql:latestを対象としています。
select version();
結果
version |
---|
PostgreSQL 12.3 (Debian 12.3-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit |
現時点*1だとPostgreSQL12.3のようです。
上記imageの初期状態で、Postgresqlで使用できる言語は下記でした。
select * from pg_language;
結果
oid | lanname | lanowner | lanispl | lanpltrusted | lanplcallfoid | laninline | lanvalidator |
---|---|---|---|---|---|---|---|
12 | internal | 10 | False | False | 0 | 0 | 2246 |
13 | c | 10 | False | False | 0 | 0 | 2247 |
14 | sql | 10 | False | True | 0 | 0 | 2248 |
13398 | plpgsql | 10 | True | True | 13395 | 13396 | 13397 |
なんと~デフォルトでcの文字がありますね!
どうやるのか?
OracleのOCIにあたるものがないのか調べているとspi(Server Programming Interface)というものがあることがわかりました。
Oracleの場合と同様、PL/SQL(PostgresだとPL/pgsql)からC/C++で書かれたプログラムを何らかの与えられた方式で呼び出せばよい感じ。
libpqというのを初めに見つけましたが、こちらはPro*C*2みたいなものっぽいですね。
- C/C++ から SQLを呼び出す(libpg) ←これじゃない
- SQLからC/C++を呼び出す(spi) ←これ
Server Programming Interface(spi)
PostgresqlはC言語とのインターフェースにサーバプログラミングインターフェースという名前の機構を用意していて、ユーザ定義のC関数をSQLから呼び出すことができるようです。
調べた限りだと、ソースコードのビルドに必要なパッケージとして以下が必要でした。*3
※imageのpostgresql:latestからの差分として必要という意味です。
apt-get install -y make clang llvm gcc postgresql-server-dev-12
またdb serverのインストール時にpg_configというプログラムが同時にインストールされ、これが様々な情報を教えてくれるようです。
例えば、PostgreSQLのサーバプログラム作成用のCヘッダファイルの格納パスとか
pg_config --includedir-server
結果
/usr/lib/postgresql/12/lib
詳しくはこちら
また、どのように作成するのかというのもかなり詳しくマニュアルに記載がありました。
C言語関数の部分を要約すると
- 共有ライブラリとして作成すること(so、dll、dylib)
- バージョン互換性マクロを一度だけ記述すること
- 「version 1」呼び出し規約をサポートすること
- 参照渡しの入力値の内容を決して変更しないこと*4
などなど。。。
なので結構マクロを多用する実装になるようです。
作るもの
せっかくなので、ちょっとだけ実用になりそうな(気がする)ものをサンプルで作ってみます。今回は、MeCabをインストールしてそちらを呼び出して、結果を返すことにします(本当はC言語のライブラリがあるので対象として適切だったのが本音)。
とはいってもこちらはメインではないのでサクっとaptで導入します
apt-get install -y mecab libmecab-dev mecab-ipadic-utf8
※MeCabの詳しい記事は↓こちら↓からどうぞ~ lab.astamuse.co.jp
作るものはこんな流れになります。
No | 使用マクロ | 意味 |
---|---|---|
1 | PG_MODULE_MAGIC | 非互換性のチェック |
2 | PG_FUNCTION_INFO_V1(function) | 関数宣言 |
3 | Datum function(PG_FUNCTION_ARGS) | Version-1呼び出し規約、引数引き渡し |
4 | PG_GETARG_xxx() | SQLからの引数取得 |
5 | palloc/palloc0 | メモリアロケーション(マクロではない。。と思います。) |
6 | SET_VARSIZE | サイズ確保 |
7 | PG_RETURN_xxx() | SQLへの返却設定 |
できたソース
ビルド用のmakefileはこちら
ビルドしてインストールしておきます
make make install
下記ディレクトリにmecab_simple.so
という名前で配置されます
/usr/lib/postgresql/12/lib
このユーザ定義関数を呼び出すにはファンクションとして定義する必要があります。
CREATE FUNCTION mecab(text) RETURNS text AS 'mecab_simple', 'mecab' LANGUAGE C STRICT;
その際、パス、ファイル名に関しては以下のルールに従います
第一引数
- 絶対パス
- 名前が$libdirという文字列から始まる場合、その部分はPostgreSQLパッケージのライブラリディレクトリ
- 名前にディレクトリ部分がない場合、そのファイルはdynamic_library_path設定変数で指定されたパス内
- 拡張子(.so、.dll、.dylib)を補って上記パスを検索
- 上記以外は失敗
第二引数
- 省略時は第一引数と同じとみなす
- 指定時は関数
結果
おお!どうやらうまくいったみたいですね!!
上記の一式をDockerfileでまとめているので、 ↓こちらに置いておきます。
おまけ
デフォルト機能ではないのですが、Postgresqlの拡張機能にplpython3uというものがあり、Postgresqlからpythonのスクリプトが実行できるようです。
事前準備として、postgresql-plpython3-12といったパッケージが必要なようです。
事前準備
apt-get install -y postgresql-plpython3-12
Postgresql上でExtensionを有効にします。
CREATE EXTENSION plpython3u;
Pythonのバージョンを確認してみます。
ファンクションを定義します。
CREATE FUNCTION pyversion() RETURNS text AS $$ import sys return sys.version $$ LANGUAGE plpython3u;
バージョン確認
select pyversion();
結果
3.7.3 (default, Dec 20 2019, 18:57:59) [GCC 8.3.0]
こちらも機会があればもう少し調べてみたいと思いました。
最後に
Oracleと同様PostgresqlでもC/C++の呼び出しはサポートされていました。今回速度検証まではできませんでした、また、このような組み込みモジュールを使うのは一般的ではないかもしれないですが、マクロが充実していて、以前のOracleの時よりは楽にプログラミングできる印象*5でした。またpythonを利用するのも比較的簡単に実現できたので、こちらはもう少し深堀したくなりました。
最後になりましたが、アスタミューゼでは現在、エンジニア・デザイナーを絶賛大大大募集中です! 興味のある方はぜひ下記バナーからご応募ください!!