기술

메타데이터를 DB에 둘까 코드에 둘까 — 판별 기준은 'FK로 떨어지는가'였습니다

2026.09.118분 읽기

어떤 B2B 채권 분석 시스템에 조건분석 화면을 붙이는 일이 있었습니다. 사용자가 조건 몇 개를 골라 조합하면 결과 지표가 표로 떨어지는 화면입니다. 업무 정의 문서를 받아 세어 보니 고를 수 있는 조건이 20종, 나오는 결과 지표가 20종이었습니다.

화면 모양 자체는 흔합니다. 그런데 개발에 들어가기 전에 정할 것이 하나 남아 있었습니다. 이 조건과 결과의 메타를 어디에 둘 것인가입니다.


목록 40개를 어디에 둘 것인가

조건구분에는 분류 체계, 업권, 거래 상대 회사, 취득 연도, 고객 속성 같은 항목이 들어갑니다. 결과구분에는 건수, 대상 인원, 잔액, 경과 개월, 회수율 같은 지표가 들어갑니다. 둘을 합치면 40개짜리 목록입니다.

조건구분 20종 — 사용자가 무엇으로 자를지 고르는 축

결과구분 20종 — 무엇을 볼지 고르는 지표

조합은 사용자가 화면에서 정하고, 백엔드는 그 조합대로 쿼리를 만들어 돌립니다

목록 하나를 어디에 두느냐는 사소해 보입니다. 다만 한 번 정하면 그 뒤로 조건이 늘 때마다 같은 결정을 반복하게 됩니다. 그래서 먼저 세 갈래를 늘어놓고 봤습니다.


세 갈래를 늘어놓고 본 것

방식

좋은 점

걸린 지점

전부 DB 마스터

조건이 늘어도 배포 없이 추가할 수 있습니다

분석 로직이 만들어내는 값까지 코드 row 가 되어, 아무도 참조하지 않는 row 가 쌓입니다

전부 코드 상수

구현이 빠르고 분석 로직과 붙어 있습니다

사용자가 관리해야 할 목록까지 상수가 되어 CRUD 가 막힙니다

성질로 가르기

항목마다 제자리에 갑니다

판별 기준을 팀이 같이 들고 있어야 합니다

표를 만들고 나니 앞의 두 줄이 같은 실수를 반대 방향으로 하고 있었습니다. 40개를 성질이 같은 한 덩어리로 본 것입니다. 실제로는 그 안에 두 종류가 섞여 있었습니다.


한쪽에 몰지 않고 성질로 갈랐습니다

업권 같은 항목은 값의 목록이 업무 쪽에 이미 있고, 데이터에도 코드값으로 들어 있습니다. 이런 항목은 DB 공통코드에 두고 FK 로 연결하면 그대로 맞아떨어집니다. 반대로 분류 체계처럼 분석 SQL 이 조건을 조합해 만들어내는 값이 있습니다. 원본 데이터에 그런 코드 컬럼이 없고, 최종 조회도 한글 라벨로 비교합니다.

두 번째 부류를 DB 공통코드로 만들면 무슨 일이 생기는지가 결정을 갈랐습니다. 코드 테이블에 row 를 넣어도 그 값을 만들어내는 쪽은 여전히 분석 쿼리입니다. 쿼리와 코드 테이블 두 군데를 항상 같이 고쳐야 하고, 한쪽만 고쳐도 화면은 멀쩡히 뜹니다. 동기화 부담만 늘고 얻는 것이 없습니다.

그래서 사용자가 관리할 마스터 데이터와 분석 로직의 내부 카탈로그를 다른 곳에 뒀습니다. 앞쪽은 DB 공통코드, 뒤쪽은 백엔드와 프론트의 코드 상수입니다.

135-1.jpg


판별 기준은 질문 세 개입니다

성질로 가른다는 말은 그 자체로는 기준이 아닙니다. 다음에 조건이 하나 더 들어왔을 때 누가 봐도 같은 답이 나와야 기준입니다. 실제로 쓴 순서는 이렇습니다.

비개발자가 값을 직접 추가하거나 고쳐야 합니까. 그렇다면 DB 마스터에 두고 관리 화면을 붙입니다.

데이터에 이미 코드값이 있고 FK 로 깔끔하게 떨어집니까. 그렇다면 DB 공통코드입니다.

분석 로직이 계산해서 만들어내는 값입니까. 그렇다면 코드 상수입니다.

1번부터 순서대로 물어보고 처음 걸리는 곳에서 멈춥니다. 순서가 중요합니다. 2번과 3번은 겉모습이 비슷해서, 목록이 있으니 코드로 만들자는 판단으로 가면 파생값까지 DB 로 끌려 들어갑니다.

135-2.jpg


조회 쪽은 조건마다 객체 하나로

메타를 어디에 둘지 정하고 나면 다음 문제가 남습니다. 사용자가 20종 중 아무거나 골라 조합하는데, 조건마다 필요한 조인도 다르고 WHERE 절 모양도 다릅니다.

