Postgres-DBに既存のテーブルがあります。デモのために、これは次のようになります。
create table myTable(
forDate date not null,
key2 int not null,
value int not null,
primary key (forDate, key2)
);
insert into myTable (forDate, key2, value) values
('2000-01-01', 1, 1),
('2000-01-01', 2, 1),
('2000-01-15', 1, 3),
('2000-03-02', 1, 19),
('2000-03-30', 15, 8),
('2011-12-15', 1, 11);
ただし、これらのいくつかの値とは対照的に、myTable
は実際には巨大であり、継続的に増加しています。このテーブルからさまざまなレポートを生成していますが、現在、レポートの98%は1か月で動作し、残りのクエリはさらに短い時間で動作します。多くの場合、私のクエリにより、Postgresはこの巨大なテーブルに対してテーブルスキャンを実行します。問題を軽減する方法を探しています。 テーブル分割 は私の問題に完全に適合しているようです。テーブルを数か月に分割できました。しかし、既存のテーブルをパーティションテーブルにするにはどうすればよいですか?マニュアルは明示的に述べています:
通常のテーブルを分割テーブルに、またはその逆に変換することはできません
したがって、現在のテーブルを分析して移行する独自の移行スクリプトを開発する必要があります。ニーズは次のとおりです。
myTable
がカバーする時間枠は不明です。パーティション化するためにテーブルをどのように移行できますか?
Postgres 10で導入された「宣言型パーティショニング」は、正しいテーブルにリダイレクトする巨大なif/elseステートメントでトリガーやルールを生成するなどの多くの作業を軽減します。 Postgresはこれを自動的に行うことができます。移行から始めましょう:
古いテーブルの名前を変更して、新しいパーティションテーブルを作成します。
alter table myTable rename to myTable_old;
create table myTable_master(
forDate date not null,
key2 int not null,
value int not null
) partition by range (forDate);
説明はほとんど必要ありません。古いテーブルの名前が変更され(データの移行後は削除します)、パーティションのマスターテーブルを取得します。これは基本的に元のテーブルと同じですが、インデックスがありません)
新しいパーティションを必要に応じて生成できる関数を作成します。
create function createPartitionIfNotExists(forDate date) returns void
as $body$
declare monthStart date := date_trunc('month', forDate);
declare monthEndExclusive date := monthStart + interval '1 month';
-- We infer the name of the table from the date that it should contain
-- E.g. a date in June 2005 should be int the table mytable_200506:
declare tableName text := 'mytable_' || to_char(forDate, 'YYYYmm');
begin
-- Check if the table we need for the supplied date exists.
-- If it does not exist...:
if to_regclass(tableName) is null then
-- Generate a new table that acts as a partition for mytable:
execute format('create table %I partition of myTable_master for values from (%L) to (%L)', tableName, monthStart, monthEndExclusive);
-- Unfortunatelly Postgres forces us to define index for each table individually:
execute format('create unique index on %I (forDate, key2)', tableName);
end if;
end;
$body$ language plpgsql;
これは後で重宝します。
基本的にマスターテーブルに委任するだけのビューを作成します。
create or replace view myTable as select * from myTable_master;
ルールを挿入すると、パーティションテーブルが更新されるだけでなく、必要に応じて新しいパーティションも作成されるように、ルールを作成します。
create or replace rule autoCall_createPartitionIfNotExists as on insert
to myTable
do instead (
select createPartitionIfNotExists(NEW.forDate);
insert into myTable_master (forDate, key2, value) values (NEW.forDate, NEW.key2, NEW.value)
);
もちろん、update
とdelete
も必要な場合は、単純なルールも必要です。
古いテーブルを実際に移行します。
-- Finally copy the data to our new partitioned table
insert into myTable (forDate, key2, value) select * from myTable_old;
-- And get rid of the old table
drop table myTable_old;
これでテーブルの移行は完了しました。パーティションがいくつ必要かを知る必要はなく、ビューmyTable
は完全に透過的です。以前と同じようにそのテーブルから簡単に挿入して選択できますが、パーティション分割によってパフォーマンスが向上する可能性があります。
パーティションテーブルは行トリガーを持つことができないため、ビューが必要なだけであることに注意してください。コードから必要なときにいつでも手動でcreatePartitionIfNotExists
を呼び出すことができる場合は、ビューとそのすべてのルールは必要ありません。この場合、移行中に手動でパーティションを追加する必要があります。
do
$$
declare rec record;
begin
-- Loop through all months that exist so far...
for rec in select distinct date_trunc('month', forDate)::date yearmonth from myTable_old loop
-- ... and create a partition for them
perform createPartitionIfNotExists(rec.yearmonth);
end loop;
end
$$;