🎧 ▶ 5분 브리핑으로 듣기
▶ 오디오북 재생 (Google Drive)
NotebookLM 오디오 개요 (AI 생성)
📕 전자책 전문 읽기 형의 기술 · 31쪽
브라우저가 PDF를 바로 표시하지 못합니다. 새 탭에서 열기
이 글은 요약해 다시 쓴 것이고, PDF가 전문입니다.

제품을 혼자 만들고 혼자 돌리는 개발자를 위한 글입니다. 읽고 나면, 기능 하나를 구현하기 전에 그 가격표를 읽는 습관. 결론부터 말씀드리겠습니다. 기능의 진짜 비용은 코드를 쓰는 날이 아닙니다. 결정되는 날은, 데이터의 형을 정한 날.

화요일 밤, 고객에게 메시지가 옵니다. 청구서에 다중 화폐를 지원해 주었으면 좋겠다고. 처음에는 작은 요청으로 들립니다. 통화 코드를 한 칸 넣는 작업이니까. 금액은 정수 한 컬럼뿐입니다. 통화 코드 자리도, 발행 시점 환율도, 세금 기준도 없습니다. 청구 문서는 바로 그 컬럼에서 그려집니다. 돈을 읽는 모든 곳을 건드려야 하고 이미 발행한 과거 행을 전부 다시 써야 합니다. 이튿날 아침, 작은 요청은 일주일짜리 프로젝트가 됩니다.

문제는 요청이 아니라 형이었습니다. 코드는 버릴 수 있습니다. 함수를 다시 쓰고 파일을 지우고 배포를 되돌리면 되니까, 코드에는 기억이 없기 때문입니다. 데이터는 다릅니다. 데이터는 제품의 기억입니다. 과거의 모든 청구서와 모든 호출 이벤트는 이미 세상 밖에 나갔습니다. 고객이 보고 세무서가 받아 보고서로 떠난 사실들입니다. 이 글의 규율은 단 하나입니다. 형의 비용을 필요할 때가 아니라 쌀 때, 작은 조각으로 내는 일.

기능의 가격은 데이터의 형이 정한다 개념을 형상화한 이미지 글의 핵심 개념을 형상화했습니다.

제품의 한계는 테이블에서 자란다

제품의 형은 화면에서 태어나지 않습니다. 테이블에서 태어납니다. 워크스페이스가 있는 제품에서, 데이터베이스에는 워크스페이스 테이블의 한 행이 있고 그 멤버는 사용자 행에 식별자 컬럼을 가집니다. 이 형은 한 사람은 한 워크스페이스에 속한다고 말해 줍니다. 그 한마디가 제품의 한계를 스스로 자라게 합니다.

첫 번째 한계는 소유권 이전입니다. 워크스페이스를 다른 사람에게 넘긴다고 식별자 값을 바꾸면 되는 줄 알았는데, 워크스페이스 설정과 청구 계정과 팀 결제 정보가 전부 소유자 행에 붙어 있습니다. 새 컬럼을 만들고 관계를 옮기는 작업이 됩니다. 두 번째 한계는 초대입니다. 초대돼 있는 중인데 아직 가입하지 않은 사람이라는 상태를 담을 자리가 형에 없습니다. 그래서 초대 플로우는 없거나, 밖에 임시 테이블로 붙여 놓게 됩니다.

세 번째 한계는 청구입니다. 청구 계좌가 소유자에 묶여 있으니, 이 워크스페이스의 청구는 소유자만 낼 수 있습니다. 다른 멤버가 낼 수 있느냐는 질문 자체가 형에 답할 자리가 없어 성립하지 않습니다. 화면을 먼저 떠올리면 보이지 않고 형을 떠올려야 보입니다.

LLM 앱도 다르지 않습니다. 채팅 제품은 메시지를 대화 식별자와 순서로 저장해 두었습니다. 화면에 답장이 답장에 답하는 줄이 지은 대화가 필요해집니다. 형에 줄의 개념이 없으니 새로운 줄을 다 다시 저장해야 합니다. 사용자가 편집한 메시지와 모델이 낸 원본을 따로 남길 자리도 없습니다. 편집이 원본을 덮어 쓰니, 나중에 모델 평가를 위한 데이터셋에 쓸 원본은 이미 사라져 있습니다. 기능의 아이디어는 회의실에서 나옵니다. 형이 구현을 막습니다.

그래서 새 기능이 들어올 때 먼저 물을 질문이 있습니다. 어떤 행에 어떤 사실이 추가되는가. 한두 행으로 답이 나오면 그 기능은 쌉니다. 세 테이블에 퍼뜨려야 한다면 그것은 기능 구현이 아니라 형 바꾸기 작업입니다. 이미, 마이그레이션.

