MongoDB 5.0 Time Series

 

MongoDB 5.0 Time Series

MongoDB가 5.x 버전으로 릴리즈 되면서 추가된 유용한 기능 중에 하나가 바로 Time Series 컬렉션을 활용할 수 있다는 것 입니다. 쉽게 표현하면 시계열 데이터를 저장하는 기능인데 더 쉽게 표현하면 일정 시간 간격으로 한 줄로 배열된 데이터를 적재하는 기능입니다.시계열 데이터는 주기적인 시간 단위로 데이터를 수집하는 용도에 적합하며 즉, 시간 표기가 따라와야 하는 로그성 데이터를 쌓는 용도에 많이 사용합니다. object property 이외의 metadata 영역은 update가 거의 일어나지 않습니다. 즉 동일한 메타 데이터로부터 수집한 object property 값의 변화만 기록하는 방식입니다.

Timeseries 컬렉션 사용에 대한 이점

Timeseries 컬렉션을 사용하게 되면 컬렉션에 저장되는 데이터의 기준이 시간값으로 설정됩니다. 이에 따라 가져오는 장점이 많이 있습니다.
  • 시간 데이터를 이용하여 데이터를 정렬하기 때문에 자동으로 시간에 대한 Index를 제공한다.
  • 일반 컬렉션보다 데이터와 인덱스의 사이즈가 작다.
  • 데이터 압축률이 좋다.
일반적으로 데이터베이스는 순차적으로 데이터를 블록에 저장합니다. 하나의 블록이 다 차면 다음 블록에 기록하는 방식으로 동작합니다.
위 그림에서 같은 색상의 데이터가 특정 필드에 대해 같은 값을 가지고 있는 데이터를 표시합니다. 일반적으로 특정 필드에 대해 검색을 하는 경우 인덱스를 사용하게 되는데,  특정 시간대의  특정 필드값의 데이터를 보기를 원한다면, 데이터를 스캔하여 메모리로 가져와 필드 값을 기준으로 먼저 정렬을 하게되고, 정렬된 필드값 내에서 다시 시간값으로 정렬을 하여 최종적으로 데이터를 fetch하여 보여주게 되는데, 이 과정에서 데이터가 적재된 모든 블록을 스캔하는 과정을 거치게 됩니다.
데이터를 메모리 안에서 순차적으로 정렬을 한 다음 최종적으로 결과 값을 보여 줍니다.
결국 이 과정에 데이터를 가지고 있는 블록의 사이즈 만큼 읽어와서 같은 사이즈의 블록을 불러와 정렬을 위해 캐싱을 하게 됩니다.  읽어온 블록과 동일한 양의 캐싱을 하는 것은 매우 비효율적인 동작이며, 이 과정에서 많은 자원이 낭비됩니다.
반면 Timeseries를 사용하게 되면, MongoDB는 같은 소스의 데이터를 비슷한 시간대의 다른 데이터와 함께 같은 블록에 저장하는 프로세스를 사용하게 됩니다. 블록이 가득차면 다음 블록에 데이터를 기록하는데, 각 블록이 하나의 소스와 특정 시간대를 가지며, 해당 값을 검색하는데 필요한 인덱스를 가진다는 특징이 있습니다. 즉, 하나의 블록에 대해 데이터 소스와 시간 범위에 대한 인덱스를 가지고 있기 때문에 100배 이상으로 인덱스 크기를 줄일 수 있습니다.
데이터 저장 측면에서 뿐만 아니라, 압축에 대한 측면에서도 훨씬 좋은 장점이 있습니다. 시간이 지나도 데이터의 업데이트가 거의 발생하지 않기 때문에, MongoDB는 비슷한 위치에 있는 데이터들을 한꺼번에 압축 할 수 있습니다. 이러한 특징 때문에 기존 데이터 대비 최소 3배 ~ 5배 정도로 데이터 사이즈를 줄일 수 있게 되었습니다.
그리고, 필요한 데이터를 읽어 오는 과정에서 원하는 데이터와 인접해 있지만, 읽어올 필요가 없는 불필요한 데이터에 접근을 하지 않기 때문에 기존대비 3~5배 빠른 읽기 성능을 제공합니다.
Timeseries 컬렉션에서 인덱스를 생성 할 때, timeField, metaField에 명시된 필드를 이용하여 복합인덱스(compound index) 생성하는 것을 추천합니다.
별도의 TTL 인덱스를 지원하지 않고, 컬렉션 생성시 TTL 값을 지정해서 생성해야 합니다. 또한 Partial, Unique 인덱스는 지원하지 않고, reindex 기능을 제공하지 않습니다. metaField에는 geospatial (2d, 2dsphere)과 Full-text 인덱스를 지원하지 않습니다.

 

