Article

PostgreSQLの引数100個制限:max_function_argsを増やす前に渡し方を見直す

PostgreSQLのmax_function_argsは既定値100の読み取り専用値で、変更にはサーバとC拡張の再ビルドが必要です。JSON生成が51組のキーと値で上限を超える仕組みと、行や配列を使って引数をまとめる方法を整理します。

Share

こはるの読みどころ

数えたいのはデータの項目数と、関数へ直接渡す引数の数だよ。JSONの型を保ったまま渡し方を変えられるか、見てみよう!

こはるの読みどころ

JSONへ出力する項目を増やしただけなのに、PostgreSQLから関数の引数が多すぎると言われる。そんな場面で見つかるのが、max_function_argsです。名前を見ると、設定値を引き上げれば解決しそうに感じますね。

Christophe Pettus氏の「All Your GUCs in a Row: max_function_args」は、この上限と再ビルドの関係を取り上げています。アプリケーション側で考えたいのは、増えた項目をすべて独立した引数として渡す必要があるのか、という点です。

JSON生成を例に、何が100個と数えられるのかをたどり、型を保ちながら渡し方を変える方法まで整理していきましょう。仕様と実装の補足はPostgreSQL 18を対象にしています。

JSONの51組が102引数になる理由

jsonb_build_objectは、キーと値を交互に引数へ並べる関数です。したがって、50組なら50 × 2 = 100引数、51組なら51 × 2 = 102引数になります。既定の上限100を超えるのは、後者です。JSON生成関数の仕様から、項目数と引数数を分けて数える必要があると分かります。

この関数は可変長引数を受け取りますが、呼び出しにいくつでも値を列挙できるわけではありません。PostgreSQLの関数呼び出しを解析する実装は、呼び出す関数を特定する前に引数リストの長さを検査します。ここで上限を超えると、関数本体の処理へ進めません。

つまり、このケースで数えられているのは、1回の呼び出しへ直接並べた引数です。JSONオブジェクトが持てるキーの数を50個へ制限している、という意味ではありません。

max_function_argsの変更はC拡張の互換性にも届く

接続先の上限は、次のSQLで読み取れます。SHOWは現在値を表示するコマンドなので、設定を書き換えずに調べられます。SHOWの公式仕様

SQL
-- 接続先のバージョンと関数引数の上限を読み取る。
SHOW server_version;
SHOW max_function_args;

max_function_argsは読み取り専用のプリセット値です。既定値は100で、サーバのビルド時にFUNC_MAX_ARGSから決まります。SETpostgresql.confの編集で増やす設定ではありません。プリセット値の公式説明

定数を書き換えること自体は可能です。ただし、定義元のpg_config_manual.hは、変更にサーバ全体とユーザー定義C関数の再コンパイルが必要だと明記しています。この変更だけを理由にinitdbを実行する必要はありませんが、必要以上に大きな値はメモリと処理時間を浪費するとも記されています。

C拡張まで対象になる理由は、共有ライブラリの互換性検査にあります。PostgreSQLはロード時にmagic blockで互換性を検査し、そのABI情報にはFUNC_MAX_ARGSも含めます。サーバを200、ライブラリを100でビルドした組み合わせは、同じメジャーバージョンでも不一致として拒否されます。上限変更を選ぶなら、利用するC拡張を同じ条件で再ビルドし、読み込みまで検証する作業として見積もる必要があります。

行をto_jsonbへ渡せば、列ごとの引数を増やさずに済む

再ビルドへ進む前に、JSONにしたいデータの形へ戻ってみます。欲しいものが1行のデータなら、列ごとにキーと値を並べず、行全体をto_jsonbの1引数として渡せます。to_jsonbは複合値をJSONオブジェクトへ変換します。行とJSONの変換仕様

次は、出力したい列をサブクエリで選び、その行を変換する最小例です。テーブル作成や更新は行いません。

SQL
-- 選択した列を持つ行を、1個の引数としてJSONBへ変換する。
SELECT to_jsonb(payload)
FROM (
  SELECT 42 AS id, true AS active, 12.5::numeric AS amount
) AS payload;

結果にはidactiveamountが入り、値はそれぞれJSONの数値、真偽値、数値になります。列が増えても、外側のto_jsonbへ渡す引数はpayloadの1個です。

この形を実際のテーブルへ適用するなら、サブクエリで出力列と別名を明示するのが扱いやすいでしょう。これは出力設計上の提案です。行全体をそのまま変換する形では、列の追加が出力項目にも反映されるため、APIで返す項目を固定したい場合は、変換前の列選択が役立ちます。

配列へまとめるときは、可変長引数の受け口と型を合わせる

行のJSON化以外にも、同じ型の値を多数渡す用途があります。可変長引数を受け取る関数では、呼び出し側でVARIADICを指定すると、対応する配列をまとめて渡せます。単にARRAY[...]を置く場合とは関数の引数解決が異なる点に注意したいですね。可変長引数の公式説明

SQL
-- 文字列の配列を可変長引数へ渡し、連結する。
SELECT concat(VARIADIC ARRAY[10::text, 20::text, 30::text]);

これは102030という文字列を返す例です。ただし、任意の関数を配列で呼べるわけではなく、受け側がその可変長引数に対応している必要があります。

また、キーと値をすべてtext[]へまとめてjsonb_build_objectへ渡すと、値も文字列として扱われます。上限を避けられても、JSONの数値や真偽値を保ちたい用途では同じ結果になりません。異なる型の列を持つ行なら、先ほどのto_jsonbの形が候補になります。

max_function_argsへ対処するときは、まず生成されたSQLのどの呼び出しが上限を超えるのかを特定します。そのうえで、行を返したいのか、同じ型の値をまとめて処理したいのかに合わせて入力を組み直せば、C拡張の再ビルドを伴う構成変更を避けられます。上限を増やす判断の前に、データのまとまりを引数へどう表すかを決めることが、保守する範囲を小さくする一歩です。

出典

Share

Related Articles

カテゴリやタグが近い記事を続けて読めるように並べています。