postgresでserial型のキーが溢れてしまった
2015-02-14 / PostgreSQL, DataBase
稼働から1年くらいで、serial型にしていたキーの値が上限に達してしまった。
データの増加量の見積もりが甘かったのが原因で、キーの型をbigintにして解決した。
再現
PostgreSQLのserial型はテーブル作成時に同時にsequenceを作ってくれる。
test=# create table sample(
test(# id serial,
test(# number integer,
test(# created_at time default current_timestamp
test(# );
test=# \d
リレーションの一覧
スキーマ | 名前 | 型 | 所有者
----------+---------------+------------+----------
public | sample | テーブル | postgres
public | sample_id_seq | シーケンス | postgres
(2 行)
sequenceを利用するテーブル(sample)では、実際のデータ型はintになる。一方、sequence(sample_id_seq)の型はbigintになっている。
test=# \d sample*
テーブル "public.sample"
列 | 型 | 修飾語
------------+------------------------+-----------------------------------------------------
id | integer | not null default nextval('sample_id_seq'::regclass)
number | integer |
created_at | time without time zone | default now()
シーケンス "public.sample_id_seq"
列 | 型 | 値
---------------+---------+---------------------
sequence_name | name | sample_id_seq
last_value | bigint | 1
start_value | bigint | 1
increment_by | bigint | 1
max_value | bigint | 9223372036854775807
min_value | bigint | 1
cache_value | bigint | 1
log_cnt | bigint | 0
is_cycled | boolean | f
is_called | boolean | f
所有者: public.sample.id
適当にインサートする。
test=# insert into sample(number) values(100);
INSERT 0 1
test=# insert into sample(number) values(200);
INSERT 0 1
test=# insert into sample(number) values(300);
INSERT 0 1
test=# select * from sample;
id | number | created_at
----+--------+-----------------
1 | 100 | 05:58:37.695097
2 | 200 | 05:58:41.566133
3 | 300 | 05:58:44.077084
(3 行)
test=# \d sample_id_seq
シーケンス "public.sample_id_seq"
列 | 型 | 値
---------------+---------+---------------------
sequence_name | name | sample_id_seq
last_value | bigint | 3
start_value | bigint | 1
increment_by | bigint | 1
max_value | bigint | 9223372036854775807
min_value | bigint | 1
cache_value | bigint | 1
log_cnt | bigint | 30
is_cycled | boolean | f
is_called | boolean | t
所有者: public.sample.id
last_valueが3になっている。
とりあえずintの上限値近くまで現在値を増やしてみる。
test=# select setval('sample_id_seq', 2147483645, false);
setval
------------
2147483645
(1 行)
test=# \d sample_id_seq
シーケンス "public.sample_id_seq"
列 | 型 | 値
---------------+---------+---------------------
sequence_name | name | sample_id_seq
last_value | bigint | 2147483645
start_value | bigint | 1
increment_by | bigint | 1
max_value | bigint | 9223372036854775807
min_value | bigint | 1
cache_value | bigint | 1
log_cnt | bigint | 0
is_cycled | boolean | f
is_called | boolean | f
所有者: public.sample.id
last_valueが2147483645になった。あと数回でintの上限に達する。
もう一度、適当にインサートする。
test=# insert into sample(number) values(400);
INSERT 0 1
test=# insert into sample(number) values(500);
INSERT 0 1
test=# insert into sample(number) values(600);
INSERT 0 1
test=# insert into sample(number) values(700);
ERROR: integerの範囲外です
test=# select * from sample;
id | number | created_at
------------+--------+-----------------
1 | 100 | 05:58:37.695097
2 | 200 | 05:58:41.566133
3 | 300 | 05:58:44.077084
2147483645 | 400 | 06:06:09.040102
2147483646 | 500 | 06:06:13.327061
2147483647 | 600 | 06:06:17.888157
(6 行)
sampleのidにintの上限値を超えた値(2147483648)を入れようとしたので、残念な結果になった。
sample_id_seqは、sampleのインサートが成功したかどうかに関係なく、値は増加していく。あくまでもbigintなので、使う方の都合なんて関係なく、intの上限を超えてくる。
test=# insert into sample(number) values(800);
ERROR: integerの範囲外です
test=# insert into sample(number) values(900);
ERROR: integerの範囲外です
test=# insert into sample(number) values(1000);
ERROR: integerの範囲外です
test=# \d sample_id_seq
シーケンス "public.sample_id_seq"
列 | 型 | 値
---------------+---------+---------------------
sequence_name | name | sample_id_seq
last_value | bigint | 2147483651
start_value | bigint | 1
increment_by | bigint | 1
max_value | bigint | 9223372036854775807
min_value | bigint | 1
cache_value | bigint | 1
log_cnt | bigint | 30
is_cycled | boolean | f
is_called | boolean | t
所有者: public.sample.id
なので、sampleの方でidをbigintに変更するしかない。
test=# alter table sample alter id type bigint;
ALTER TABLE
test=# \d sample
テーブル "public.sample"
列 | 型 | 修飾語
------------+------------------------+-----------------------------------------------------
id | bigint | not null default nextval('sample_id_seq'::regclass)
number | integer |
created_at | time without time zone | default now()
ちゃんとカラムがbigintになっている。
もう一度インサートしてみる。
test=# insert into sample(number) values(1100);
INSERT 0 1
test=# insert into sample(number) values(1200);
INSERT 0 1
test=# insert into sample(number) values(1300);
INSERT 0 1
test=# select * from sample;
id | number | created_at
------------+--------+-----------------
1 | 100 | 05:58:37.695097
2 | 200 | 05:58:41.566133
3 | 300 | 05:58:44.077084
2147483645 | 400 | 06:06:09.040102
2147483646 | 500 | 06:06:13.327061
2147483647 | 600 | 06:06:17.888157
2147483652 | 1100 | 06:17:54.635158
2147483653 | 1200 | 06:17:57.313085
2147483654 | 1300 | 06:17:59.722082
(9 行)
インサートできた。
作成時
初めからbigintにしたければ、bigserialを使う。
test=# create table sample2(
test(# id bigserial,
test(# number integer,
test(# created_at time default current_timestamp
test(# );
CREATE TABLE
est=# \d sample2*
テーブル "public.sample2"
列 | 型 | 修飾語
------------+------------------------+------------------------------------------------------
id | bigint | not null default nextval('sample2_id_seq'::regclass)
number | integer |
created_at | time without time zone | default now()
シーケンス "public.sample2_id_seq"
列 | 型 | 値
---------------+---------+---------------------
sequence_name | name | sample2_id_seq
last_value | bigint | 1
start_value | bigint | 1
increment_by | bigint | 1
max_value | bigint | 9223372036854775807
min_value | bigint | 1
cache_value | bigint | 1
log_cnt | bigint | 0
is_cycled | boolean | f
is_called | boolean | f
所有者: public.sample2.id
あと何年運用できる?
bigintに変更したのは良いんだけど、また上限に達したらどうするのか?
約1年の運用でintの上限値に達したので、同じペースでデータが増え続けると、
9223372036854775807 / 2147483647 = 4294967298
ということで、4294967298年運用できる。
仮に今年から100万倍のデータ量になっても、
9223372036854775807 / 2147483647000000 = 4294.967298
4294年運用できるらしい。
上限に達する前に、システムリプレースしてそうだし、現実的には大丈夫そうだ。