소비관리 서비스 탄생기 - 7. 데이터 스키마

Seoyong Lee·2022년 11월 22일
0

프로젝트

목록 보기
7/16
post-thumbnail

지난 시간에는 대략적인 요구사항을 정리했었다. 이번 글에서는 제작하려는 서비스가 구체적으로 어떤 데이터 구조를 가져야 할지 스키마에 대해 고민해보았다.

가까워질 필요가 있는 백엔드

백엔드는 눈에 보이지 않는다. 시각디자인 전공자에게는 마치 반대편 끝에 존재하는 분야처럼 느껴졌다. 실무를 진행하면 알쏭달쏭한 이야기들이 들려온다. 트랜잭션의 동시성 이슈를 어떤 식으로 방지한다 라던지, 어떤 식으로 구조를 짜야 부하 테스트를 통과하고 안정성이 높아지는지 등등 보이지 않는 것들에 대해 말하는 것은 매번 들어도 어렵다. 요즘은 아예 프론트엔드에서 BFF(Backend For FE)를 구축해서 필요한 데이터만 받는다고 하던데 그렇다면 그 백엔드는 프론트엔드 개발자가 구축해야 하나? 그렇다고 한다.

백엔드는 어쩔 수 없이 조금 덜 친근한 분야이긴 하지만 데이터 기반 예산수정 문제를 해결하려면 가장 깊이 알아야 한다. 먼저 데이터 구조에 대한 고민이 있다. 사실 스키마를 처음부터 짜본 경험이 없었다. 앞으로 계속 변하겠지만 그나마 처음부터 쓸 수 있게 만들어야 수정이 불가능해 작업물을 모두 버려야 하는 상황을 막을 수 있을 것 같았다. 따라서 형식에 제한이 없는 NoSQL로 기본 틀을 구성해 보기로 하였고 MongoDB의 Schema Design Best Practices를 참고했다.

MongoDB의 베스트 프랙티스

MongoDB의 베스트 프랙티스는 일단 스키마를 잘 짜는 단 하나의 베스트 프랙티스는 없다고 한다. 그럼 어떻게 해야 될까? 다음과 같이 만들려는 서비스의 요구사항에 따라 달라진다.

  • 읽고 쓸려는 데이터가 얼마나 무거운지?
  • 고려하는 퍼포먼스가 어느정도인지?
  • 데이터가 어떤 형태로 scale할 것인지?

그렇다면 MongoDB 스키마는 기존 관계형 DB 구조와 어떻게 다를까?

기존 관계형 스키마를 작성하기 위해선 데이터를 정규화(주로 제3정규화)해야 했다. 대략 이해한 내용은 데이터를 테이블들로 쪼개어 저장하기 위해선 정규화라는 처리가 필요하다. 이러한 처리 없이는 관계형 스키마에 데이터를 집어넣을 수 없다. 이렇게 쪼개어진 테이블은 Foreign Key를 통해 조인한다.

MongoDB 스키마는 이와 다르게 데이터를 쪼개지 않는다.

the only thing that matters is that you design a schema that will work well for your application.
제일 중요한 점은 당신의 어플리케이션에 잘 맞도록 스키마를 디자인하는 것입니다.

{
    "first_name": "Paul",
    "surname": "Miller",
    "cell": "447557505611",
    "city": "London",
    "location": [45.123, 47.232],
    "profession": ["banking", "finance", "trader"],
    "cars": [
        {
            "model": "Bentley",
            "year": 1973
        },
        {
            "model": "Rolls Royce",
            "year": 1965
        }
    ]
}

위 예시와 같이 MongoDB 스키마는 하나의 object 형태로 저장할 수 있다. 그렇다면 데이터끼리 연결되는 경우는 어떤 식으로 가져와야 할까? 다음과 같이 두 가지 옵션이 있다.

  • Embedding: 데이터를 하나의 도큐먼트에 모두 넣는 방식
  • Referencing: 다른 콜렉션의 데이터를 id 등으로 참조하는 방식

