💻 개발 이야기/Database

[MongoDB] Dynamic field name(동적 필드명)을 꼭 사용해야 하나요?

Jinseong Hwang 2023. 12. 24. 23:57

 

개요

최근 Dynamic field name(이하 동적 필드명)을 사용하는 데이터베이스에서 많은 쿼리를 실행해야 하는 상황이 있었는데, 굉장히 불편하고 느렸던 경험이 있어서 공유하고자 글을 작성합니다. NoSQL과 설계 방법부터 차근차근 얘기해 보겠습니다.

 

 

NoSQL 사용 목적

데이터베이스는 전통적으로 2개의 패러다임을 가지고 있습니다. MySQL과 같은 관계형 데이터베이스와 MongoDB 같은 NoSQL 데이터베이스가 있습니다. NoSQL 데이터베이스는 특히 대량의 데이터와 비정형 데이터를 처리할 때 확장성, 유연성, 고성능을 제공하도록 설계됐다는 특징이 있습니다. 스키마가 없기 때문에 스키마 설계에 큰 시간을 들이지 않을 수 있으며, 다양한 형식의 데이터를 처리할 수 있으므로 웹 애플리케이션, 실시간 분석, 빅데이터 처리 등 다양한 분야에 사용됩니다.

 

https://www.boardinfinity.com/blog/nosql/

조금 더 정확히 말하면, NoSQL 데이터베이스는 하나의 분류만 있는 것이 아니라 다양한 분류가 있습니다.

다양한 기능을 가지고 있기 때문에 명확한 분류는 힘들지만, 위키피디아를 참고해 분류해 보면 다음과 같습니다.

  • Document : MongoDB, Couchbase, ...
  • Key-value : Redis, Memcached, ...
  • Wide-column : HBase, Cassandra, ...
  • Graph : Neo4j, ...

각 클라우드 서비스에서도 다양한 서비스/리소스와 통합이 편리한 NoSQL 서비스를 제공하고 있습니다.

대표적으로 AWS를 살펴보면 DocumentDB, DynamoDB, ElastiCache 등을 제공하고 있습니다.

 

 

MongoDB 스키마 설계 시 고려해야 할 점

https://devopedia.org/data-modelling-with-mongodb

데이터 표현의 핵심은 스키마 설계로부터 출발합니다. 가장 좋은 설계 접근 방식은 애플리케이션에서 원하는 방식으로 데이터를 표현하는 것입니다. 따라서 스키마를 모델링하기 전에 먼저 쿼리 또는 데이터 접근 패턴을 이해하고 있어야 합니다.

 

다음은 스키마 설계 시 고려해야 할 주요 요소입니다.

 

1. 제약 사항

MongoDB의 특성을 고려해야 합니다. 예를 들어 다음과 같은 특성이 있습니다.

  • Document의 최대 크기는 16MB입니다.
  • 디스크에서 Document를 읽고 씁니다.
  • 갱신은 Document 단위로 진행되며, 원자성 갱신도 마찬가지입니다.

 

2. 쿼리 및 쓰기의 접근 패턴

쿼리가 실행되는 시기와 빈도를 알면 가장 '일반적인 쿼리'를 식별할 수 있습니다. '일반적인 쿼리'가 스키마를 설계하는 데 필요합니다. 쿼리 식별 후에는 쿼리 수를 최소화하고, 함께 쿼리 되는 데이터가 동일한 Document에 저장되도록 설계해야 합니다.

 

이러한 쿼리에 사용되지 않거나 자주 사용되지 않는 데이터는 다른 컬렉션에 넣어야 합니다. 변경이 잦은 데이터와 불변 데이터로 구분해서 저장하는 것도 고려하면 좋습니다. 스키마 설계의 우선순위를 가장 일반적인 쿼리에 지정할 때 최상의 결과를 얻을 수 있습니다.

 

3. 관계 유형

애플리케이션 요구 사항과 Document 간 어떤 데이터가 관계를 맺어주고 있는지 고려해야 합니다. 불필요한 쿼리 없이 Document를 바로 참조할 수 있는 방법을 파악해야 하며, 관계 형태가 변경될 때 갱신되는 Document 개수가 몇 개인지 알아야 합니다. 또한 쿼리 하기 쉬운 구조인지도 고려해야 합니다.

 

4. 카디널리티

카디널리티도 고려해야 합니다. 예를 들어, 현재 데이터 관계가 일대일인지, 일대다인지, 다대다인지, 일대수백만인지, 다대수십억인지 고려해야 합니다. 대량의 개체에 직접 접근해야 하는지, 상위 개체에만 접근되는지 고려해야 하며, 읽기/쓰기 비율도 고려해야 합니다. 결과 값들을 참고해서 비정규와 여부와 개체를 Document 내부에 둘지 참조 형태로 둘지 여부를 결정할 수 있습니다.

 

 

동적 필드명이란?

MongoDB Document는 JSON 형태로 저장됩니다. JSON은 key, value로 구성되는데, value에는 숫자, 문자열, 리스트, 오브젝트 등 다양한 값이 저장될 수 있습니다. 여기서 key는 일반적으로 변하지 않는, 정적인 값을 사용하는 경우가 많습니다.

 

과일 별 농장, 가격, 유통마감일 값으로 Document가 구성되는 `farm`이라는 Collection이 있다고 가정합시다. Document 예시는 다음과 같습니다.

 

