PostgreSQLには
- varchar(n) 型
- char(n) 型
- text型
の文字列型があります。他にバイナリも保存できる
- bytea型
もあります。
text/byteaの最大サイズは1GiBだ、と私も思っていたのですが違いました。
varchar/char型
PostgreSQL 10で試したところ
- 100Ki”文字”まで
保存できます。”文字”なので文字エンコーディングと文字によって使用されるバイト数は変わってきます。英数字のみなら100KiB、UTF-8なら最大でも400KiB、日本語文字列のUTF-8なら凡そ300KiBです。1
マニュアルにも記載されていますが、実際のストレージとしては最大約1GiB保存できる、としてあります。しかし、100Ki文字より大きな値を設定するとCREATE TABLE時にエラーになります。
text型
同じくPostgreSQL 10で試したところ
- 1GiB – 53 byteまで
保存できました。以前のマニュアルには1GiBまで保存可能との記載があったように思います。現在のマニュアルにはtext型を含め、最大値の記載が無い(?)ようです。
ただし、E’abc\nxyz’のようにエスケープシークエンスを使った文字列を使う場合、保存できる最大文字列は1GiB – 53byteより短くなります。入力文字列のエスケープシークエンスを含み1GiB – 53byte以下でなければなりません。
bytea型
同じくPostgreSQL 10で試しまた。bytea型は1GiBくらいまで保存できるハズですが、半分くらいしか保存できませんでした。
PHP 7.2を使ってテストしています。
pg_query_params($db, $sql, [pg_escape_bytea(str_repeat('a', (int)1024*1024*1024*0.5 - 9))]) or die('Query Failed: '. $sql);
512MiB – 9 byte までしか保存できません。これ以上にすると
PHP Warning: pg_query_params(): Query failed: ERROR: out of memory
DETAIL: Cannot enlarge string buffer containing 0 bytes by 1073741824 more bytes.
Query Failed: INSERT INTO mytable (b) VALUES ($1)
とエラーになります。念の為、PHPのエラーメッセージで無いことを確認するため
Cannot enlarge string buffer containing
をPHPのソースディレクトリから
find . -type f | xargs grep 'Cannot enlarge string buffer containing'
を実行しましたが、定義されていませんでした。
因みに今のPostgreSQLのネイティブプリペアードクエリはbyteaエスケープ無しでも使えます。この場合、ほぼ1GiBまで保存できます。しかし、プリペアードクエリをエミュレートしているモノあります。データの最初の部分がbyteaエスケープと同じだと正しく処理できない問題もあります。確実にバイナリデータを保存するにはbyteaエスケープが欠かせません。
約1GiBまでが最大値である原因
エスケープ後のデータサイズが約1GiBでなければならない、というPostgreSQLの制限があります。これがtextで約1GiBよりかなり小さいテキストまで、byteaのHEXエンコーディングでは最大値の約半分しか保存できない理由です。
textではUnicodeエスケープも可能です。例えば、全ての文字をUnicodeエスケープした場合、1GiBの数分の一までしか保存できません。
PostgreSQLのByteaデータエスケープには2種類の方式があります。HexとOctal形式のエスケープ方法があります。Hexだと最大値の約半分のおよそ512MiB、Octalだと最大値約1/4の256MiB程度になります。最近のPostgreSQLではHex形式のエスケープがデフォルトになります。
PostgreSQL文字列型のデータ最大長制限の理由
Facebookで「こんな動作になっている」と書いたら、こんな仕様になっている、とコメントを頂きました。
これはTOASTが導入された際の仕様として、pallocする際にデータの最大値を制限していることが原因のようです。2
今のPostgreSQLのプリペアードクエリはbytea型のカラムに対して、エスケープ無しでも挿入できます。この場合、text型と同じ1GiB – 53 byteまで保存できます。
プリペアードクエリがエミュレートされているライブラリ(パラメーターがエスケープされるライブラリ)では、バイナリ値はPQescapeBytea/PQescapeByteaConnでエスケープしなければなりません。これらのエスケープ関数はHEX形式と古いOCTAL形式の2種類があり、これらを使う環境では約1GiBのバイナリデータは保存できません。半分以下になります。
大きなバイナリデータを取り扱う場合には注意が必要です。
まとめ
リソースは有限です。どんな物でも最大限/最小限の限界があります。限界がメモリ量やディスク容量依存の場合もありますが、限界があることには変りありません。
ライブラリなどの場合、「使えるところまで使う」でも構いません。しかし、アプリケーションの場合は「ここまでは使える」という制限を設定しておく方が良いです。
こういった制限は
- 先ずは入力データバリデーションで制限以内であることを保証する
- ロジックのバリデーション/エラー処理でビジネスルールとの適合性を検証する
- フェイルセーフ対策として、実際に使う場合に制限以内であることをバリデーションする
といった多層防御で護る必要があります。アプリの場合、「フェイルセーフ対策だけに頼る」のは脆弱な作り方になります。
関連: 取り扱える文字列の最大サイズは?
単体の文字列として取り扱える最大サイズはStringInfoの定義で決まっています。
typedef struct StringInfoData
{
char *data;
int len;
int maxlen;
int cursor;
} StringInfoData;
typedef StringInfoData *StringInfo;
src/include/lib/stringinfo.h
とあるので最大でも符号付き32ビット整数の正の数、2GBまでです。最大クエリ長もこれに制限されるので、複数のTEXT/BTYEA型がある場合はこの制限の影響も受けます。2GBのクエリをそうそう送ることはないとは思いますが、限界に挑戦する場合には注意が必要でしょう。
Leave a Comment