Mongo DB에는 관계형 DB에서 사용하는 Join 개념이 없다. 대신 lookup 연산자를 사용할 수 있지만 이는 성능 문제로 권장되는 방법은 아니다. 언제 각 방법을 사용해야 할지는 다음의 지침을 참고했다.

  • 상위 문서를 부분적으로 read해서 많은 양의 데이터를 다시 write 하려는 경우 Referencing 하라. Embedding은 수 초가 걸릴 수 있다.
  • document의 대부분을 불러와야 하는 경우 Embedding 한다. Referencing 하면 필요한 것보다 더 많은 쿼리가 실행되어 응답 시간이 불필요하게 늘어난다.
  • 두 개의 관련 document에 있는 데이터의 subset만 주기적으로 필요한 경우 두 가지 방식을 혼합하는 것이 좋다. 예를 들어 현재 주소를 사용자 document와 함께 embedded document로 저장하고 사용되지 않는 과거 주소를 referenced documents로 저장하도록 선택할 수 있다.

다음의 rules of thumb도 참고하였다.

  • 함께 액세스되는 데이터는 함께 저장되어야 한다.
  • Massive Array(한 도큐먼트에 거대한 양의 데이터를 넣는 것)는 안티패턴으로 콜렉션을 분리한다.
  • 가능하다면 join과 lookup을 피해야 하지만 꼭 필요하다면 사용하라

결론적으로는 'Embedding을 기본으로 하되, 지나치게 큰 크기의 데이터는 Referencing하라' 로 정리해 볼 수 있겠다.

서비스 스키마 구성

그렇다면 위 내용을 기반으로 서비스의 스키마를 구성해 보자.

먼저 로그인 후엔 사용자라는 콜렉션에 관련 정보가 저장될 것이고, 사용자가 만들어지면 동시에 거래내역과 예산 리스트, 그리고 자산 리스트에 대한 콜렉션을 사용자의 id로 생성해 보려 한다(사용자와 연결시키기 위해). 사실 예산 관련 정보는 거의 모든 페이지에서 사용될 예정이라 사용자와 embedded 될 수도 있을 것 같지만 예산이 앞으로 계속 늘어난다면 massive array가 될 것 같아서 분리해 주었다. 거래내역은 엄청나게 양이 많을 것 같아서 콜렉션을 분리하였다.

// User
_id: 'objectId',
name: 'string',
createdAt: 'date',	
// 모바일 앱 알림 관련 설정 - 어떤 구조로 잡아야 할지 아직 모르겠음
// 기타 금융정보?
// Budgets
_id: 'objectId',  // = UserId
budgets: 'BudgetId[]',
createdAt: 'date',	
// Transactions
_id: 'objectId', // = UserId
transactions: 'TransactionId[]',
createdAt: 'date',	

고민중인 부분은 나의 자산 내역으로 사실 이 부분이 MVP에 속할지는 아직 불확실하다. 따라서 일단은 콜렉션만 존재하는 상태로 추후에 내용을 추가하기로 하였다.

// Assets
_id: 'objectId', // = UserId
// 어떻게 자산 내역을 가져올지 외부 API와 맞춰보아야 할 것 같다.
Assets: 
createdAt: 'date',	

이제 각 예산과 거래내역에 대한 콜렉션을 만들었다. 거래 내역같은 경우 외부 API의 형태에 맞춰 필요한 데이터만 다시 골라서 저장하면 어떨까 생각해보았다.

// Budget
_id: 'objectId',
name: 'string',
amount: 'number',
createdAt: 'date',	
// Transaction
_id: 'objectId',
name: 'string',
// 금액
amount: 'number',
// 결제수단
paymentMethod: 'string',
// 카드결제인 경우 카드명
cardName: 'string',
// 카드결제인 경우 카드번호
cardNo: 'string',
// 할부 개월
InstallmentMonth: 'number',
// 가맹점 업종
storeType: 'string', 
// 소비 카테고리
expenseCategoryType: 'string',
createdAt: 'date',	

다음 글에선 홈 화면 UX에 대해 다시 고민해 보았습니다.

References
MongoDB Schema Design Best Practices
Embedded vs. Referenced Documents in MongoDB: How To Choose Correctly For Increased Performance

0개의 댓글