Timeseries Sharding

샤딩은 가능하나 몇몇 기능이 제한 됩니다.
  • granularity 적용 불가
  • 일부 Admin Command 사용 불가

 

MongoDB에서 Timeseries 컬렉션을 생성하는 방법

db.createCollection(
    "weather",                           /* <-- 컬렉션 이름 */
    {
       timeseries: {                     /* <-- Timeseries 컬렉션임을 정의 */
          timeField: "timestamp",        /* <-- 시간이 들어가는 필드명 지정 */
          metaField: "metadata",         /* <-- 메타 데이터 필드명 지정 */
          granularity: "hours"           /* <-- granularity 설정(옵션) */
       },
       expireAfterSeconds: 604800        /* <-- TTL 인덱스 사용가능 */
    }   
)
일반적으로 MongoDB는 컬렉션을 생성하는 명령을 사용하지 않아도 insert 문으로 컬렉션 생성이 가능했습니다. 하지만, Timeseries 컬렉션은 사전에 컬렉션을 정의 해줘야 합니다.

 

데이터 적재

db.weather.insert({
    "metadata": {"sensorId": 5578, "type": "temperature"},
    "timestamp": new Date(),
    "temp": 12.8
})

insertOne과 insertMany를 모두 사용할 수 있습니다. 하지만 일반적인 update와 delete 명령을 사용할 수 없으며 metaField만 업데이트가 가능합니다.

데이터를 지울 때에는 TTL 인덱스를 사용하여 생성 시간이 오래된 데이터부터 지울 수 있습니다.
  • timeField: 인덱스을 생성하지 않는것 처럼 보이지만, 시간을 기준으로 자동으로 인덱스를 생성합니다. 정렬(sort)를 지원하지 않기 때문에 정렬이 필요한 환경에서는 별도의 인덱스를 생성해 줘야합니다.
  • metaField: Timeseries 컬렉션 생성시 metaField를 정의하면 다시는 바꿀수 없기 때문에 초기 설계 시 모델링을 잘해야 합니다. 변경을 위해서는 컬렉션을 Drop 하고 다시 생성해야 합니다.
  • granularity: “hours”, “minutes”, “seconds” 세가지 값을 가지고 있으며, 이 값은 옵션값으로 사용해도 되고, 사용하지 않을 수도 있습니다. 해당 값은 timeField에 대한 세분성을 나타내는데  내부적으로 저장하는 방식을 최적화 하여 성능을 향상시킬 수 있습니다. 데이터의 적재 주기를 설정하여 시간 범위에 가까운 값을 설정하면 됩니다. metaField를 지정하지 않으면 컬렉션에 삽입된 모든 측정 시간 범위를 고려해야 합니다. 더 큰 값으로는 바꿀 수 있으나, 더 작은 값으로 수정할 수 없으며, 한번에 두 단계를 넘어 바꿀 수 없습니다.  예> second → minute (O), hour → minute(x), minute → second → hour(x)

 

실제 데이터 적재 테스트

  • 사양: 아틀라스 M30, 3-node ReplicaSet
  • 100만건 데이터 입력시
일반 컬렉션 Timeseries 컬렉션
Insert 소요시간 102.875s 126.129s
데이터 크기 100MB 40MB
스토리지 크기(압축) 27.6MB 13MB
인덱스 크기 29MB 0MB (생성안됨)
실제 데이터 크기 27.6 + 29 = 56.6MB 13MB

 

이런 결과를 보이는 Time Series 컬렉션을 이용한다면, 시간을 기준으로 로그성 데이터를 쌓는다면 좀 더 효율적인 스토리지 공간을 사용할 수 있으며, 빠른 읽기 속도를 통해 데이터 통계나 분석 작업에도 효율적으로 이용할 수 있을 것 같습니다.

You may also like...

답글 남기기

이메일 주소는 공개되지 않습니다.