달력

4

« 2026/4 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

'Backend'에 해당되는 글 2

  1. 2026.03.14 MongoDB collMod로 collection index 수정하기
  2. 2025.10.25 kotlin.test.Test vs jupiter.api.Test

Intro

: MongoDB Collection에, Document들이 생성 후 10일이 지나면 제거되도록 TTL을 설정했었습니다.

db.MyCollection.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 864000 }  // 10 * 86400, 10 Days
)

 

 그러던 어느 날, 설정한 TTL을 변경하려고 아래와 같이 명령어를 입력했는데요. System에 부하가 걸리면서 문제가 발생했습니다. 무엇이 문제였고, 그렇다면 어떻게 해야 했었을까요?

db.MyCollection.dropIndex("createdAt_1")
db.MyCollection.createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 432000 }  // 5 * 86400, 5 Days
)

 

많은 비용이 발생할 수 있는  createIndex

: dropIndex는 지우기만 하면 되므로 부하없이 순식간에 완료됩니다. 하지만 createIndex는 Collection에 존재하는 Document들을 전부 읽으면서 index를 생성하므로 Document가 많다면 부하가 크게 발생할 수 있습니다.

 과거에는 이러한 index 생성을 Foreground로도 실행할 수 있었는데, 다행히 4.4 Version 이후부터는 index 생성이 Background로만 동작하도록 개선되긴 했습니다. 그래도 여전히 I/O 부하가 커지는 문제는 발생할 수 있습니다.

 

 

collMod

1) collMod란?

: Collection의 option과 Index의 property를 설정하는 Command.

 

2) 변경 가능한 것들 (options)

2-1) Index Property

Property Description
expireAfterSeconds 만료 임계값을 정하는 시간(초).
hidden Query Planner에서 Index를 숨길지 여부.
prepareUnique Index에 중복을 허용할지 말지를 설정하는 것.
unique true인 경우, Index에 중복이 없다면 Unique Index로 전환함(false는 지원하지 않음).

 

2-2) Collection Options

Option Description
validator Collection에 대한 유효성 검사 또는 표현식
validationLevel update 중에, 기존 문서에 대해 유효성 검사를 어느정도로 엄격하게 적용할지에 대한 정도
validationAction 유효하지 않은 문서에 대한 처리 방법

 

이 외에도 View, Time-Series Collection를 수정하거나 Capped Collection의 크기를 조정할 떄에는 collMod를 쓸 수 있습니다.

 

3) 사용 방법

3-1) syntax

: collMod를 사용하기 위한 syntax는 다음과 같습니다.

 

db.runCommand(
   {
     collMod: <collection name or view name>,
     <option1>: <value1>,
     <option2>: <value2>,
     ...
   }
)

 

TTL 변경처럼, Index Property를 변경하려고 할 때에는 Option에 명시해야 하는 것이 조금 복잡한데요. 다음과 같습니다.

 

db.runCommand( 
   {
     collMod: <collection>,
     index: {
        keyPattern: <index_spec> | name: <index_name>,
        expireAfterSeconds: <number>,  // Set the TTL expiration threshold
        hidden: <boolean>,             // Change index visibility in the query planner
        prepareUnique: <boolean>,      // Reject new duplicate index entries
        unique: <boolean>              // Convert an index to a unique index
     },
     dryRun: <boolean>                 // default: false, Only for index.unique is true.
   } 
)

 

3-2) example

3-2-1) TTL 변경하기

db.runCommand({
  collMod: "MyCollection",
  index: {
    keyPattern: { createdAt: 1 },
    expireAfterSeconds: 432000
  }
})

 

3-2-2) validator 변경하기

db.runCommand({
  collMod: "MyCollection",
  validator: {
    age: { $gte: 0 }
  }
})

 

 

4) collMod 사용시 주의사항

4-1) collMod는 replication됨 => oplog entry 생성.

: oplog entry 생성의 의미는 다음과 같습니다.

 

primary에서 실행된 command를 replica set에서도 실행되도록
operation log에 기록하는 것

 

 따라서 replica set이 있는 환경이라면, collMod로 무거운 작업을 수행히 Secondary에서도 동일하게 무거운 작업을 수행하게 됩니다. 이로인해 발생할 수 있는 문제점들로 다음과 같은 것들이 있습니다.

  • Replication Lag 증가
  • Disk I/O 증가
  • oplog Overflow
    : oplog의 size가 작다면, Secondary가 처리하지 못했는데 oplog entry가 삭제될 수 있습니다. 이런 문제가 생긴 상태를 oplog gap이라고 하고, 이때부터는 Secondary는 더 이상 Incremental Replication을 할 수 없습니다.
    • MongoDB는 oplog를 감지하고 자동으로 initial sync를 하게 되는데, 이건 심각항 상황을 초래할 수 있어요.

 

※ initial sync

: replica set에서 secondary가 primary의 oplog를 따라잡지 못하면, MongoDB가 수행하는 작업입니다. 다음과 같은 과정을 수행하게 되는데...

 

1. primary의 Collection들 복사
2. index 복사
3. oplog replay

 

