2013-04-17 50 views
3

我可以想象,由日期(特别是对于日志)的表分区是广泛使用的东西,但我无法为我的问题找到一个很好的答案。Postgres按星期分区

我想按周创建一个表分区(记录的数量要大到使其每月)。它每周的原因是我需要一个算法的数据,该算法将查找日期作为过程的一部分。

我的问题是我希望它创建分区考虑周和使用“典型”的方法,我将不得不手动创建它。像这样的东西。

CREATE TABLE measurement_y2013w01 (
CHECK (logdate >= DATE '2013-01-07' AND logdate < DATE '2013-01-14') 
    ) INHERITS (measurement); 

    CREATE TABLE measurement_y2006w02 (
CHECK (logdate >= DATE '2013-01-14' AND logdate < DATE '2013-01-21') 
    ) INHERITS (measurement); 

... 

但我希望它自动生成。我不想为每个星期一个一个地创建分区。

我的命名规则是yYYYYwWW,用于命名分区或启动datadYYYYMMDD。

我想使用这样的事情在插入时检查分区:

SELECT 
    nmsp_parent.nspname AS parent_schema, 
    parent.relname  AS parent, 
    nmsp_child.nspname AS child, 
    child.relname  AS child_schema 
FROM pg_inherits 
    JOIN pg_class parent  ON pg_inherits.inhparent = parent.oid 
    JOIN pg_class child   ON pg_inherits.inhrelid = child.oid 
    JOIN pg_namespace nmsp_parent ON nmsp_parent.oid = parent.relnamespace 
    JOIN pg_namespace nmsp_child ON nmsp_child.oid = child.relnamespace 

,如果分区不存在插入之前建立,但是,这个缝了很多效率低下,考虑到插入的记录数。

我的另一种选择是每周创建一个外部进程创建此分区,但我试图避免这种情况。

有没有更有效的解决方案,我缺少这个,例如,用于月度检查?

+0

您将有一个外部进程删除或移动旧分区不是吗?如果是这样,创建分区有什么问题? –

+0

您可能想看看pg_partman,它可以自动执行关于分区的许多事情:https://github.com/keithf4/pg_partman/blob/master/doc/pg_partman.md –

回答

2

您可以使用date_trunc函数将数据值舍入到一周的第一天。对于分区的命名,你可以在今年YYWW使用年份和周数:

CREATE TABLE measurement_1301 (
    CHECK (date_trunc('week', logdate)::date = DATE '2013-01-07')) 
    INHERITS (measurement); 

CREATE TABLE measurement_1302 (
    CHECK (date_trunc('week', logdate)::date = DATE '2013-01-14')) 
    INHERITS (measurement); 

CREATE TABLE measurement_1303 (
    CHECK (date_trunc('week', logdate)::date = DATE '2013-01-21')) 
    INHERITS (measurement); 

-- Default partition: 
CREATE TABLE measurement_default() INHERITS (measurement); 

对于分区名代使用to_char(logdate::date, 'YYWW'),如果你,如果你喜欢yYYYYwWWto_char(logdate::date, '"y"YYYY"w"WW')

,并检查现有的您可以使用非常简单的查询分区:

SELECT relname FROM pg_class 
WHERE relname ~ '^measurement_[0-9]{4}$' 
ORDER BY RIGHT(relname,4) DESC 

数据路由触发器插入适当的部分如果在给定的一周内没有分区,则会回到默认值。

CREATE OR REPLACE FUNCTION measurement_insert_trigger() 
RETURNS TRIGGER AS $$ 
BEGIN 

    IF to_char(NEW.logdate::date, 'YYWW') = '1301' THEN 
     INSERT INTO measurement_1301 VALUES (NEW.*); 
    ELSIF to_char(NEW.logdate::date, 'YYWW') = '1302' THEN 
     INSERT INTO measurement_1302 VALUES (NEW.*); 
    ELSIF to_char(NEW.logdate::date, 'YYWW') = '1303' THEN 
     INSERT INTO measurement_1303 VALUES (NEW.*); 
    -- ... 
    ELSE 
     INSERT INTO measurement_default VALUES (NEW.*); 
    END IF; 
    RETURN NULL; 
END; 
$$ LANGUAGE plpgsql; 

CREATE TRIGGER measurement_insert_tr BEFORE INSERT ON measurement 
FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger() 

您将预先创建所有分区。或者,您可以使用默认分区和时间重新分区存储在那里的数据,创建新分区并调整插入触发器。

PS你可以找到基于触发器这里http://hg.nowitworks.eu/postgresql-triggers-and-partitions

+0

+1,因为它对于大部分步骤。如果它们不存在,仍然不能完全回答创建分区的主要部分。 – RGPT

4

确定了分区解决方案,脚本,所以让我们创建自己的函数来处理!

CREATE OR REPLACE FUNCTION create_partition_measurement(DATE, DATE) 
returns void AS $$ 
DECLARE 
create_query text; 
BEGIN 
    FOR create_query IN SELECT 

     'CREATE TABLE measurement_' || TO_CHAR(d, 'YYYY_WW') || ' (
     CHECK (EXTRACT(YEAR FROM logdate) = EXTRACT(YEAR FROM TIMESTAMP ''' || d || ''') AND EXTRACT(WEEK FROM logdate) = EXTRACT(WEEK FROM TIMESTAMP ''' || d || ''')) 
     ) INHERITS (measurement);' 

     FROM generate_series($1, $2, '1 week') AS d LOOP 

     EXECUTE create_query; 

    END LOOP; 
END; 
$$ 
language plpgsql; 

有了这个,你现在可以调用类似

SELECT create_partition_measurement ('2015/02/08','2015/03/01'); 

,有你的分区上创建。自动化的第一步,完成。

我测试了这一切在我自己的数据库使用下面的测试表:

CREATE TABLE measurement (id INT NOT NULL PRIMARY KEY, id_user INT NOT NULL, logdate TIMESTAMP NOT NULL); 

用上面的功能创建的分区后,我能:

  • 将数据插入正确的划分;
  • 尝试将数据从一周插入到另一周的分区时出现错误;
  • 自动创建分区几个星期,并;
  • 如果我尝试创建一个已经存在一个星期的分区,请获取一个错误。

这应该是足够=)

现在,有关自动创建过程。我使用一个简单的cron脚本来为我每个月调用一次这个函数和一些监控脚本,以确保一切正常。 这个cron是这样的:

0 0 1 * * /var/lib/postgresql/create_partitions.sh 

和脚本将使用当前日期和当前日期+ 1个月运行的命令。它看起来是这样的:

startDate=`date "+%Y/%m/%d"` 
endDate=`date -u -d "+1 month -$(date +%d) days" "+%Y/%m/%d" 
psql -U "$dbUser" -w -c "SELECT create_partition_measurement('$startDate','$endDate');" 

如果您需要包括索引的PK,FKS表中,或与触发器有助于使这一切工作,只是让我知道。

0

如果有帮助,我写了一个postgres触发器来创建一个表格,该表格是按日自动分区的。创建继承表会自动发生。要按星期分区,您必须更改day->字符串映射,就是这样。

https://github.com/bitdivine/pg_day_partitions