PostgreSQLの文字列型の最大長は?

(Last Updated On: )

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のバイナリデータは保存できません。半分以下になります。

大きなバイナリデータを取り扱う場合には注意が必要です。

まとめ

リソースは有限です。どんな物でも最大限/最小限の限界があります。限界がメモリ量やディスク容量依存の場合もありますが、限界があることには変りありません。

ライブラリなどの場合、「使えるところまで使う」でも構いません。しかし、アプリケーションの場合は「ここまでは使える」という制限を設定しておく方が良いです。

こういった制限は

  • 先ずは入力データバリデーションで制限以内であることを保証する
  • ロジックのバリデーション/エラー処理でビジネスルールとの適合性を検証する
  • フェイルセーフ対策として、実際に使う場合に制限以内であることをバリデーションする

といった多層防御で護る必要があります。アプリの場合、「フェイルセーフ対策だけに頼る」のは脆弱な作り方になります。

バリデーションには3種類のバリデーションがある 〜 セキュアなアプリケーションの構造 〜

 


    1. UTF-8の一文字は1バイトから4バイトまでの可変長です。 
  1. ここはソースで確認していないですが、海外さんが仰るの間違いないでしょう。 

関連: 取り扱える文字列の最大サイズは?

単体の文字列として取り扱える最大サイズは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のクエリをそうそう送ることはないとは思いますが、限界に挑戦する場合には注意が必要でしょう。

投稿者: yohgaki