그냥 DB 전체를 copy한다고 보시면 되겠습니다...

 

 initial sync는 매우 오래 걸리는 작업이고 그동안 해당 Secondary는 replication에 참여할 수 없습니다(Fault Tolerance가 낮아지겠네요). Primary에 장애가 발생하고, failover에 참여할 수 있는 Secondary가 없다면 큰일이겠네요. 또, Primary에서 Disk I/O, Network I/O가 높아지게 될 겁니다.

 

※ TTL을 갑자기 크게 줄이지 말 것
: collMod로 TTL을 줄이는 것 자체는 굉장히 짧게 끝나는 연산이지만, TTL을 변경함으로 인해 삭제해야 하는 Document가 많아진다면 initial sync가 발생할 수 있습니다. 어쩌면 여러 Secondary가 동시에 initial sync를 할지도 모릅니다.

 

4-2) collMod는 해당 collection에 lock을 건다.

: collMod는 보통 가벼운 작업입니다. Index의 TTL을 변경하는 정도의 작업에선 작업 시간이 ms 단위여서 lock이 걸려도 크게 문제될 일이 없습니다. 하지만, 크기가 매우 큰 Collection에서 다음과 같은 작업을 하면 문제가 될 수 있습니다.

 

collMod로 prepareUnique를 true로 변경
그 다음, collMod로 unique를 true로 변경

 

 위와 같은 작업을 하면 index 중복 검사를 하게 되는데, 이때 Collection Scan이나 Index Scan이 발생할 수 있습니다. 그러면 생각보다 lock이 오래 지속될 수 있습니다.

 

Reference

:
Posted by syjdev
2025. 10. 25. 23:44

kotlin.test.Test vs jupiter.api.Test Backend/Spring, ETC2025. 10. 25. 23:44

Intro

: 어느 날 Unit Test를 작성하던 중에, 사용하고 있던 Test Annotation에 궁금증이 생겼습니다. kotlin.test.Testorg.junit.jupiter.api.Test 모두 @Test Annotation을 제공하는데 각각 어떤 차이가 있는 것일까요? 그리고 둘중 하나를 선택해서 쓰는 이유는 무엇일까요? 내가 사용하는 기술 셋에 대해, 사용 목적조차 모르고 있었다는 것에 반성하며 정리해 보았습니다.

 

kotlin.test.Test

: kotlin.test.Test는 kotlin의 표준 Test annotation이고, 언어 차원에서 제공하며 특정 Framework/Platform에 의존적이지 않은 Test Annotation입니다(kotlin 표준 library에 포함돼 있음 - kotlin.test package).

 

이 annotation이 붙은 test 실행은 Platform에 따라 차이가 있어요.

  • JVM에서는 JUnit으로 Mapping되어 실행됨.
  • Kotlin Native에서는 kotlin/native test runner로 Mapping되어 실행됨.

 

저의 경우는 JVM에서 돌아가는 app을 개발하고 있었으니, kotlin.test.Test annotation을 붙이더라도 JUnit test와 별반 차이가 없었습니다. 다만 JUnit의 기능인 @BeforeEach 같은 것은 지원하지 않는다고 하네요.

 

org.junit.jupiter.api.Test

: JUnit에서 제공하는 Test annotation입니다. annotation 자체에는 별 속성같은 것도 없지만, JUnit에서 annotation을 인식하여 test를 실행합니다.

 

※ kotlin.test.Test annotation과 JUnit의 기능을 같이 쓰면...?

: 만약에 @Test annotation에 대한 의존성을 잘못 추가하여, kotlin.test.Test annotation과 JUnit의 기능을 함께 사용하도록 test code를 작성하면 어떻게 될까요(예를들면 아래와 같은 code)?

import org.junit.jupiter.api.BeforeEach
import kotlin.test.Test

class AddUseCaseTestMultipleCase {
    lateinit var addUseCase: AddUseCase

    @BeforeEach
    fun setUp() {
        addUseCase = AddUseCase()
    }

    @Test
    fun 1 + 2 = 3() {
        val result = addUseCase.add(1, 2)
        assertEquals(result, 3)
    }

    @Test
    fun -1 + 2 = 1() {
        val result = addUseCase.add(-1, 2)
        assertEquals(result, 1)
    }
}

 

다행히도 문제없이 @BeforeEach annotation이 개별 test가 실행되기 전에 실행됩니다.

 

kotlin.test.Test는 어떤 Framework/Platform에서도 실행될 수 있도록, 다른 Test framework로 Mapping됩니다(JVM 환경이라면, kotlin.test.Test -> org.junit.jupiter.api.Test로 Mapping). 그래서 각 @Test는 JUnit의 Test annotation과 동일하게 처리되고, @BeforeEach도 개별 test가 실행되기 전에 호출됩니다.

 

Conclusion

: 당장은 kotlin.test.Test annotation을 사용하더라도 문제가 되지 않을 수 있지만, 지금 문제없이 동작한다고 다가 아닌 것 같아요. 제가 의도한대로 code를 작성했느냐가 중요하다고 생각합니다. 그런 관점에서 kotlin.test.Test를 사용하는 것은 제가 의도한 바가 아니고, 그걸 확인하는 과정에서 JUnit의 Test Annotation과 비교해본 것은 의미가 있었던 것 같습니다.

:
Posted by syjdev