핵심 개념 요약 인포그래픽 1 NotebookLM이 소스를 종합해 생성한 인포그래픽입니다.

새 요청은 ‘새 사실이 어디에 붙나’로 읽는다

기능 요청이 들어올 때, 화면이나 코드를 먼저 떠올리지 말고 형으로 읽어 보십시오. 두 개의 요청을 나란히 놓으면 차이가 보입니다. 요구서의 길이가 아니라, 새 사실이 붙는 자리의 깊이가 가격을 정합니다.

첫 번째 요청은 사용량 내역을 파일로 내려받게 해 달라는 것입니다. 사용량 내역이라는 사실은, 이미 사용량 이력 테이블에. 그런데 내역을 내려받았다는 사실은 어디에 저장되는가. 누가, 어떤 기간을, 어디로, 어떤 상태로. 형에 그 자리가 없습니다. 이 요청은 새 테이블입니다. 식별자, 사용자, 기간, 파일 위치, 상태 컬럼을 가진 내보내기 테이블. 새 요청이 새 엔티티를 낳는 순간, 그것은 기능 작업이 아니라 모델 작업입니다.

두 번째 요청은 로그인 화면에 회사 로고를 넣게 해 달라는 것입니다. 로고는 파일 저장소에 저장하고 사용자 행에 경로 한 컬럼을 더하면 됩니다. 새 사실은, 이미 있는 자리에. 이 요청은 쌉니다. 요구서는 한 줄, 붙는 자리는 한 컬럼.

이 차이가 요청 평가의 전부입니다. 요구서가 길어도 형에는 한 컬럼도 안 붙으면 싸고, 요구서가 한 줄이어도 새 테이블이 필요하면 비쌉니다. 화면 수준에서 읽으면 화면 작업 시간만 추정하게 되고 비싼 부분은 요구서에 쓰이지 않아서 추정에서 빠집니다. 비싼 부분은, 이미 저장된 행에 있습니다.

작업량 추정 전에 새 사실이 어디에 붙나를 묻는 습관 하나만 들이면, 이 글의 절반은 끝난 것입니다.

the-data-modeling-discipline 슬라이드 1

형 바꾸기의 비용은 세 덩어리로 온다

빈 테이블의 형을 바꾸는 것은 무료입니다. 데이터가 있는 테이블의 형을 바꾸는 것은 프로젝트입니다. 프로덕션 테이블에는 데이터가 있고 고객은 지금 그 데이터를 보고 있습니다. 바꾸기의 비용은 세 덩어리로 옵니다.

첫 번째 덩어리는 변환 비용입니다. 기존 행을 새 형으로 다시 써야 합니다. 행이 백만 개면 세트로 나누어 조심스레 돌리는 작업이 되고 그 사이에도 서비스는 살아 있어야 합니다. 백만 행을 천 행씩 돌리면 십칠분이지만, 부하를 보정하면 한두 시간 [추정]. 배포 윈도우 밖에서 조용히 흘러가야 합니다.

두 번째 덩어리는 이중 도는 기간입니다. 마이그레이션 중에는 구 코드와 새 코드가 동시에 돌고 둘 다 같은 테이블을 읽고 씁니다. 구 코드는 새 형을 모르고 새 코드는 구 형을 참아야 합니다. 그래서 형은 두 버전이 같이 살아야 할 모양이어야 합니다. 이 기간의 길이는 배포 리듬이 정합니다. 1인 팀이면, 그 리듬이 곧 나. 구 버전을 완전히 내리기까지 하루를 걸면, 형은 하루 동안 두 세계를 다 이해해야 합니다.

세 번째 덩어리는 검증 비용입니다. 구 형과 새 형이 같은 값을 가짐을 증명해야 합니다. 상태별 행 수의 합이 같고 열려 있는 청구의 금액 합계가 한 원의 차이도 없이 같고, 무작위로 뽑은 백 개 행이 컬럼마다 같은 값을 가집니다. 청구 제품이면 금액 합계가 한 원이라도 어긋나면 끝입니다. 이 검증 쿼리는 마이그레이션을 시작하기 전에 써 두는 것이 좋습니다.

코드 변경과 비교해 보십시오. 버그 수정은 테스트로 검증하고 영향 범위는 함수입니다. 형 변경은 쿼리로 검증하고 영향 범위는 제품 전체입니다. 그래서 기능 릴리스보다 코드 줄 수는 적어도, 마이그레이션 릴리스는 훨씬 무겁게 느껴집니다. 형 바꾸기는, 이사. 짐을 옮기는 비용, 두 집에서 사는 비용, 새 집에서 짐이 전부 온 증명의 비용.

