Page cover

🙂2024.08.14

일일 회고 20회차

오늘 할 일

경험 및 배움

회사 업무

LWLcok 발생 원인 및 해결 방법 분석

  • 29CM 팀의 PostgreSQL Autovacuum 장애 대응기 글을 보고 LWLock 발생 원인을 유추할 수 있게 됨

    • 회사 시스템에 특정 테이블을 주기적으로 초기화하고 데이터를 다시 넣는 스케줄링이 있음

    • 테이블이 초기화될 경우 dead tuple이 발생하여 Autovacuum이 동작함

    • vacuum이 실행되면서 block된 row들이 생겨 LWLock이 발생하게 됨

  • 위 내용은 추측이므로 문제가 재현될 경우 명확한 원인 파악 필요

주기적으로 실행하는 로직에 Timer 적용

  • 스케줄링을 10초에 한 번씩 실행되어 시스템이 전반적으로 느려지는 문제 발생

  • 스케줄링을 하루에 한 번씩 실행되도록 수정이 요구됨

  • 기존의 스케줄링 로직은 서버가 실행될 경우 바로 스케줄링을 실행하는 로직이였음

    • while (true) 의 실행문에서 delay를 추가한 로직

  • 사용자가 사용하는 시간과 스케줄링이 수행되는 시간을 완전히 분리하기 위해 자정 시간에 실행되도록 수정

    • java.util.Timer 객체를 활용하여 자정 시간에 실행되도록 수정

private const val SCENE_LABEL_VIEW_CACHE_TIME = 24 * 60 * 60 * 1000L

private fun cacheSceneLabelView() {
    val timer = Timer()
    val midnight = getNextMidnight()
    logger.info { "SceneLabelView 업데이트 스케줄링 시작 (한국 시간 기준 매일 자정에 수행)" }
    timer.scheduleAtFixedRate(midnight, SCENE_LABEL_VIEW_CACHE_TIME) {
        CoroutineScope(Dispatchers.IO).launch {
            logger.info { "SceneLabelView 업데이트 시작" }
            runCatching {
                injector().get<SceneLabelViewRepository>().use { it.refresh() }
            }.onSuccess {
                logger.info { "SceneLabelView 업데이트 완료" }
            }.onFailure {
                logger.error(it) { "SceneLabelView 업데이트 실패" }
            }
        }
    }
}

private fun getNextMidnight(): Date {
    val calendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Seoul"))
    calendar.set(Calendar.HOUR_OF_DAY, 0)
    calendar.set(Calendar.MINUTE, 0)
    calendar.set(Calendar.SECOND, 0)
    calendar.set(Calendar.MILLISECOND, 0)

    if (calendar.time.before(Date())) {
        calendar.add(Calendar.DAY_OF_MONTH, 1)
    }

    return calendar.time
}

사이드 프로젝트

알림 읽음 상태 변경 테스트 구현

  • 하나의 API로 단일 상태 변경과 전체 상태 변경을 수행하려 했으나 테스트를 실패함

    • API를 /notifications/{notificationId}/checked 로 정의함

    • /notifications//checked 와 /notification/{UUID}/checked로 두 가지의 경우를 처리하려 했음

  • PatchMapping에 path 값으로 /notifications/{notificationId}/checked 와 /notifications/checked로 두 가지의 경우를 처리할 수 있게 수정함

    • 테스트 코드로 정상 동작하는 것 확인함

@PatchMapping(path = ["/notifications/{notificationId}/checked", "/notifications/checked"])
suspend fun checkNotifications() {}
// 읽지 않은 모든 알림의 읽음 상태 변경 테스트
val count = webTestClient
            .patch()
            .uri("/notifications/checked")
            // ...

// 읽지 않은 특정 알림의 읽음 상태 변경 테스트
val count = webTestClient
            .patch()
            .uri("/notifications/${expected.id}/checked")
            // ...

앞으로 할 일

Last updated