11 버전에서 일반 테이블을 파티션 테이블로 바꾸기

11 버전에서 일반 테이블을 파티션 테이블로 바꾸기

운영 환경에서 기존 테이블을 파티션 테이블로 바꾸기는 작업은 생각보다 까다롭습니다.

이 글은 지난 PGConf APAC 2019 에서 대형 테이블 다루기 발표자료에 대한 글쓴이 나름대로의 해석입니다.

시나리오는 다음과 같습니다.

  • 준비 (단일 트랜잭션)
    • 새 파티션 테이블을 만든다
    • 기존 테이블을 다른 이름으로 바꾼다
    • 기존 테이블과 이름이 같은 뷰를 만드는데, 그 내용은 새 파티션 테이블과 이름이 바뀐 옛 일반 테이블을 모두 함께 조회하는 것이다
    • 새로 만든 뷰에 insert, delete rule을 추가하고
    • update 에서 사용할 트리거 함수를 만들고, 새로 만든 뷰에 트리거를 지정한다.
  • 자료 이동 (단일 트랜잭션)
    • 기본키 단위로 하나씩 모든 자료에 대해서 update 구문을 실행한다
  • 뒷 정리 (단일 트랜잭션)
    • 뷰를 지우고,
    • 파티션 테이블을 뷰 이름으로 바꾼다
다음은 뷰에 insert, delete 룰과 update 트리거를 지정하는 작업을 pgbench 테이블을 대상으로 한 것입니다.

BEGIN;
-- 새 해쉬 파티션 상위 테이블을 만들고
CREATE TABLE new_pgbench_accounts (
  aid INT NOT NULL PRIMARY KEY, 
  bid INT, 
  abalance INT, 
  filler CHAR(84)
) PARTITION BY hash (aid);

-- 다섯개의 하위 테이블을 만들고
CREATE TABLE pgbench_accounts_0 PARTITION OF new_pgbench_accounts FOR 
VALUES 
  WITH (modulus 5, remainder 0);
CREATE TABLE pgbench_accounts_1 PARTITION OF new_pgbench_accounts FOR 
VALUES 
  WITH (modulus 5, remainder 1);
CREATE TABLE pgbench_accounts_2 PARTITION OF new_pgbench_accounts FOR 
VALUES 
  WITH (modulus 5, remainder 2);
CREATE TABLE pgbench_accounts_3 PARTITION OF new_pgbench_accounts FOR 
VALUES 
  WITH (modulus 5, remainder 3);
CREATE TABLE pgbench_accounts_4 PARTITION OF new_pgbench_accounts FOR 
VALUES 
  WITH (modulus 5, remainder 4);

-- 기존 테이블의 이름을 바꾸고
ALTER TABLE 
  pgbench_accounts RENAME TO old_pgbench_accounts;

-- 기존 테이블 이름으로 뷰를 만들고
CREATE VIEW pgbench_accounts AS 
SELECT 
  * 
FROM 
  old_pgbench_accounts 
UNION ALL 
SELECT 
  * 
FROM 
  new_pgbench_accounts;

-- 그 뷰에 insert 룰 추가
CREATE RULE pgbench_accounts_insert AS 
ON INSERT TO pgbench_accounts 
DO INSTEAD 
INSERT INTO new_pgbench_accounts (aid, bid, abalance, filler) 
VALUES 
  (new.*);

-- 그 뷰에 delete 룰 추가
CREATE RULE pgbench_accounts_delete AS ON DELETE TO pgbench_accounts 
DO INSTEAD (
  DELETE FROM 
    new_pgbench_accounts 
  WHERE 
    (
      new_pgbench_accounts.aid = old.aid
    );
DELETE FROM 
  old_pgbench_accounts 
WHERE 
  (
    old_pgbench_accounts.aid = old.aid
  );
);

-- update 트리거추가 
-- (with delete returing insert on conflict 룰을 지원하지 않아 트리거로 만들었다)
CREATE FUNCTION tr_pgbench_accounts_update() RETURNS TRIGGER 
LANGUAGE plpgsql AS $$
begin 
delete from 
  old_pgbench_accounts 
where 
  aid = old.aid;
insert into new_pgbench_accounts 
values 
  (new.*) ON CONFLICT (aid) do 
update 
set 
  (aid, bid, abalance, filler) = row(EXCLUDED.*);
return new;
end;
$$;

CREATE TRIGGER tr_pgbench_accounts_update INSTEAD OF 
UPDATE 
  ON pgbench_accounts FOR EACH ROW 
  EXECUTE PROCEDURE tr_pgbench_accounts_update();
END;