
내가 생각하는 엔지니어의 핵심 역량은 요구사항을 충족시키면서 성능과 보안을 고려한 데이터 모델링이라고 생각한다. 처리 시간을 빠르게 하기 위해 아무리 알고리즘을 최적화해도 데이터 모델 자체가 비효율적이면 코드만으로는 한계가 존재한다. 시스템이 게시판 날짜 데이터를 YYYYMMDD 문자열로 사용하는 것과 DateTime 타입으로 사용하는 것은 아주 큰 차이다. 또한 데이터 모델링은 단순히 데이터베이스에 저장되는 데이터 모델만을 말하는 게 아니라 다음 모든 유형을 포함한다.
유저의 입력 데이터 구조
서버 메모리상 데이터 구조
데이터베이스에 저장되는 데이터 구조
캐시 서버에 저장되는 데이터 구조
유저에게 출력 되는 데이터 구조(DTO)
외부 API와 주고 받는 데이터 구조(DTO)
운영자 전용 데이터 구조
알다시피 몽고(Mongo)는 문서 기반 NoSQL이고 포스트그레스큐엘(PostgreSQL)은 관계형(RDBMS)이다. 몽고디비의 장점은 스키마리스라는 것인데, 이는 데이터 스키마가 변경되어도 DB에 직접 DDL 쿼리를 통해 변경된 스키마를 관리할 필요가 없는 걸 말한다.
배달 앱을 예시로 들면 유저는 배달받을 주소를 여러 개 가질 수 있으니 유저와 주소록은 1:N 관계가 되어야 한다.

Rust 코드로 엔티티를 정의하면 아래와 같다.
use serde::{Serialize, Deserialize};
// Uuid는 PostgreSQL용 sqlx Uuid일수도 mongo bson의 Uuid일수도 있다.
// DateTime도 PostgreSQL용 chrono Datetime 일수도 mongo bson의 Datetime 일수도 있다.
#[derive(Serialize, Deserialize)]
struct User {
id: Uuid,
name: String,
joined_at: DateTime,
}
#[derive(Serialize, Deserialize)]
struct Address {
id: Uuid,
address: String,
added_at: DateTime,
}신규 기능으로 주소록에 별칭을 저장하고 보여주기 위해 주소에 문자열 타입의 별칭 데이터가 필요하다고 하자. 그럼 먼저 Rust 코드에서는 Address 구조체에 하위 호환을 위해 Option<String>으로 alias 필드를 추가한다. Option으로 하면 기존에 별칭이 없던 유저들을 위해 기본값 빈 문자열로 마이그레이션 쿼리를 실행 할 필요가 없다.
#[derive(Serialize, Deserialize)]
struct Address {
id: Uuid,
address: String,
added_at: DateTime,
alias: Option<String>, // 새로 추가된 필드
}데이터베이스로 PostgreSQL을 사용한다면 DDL 쿼리로 ALTER TABLE 주소록 ADD COLUMN alias VARCHAR(255)를 실행해 테이블에 컬럼을 추가해주어야 한다. 그러나 몽고디비는 스키마리스이기 때문에 이 작업이 필요 없다.
하지만 스키마리스라고 해서 방심하면 안 된다. 만약 별칭 필드를 nullable이 아닌 String 타입으로 추가했다면 기존 데이터에 alias 필드가 없기 때문에 Address 컬렉션을 읽을 때 에러가 발생해 주소록 조회 기능에 장애가 생긴다.
오히려 PostgreSQL을 사용했다면 신규 컬럼 추가 시 쿼리에 DEFAULT ''만 추가해주면 기본값을 넣어 자동으로 마이그레이션되지만 몽고디비는 데이터 모델 변경에 따른 마이그레이션 필요 여부를 꼭 신경 써야 한다.
나는 언제나 신규 필드는 Option<T> 타입으로 추가하고, 만약 기존 데이터에 반드시 값이 있어야 한다면 별도의 마이그레이션 스크립트를 작성해 실행한다. Rust 언어의 특성과 git 덕분에 엄격한 스키마 버저닝이 자동으로 이루어지기 때문에 데이터 모델의 변경사항은 항상 코드 변경사항에 포함되어 추적 가능하다.
위 주소록은 RDBMS에서 사용하는 방식으로 유저와 주소록을 별개의 컬렉션으로 분리해 유저 아이디를 외래키로 참조하는 Referencing 방식을 사용했다. 배달 서비스에서 주소록 목록을 보여줄 때 해당 유저의 이름도 보여주어야 한다면 방법은 2가지다.
클라이언트가 유저, 주소록 데이터를 각각 fetch해서 UI에서 표시
서버가 주소록 목록 조회시 유저 이름도 조인해서 같이 응답
Ajax, React 등 REST API 관점에서 보면 1번 방식이 대부분이고 또 그게 맞다. 그렇게 요구사항별로 클라이언트가 조합해서 구현하라고 애초에 API 형태로 만든 것이니까. 그런데 만약 어드민 관리 화면에서 유저 목록 조회 시 각 유저의 1번째 주소를 보여줘야 한다면 어드민은 유저 ID 목록으로 유저 정보를 전부 각각 조회하고 또 각 유저의 주소록 정보를 각각 조회해야 한다. 어드민은 요청량이 크지 않아 1번 방식으로 그냥 구현할 수도 있겠지만, 만약에 어드민 용이 아니고 주소록이 아니라 다른 형태의 데이터가 항상 다른 엔티티랑 같이 조회해야 한다면? 서버가 조인해서...