PostgreSQL の Oid 問題を解決しようとして失敗

ネタです。
PostgreSQL はオブジェクトの管理に Oid という 32bit 値を使っている。32 bit なので約 40 億個しか Oid で管理されるオブジェクトを作る事が出来ない。
これは BLOB のデータを 40億個以上、つっこもうとするとうれしくない。


先日 PostgreSQL のコードを読んでいたら、postgres_ext.h で核心部分を発見。

/*
 * Object ID is a fundamental type in Postgres.
 */
typedef unsigned int Oid;

32bit 前提で unsigned int はどうなんだろうというのはおいておく。ここを

typedef uint64_t Oid;

と書き換えたらどうなるか。ひょっとしたらうまく動いて、2^64 個のオブジェクトを扱えるようになるかもれない!。
まあ現実的な予想では、ビルドできないだろうに 1票。


make すると、警告がいくつか出る(後述)がビルドできてしまった。
make check でひっかかる。

pg_regress: initdb failed
Examine ./log/initdb.log for the reason.
Command was: "/Users/taro/Desktop/postgresql-8.3.6/src/test/regress/./tmp_check/install//tmp/local/bin/initdb" -D "/Users/taro/Desktop/postgresql-8.3.6/src/test/regress/./tmp_check/data" -L "/Users/taro/Desktop/postgresql-8.3.6/src/test/regress/./tmp_check/install//tmp/local/share/postgresql" --noclean > "./log/initdb.log" 2>&1

initdb.log を見ると

Running in noclean mode.  Mistakes will not be cleaned up.
The files belonging to this database system will be owned by user "taro".
This user must also own the server process.

The database cluster will be initialized with locale C.
The default database encoding has accordingly been set to SQL_ASCII.
The default text search configuration will be set to "english".

creating directory /Users/taro/Desktop/postgresql-8.3.6/src/test/regress/./tmp_check/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 20
selecting default shared_buffers/max_fsm_pages ... 2400kB/20000
creating configuration files ... ok
creating template1 database in /Users/taro/Desktop/postgresql-8.3.6/src/test/regress/./tmp_check/data/base/1 ... FATAL:  cache lookup failed for function 0
child process exited with exit code 1
initdb: data directory "/Users/taro/Desktop/postgresql-8.3.6/src/test/regress/./tmp_check/data" not removed at user's request

エラーメッセージを手がかりに探そうと思ったら 84 箇所も同じようなメッセージがあるよ。コピペ。。


がんばって該当箇所を見つけた。fmgr_info_cxt_security 。
fmgr_info で FmgrInfo 構造体を埋める処理で呼び出されるもの。

/* Otherwise we need the pg_proc entry */
procedureTuple = SearchSysCache(PROCOID, ObjectIdGetDatum(functionId),
                                0, 0, 0);

if (!HeapTupleIsValid(procedureTuple))
elog(ERROR, "cache lookup failed for function %u", functionId);
procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);


で、見ていくと ObjectIdGetDatum がいかにも怪しい。

/*
 * ObjectIdGetDatum
 *		Returns datum representation for an object identifier.
 */

#define ObjectIdGetDatum(X) ((Datum) SET_4_BYTES(X))
#define SET_4_BYTES(value)	(((Datum) (value)) & 0xffffffff)

Oid は 4 byte 前提コード 発見。関連するコメントを見つけた。
Datum に値をつっこむときは下位4byte 以外は捨ててしまうらしい。

/*
 * Port Notes:
 *	Postgres makes the following assumption about machines:
 *
 *	sizeof(Datum) == sizeof(long) >= sizeof(void *) >= 4

今回はこれを満たせないのでどうしようもない。(32bitでコンパイルしているから)


ちなみにコンパイラの警告ではまずそうな場所は見つけられなかった。
↓こういうのばっかり。

droplang.c:231: warning: format ‘%u’ expects type ‘unsigned int’, but argument 3 has type ‘Oid’

所感

  • BLOB の実装が Oid を使うのが悪い
  • 64bit 環境でビルドすればひょっとしたらうまくいくかも。
  • 言語処理系もデータベースも OS もオブジェクトの管理に使用する ID には注意しましょう。(uintptr_t とか使えば良いのではないか)


ところで frontend/backend のやり取りの プロトコル: http://developer.postgresql.org/pgdocs/postgres/protocol.html では特に Oid のサイズは決まっていないようだから、この点においては大丈夫かな。