the-data-modeling-discipline 슬라이드 2

모델은 조용하게 무너진다. 네 가지 형

대부분의 모델은 드라마틱하게 무너지지 않습니다. 네 가지 조용한 모양으로 무너집니다. 이 네 형의 함정은, 한 가지. 형은 가장 쌀 때 정해지고 가장 비쌀 때 대가를 치릅니다.

첫 번째는 숨은 1:N입니다. 설계 시점에는 하나로 보입니다. 사용자의 회사, 워크스페이스의 청구 계정, 제품의 가격표. 그래서 단일 필드로 만듭니다. 반년 뒤, 한 사람이 두 회사에 속할 수 있다고 제품이 말합니다. 단일 필드가 테이블이 되어야 하고 그 필드를 전제로 한 모든 쿼리가 고쳐져야 합니다. 방어법은 제품의 언어를 읽는 것입니다. 목적지, 계정, 소속 같은 단어가 보이면 관계가 커질 가능성이 있습니다. 성장 수평선이 1년 이내라면 처음부터 조인 테이블을 둡니다.

두 번째는 동결된 열거형입니다. 상태 컬럼에 값이 네 개 있고 코드가 그것으로 분기합니다. 환불됐다는 다섯 번째 값이 들어옵니다. 값을 추가하는 것은 어렵지 않습니다. 어려운 것은 그 값을 보게 될 구 코드입니다. 네 값을 전부 처리하고 나머지는 오류로 던지던 분기는, 이제 합법적 상태를 오류로 삼킵니다. 열거형은 읽는 이와 맺은 계약입니다. 값을 추가하기 전에 그 컬럼을 읽는 모든 곳을 나열하십시오. 상태 전이를 아는 곳이 한두 개도 아니면, 그것은 다음 버그의 씨입니다.

세 번째는 돈과 시간의 형입니다. 금액이 부동 소수점이고 타임스탬프에 지역 시간대가 있고 날짜가 문자열인 경우. 어느 것도 오늘 터지지 않습니다. 다중 화폐와 여름 시간제와 월 단위를 넘는 보고서가 들어오는 날에 터집니다. 부동 소수점 금액의 반올림 오차는 청구 분쟁이 되고 지역 시간의 날짜는 정산 기간의 경계에서 한 달이 엇나갑니다. 금액은 최소 단위의 정수에 통화 코드를 처음부터 두고, 시간은 세계 표준시. 나중에 한꺼번에 변환 비용을 치르지 않으려는 계산.

네 번째는 사라지는 이력입니다. 모델이 현재 상태만 남깁니다. 마지막으로 쓴 모델, 현재 잔액, 최근 로그인. 과거는 덮어 쓰입니다. 그러다가 이력 화면이나 사용 보고서를 요구하면, 데이터가 없습니다. 저장하지 않은 것은 재구성할 방법이 없습니다. 추가만 하는 이력 테이블은, 처음에 싸고 나중에는 대체 불가능. 제품에서 가장 비싼 쿼리는 존재하지 않는 데이터에 대한 쿼리입니다.

드러나는 자리 아픈 시점
숨은 1:N 설계 시 단일 필드 관계가 커질 때
동결된 열거형 상태 값 추가 구 코드가 새 상태를 볼 때
돈과 시간의 형 부동 소수점, 지역 시간 다중 화폐, 월 보고서
사라지는 이력 덮어 쓰는 컬럼 이력 화면 요구

the-data-modeling-discipline 슬라이드 3

코딩 전에 한 시간에 끝내는 형 체크

그러면 첫 모델을 정할 때 무엇을 하는가. 완벽을 찾지 않습니다. 형 체크라는 짧은 의식을 치릅니다. 새 테이블마다, 코드 작성 전에 여섯 가지 질문에 답합니다. 테이블당 한 시간 안에 끝나는 일입니다 [추정].

첫째, 정체성. 이 행을 행으로 만드는 것은 무엇인가. 식별자라는 답은 답이 아닙니다. 답은 특정 청구서의 특정 날 특정 결제라는 모양이어야 합니다. 정체성이 기본 키를 정하고 기본 키가 모든 것을 정합니다. 둘째, 시간축. 이 행에 무슨 일이 있었는가. 최소한, 만들어진 시각과 갱신된 시각은 있어야 합니다. 유효 기간이 있는 행이면 시작된 시각과 끝난 시각도 둡니다. 시간이 없는 행은 추론할 수 없습니다. 언제 생긴 값인지 모르면 그 값은 증거가 되지 못합니다.

