온라인 서비스에서 모든 내용은 데이터를 표현한다. → 가능한 모든 데이터를 저장하고 활용? → 데이터를 저장 보관하고 조회하는 것 모두가 비용이다 → 데이터를 더 잘 사용하기 위해서는 데이터 모델링이 필요하다.
추상적인 요구사항으로부터 구체적인 구현 방식
까지 좁혀들어오면서 사고하는 방식이다. Top-Down 방식의 장점은 도구를 한정하지 않고 논리적인 과정을 거치기 때문에 서비스/비즈니스/기술의 수준이 높아질 수 있는 가능성이 있다는 것이다. 다만, 이것은 추상적인 모델링을 하는 이들이 논리적으로 타당함을 갖출 뿐만 아니라 모델링 능력과 경험이 있어야 가능하다. Top-Down이기는 하나 이론적인 배경이 없거나, 구현체를 하나도 모른다면 Top-Down 이 불가능하다.
1. 서비스/비즈니스 요구사항을 한다.
2. 서비스/비즈니스 요구사항으로부터 시스템/소프트웨어 요구사항을 도출한다.
3. 시스템 요구사항을 기반으로 Top-Down 방식으로 추상적인 모델링을 한다.
기술 스택을 기반으로 구체적인 구현방식으로 부터 요구사항을 구현
한다. 쓸 수 있는 도구가 한정되어있을 때 사용하는 방식이다. 이유는 다양할 수 있다. 개발자의 역량, 비용, 인프라, 그리고 개발문화 등 여러이유 때문에 써야하는 도구가 정해질 수 있다. 이런 경우에는 해당 도구로 지원가능한 기능, 기술을 기반으로 요구사항에서 불가능한 것들을 제외하거나, 요구사항을 변경해야 하는 경우가 생긴다.
1. 내가 선택할 수 있는 DB의 종류를 열거한다.
a. 사용할 수 있는 기술 스택
b. 물리적/비용적으로 사용 가능한 것
2. 해당 DB 종류들의 특징(장점, 단점, 제약사항)을 파악한다.
3. DB 선택지별로 trade-off를 고려한다.
위의 Top-Down, Bottom-Up을 고려했을 때 가능한 선택지를 뽑는다.
가능한 선택지 중에 trade-off와 개발/운영자의 의사를 반영해서 결정한다.
RDBMS의 설계에서 불필요한 데이터(중복 등)을 최소화하고 데이터 정확성(accuracy)과 일관성(consistency)을 향상 시키도록 데이터를 구조화하는 과정 또는 그 결과를 정규화라고 한다.
비정상적인 것을 찾아서 정상화(normalize)하는 과정
으로 진행한다.
데이터가 여러개가 조합되어 있으(테이블)면 필요한 것 별로 데이터 묶음(테이블)을 분리하는 것 (RDBMS 테이블을 쪼개는 것)
정상화 하는 정도에 따라서 제 1정규화, 2정규화, 3정규화.. 식으로 단계가 진화한다. 참고
관계들의 집합들에 대해 의도하지 않은 않은 삽입, 업데이트 및 삭제 종속성에서 자유롭게 한다.
새로운 유형의 데이터가 도입돼도 관계 집합에 대한 재설정의 필요성을 줄인다. → 프로그램의 수명 증가
관계형 모델 사용자들이 모델을 더 효율적으로 사용할 수 있도록 한다.
어떤 쿼리가 나와도 내가 원하는 결과를 다양하게 쓸 수 있는 것을 목표로 한다.
정규화를 섬세하게 할 수록 성능이 좋아지는 것이 아니다. 정규화를 섬세하게 할수록 프로그래밍하기 좋아지는 것 또한 아니다. 또 정규화의 목적과 효과를 잘 이해하고 자신의 서비스/시스템에 맞게 의사결정하고 결과를 낼 수 있는 능력이 더 중요하다.
서비스의 특징, UX의 설계에 따라서 결정하는 방식이다. RDBMS는 Update가 가능하지만, 데이터 관리 차원에서는 update가 없는 것이 좋다. 또한 optional(nullable)한 필드가 많다면 비즈니스 로직과 데이터에 버그를 많이 유발한다. 따라서 idempotent(멱등성)한 아키텍처의 구성, 데이터 관리의 용이성, 비즈니스 로직의 버그 방지를 위해서 한 번에 확정되는 데이터의 기준으로 테이블을 분리하는 방식을 사용할 수 있다.
쇼핑몰을 구축한다. 유저는 상품을 장바구니에 담는다. 주문서를 작성한다. 유저의 결제를 기다린다. 결제가 완료되면 배송정보가 생성된다. 배송이 완료되면 최종 주문이 완료된다. 유저의 구매내역을 하나의 테이블로 만들면 다음과 같다.
Table: order
order_no | items | payment | delivery | status | review |
---|---|---|---|---|---|
1 | [1029,2910,3829] | null | null | ordered | null |
nullable column 은 개발자들이 항상 null 처리를 해야하는 불편함을 발생시킨다. 또한 null 의 의미가 무엇인지 - 잘못 된 값인지, 유저의 상태가 이상한 것인지, 없어도 되는건지 등의 오해를 발생시키고 커뮤니케이션이 늘어나게 만든다. 게다가 기본 테이블은 null 인 컬럼이 많기 때문에 null에 대한 복잡도도 기하급수적으로 증가한다.
Table: order
order_no | items | amount | payment | delivery | status | review |
---|---|---|---|---|---|---|
1 | [1029,2910,3829] | 1000 | 1000 | not_yet | ordered | not_yet |
필드를 non null로 선언하고, default 값을 테이블에 선언, 프로그램에서는 enum으로 상태 값을 관리하도록 해서 문제를 해결했다.
order_no | items | amount | payment | delivery | status | review |
---|---|---|---|---|---|---|
1 | [1029,2910,3829] | 1000 | 100 | not_yet | ordered | not_yet |
결제정보가 도착해서 유저의 구매정보를 업데이트 한다. 유저가 결제 취소 후 재결제를 했는데, 첫 번째에 입금된 금액으로만 남아있고 두 번째 입금 금액이 없었다. 유저는 자신의 입금 내역이 다르다는데 확인할 길이 없다.
Table: payment
payment_no | datetime | pay_method | pay_vendor | status | cost |
---|---|---|---|---|---|
1 | 2020.01.01 | 10:00:00 | card | samsungcard | confirmed |
2 | 2020.01.01 | 10:00:01 | card | samsungcard | cancelled |
3 | 2020.01.01 | 10:00:02 | card | samsungcard | confirmed |
Table: order
order_no | items | amount | FK_payment | delivery | status | review |
---|---|---|---|---|---|---|
1 | [1029,2910,3829] | 1000 | 3 | not_yet | ordered | not_yet |
입금 내역을 기록하는 테이블(payment)을 따로 만들었다. order 테이블은 payment 의 마지막 유효한 결제 내역을 FK로 가진다.
배송도 택배사의 데이터를 연동해서 값을 채워넣었는데, 배송이 완료 이벤트가 배송출발 이벤트보다 먼저 들어와서 배송중인 상태로 잘못 업데이트가 되었다. 판매자에게서 완료가 처리 안된다는 CS가 들어왔다.
Table: order
order_no | item | amount | FK_payment | delivery | status | review |
---|---|---|---|---|---|---|
1 | [1029,2910,3829] | 1000 | 3 | 배송출발 | ordered | not_yet |
Table: delivery
payment_no | datetime | partner | delivery_vendor | status |
---|---|---|---|---|
1 | 2020.01.01 | 10:00:00 | Nancy | Logen |
2 | 2020.01.01 | 10:00:01 | Paul | Logen |
3 | 2020.01.01 | 10:00:02 | Paul | Logen |
Table: order
order_no | item | amount | FK_payment | FK_delivery | status | review |
---|---|---|---|---|---|---|
1 | [1029,2910,3829] | 1000 | 3 | 2 | ordered | not_yet |
결제정보의 업데이트와 배송정보 업데이트에 순서가 보장되지 않았다. 거의 동시에 요청이 오면 race condition에 의해서 업데이트가 무시될 수 있었다.
서버를 여러대 운영하고 있었다. server1은 payment 정보를 업데이트하려고 하고, server2는 동시에 delivery 정보를 업데이트하는 쿼리를 날렸다. 이 경우에 2번이 근소한 차이로 나중에 적용된다면 FK_pament의 값은 다시 null로 바뀌게 된다.
→ lock을 걸어 select와 update를 할 때 다른 서버는 접근을 못하게 했다. → 조회 지연시간이 늘어났다.
유저의 구매과정에서 생기는 이벤트 별로 테이블을 분리(정규화)했다. 배송 이벤트 테이블, 결제 이벤트 테이블이 order 테이블을 참조하고, 주문 내역의 상태의 판단과 통합 조회는 join 쿼리로 해결하기로 했다.
Table: payment
payment_no | datetime | pay_method | pay_vendor | status | cost | FK_order |
---|---|---|---|---|---|---|
1 | 2020.01.01 | 10:00:00 | card | samsungcard | confirmed | 100 |
2 | 2020.01.01 | 10:00:01 | card | samsungcard | cancelled | 100 |
3 | 2020.01.01 | 10:00:02 | card | samsungcard | confirmed | 1,000 |
Table: delivery
payment_no | datetime | pay_method | pay_vendor | status | cost | FK_order |
---|---|---|---|---|---|---|
1 | 2020.01.01 | 10:00:00 | card | samsungcard | confirmed | 100 |
2 | 2020.01.01 | 10:00:01 | card | samsungcard | cancelled | 100 |
3 | 2020.01.01 | 10:00:02 | card | samsungcard | confirmed | 1,000 |
Table: order
order_no | items | amount |
---|---|---|
1 | [1029,2910,3829] | 1000 |
상태의 판단은 join 쿼리안에서 로직으로 column을 추가하거나, 서버에서 로직으로 추가하는 것으로 했다. 다음과 같은 정규화 목표를 달성했다.
1. 데이터의 중복이 없다.
2. 하나의 이벤트가 하나의 null이 없는 데이터셋(row)으로 완결성이 있다.
3. update가 없어서 race condition이 발생하지 않는다.
4. 데이터의 누락이 없어 데이터가 정확하다.
5. 모든 데이터가 남아서 문제 상황시 CS를 정확히 대응할 수 있다.
정규화 과정에서 정규화 조건은 만족하지만, 의도하지 않은 side effect가 발생할 수 있다. 다음 세 가지 상황이 발생하지 않도록 정규화를 해야한다.
데이터를 삽입할 수 없는 오류 (특정 속성이 비어있는 데이터가 들어오는 경우)
업데이트가 잘 못 되는 경우 (같은 데이터에 다른 속성이 두개 등록)
잘 못 지우는 경우 (cascade 옵션 때문에 모든 데이터가 지워져 버림)
정규화로 잘 구조화되어 나누어져 있는 데이터를 다시 합치는 것을 말한다. 데이터가 나누어져 있으면 복잡도가 높아진다. 또한 활용할 때마다 Join을 해야하는 불편함이나 성능의 손해가 발생한다. 전체적으로 데이터 활용에 있어서 생산성의 저하가 나타나기 때문에, 활용 목적에 따라 데이터를 비정규화 하는 경우가 필요하다.
빅데이터 데이터베이스에서는 비정규화를 이용해서 데이터를 저장하고 조회하는 방식을 사용한다.
시스템 전체에서 데이터를 어떻게 활용할지에 대한 예상과 계획을 가지고 모델링 하는 것이다. 저장하는 쪽 보다는 가공하고 활용하는 쪽의 요구사항을 기반으로 모델링을 한다. 키를 기준으로 테이블을 합치는 의사결정을 한다. 빅데이터용 데이터베이스에 적합하다.
이 앱은 슈퍼앱이다. 유저의 일상 생활에 파고들어서 없는 서비스가 없다. 메신저, 쇼핑, 검색, 콘텐츠 등의 서비스가 연계된다. 최근에 ML을 이용해서 경계를 가리지 않고 자동화된 추천기반의 서비스를 제공한다. 여기서 학습된 내용은 광고 타게팅 효율에 큰 영향을 미쳐서 기하급수적인 매출 성장을 이룰 수 있다. 그런데 데이터가 정규화로 너무 잘게 나눠져 있다. ML서비스는 다음과 같은 영향이 있다. 데이터를 활용하기 위해서는 매번 권한을 따로 받아야 하는 것이 불편하다. 데이터를 여러군데서 읽어야 하므로 I/O 비용때문에 모델 학습비용이 커진다. 모델의 종류가 많아지고 관리 파일이 커진다.
유저 정보를 중심으로 유저의 활동 정보를 비정규화해서 하나의 join된 테이블로 만들었다. 데이터 조회 속도가 빨라지고, 모델 학습 속도 또한 빨라져서 online ML을 할 수 있게 되었다. 관리포인트가 적어지니 기술부채와 불필요한 리소스 사용량이 모두 감소했다.
이 앱은 슈퍼앱이다. 하나의 유저에 대해서 여러 종류의 데이터가 있고, 각 부서에서 관심있는 데이터는 제각기 다르고, 강한 개인 정보보호 규약때문에 데이터 접근 권한에 대해서 엄격히 관리된다고 하자. 이 경우 정규화를 하는 것이 데이터 관리 측면에서 좋다. 회사 전체의 생산성을 높이는 것이 top priority이다. 만약 비정규화 되어있다고 해보자, 모든 부서에서 불필요한 유저의 속성을 데이터 조회할 때마다 읽을 수 밖에 없다. 불필요하게 낭비되는 디스크 용량, I/O 비용, 데이터 유출에 의한 보안사고 우려까지 비용과 리스크가 크다.