인계받은 DLP 고도화 쪽 PostgreSQL 스키마의 모든 테이블에 같은 네 개의 컬럼이 있었다. created_at·created_by·updated_at·updated_by, 즉 등록한 시각과 사람, 수정한 시각과 사람이다.
Table of contents
Open Table of contents
배경 — 쉰여덟 번 반복된 네 줄
같은 네 줄이 테이블 수만큼 반복돼 있으니 schema.sql 이 그만큼 길어진다. 새 테이블을 만들 때마다 그 네 줄을 다시 적어야 하고 적는 것을 잊으면 그 테이블만 빠진다.
반복을 없애는 방법을 찾아보다가 이 문제가 방법 선택의 문제라는 것을 알게 됐다. 어느 방법을 골라도 무언가를 포기하게 된다.
비교 — 반복을 없애는 세 방법
첫째는 DB 쪽에 BEFORE INSERT OR UPDATE 트리거를 걸어서 자동으로 채우는 것이다. PostgreSQL 11 매뉴얼에 따르면 행 단위 BEFORE 트리거가 돌려주는 행이 실제로 들어갈 행이 되므로, updated_at 에 now() 를 채우는 것은 어느 경로로 들어와도 반드시 된다. 대신 그 로직이 코드 밖에 있어서 안 보이고, updated_by 에 넣을 사람이 누구인지는 DB 가 모른다 — current_user 는 접속 계정이지 로그인한 사용자가 아니다.
둘째는 코드 쪽에서 공통 부모 BaseRepository 를 두고 insert()·update() 가 네 값을 채우게 하는 것이다. 코드는 깔끔해지지만 그 계층을 안 거치고 직접 넣는 경로에서는 안 채워진다.
판단 기준 — 방법마다 다른 것을 포기한다
셋째는 지금처럼 그냥 반복해서 적는 것인데 무엇이 어디 있는지가 명시적으로 보인다. 대신 빠뜨려도 아무도 안 알려 준다.
셋 다 완전한 것이 없으므로 무엇을 포기할지를 정하는 문제였다. 우리는 BaseRepository 쪽을 골랐다. 이유는 네 값 가운데 사람 둘은 어차피 앱만 알아서, DB 트리거로는 절반밖에 못 채우기 때문이다. 그 계층을 안 거치는 경로는 따로 목록으로 관리하기로 했다.
검증 — 컬럼과 값의 구분
방법을 고르고 나서 지금 실제로 값이 들어가고 있는지를 집계했다. 컬럼이 있다는 것과 감사가 되고 있다는 것은 다른 말이기 때문이다.
count(*) 와 count(updated_by) 를 나란히 세어 보니 몇 테이블은 updated_by 가 거의 비어 있었다. 비어 있는 것이 아무도 안 건드렸다는 뜻일 수도 있지만 그 경로가 값을 안 채웠다는 뜻일 수도 있었다.
원인 — 배치 경로가 값을 안 채웠다
확인해 보니 후자였고, 밤에 psql 로 도는 갱신 스크립트가 UPDATE … SET status = … 만 하고 updated_by 를 안 채우고 있었다. 컬럼만 보고 감사가 되고 있다고 생각했으면 그 사실을 몰랐을 것이다.
재발 방지 — 빠진 테이블을 찾는 검사
그래서 네 컬럼이 없는 테이블을 information_schema.columns 에서 NOT EXISTS 로 찾는 조회와, 값이 안 채워지는 경로를 찾는 집계를 검사로 두었다. 어느 방법을 택하든 빠진 것을 찾는 장치는 따로 필요했다.
정리
- 감사 컬럼을 반복 없이 두는 방법마다 다른 것을 포기한다
- DB 트리거는 모든 경로를 잡지만 앱 사용자를 모른다
BaseRepository는 그 계층을 안 거치면 안 채워진다- 반복해서 적는 것은 명시적이지만 빠뜨려도 조용하다
- 어느 쪽을 택하든 빠진 테이블을 찾는 검사를
information_schema.columns로 만든다 - 컬럼이 있다고 감사가 되는 것이 아니다
count(*)와count(updated_by)로 실제로 채워지는지 세어 본다- 비어 있는 것이 그 경로가 안 채웠다는 뜻일 수 있다