셋째, 소유자. 누구의 데이터인가. 사용자의, 조직의, 플랫폼의. 소유자가 없으면 접근 경계가 없고 멀티 테넌트 제품은 여기서부터 흐트러집니다. 넷째, 상태. 상태가 있으면 상태 목록은 어디에 있고 전이는 누가 기록하는가. 전이 기록 없는 상태는 앞의 동결된 열거형이 됩니다.

다섯째, 확장 여지. 어떤 컬럼이 1년 뒤 바뀔 것 같은가. 빈 값이 허용되는 컬럼 한두 개는 거의 무료이고 조인 테이블 하나도 1년 안쪽에서는 쌉니다. 그 자리에 미리 여지를 두는 계산. 여섯째, 삭제. 이 행은 사라질 수 있는가. 사라진다면 물리 삭제인지 플래그인지 미리 정합니다. 청구 제품의 사라진 행은 사라지지 않습니다. 그것이 채무입니다. 삭제한 행을 다시 보여 달라는 요청은 늘 옵니다.

형 체크가 보장하는 것은 모델이 맞다는 것이 아닙니다. 보장이 되는 것은, 모델이 우연으로 정해지지 않았다는 점입니다. 우연으로 정한 형은 새벽 2시에 값을 치릅니다. 의식으로 정한 형은 설계 그날, 감당 가능한 금액으로 값을 치릅니다.

the-data-modeling-discipline 슬라이드 4

비용을 필요할 때가 아니라 쌀 때 낸다

규율은 이 비용을 필요할 때에서 쌀 때로 옮기는 일입니다. 미리 정한 형은 나누어 낸 선불입니다. 설계 단계에서 컬럼 한 개를 더 두는 것은 무료에 가깝고, 운영 단계에서 컬럼 한 개를 더 만드는 것은 프로젝트입니다.

사실을 어디에, 몇 번 저장할지는 옳고 그름의 문제가 아니라 예산의 문제입니다. 정규화는 사실을 한 번만 저장하는 규율이고, 비정규화는 그것을 고의로 깨는 일. 하나의 예산은 쓰기 비용이고 다른 하나는 읽기 비용입니다. 사용량 행마다 조직 식별자를 복사해 두는 것은 읽기 비용에 미리 낸 돈입니다. 가장 뜨거운 조회가 매번 조인을 치르지 않도록.

그런데 모든 복사는 약속입니다. 복사본이 소스를 따라갈 것이다, 라는 약속. 약속은 지킬 자가 필요합니다. 조직 행의 멤버 수 컬럼은 멤버십의 추가와 삭제에서 같은 트랜잭션으로 갱신하고, 밤마다 다시 계산하는 잡으로 치유합니다. 컬럼, 트랜잭션, 잡, 대조 쿼리. 네 가지가 한 묶음입니다. 하나를 빠뜨리면 지킬 자 없는 약속이 됩니다.

교정되면 안 되는 사실은, 문서 쪽에. 청구서는 문서이지 뷰가 아닙니다. 발행 시점의 금액과 통화와 세율을 저장하고 이후로는 교정하지 않습니다. 모델 가격이 바뀌어도 이미 일어난 사용량은 소급 바뀌지 않습니다. 과거 청구서를 소급 정정해야겠다,는 기분이 드는 날은 제품의 약속이 틀렸다는 신호입니다.

형이 바뀌어야 하는 날은, 확장하고 이동하고 축소합니다. 둘 다 살 수 있게 형을 넓히고 데이터를 세트로 옮기고 며칠을 본 뒤에 구 형을 지웁니다. 각 단계는 작고 관찰 가능하고 멈출 수 있습니다. 멈출 수 있는 것이 마이그레이션의 가장 중요한 성질입니다. 어느 순간에 멈춰도 동작하는 형에 있기 때문입니다.

빈도는 크기보다 중요합니다. 한 달에 한 번 큰 마이그레이션을 하는 팀은 매일 그 불안과 살고 주에 다섯 번 작은 마이그레이션을 하는 팀은 그것을 생각하지 않습니다. 위험은 이중 도는 기간의 길이에 있습니다. 스키마 일만 하는 30분 칸을 달력에 넣고, 스키마 변경과 동작 변경을 하나의 릴리스에 섞지 않습니다. 새벽 2시의 마이그레이션은 운이 아닙니다. 설계의 날에 정하지 않은 형이 쌓은 청구서입니다. 이 규율은 그 청구서를 조용한 화요일 오후에, 나누어 내는 일입니다. 이 규율의 전부를 담은 전자책 한 권도 함께 준비했습니다.

참고 자료

핵심 개념 요약 인포그래픽 2 NotebookLM이 소스를 종합해 생성한 인포그래픽입니다.

태그: data-modeling, database-schema, llm-apps, migrations, normalization, saas, solo-developer

카테고리:

업데이트: