2026-11-09 (월)¶
오늘 요약 - 정규화 파이프라인부터 대시보드 설계(ERD·아키텍처)와 로컬 구현까지 하루에 이어 갔다. 새로 익힌 것은 ERD의 1:N, PK·FK, OIDC, Next.js 서버 구조. - 가장 오래 헷갈린 것은 "메타데이터만 든 DB가 어떻게 필터를 하나"(15번). DB들이 관처럼 이어져 데이터가 흐른다고 상상한 게 뿌리였다.
🗂️ 오늘의 세션 (3개)¶
| 시간 | 프로젝트 | 무엇을 했나 |
|---|---|---|
| 10:31~18:59 | Project | 정규화부터 대시보드 구현까지 |
| 10:45~10:49 | smartoffice-frontend | (기술 질문 없음) PR #187 리뷰 검토 |
| 11:22~14:53 | dhub2-manager | (기술 질문 없음) NERP PoC 질문 답변 작성 |
🛠️ 오늘 한 작업¶
1. 지난 대화 복기와 정규화 범위 정리 (Project · 10:31~10:55)¶
- 배경: 지난 목요일(11/05) 대화가 기억나지 않아 정리부터 요청했다. 위치·시간 데이터를 최대한 범용적으로 H3에 올리고 싶었다.
- 진행:
- 지난 대화를 주제별로 정리받았다.
- "모든 경우 정규화가 되나?" → 아니다. 지금 되는 것 / 기준 자료만 있으면 되는 것 / 변환기를 새로 만들 것 / 원래 칸이 안 되는 것으로 갈린다.
- 지금 되는 건 좌표계가 확실한 점과 면의 대표점까지라는 걸 확인했다.
- 입구(변환 함수)는 데이터셋마다가 아니라 "위치를 찾는 방식"마다 하나로 정했다. 큰 인터페이스는 만들지 않는다.
- pyproj를 쓰는 이유와 "기준 자료"의 뜻을 물어 흐름을 확정했다: 좌표 있음 → pyproj로 4326 → H3, 좌표 없음 → 기준 자료와 매칭 → 4326 → H3.
- 새 데이터 라인이 들어오면 검토 후 정규화 라인에 넣도록 프로젝트 전용 메모리를 만들었다.
- 결과: 정규화 흐름과 입구 목록 확정. 파이프라인 구축으로 이어졌다.
2. smartoffice PR #187 리뷰 검토 (smartoffice-frontend · 10:45~10:49)¶
- 배경: 내게 리뷰 요청이 와서, 코멘트를 달기 전에 검토 결과만 먼저 받기로 했다.
- 진행: 팝업·피커 쪽을 먼저 읽고, 변경량이 가장 큰
MonthlyGrid.tsxdiff를 검토했다. - 결과: 검토 결과를 세션에 받았다. 코멘트는 아직 달지 않았다.
3. 시공간 정규화 파이프라인 구축 (Project · 10:55~11:30)¶
- 배경: 작업 1에서 정한 흐름을 실제 플랫폼 파이프라인으로 만들기로 했다. 번들 동기화는 맨 마지막에 하기로 하고 hub-dev에 먼저 만들었다.
- 진행:
- 로컬 라이브러리 버전(Polars·h3·pyproj)을 플랫폼 실행 이미지와 맞추고, 서울 행정동 경계로 기준 표 두 개(지역 표, 지역↔칸 표)를 만들었다.
- 파이프라인
norm_spatiotemporal을 만들어 7단계 첫 실행이 모두 성공했다. 공통 표에 파티션 열이 없어 생성이 거절돼dataset_id파티션으로 다시 만들었다. - 원천 7종은 시연 페이지와 같은 데이터로 골랐다. 플랫폼에서도 같은 결과(강남 대피시설 197, 지하도 노드 386)가 나오는지 보려는 목적이다.
- 처음엔 원천 7개가 공통 표 4개에 섞여 들어갔다. API로 받을 때 매번 전부 받아야 해서 원천별 정규화 데이터셋 7개로 바꿨다.
- 원본과 norm 버전을 비교했고, 단계를 여러 개 두던 구조를 코드 노드 하나 구조로 바꿔 검증했다. 원천별 옵션은 둘 다 있고, 적히는 위치만 다르다.
- 결과: 플랫폼에서 H3 정규화가 돈다는 건 검증됐다. 남은 것: 서울 밖 교통량 지점, 구 이름만 있는 3종(강우·지반침하·도로굴착)은 칸 없이 구 코드만 붙음, 면 데이터는 칸 하나로만 정함.
4. 중간 DB와 메타데이터 구조 결정 (Project · 11:04~14:00)¶
- 배경: 시니어가 "매니저 API로는 필터가 안 되니, DB를 하나 더 만들어 메타정보를 따로 보관하라"고 했다. 그 말이 무슨 구조인지 이해하는 게 목표였다.
- 진행:
- 정규화 데이터셋이 플랫폼의 ClickHouse에
disaster.norm_*로 올라가 있다는 걸 확인했다. - 플랫폼에 문이 두 개라는 걸 정리했다: 매니저 다운로드 API(조건 못 받음, 통째로)와 SQL 조회 창구(조건을 받아 걸러 줌, 최대 1,000줄).
- hub-dev가 외부에서 안 열린다는 점을 짚었다. 외부 시연이라면 메타데이터만으로는 안 되고 데이터 사본이 필요하다.
- 카탈로그, 태그, 코드표 개념을 묻고, 로컬 임시 중간 DB와 쉬운 설명서를 만들었다.
- "그럼 ClickHouse에 SQL을 바로 보내면 되지 않나?" → SQL 창구(방법 A)를 시험했다. 읽기 조회 7건 성공, 강남 2022년 8월 침수흔적 679건을 0.57초에 받았다.
- 쿼리 틀(템플릿)에 값만 바꿔 끼우는 방식, 매칭이 어디서 일어나는지 정리했다.
- 시니어의 뜻은 "11680 = 강남구" 같은 코드표일 가능성이 크다고 보고, 메타데이터를 플랫폼 데이터셋으로 두는 안을 추천받았다. 시니어 설득용 설명 순서도 받았다.
- 메타데이터는 정적인 정보만 두고, 보유 기간·행 수처럼 바뀌는 값은 그때그때 묻기로 정리했다.
- 결과: "별도 DB 없이 플랫폼 SQL 창구 + 메타데이터 데이터셋" 안까지 갔다. 이 결정은 작업 7에서 시니어 피드백으로 다시 바뀌었다.
5. NERP(삼성 PoC 1·2차) 질문 답변 작성 (dhub2-manager · 11:22~14:52)¶
- 배경: NERP 삼성 PoC 1·2차에 대해 질문 5개가 들어왔다. 답하려면 사전 지식이 필요했다.
- 진행:
- 관련 자료를 먼저 읽게 하고, 질문 5개(데이터 첫 확인, 엔티티·식별 키 기준, 직송 판정과 결정 기록 분리, 1차 세 경로의 목적, 검증 방법)를 저장소 근거로 답하게 했다.
- 초안을 받은 뒤, 1·2·4번은 내가 할 말을 직접 주고 문장만 다듬게 했다.
- 저장소 기록과 대조해 고칠 곳 2곳, 다듬을 곳 2곳을 찾아 최종본을 만들었다. 5번에는 골든셋 재계산 내용을 넣었다.
- Teams에 바로 붙는 마크다운으로 바꿔 클립보드에 넣었다. 질문과 상관없는 내용은 빼고 약 3분의 2로 줄였다.
- 결과: Teams용 최종 답변 완성.
6. 다운로드 포털 UI 프로토타입 (Project · 11:53~13:03)¶
- 배경: 필터는 재난 종류·기간·지역 세 개면 된다고 봤고, 화면으로 보고 싶었다.
- 진행:
- 세 필터 대시보드 시제품을 먼저 만들었다.
- 이 대시보드의 주 역할은 보는 게 아니라 다운로드라서 다운로드 중심으로 다시 만들었다.
- 미리보기에 열 설명을 넣고, 보조 지도를 붙였다.
- 확인해 보니 이 페이지에는 실제 중간 DB가 없었다.
- 결과: 보여 줄 형태는 이걸로 정했다. 그대로 쓰지는 않는다.
7. 외부 대시보드 아키텍처와 로그인 방식 (Project · 14:04~16:12)¶
- 배경: 대시보드는 외부 써드파티 앱처럼 따로 만든다. 14:28 시니어와 다시 이야기해 보니, 메타정보 수정·즐겨찾기·사용자 표·트랜잭션까지 생각하면 쓰기가 필요하다는 게 핵심이었다. ClickHouse에 쓸 수는 없다.
- 진행:
- dhub2-manager가 백엔드여도 얇은 중계 서버는 필요하다. 토큰을 숨기고, CORS를 피하기 위해서다.
- dhub2 계정으로 로그인하는 OIDC가 hub-dev에서 켜져 있다는 걸 확인했다.
- 시니어 피드백에 따라 외부 앱 + 중계 서버 + 대시보드 전용 PostgreSQL로 방향을 바꿨다. 플랫폼에
meta_*데이터셋은 만들지 않는다. - SSO를 써도 즐겨찾기는 대시보드 DB에
sub기준으로 저장하면 된다. - "대시보드 ↔ 중계 ↔ postgres ↔ clickhouse" 일렬 구조라고 생각했는데, 실제로는 중계 서버에서 두 갈래로 갈라지는 구조다.
- 백엔드는 별도 서버 없이 Next.js 하나(화면 + Route Handler)로 정했다.
- 같은 쿠버네티스 클러스터의 namespace에 띄우면 내부 통신이 되는지, 확인할 점 4가지를 정리했다.
- ERD와 아키텍처를 합친 설계서를 만들었다. "ClickHouse를 복제하나?" → 아니다, 필터는 ClickHouse에서 걸린다.
- 결과: 전체 구조와 기술 스택 확정, 설계서 게시.
8. 기능 정의와 ERD·스키마 설계 (Project · 14:35~16:30)¶
- 배경: 쓰기 기능이 생기면서 대시보드 DB 스키마가 필요해졌다. 먼저 넣을 기능을 정하기로 했다.
- 진행:
- 비슷한 서비스를 백그라운드로 조사했다. 과제용이라 차별점 없이 범용 기능(42곳 중 여러 곳에 공통으로 나온 것)만 남겼다.
- 기능 6개를 골랐다가 이메일 알림을 뺐다. 1차 기능: 키워드 검색, 필터, 마이페이지(즐겨찾기·최근 항목), 메타데이터 편집 제안·승인, 문의.
- 1:N, 사용자별 테이블 오해를 풀고 ERD를 그렸다. 이미지로 따로 저장하고, 선이 겹치지 않게 다시 배치했다.
- ERDCloud에 넣을 수 있게 PostgreSQL·MySQL SQL 파일을 만들었다. PostgreSQL만 실행 검증, MySQL은 이 PC에 없어 못 했다.
- 게시판(
post,post_reply)은 빼고 관리자 문의함inquiry로 바꿨다. 표 13개, FK 19개. - 처음 말한 메타정보가 ERD 표 6개에 나뉘어 들어 있다는 걸 확인했다. 태그 표를 나눈 이유(DB 정규화)와 표 나누는 6가지 기준을 들었다.
- PostgreSQL에서는 데이터베이스 +
app스키마를 만들기로 했다. - 설계서에 1차 기능 상세를 넣고,
dataset에table_name,t_min,t_max를 추가해 v0.2로 올렸다. - 결과: 스키마 v0.2와 설계서 완성.
9. 로컬 구현 L0~L5와 태그 동기화 (Project · 16:35~17:33)¶
- 배경: 설계가 어느 정도 됐으니 로컬에서 띄워 시험하고 싶었다. hub-dev는 직접 연결하기로 했다.
- 진행:
- dtonic 조직에 Next.js 저장소가 14개 있고, 그중 하나를 템플릿으로 쓸 수 있다는 걸 확인했다.
- L0: Next 16 + Docker PostgreSQL 17로 프로젝트를 만들었다. docker-compose에 왜 postgres가 있는지 물었다.
- L1: 표 13개를 빈 표로 만들었다. pgAdmin 4를 설치했다.
- L2: 데이터 채우기(지역 코드 453줄, 데이터셋 7줄 등). 동기화를 두 번 돌려도 줄 수가 같은 것까지 확인했다.
- 저장된 값이 ClickHouse SQL 빈칸(표 이름, 구 코드, 기간 조건 여부)에 들어가는 과정을 실제로 찍어 봤다(386건, 679건).
- L4·L5(읽기 API와 화면)를 백그라운드로 맡겼다. 보조 지도까지 동작한다.
- 퓨전DB를 못 쓴 이유를 시니어에게 설명해야 해서, 설계서와 메모리의 해당 내용을 최신 사실로 고쳤다.
- 대시보드 태그와 hub-dev 태그가 이름만 같고 다른 것이었다. 모든 수정은 내 승인을 거치니 hub-dev 태그를 원본으로 하기로 했다. 7개 데이터셋에
재난:형식 태그를 붙였다. - 로컬에 커밋했다(원격이 없어 푸시는 없음). 푸시할 때는 PR → 머지 방식으로 하기로 메모리에 남겼다.
- 결과: 화면 데이터가 실제 Docker PostgreSQL과 hub-dev에서 온다는 걸 확인. 로그인·마이페이지가 남았다.
10. 로그인·마이페이지와 1차 기능 완성 (Project · 17:39~18:12)¶
- 배경: 마이페이지·즐겨찾기를 하려면 "나"가 누구인지 알아야 한다. 진짜 SSO는 dhub2에 클라이언트 등록(관리자 권한)이 필요했다.
- 진행:
- 로그인이 먼저라는 것, 관리자/일반 사용자는 앱이 정한다는 것을 정리했다.
- 진짜 SSO 대신 공용 인증 모듈의 로컬 계정 + 페르소나로 가기로 했다. 원본 모듈에는 영향이 없다.
feat/auth-poc브랜치에서 로그인·마이페이지를 백그라운드로 만들었다.- 공용 인증 모듈의 원본과, 내가 써도 되는지를 확인했다. 출처는
VENDORED.md와 메모리에 남겼다. - 로그인 첫 화면을 대시보드에 맞는 밝은 화면으로 바꿨다. 모듈을 안 쓰게 되면
src/server/auth/한 곳만 바꾸면 되게 해 뒀다. - 남은 기능까지 마저 구현했다.
- 결과: 1차 기능 5개 모두 구현. 타입 검사·lint 통과, 자동 테스트 35개 통과. 커밋은 보류.
11. 하루 회고와 백엔드 공부 계획 (Project · 18:52~18:58)¶
- 배경: 오늘 질문이 많았던 만큼 내 부족한 점을 짚고 싶었다.
- 진행: 부족한 점(데이터 흐름 그림, 관계형 DB 기본 단위, 인프라 용어, 요구사항 전 설계 시작, 결정 이유 기록)을 받았다. 백엔드 공부는 HTTP → SQL·관계형 모델링 → 트랜잭션·동시성 순으로 오늘 만든 코드를 교재로 삼기로 했다.
- 결과: 아래 약점 정리에 반영.
🧠 부족했던 개념¶
1. 메타데이터 카탈로그 기반 조회 · 🔁 15번 물음¶
- 어떻게 드러났나: 중간 DB 구조를 정할 때(→ 작업 4) 시작해서 ERD·로컬 구현(→ 작업 7, 9)까지 하루 종일 이어졌다. "메타데이터만 넣고 어떻게 필터를 하나", "중간 DB가 매니저 API에 보내서 거르나", "PostgreSQL이 ClickHouse의 필터로만 쓰이나", "저장된 값이 어떻게 쿼리에 쓰이나"로 말을 바꿔 가며 물었다.
- 헷갈린 점: 메타데이터 DB가 직접 줄을 거른다고, 또는 데이터가 메타데이터 DB를 거쳐 흘러간다고 생각했다. 메타데이터가 SQL의 "재료"일 뿐이라는 그림이 없었다.
- 핵심:
- 카탈로그는 데이터셋마다 한 줄씩 "어느 표에 있고, 어떻게 거르는지"를 적은 목록 표다. 도서관 목록 카드함과 같다. 책(데이터)은 없다.
- 필터는 두 단계다. ① 서버 코드가 메타데이터를 읽어 어느 표(FROM), 어떤 값(WHERE)인지 정한다. ② 그 값으로 SQL을 조립해 데이터가 있는 DB에 보내고, 거기서 거른다(푸시다운).
- 두 표를 잇는 건 양쪽에 똑같이 든 공통 키(구 코드, H3 칸 번호 등)다. 정규화를 하는 이유가 이 키를 맞추기 위해서다.
- 보통 쿼리 틀(템플릿) 몇 개를 정해 두고 값만 바꿔 끼운다. 값은 문자열로 그냥 붙이지 않고 검사를 거쳐 넣는다.
- 예: "침수·2022-08·강남구" → 태그로 데이터셋 고르기, 코드표로 강남구=11680, 시간 없는 데이터는 기간 조건 빼기 → 데이터셋마다 다른 SQL.
- 👉 메타데이터 카탈로그 기반 조회
2. 애플리케이션 서버와 DB의 역할 분담 · 🔁 5번 물음¶
- 어떻게 드러났나: 중간 DB가 있으면 왜 필터가 되는지(→ 작업 4), "그럼 ClickHouse에 바로 SQL을 날리면 되지 않나"를 두 번 물었다. 아키텍처를 정할 때(→ 작업 7) "대시보드 ↔ 중계 ↔ postgres ↔ clickhouse"가 맞냐고 물었다.
- 헷갈린 점: 필터가 "데이터의 성질"이라고 봤고, DB끼리 서로 연결돼 있다고 생각했다. 직접 DB에 붙는 것과 공식 API를 거치는 것의 차이도 몰랐다.
- 핵심:
- 필터는 데이터를 들고 있는 프로그램이 하는 일이다. 통째로만 주는 API 뒤의 데이터는 받는 쪽이 직접 걸러야 하고, SQL을 받는 DB는 색인으로 바로 골라 준다.
- DB는 스스로 움직이지 않는 창고다. DB끼리 서로 부르지 않는다. 서버 코드가 허브가 되어 각 DB를 따로 부른다.
- 내부 DB에 직접 붙으면 권한 검사를 건너뛰고, 관리자 계정을 앱에 넣어야 한다. 그래서 플랫폼의 공식 입구(SQL 창구)를 거친다.
- 👉 애플리케이션 서버와 DB의 역할 분담
3. ERD 관계와 카디널리티(1:N) · 🔁 4번 물음¶
- 어떻게 드러났나: 기능별 표를 설계할 때(→ 작업 8) "user마다 즐겨찾기 테이블이 하나씩 있어야 하지 않나", "1:N이면 데이터셋 하나에 즐겨찾기 테이블이 여러 개 매핑되나"를 물었다. 뒤에는
column_meta와dataset_meta를 섞어 확인했다. - 헷갈린 점: 표와 줄의 단위를 섞었다. 1:N을 "표의 개수"로 읽었다.
- 핵심:
- ERD 상자 하나 = 서비스 전체에 하나뿐인 표. 사용자마다 표를 따로 만들지 않고, 줄마다
user_id를 적는다. - 1:N은 줄의 개수다. 1쪽 표의 한 줄에 N쪽 표의 줄이 0~여러 개 붙는다.
- FK 값이 반복되는 쪽이 N쪽이다. ERD에서 까마귀발이 붙는 쪽이다.
- FK 열이 혼자 PK면 반복될 수 없으니 1:1, PK의 일부이거나 PK가 아니면 1:N이다.
- 👉 ERD 관계와 카디널리티
4. 지오코딩과 참조 데이터 매칭 · 🔁 3번 물음¶
- 어떻게 드러났나: 정규화 범위를 정리할 때(→ 작업 1) "좌표 없는 데이터마다 입구를 새로 만드나", "기존 자료가 뭐냐"(실제로는 "기준 자료"), "필요한 기준 자료가 뭐냐"를 이어서 물었다.
- 헷갈린 점: 좌표 없는 데이터에 위치를 붙이려면 코드가 아니라 짝꿍 데이터가 필요하다는 걸 몰랐다.
- 핵심:
- 이름·번호·코드만 있는 데이터는 "이 이름은 여기다"라고 적힌 기준(참조) 자료와 이어 붙여야 좌표가 생긴다. 예: 강우계 이름 ↔ 강우계 위치 표, 동 이름 ↔ 동 경계.
- 변환 함수는 "위치를 찾는 방식"마다 하나다. 대부분은 함수는 있고 기준 자료만 구하면 된다.
- 찾을 근거가 없으면 빈칸으로 둔다. 구 합계를 칸에 나눠 넣는 식으로 없는 정밀도를 만들지 않는다.
- 👉 지오코딩과 참조 데이터 매칭
5. PostgreSQL 데이터베이스·스키마 구조 · 🔁 3번 물음¶
- 어떻게 드러났나: ERD를 만든 뒤(→ 작업 8) "postgres에 워크스페이스 같은 걸 만드나", "워크스페이스냐 스키마냐"를 세 번에 걸쳐 물었다.
- 헷갈린 점: "워크스페이스"라는 다른 도구의 말로 생각해서, 데이터베이스와 스키마가 각각 무엇을 나누는지 몰랐다.
- 핵심:
- 계층: 서버(클러스터) > 데이터베이스 > 스키마 > 표 > 줄. "워크스페이스"는 PostgreSQL 공식 용어가 아니고, 보통 데이터베이스를 가리킨다.
- 데이터베이스는 완전 분리(백업·권한·삭제 단위), 스키마는 같은 DB 안의 정리용 칸막이다.
- 별도 서비스라면 전용 데이터베이스 + 그 안의 스키마를 만들고, 앱 전용 계정에 그 스키마 권한만 준다. 기존 DB에 자리만 받으면 스키마만 만든다.
- 👉 PostgreSQL 데이터베이스와 스키마
6. 데이터 파이프라인 실행과 결과 갱신 · 🔁 2번 물음¶
- 어떻게 드러났나: 시니어 설명 자료를 받다가(→ 작업 4) "파이프라인을 돌리면 표가 갱신된다는 게 무슨 소리냐", "강남구 = 11680 같은 정적 데이터만 들어가는데 갱신할 이유가 있냐"를 물었다.
- 헷갈린 점: "파이프라인 실행"과 "표 갱신"의 관계를 몰랐다. 메타데이터에 정적인 값과 바뀌는 값이 섞여 있다는 것도 몰랐다.
- 핵심:
- 파이프라인을 돌린다 = 정해 둔 프로그램을 한 번 실행해 입력 표를 읽고 결과를 출력 표에 쓰는 것. overwrite 방식이면 매번 통째로 바뀐다.
- 수집 → 정규화 순으로 이어지므로, 일정을 켤 때는 정규화가 수집보다 늦게 돌게 잡는다.
- 메타데이터는 정적인 정보(코드표, 태그, 표 이름, 열 설명)만 두고, 행 수·보유 기간처럼 바뀌는 값은 필요할 때 직접 조회하면 갱신 작업이 필요 없다.
- 👉 데이터 파이프라인 실행과 갱신
7. 프론트엔드와 백엔드 서버 구조 · 🔁 2번 물음¶
- 어떻게 드러났나: 아키텍처를 정할 때(→ 작업 7) "백엔드는 dhub2-manager를 쓰는데 서버를 따로 만들어야 하나", "Next.js로 서버 없이 하는 건 불가능한가"를 물었다.
- 헷갈린 점: 브라우저 코드가 API를 바로 부르면 된다고 생각했다. Next.js가 그 자체로 서버라는 것도 몰랐다.
- 핵심:
- 브라우저에는 비밀값(토큰, client secret, DB 비밀번호)을 둘 수 없다. 그래서 이를 들고 대신 요청하는 서버(중계, 프록시)가 필요하다.
- 다른 주소에서 온 브라우저 요청은 CORS로 막힐 수 있다. 서버끼리 통신에는 CORS가 걸리지 않는다.
- Next.js는 정적 내보내기(진짜 서버 없음)와 Node 서버 실행(화면 + Route Handler API) 두 방식이 있다. 후자면 "별도 백엔드 없이" 한 프로그램이 중계 서버까지 맡는다. 정기 작업에는 약하다.
- 👉 프론트엔드와 백엔드 서버 구조
8. OIDC 로그인 흐름 · 🔁 2번 물음¶
- 어떻게 드러났나: 대시보드 로그인을 고민하다(→ 작업 7) "dhub2 사용자가 대시보드에도 똑같이 로그인되게 할 수 있나", "SSO를 하면 즐겨찾기 기능을 넣을 수 있나"를 물었다.
- 헷갈린 점: SSO를 쓰면 사용자 데이터도 로그인 서버에 있어야 한다고 생각했다. "누구인지 확인"과 "그 사람의 데이터"가 별개라는 걸 몰랐다.
- 핵심:
- OIDC는 "구글로 로그인"처럼 로그인을 다른 서비스에 맡기는 표준이다. 브라우저가 로그인 서버로 갔다가 일회용 코드를 들고 돌아오고, 서버가 그 코드를 토큰으로 바꾼다(authorization code 흐름). 앱은 비밀번호를 보지 않는다.
- 즐겨찾기 같은 데이터는 앱 자기 DB에 둔다. 사람은 이메일이 아니라 바뀌지 않는
sub(+ 어느 발급처인지issuer)로 구분한다. - 앱 사용자 표에는 비밀번호가 없다. 이름·이메일은 로그인할 때마다 갱신한다.
- 👉 OIDC 로그인 흐름
9. DB 정규화와 연결 표 · 🔁 2번 물음¶
- 어떻게 드러났나: ERD를 보다가(→ 작업 8) "
dataset_tag에 tag의 kind·name을 다 써도 될 것 같은데 왜 나눴나", "테이블을 나눈 기준을 설명해 달라"고 물었다. - 헷갈린 점: 표를 나누는 이유를 몰랐다. 같은 "정규화"라는 말을 H3 정규화와 같은 뜻으로 들을 여지도 있었다.
- 핵심:
- DB 정규화는 같은 정보를 한 곳에만 두고 다른 곳에서는 ID로 가리키는 원칙이다. 이름을 한 곳만 고치면 되고, FK로 오타를 막고, 아직 안 쓰인 값도 목록에 둘 수 있다.
- N:M 관계(사람 ↔ 데이터셋 즐겨찾기)는 연결 표로 담는다. 1:N은 N쪽에 FK 열 하나면 된다.
- 쓰는 주체(프로그램 vs 사람)가 다르면 1:1이라도 표를 나눠 덮어쓰기를 막는다. 일부러 중복을 허용하는 건 비정규화다.
- H3 정규화(형식 맞추기)와 DB 정규화(중복 없애기)는 전혀 다른 뜻이다.
- 👉 DB 정규화와 연결 표
10. 좌표계 변환과 pyproj¶
- 어떻게 드러났나: 위치 입구 목록을 정리하다(→ 작업 1) "pyproj를 쓰는 이유가 뭐냐"고 물었다.
- 헷갈린 점: 좌표계가 여러 개라는 것, 그리고 좌표계 변환이 왜 별도 도구가 필요할 만큼 까다로운지 몰랐다.
- 핵심:
- H3는 위경도(EPSG:4326)만 받는다. 한국 공공데이터는 미터 단위 평면 좌표(5179·5181·5186·5174 등)로 오는 경우가 많다.
- 변환에는 투영 해제와 측지계 변환(옛 베셀 타원체 → GRS80·WGS84)이 같이 들어간다. 손으로 짜면 수백 m씩 틀린다.
- pyproj는 PostGIS·QGIS가 쓰는 PROJ 엔진을 파이썬에서 쓰게 해 주고, EPSG 번호만 주면 된다.
always_xy=True로 경도·위도 순서를 고정한다. 원본 좌표계가 맞는지는 사람이 확인해야 한다(잘못 읽으면 100km까지 어긋남).- 👉 좌표계 변환과 pyproj
11. 쿠버네티스 클러스터 내부 통신¶
- 어떻게 드러났나: 배포 위치를 정할 때(→ 작업 7) "Next.js 컨테이너 파드를 우리 쪽 노드에 띄우면 내부 통신에 문제없지?"라고 물었다.
- 헷갈린 점: "노드"와 "클러스터·namespace"를 섞었다. 내부망이면 다 통한다고 봤다.
- 핵심:
- 노드는 서버 한 대이고, 파드가 어느 노드에 갈지는 쿠버네티스가 정한다. 우리가 정하는 건 어느 클러스터의 어느 namespace인가다.
- 같은 클러스터면
서비스.namespace.svc.cluster.local같은 내부 주소로 통신한다. 단, NetworkPolicy나 서비스 메시 규칙으로 막혀 있을 수 있다. - 내부망이어도 토큰과 권한 검사는 그대로 필요하다. 브라우저 → 앱 구간과 OIDC 로그인 화면은 공개 주소가 따로 필요하다.
- 👉 쿠버네티스 클러스터 내부 통신
12. Docker Compose 로컬 개발 환경¶
- 어떻게 드러났나: L0 준비 뒤(→ 작업 9) "next와 postgres는 다른 컨테이너인데, next 레포의 docker-compose에 왜 postgres가 있냐"고 물었다.
- 헷갈린 점: 실행 단위(컨테이너)와 관리 단위(저장소)를 같은 것으로 봤다. docker-compose를 배포 설정으로 생각했다.
- 핵심:
- docker-compose는 여기서 로컬 개발용 준비물 목록이다. "이 앱을 개발하려면 옆에 이 DB가 필요하다"를 한 명령으로 띄운다.
- 앱 자체는 개발 중엔 보통 직접 실행(
npm run dev)하고, 설치가 번거로운 DB만 컨테이너로 빌려 쓴다. - 배포는 따로다: Dockerfile(앱 이미지 만들기), Helm·ArgoCD(클러스터에 어떻게 띄울지).
- 👉 Docker Compose 로컬 개발환경
13. 시드 데이터와 초기 적재¶
- 어떻게 드러났나: L2를 앞두고(→ 작업 9) "데이터 채우기는 뭘 채운다는 거냐"고 물었다. 직전에는 "기능용 테이블도 결국 ClickHouse로 채워지는 거 아닌가"를 물었다.
- 헷갈린 점: 모든 표가 원천 데이터에서 채워진다고 생각했다. 비어 있는 게 정상인 표가 있다는 걸 몰랐다.
- 핵심:
- 표는 채워지는 곳에 따라 세 종류다. ① 외부에서 동기화(데이터셋 목록, 열 정보, 코드표), ② 처음에 사람이 넣는 시드 데이터(태그, 역할 대응, 설명 초안), ③ 이용자가 쓰면서 쌓이는 것(사용자, 즐겨찾기, 문의).
- 쇼핑몰이 문 열기 전에 상품 목록·카테고리를 등록하고, 회원·장바구니는 비워 두는 것과 같다.
- 초기 적재·동기화는 두 번 돌려도 결과가 같게(멱등) 만든다.
- 👉 시드 데이터와 초기 적재
14. 역할 기반 권한 구분(RBAC)¶
- 어떻게 드러났나: 로그인 방식을 고르다(→ 작업 10) "마이페이지를 먼저 만들어야 하지 않나, 관리자와 일반 사용자는 어떻게 구분하냐"고 물었다.
- 헷갈린 점: 로그인이 역할까지 알려 준다고 생각했다. 화면에서 버튼을 숨기는 것과 실제 보안의 차이를 몰랐다.
- 핵심:
- 로그인(인증)은 "누구인지"만 알려 준다. "관리자인지"(인가)는 앱이 규칙(관리자 목록, 그룹 ↔ 역할 표)으로 정한다.
- 역할은 서명된 세션에 적어 이용자가 고칠 수 없게 한다.
- 화면에서 버튼을 숨기는 건 편의일 뿐이다. 서버 API가 요청마다 역할을 확인해야 한다: 로그인 안 함 401, 권한 없음 403.
- 👉 역할 기반 권한 RBAC
🩹 오늘 드러난 약점¶
- 데이터 흐름을 "관"으로 상상한다 — 근거: 개념 1·2에서 20번. "postgres ↔ clickhouse 일렬", "복제 후 필터", "기능 표도 ClickHouse로 채워지나"가 모두 같은 그림에서 나왔다(16:31 회고). → 다음에: 새 구조를 보면 "누가 누구를 부르나" 화살표부터 종이에 그린다. DB에서는 화살표가 나가지 않는다.
- 관계형 DB의 기본 단위가 아직 몸에 안 붙었다 — 근거: 개념 3·5·9. 표/줄, 1:N/1:1, 데이터베이스/스키마를 섞었다. → 다음에: 표를 볼 때 PK·FK 열부터 찾고, 예시 줄 3개를 직접 써 본다. pgAdmin에서 즐겨찾기 조회 SQL을 손으로 짜 본다.
- 인프라 용어가 무엇을 나누는 단위인지 모른다 — 근거: 개념 5·7·11·12. 노드/클러스터, 컨테이너/저장소, 워크스페이스/스키마, "서버 없이". → 다음에: 새 용어마다 "이건 무엇을 나누는 단위인가" 한 줄로 정의해 둔다.
- 요구사항을 굳히기 전에 설계하고, 결정 이유를 남기지 않는다 — 근거: 게시판 → 문의함, 이메일 넣었다 빼기, 태그 원본 변경으로 ERD·SQL을 여러 번 고쳤다. 퓨전DB를 못 쓴 이유가 바로 안 나왔다(18:52 회고). → 다음에: 기능을 한 장에 먼저 적고 표를 그린다. 결정마다 3~5줄 ADR을 남긴다.
🧪 복습 퀴즈¶
1. 메타데이터(카탈로그·태그·코드표)만 든 DB는 필터 요청에서 정확히 무슨 일을 하고, 실제로 줄을 거르는 곳은 어디인가?
답 보기
메타데이터는 SQL의 **어느 표(FROM)**와 **어떤 값·조건(WHERE)**을 정하는 재료일 뿐이다. 서버 코드가 그 값으로 SQL을 조립해 **데이터가 있는 DB**에 보내고, 거기서 줄이 걸러진다. DB끼리는 서로 부르지 않는다.2. column_meta의 PK는 (dataset_id, column_name), dataset_meta의 PK는 dataset_id다. 각각 dataset과 어떤 관계인가?
답 보기
`column_meta`는 **1:N**(dataset_id가 PK의 일부라 반복 가능), `dataset_meta`는 **1:1**(FK 열이 혼자 PK라 반복 불가).3. 관리자 화면의 "승인" 버튼을 일반 사용자에게 숨겼다. 이것으로 충분한가?