LoginSignup
10
13

More than 5 years have passed since last update.

PostgreSQLのタイムスタンプ・パーティション用汎用トリガ関数

Last updated at Posted at 2016-10-06

はじめに

この記事では、
PostgreSQLのパーティションテーブル自動生成を参考に、timestamp型のtimeカラムを分割キーとするパーティションを自動生成するための汎用トリガ関数を例示します。

パーティション用汎用トリガ関数

このトリガ関数は、参考記事のサンプルと同様に、まずはパーティションの子テーブルにINSERTを試みます。子テーブルが未作成でINSERTが失敗したときには、その子テーブルを自動作成し、再度INSERTを試みます。

このトリガ関数では、パーティション分割の単位(日単位や月単位など)と子テーブル名の添え字フォーマットをCREATE TRIGGER実行時にユーザが指定します。つまり、異なる単位のパーティションを作成するにあたって、別途トリガ関数を用意する必要はありません。このトリガ関数のみで様々な分割単位のパーティションを生成できます。

-- パーティションの子テーブルを作成する関数
CREATE OR REPLACE FUNCTION create_timerange_partition (
  partition_key timestamp,  -- INSERTするレコードの分割キーの値
  parent_table name,        -- パーティションの親テーブルの名前
  range_interval text,      -- パーティションの分割単位(例えば、hour, day, monthなど)
  suffix_format text)       -- 子テーブルの名前の添え字フォーマット
                            -- ※分割単位と添え字フォーマットは整合していること
RETURNS void AS $$
DECLARE
  mintime timestamp := date_trunc(range_interval, $1);
  maxtime timestamp := mintime + ('1 ' || range_interval)::interval;
BEGIN
  -- 引数の値から子テーブルの名前やパーティション範囲を計算して、子テーブルを作成する
  EXECUTE 'CREATE TABLE IF NOT EXISTS ' ||
    parent_table || to_char(partition_key, suffix_format) ||
    ' (LIKE ' || parent_table || ' INCLUDING INDEXES, CHECK(''' ||
    mintime || ''' <= time AND time < ''' || maxtime ||
    ''')) INHERITS (' || parent_table || ')';
END;
$$ LANGUAGE plpgsql;


-- パーティションへのINSERTトリガ関数
CREATE OR REPLACE FUNCTION insert_timerange_partition ()
RETURNS TRIGGER AS $$
DECLARE
  -- 親テーブルの名前はトリガ関数の変数から自動的に取得
  parent_table name := TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME;
  -- パーティションの分割単位は、トリガ作成時に指定
  range_interval text := TG_ARGV[0];
  -- 子テーブル名の添え字フォーマットは、トリガ作成時に指定
  suffix_format text := TG_ARGV[1];
  retry_count integer;
BEGIN
  -- 無限ループに陥らないように、INSERT→子テーブル作成→INSERTの試行は1回のみに制限
  FOR retry_count IN 0 .. 1 LOOP
    BEGIN
      EXECUTE 'INSERT INTO ' || parent_table ||
        to_char(NEW.time, suffix_format) ||
        ' VALUES (($1).*)' USING NEW;
      RETURN NULL;
    EXCEPTION WHEN undefined_table THEN
      -- 無限ループに陥りそうな場合は例外発生
      IF retry_count >= 1 THEN
        RAISE EXCEPTION 'could not insert data into "%" partition',
          parent_table;
      END IF;

      -- 子テーブルが未作成の場合は、作成して再度INSERTを試みる
      PERFORM create_timerange_partition(
        NEW.time, parent_table,
        range_interval, suffix_format);
    END;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

汎用トリガ関数を使ったパーティション化

ここでは、上記の汎用トリガ関数を使い、psql経由でのCPU使用率のテーブル蓄積で利用したcpu_usageテーブルをパーティション化してみます。cpu_usageテーブルの定義は以下のとおりです。

CREATE TABLE cpu_usage (
  time timestamp DEFAULT now(),
  us smallint,
  sy smallint,
  id smallint,
  wa smallint
);

日単位パーティション

cpu_usageテーブルを日単位にパーティション化したい場合は、CREATE TRIGGER実行時に、パーティションの分割単位として'day'、子テーブル名の添え字フォーマットとして(例えば)'_YYYYMMDD'を指定します。

CREATE TRIGGER insert_cpu_usage_partition
  BEFORE INSERT ON cpu_usage FOR EACH ROW
  EXECUTE PROCEDURE
  -- 第1引数に分割単位、第2引数に添え字フォーマットを指定
  insert_timerange_partition('day', '_YYYYMMDD');

これでcpu_usageテーブルのパーティション化は終わりです。cpu_usageテーブルにレコードをINSERTすると、日単位パーティションの子テーブルが自動的に作成されて、そこにレコードがINSERTされます。

-- COPY FROM PROGRAMを使い、vmstatからCPU使用率を取得してテーブルにコピー
COPY cpu_usage(us, sy, id, wa)
  FROM PROGRAM 'vmstat | tail -1 | awk ''{print $13 "," $14 "," $15 "," $16}'''
  WITH (FORMAT csv);

-- 日単位で子テーブルが自動作成されたのを確認
\dt
               List of relations
 Schema |        Name        | Type  |  Owner   
--------+--------------------+-------+----------
 public | cpu_usage          | table | postgres
 public | cpu_usage_20161007 | table | postgres

月単位パーティション

汎用トリガ関数を使って、CREATE TRIGGER実行時の指定を変えるだけで、cpu_usageテーブルを月単位にパーティション化できます。

CREATE TRIGGER insert_cpu_usage_partition
  BEFORE INSERT ON cpu_usage FOR EACH ROW
  EXECUTE PROCEDURE
  insert_timerange_partition('month', '_YYYYMM');
10
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
13