시드 데이터와 초기 적재¶
한 줄 요약: 서비스를 열기 전에 표에 넣어 두는 기본 정보가 시드 데이터다. 모든 표가 원천 데이터로 채워지는 게 아니다. 표마다 채워지는 곳이 다르고, 처음엔 비어 있는 게 정상인 표도 많다.
📍 만난 상황¶
- 2026-11-09 16:55 — pgAdmin 4를 설치하고 대시보드 DB 접속 방법을 들은 직후였다. 다음 단계가 "L2 데이터 채우기"라는 말에 "데이터 채우기는 뭘 채운다는 거야?"라고 물었다. → 그날 일지
- 직전(16:30)에도 "대시보드 기능용 표도 데이터가 없으면 안 되고, 그 데이터가 클릭하우스로 채워지는 거 아닌가"를 물었다.
- 답: 재난 데이터 줄을 복사하는 게 아니다. 표 13개 중 7개에 기본 정보를 넣고, 6개는 비워 둔다.
- 17:01 실제로 채웠다. 지역 코드 453줄, 데이터셋 7줄 등. 두 번 돌려도 줄 수가 같은 것까지 확인했다.
🧩 쉽게 말하면¶
새로 여는 온라인 쇼핑몰과 같다. 문을 열기 전에 상품 목록과 카테고리는 등록해 둬야 한다. 그래야 손님이 들어와서 고를 게 있다.
회원, 장바구니, 찜, 주문 내역은 개업 첫날 비어 있는 게 정상이다. 손님이 와서 쓰면서 쌓인다.
상품 목록도 다 같은 데서 오지 않는다. 상품은 공급처(외부)에서 받고, 카테고리 이름은 사장이 직접 정한다.
시드가 없으면 대시보드를 열어도 "재난 종류" 선택지가 비어 있다. 아무것도 고를 수 없다.
📖 제대로 알기¶
용어¶
- 시드 데이터(seed data: 씨앗 데이터): 서비스가 동작하려면 처음부터 있어야 하는, 사람이 정해 넣는 기본 값. 예: 재난 종류 목록, 관리자 그룹.
- 참조 데이터(reference data: 기준이 되는 목록): 다른 표가 가리키는 코드표. 예: "11680 = 강남구". 외부 기준 자료에서 한 번 옮겨 온다.
- 초기 적재(initial load): 빈 DB에 처음으로 데이터를 넣는 일 전체. 시드 + 참조 데이터 + 첫 동기화.
- 동기화(sync): 다른 시스템의 정보를 우리 표에 맞춰 옮기는 일. 여러 번 반복한다.
- 이용 데이터(트랜잭션 데이터라고도 함): 사람이 서비스를 쓰면서 생기는 기록. 예: 즐겨찾기, 문의.
- 멱등(idempotent: 여러 번 해도 결과가 같음): 적재를 두 번 돌려도 줄이 두 배가 되지 않는 성질.
- 업서트(upsert: update + insert): "있으면 고치고 없으면 넣기". 멱등하게 만드는 흔한 방법이다.
대시보드 표 13개는 세 갈래다¶
| 갈래 | 표 | 처음 상태 |
|---|---|---|
| 플랫폼에서 옴 | dataset, column_meta, region_code |
채워져 있어야 함 |
| 사람이 처음 넣음(시드) | tag, dataset_tag, group_role_map, dataset_meta 초안 |
채워져 있어야 함 |
| 쓰면서 쌓임 | app_user, favorite, user_activity, meta_suggestion, meta_change_log, inquiry |
비어 있는 게 정상 |
"플랫폼에서 옴"도 데이터 줄을 복사하는 게 아니다. 데이터셋 목록, 열 이름, 행 수, 보유 기간 같은 요약 정보만 가져온다.
⚙️ 어떻게 동작하나¶
- 빈 표 만들기: 스키마 SQL(또는 마이그레이션)로 표 13개를 만든다. 이때는 전부 비어 있다(L1).
- 참조 데이터 넣기: 플랫폼의 지역 기준표에서
region_code를 옮긴다. 서울 1, 구 25, 행정동 427로 453줄. - 동기화 돌리기: 동기화 작업이 플랫폼에 요약만 묻는다. 그 답으로
dataset7줄,column_meta100여 줄을 채운다. - 시드 넣기: 태그, 데이터셋↔태그, 관리자 그룹, 설명 초안을 넣는다.
- 순서 지키기:
dataset_tag는dataset과tag를 가리킨다(FK). 그래서 두 표가 먼저 채워져야 한다. - 검증하기: 줄 수를 세고, 한 번 더 돌려 줄 수가 그대로인지 본다(멱등 확인).
- 문 열기: 이제 사람이 쓰면서 나머지 6개 표가 쌓인다.
flowchart LR
P["플랫폼<br/>(요약 정보)"] -->|"동기화"| A["dataset<br/>column_meta<br/>region_code"]
H["관리자<br/>(처음 한 번)"] -->|"시드"| B["tag · dataset_tag<br/>group_role_map<br/>dataset_meta 초안"]
U["이용자<br/>(쓰면서)"] -->|"이용 중 적재"| C["app_user · favorite<br/>user_activity · 제안<br/>변경 기록 · inquiry"]
채워지는 곳이 세 군데다. 사용자가 떠올린 "ClickHouse 하나에서 모든 표로 흘러간다"는 그림과 다르다. 이용자 쪽 표는 ClickHouse와 아무 관계가 없다.
🧪 예시¶
- 예시 1 · 일상에 빗대어: 쇼핑몰 개업 전날이다. 공급처에서 상품 7개 정보를 받았다(동기화). 사장이 카테고리 5개를 만들고, 상품마다 카테고리를 붙였다(시드). 개업 날 아침, 회원 0명·장바구니 0개다. 오후에 손님 3명이 가입하고 찜을 5개 눌렀다(이용 중 적재). 아침의 0은 고장이 아니다.
- 예시 2 · 실제로는: 시드를 멱등하게 넣는 SQL이다.
INSERT INTO app.tag (id, kind, name) VALUES
(1, 'disaster', '침수'),
(2, 'disaster', '지반함몰')
ON CONFLICT (id) DO UPDATE
SET kind = EXCLUDED.kind,
name = EXCLUDED.name;
INSERT INTO app.dataset_tag (dataset_id, tag_id)
SELECT d.id, 1 FROM app.dataset d
WHERE d.table_name = 'norm_flood_trace'
ON CONFLICT DO NOTHING;
INSERT: 태그 두 줄을 넣는다.
- ② ON CONFLICT (id) DO UPDATE: 같은 id가 이미 있으면 새로 넣지 않고 값만 고친다. 그래서 두 번 돌려도 2줄이다.
- ③ 두 번째 INSERT: 데이터셋 id를 직접 적지 않고 table_name으로 찾는다. 플랫폼이 정한 id를 시드 파일에 박아 두지 않으려는 것이다.
- ④ ON CONFLICT DO NOTHING: 연결이 이미 있으면 그냥 넘어간다.
동기화 쪽은 플랫폼에 이런 요약 질문만 던진다.
SELECT count(), min(t_start), max(t_start)
FROM disaster.norm_flood_trace
dataset 한 줄에 적는다.
⚖️ 비교 · 흔한 오해¶
비슷한 말과의 차이¶
| 말 | 넣는 것 | 언제 |
|---|---|---|
| 마이그레이션 | 표의 모양(열, 제약) | 구조가 바뀔 때 |
| 시드 | 처음부터 필요한 값 | 처음 한 번(+ 추가할 때) |
| 픽스처 | 테스트용 가짜 값 | 테스트 돌릴 때만 |
| 동기화 | 외부 시스템의 값 | 반복적으로 |
오해 1: "모든 표는 원천(ClickHouse)으로 채워진다"¶
- 실제: 플랫폼에서 오는 건 3개뿐이고, 그것도 요약 정보다. 4개는 사람이, 6개는 이용자가 채운다.
- 왜 그렇게 생각하기 쉬운가: 오늘 하루 종일 "데이터는 ClickHouse에 있다"를 들었다. 그래서 "DB에 들어가는 건 다 거기서 온다"는 그림이 생겼다. 또 DB를 데이터가 흐르는 관으로 상상하면, 모든 표에 위쪽 수원지가 있어야 할 것처럼 느껴진다.
오해 2: "빈 표가 있으면 뭔가 잘못된 것"¶
- 실제: 즐겨찾기, 문의 같은 표는 개업 첫날 비어 있어야 정상이다. 오히려 가짜 사용자가 운영 DB에 들어 있으면 문제다.
오해 3: "데이터 채우기 = 재난 데이터 복사"¶
- 실제: 재난 데이터 줄은 플랫폼에 그대로 있다. 대시보드 DB에 넣는 건 목록·코드·설명뿐이다.
🛠️ 실무에서는¶
- 시드는 코드로 저장소에 둔다. 손으로 pgAdmin에서 넣으면 다음 사람이 똑같이 만들 수 없다. 스크립트 하나로 다시 만들 수 있어야 한다.
- 체크리스트
- 두 번 돌려도 줄 수가 같다(업서트,
ON CONFLICT). - FK 순서대로 넣는다(가리켜지는 표 먼저).
- 개발용 가짜 데이터와 운영 시드를 나눴다. 테스트 계정은 운영에 들어가지 않는다.
- 비밀번호·토큰은 시드에 넣지 않는다.
- 다른 시스템이 정한 id는 시드 파일에 박지 않고 이름 등으로 찾는다.
- 원본이 어디인지 정한다. 그날 저녁, 대시보드 태그와 hub-dev 태그가 이름만 같고 다른 것이었다. 그래서 hub-dev 태그를 원본으로 정했다. 그러자
tag는 "사람이 넣는 시드"에서 "동기화로 옮기는 것"으로 바뀌었다. 같은 정보를 두 곳에서 따로 쓰면 언젠가 어긋난다. - 문제가 생기면 먼저 볼 것
- 화면 선택지가 빔 → 시드가 안 들어갔거나 다른 DB에 들어감.
- FK 오류 → 넣는 순서.
- 줄 수가 늘어남 → 멱등하지 않은
INSERT.
❓ 자주 묻는 질문¶
- Q. 시드를 고치고 싶으면 운영 DB를 직접 고치나? A. 시드 파일을 고쳐 다시 돌린다. 업서트라 기존 줄은 고쳐지고 새 줄만 더해진다. 이용자가 바꾼 값을 덮지 않게 주의한다.
- Q.
dataset_meta초안은 시드인가 이용 데이터인가? A. 시작은 시드다. 이후에는 제안·승인으로 사람이 고친다. 그래서 시드를 다시 돌릴 때 승인된 설명을 덮어쓰지 않게 해야 한다. - Q. 동기화와 시드를 한 스크립트로 묶어도 되나? A. 처음엔 묶어도 된다. 다만 동기화는 반복, 시드는 거의 한 번이라 나중에 나누는 편이 관리가 쉽다.
🔭 더 깊이 보기¶
- 마이그레이션 도구(Drizzle·Prisma·Alembic)는 시드를 어떻게 다루나? — 표 모양과 기본 값을 함께 버전 관리하는 방법이다.
- 업서트에서 "덮어쓰면 안 되는 열"은 어떻게 지키나? — 사람이 고친 값과 자동 값이 섞인 표에서 꼭 부딪힌다.
- 원본(Source of Truth)은 어떻게 정하나? — 태그처럼 같은 정보가 두 곳에 생길 때의 판단 기준이다.
- 관련 주제: 메타데이터 카탈로그 기반 조회 · 데이터 파이프라인 실행과 갱신 · 애플리케이션 서버와 DB의 역할 분담 · DB 정규화와 연결 표
📚 참고 자료¶
- 멱등성 (Idempotent) (MDN 한국어 용어 사전) — "여러 번 해도 결과가 같다"의 정확한 뜻 · 링크
- INSERT — ON CONFLICT 절 (PostgreSQL 공식 문서, 영문) — 업서트 문법과
EXCLUDED사용법 · 링크 - Seeding your database (Prisma 공식 문서, 영문) — 시드 스크립트를 저장소에 두고 명령으로 돌리는 흐름. 제목으로 검색
🗓️ 이력¶
- 2026-11-09: "데이터 채우기는 뭘 채우나" 물음. 표 13개를 동기화·시드·이용 중 적재로 나누고, 처음엔 비어 있는 표가 정상임을 정리 → 일지
🕸️ 연결된 주제¶
바로 이어진 주제 5개전체 지도에서 보기 →