{
  "_id":"507f191e810c19729de860ea",
  "createdAt":{
     "$date":"2023-12-24T00:00:00.000Z"
  },
  "data":[
     {
     	"name":"수박",
        "farmIds":[123, 456, 789],
        "price":20000,
        "expirationDate":{
           "$date":"2024-01-12T00:00:00.000Z"
        }
     },
     {
     	"name":"딸기",
        "farmIds":[111, 222, 333, 444, 555],
        "price":16000,
        "expirationDate":{
           "$date":"2024-01-08T00:00:00.000Z"
        }
     }
  ]
}

 

_id, createdAt, data는 컬렉션 내 모든 Document가 가집니다. 그리고 data 안에서도 name, farmIds, price, expirationDate가 있습니다. 이 Document의 name을 동적 필드명으로 사용한 예시는 다음과 같습니다.

 

{
  "_id":"507f191e810c19729de860ea",
  "createdAt":{
     "$date":"2023-12-24T00:00:00.000Z"
  },
  "data":{
     "수박":{
        "farmIds":[123, 456, 789],
        "price":20000,
        "expirationDate":{
           "$date":"2024-01-12T00:00:00.000Z"
        }
     },
     "딸기":{
        "farmIds":[111, 222, 333, 444, 555],
        "price":16000,
        "expirationDate":{
           "$date":"2024-01-08T00:00:00.000Z"
        }
     }
  }
}

 

 

동적 필드명 사용 시 단점

동적 필드명을 사용했을 때 제가 느낀 단점들입니다. 핵심 단점은 '복잡도 증가'입니다.

 

1. 복잡한 쿼리 및 성능 문제

MongoDB의 동적 필드명은 뛰어난 유연성을 제공하여 각 Document가 서로 다른 필드 이름을 가지는 구조를 허용합니다. 하지만 이로 인해 복잡하고 비효율적인 쿼리가 발생할 수 있습니다.

 

2. 인덱싱 문제

대규모 데이터를 처리할 때 인덱싱은 데이터베이스 성능에 굉장히 큰 영향을 미칩니다. 그러나 동적 필드를 인덱스 하는 것은 어렵습니다. 동적 필드가 없는 스키마에서는 자주 쿼리되는 필드에 인덱스 생성합니다. 이와는 대조적으로 동적 필드의 경우, 어떤 필드를 인덱스 할지 예측하기가 어렵습니다. 따라서 쿼리 성능에 엄청난 악영향을 미칠 수 있습니다.


3. 데이터 일관성 및 무결성

동적 필드명은 데이터 구조에 불일치를 초래할 수 있습니다. 정적 스키마에서는 각 Document가 미리 정의된 구조를 준수하므로 데이터베이스 전체에서 일관성이 보장됩니다. 그러나 필드가 동적인 경우, 한 Document에서는 필드가 문자열인 반면 다른 문서에서는 동일한 필드에 숫자 유형을 사용할 수 있습니다. 또한 ORM 등을 사용해서 애플리케이션 코드와 통합이 어려워질 수도 있습니다.

 

4. 유지 보수 어려움

동적 필드명을 사용하면 유지 보수가 어려워질 수 있습니다. 정적인 구조가 아니라면 데이터베이스의 현재 상태를 신뢰하고 파악하기가 어려워지고, 개발자가 변경 사항이 시스템에 어떤 영향을 미칠지 예측하기가 더 어려워집니다.

 

5. 복잡한 Aggregation

데이터 집계는 동적 필드 이름으로 인해 더욱 복잡해집니다. 데이터 집계는 Document 전체에 걸친 필드 이름에 의존합니다. 동적 필드는 그 특성상 이러한 균일성을 깨뜨리기 때문에 의미 있는 집계를 수행하기가 어렵습니다.

 

 

그럼에도 동적 필드 쿼리를 해야겠다면?

위 동적 필드명을 사용하는 예시에서 "수박"을 생산하는 농장을 찾고 싶다면 아래와 같이 쿼리를 작성할 수 있습니다.

 

db.farm.aggregate([
  {
    $addFields: {
      "dataAsArray": { $objectToArray: "$data" }
    }
  },
  {
    $match: {
      "dataAsArray.k": "수박"
    }
  },
  {
    $project: {
      "dataAsArray": 0
    }
  }
]);

 

MongoDB Aggregate 기능은 차례대로 진행되기 때문에 설명을 해보면 다음과 같습니다.

 

01 $addFields

`$objectToArray`를 통해 `data`의 값들을 Key, Value 형태의 배열로 변경합니다. 그 데이터를 `dataAsArray`라는 필드로 추가합니다.

 

02 $match

`dataAsArray` 필드에서 k, v로 Key, Value에 접근할 수 있는데, Key 값이 "수박"인 Document를 모두 검색합니다.

 

03 $project

Projection 기능입니다. 검색을 위해 임의로 추가한 `dataAsArray` 필드는 필요 없으므로 출력 결과에서 제거합니다.

 

 

 

결론

MongoDB의 동적 필드명이 무조건 나쁜 것은 아니며 애플리케이션 코드와 결합 시 Map 자료구조와 함께 사용할 수 있기 때문에 좋은 경우도 있습니다. 하지만 동적 필드명은 유연성을 제공하지만 쿼리 복잡성, 인덱싱 문제 등 다양한 측면에서 단점이 있습니다. MongoDB 스키마 설계 시 동적 필드명 사용을 결정할 때는 신중하게 고려해야 하며 쿼리 패턴을 통한 전략적 계획이 필수적입니다.

 

 

References