조건 종류로 분기하는 큰 switch 문 하나를 두는 방법이 있습니다. 20종이면 그 함수가 곧 수백 줄이 되고, 조건 하나를 고칠 때마다 나머지 19종이 들어 있는 파일을 열게 됩니다.

그래서 조건구분마다 Resolver 객체를 하나씩 뒀습니다. Resolver 는 두 가지만 압니다.

requiredJoins — 이 조건을 쓰려면 어떤 테이블을 조인해야 하는가

toWhere — 사용자가 넣은 값을 받아 WHERE 절 조각을 어떻게 만드는가

선택된 조건 목록
  → 레지스트리에서 조건별 Resolver 조회
  → requiredJoins 합집합 → FROM / JOIN 조립
  → toWhere 결과 연결 → WHERE 조립
  → 최종 쿼리 실행

Resolver 들을 레지스트리에 등록해 두고, QueryBuilder 가 선택된 조건들의 Resolver 만 꺼내 조립합니다. 조인은 여러 조건이 같은 테이블을 요구할 수 있으니 합집합으로 모으고, WHERE 조각은 순서대로 이어 붙입니다. 새 조건이 들어오면 Resolver 를 하나 추가하고 레지스트리에 등록하는 것으로 끝납니다. 기존 19종이 든 파일은 열지 않습니다.

135-3.jpg


베이스 테이블을 먼저 정해야 조립이 됩니다

조립 구조를 짜는 동안 데이터 쪽에서 걸린 것이 있었습니다. 관련 테이블이 여럿인데 성격이 같지 않았습니다. 평가 시점 기준 마스터 테이블이 약 48만 행으로 전체 집합이고, 나머지 두 테이블은 그 부분집합이었습니다. 취득 시점 테이블은 컬럼 구성까지 달라서 진행 상태에 해당하는 컬럼이 아예 없었습니다.

동적 조립은 베이스 테이블이 고정돼야 성립합니다. 어느 테이블에서 출발하고 어떤 순서로 값을 채울지 우선순위 규칙을 먼저 정하고, 그 위에 Resolver 를 얹었습니다. 이 순서를 반대로 하면 Resolver 마다 기준 테이블이 달라져 조인 합집합이 의미를 잃습니다.


정하지 못한 것은 막지 않고 빼뒀습니다

조건 20종 가운데 그날 매핑까지 확정한 것은 두 종이었습니다. 나머지는 어느 컬럼에 붙는지 업무 쪽 확인이 필요해 매핑표에 TBD 로 남았습니다.

전부 정해질 때까지 기다릴 수도 있었습니다. 대신 확정된 부분부터 구조를 세우고, 미결은 QnA 로 등록해 의사결정자에게 넘겼습니다. 앞의 스키마 비대칭도 같은 자리에 올렸습니다. Resolver 구조는 조건 단위로 독립적이라 매핑이 나중에 확정돼도 그 조건의 Resolver 만 채우면 됩니다. 구조를 먼저 세워두면 미결이 진행을 막지 않습니다.


이 방식이 과한 경우

조건이 서너 종뿐이면 Resolver 와 레지스트리는 과한 추상화입니다. 단순 분기가 읽기 편합니다.

메타가 자주 바뀌고 비개발자가 직접 관리해야 하면, 분석 파생값이라도 결국 DB 와 관리 화면이 필요해집니다.

FK 로 떨어지는가의 판별이 항상 명확하지는 않습니다. 경계에 걸리는 항목은 결국 사람이 정합니다.

경계 사례는 결정 자체보다 이유를 남기는 쪽이 중요했습니다. 조건 매핑 문서에 어느 쪽으로 보냈는지와 그 근거를 한 줄씩 적어뒀습니다.


메타 관리 방식을 정하기 전에 확인할 것

이 목록을 비개발자가 직접 늘리거나 고쳐야 합니까

원본 데이터에 이미 코드 컬럼이 있고 FK 로 연결됩니까

값을 만들어내는 주체가 사람입니까, 분석 로직입니까

목록이 앞으로 몇 종까지 늘어날 예정입니까

지금 못 정하는 항목을 미결로 빼도 나머지 구현이 진행됩니까


마무리

메타데이터 관리 방식을 정하는 자리에서는 질문이 DB 냐 코드냐로 잡히기 쉽습니다. 답이 하나여야 한다고 보면, 어느 쪽을 골라도 절반은 어색한 자리에 놓입니다.

판별을 성질 쪽으로 옮기면 질문이 어디에 둘까에서 이 메타가 무엇이냐로 바뀝니다. 사용자가 관리하는 마스터인지, 데이터에 박힌 코드값인지, 분석 로직이 만들어내는 파생값인지 세 갈래로 물어보면 항목마다 갈 자리가 정해집니다.

조회 구조도 같은 성질입니다. 조건이 20종이면 분기 20개를 한 함수에 모으는 대신 조건 단위로 가둡니다. 새 조건이 들어올 때 여는 파일이 하나면, 그 구조는 조건이 30종이 되어도 같은 비용으로 굴러갑니다.

#메타데이터#공통코드#코드상수#Resolver패턴#동적쿼리#데이터모델링