모니터링 알람 시스템 구축 및 비즈니스 매트릭 등록

YoonJuHo·2025년 4월 18일

포트폴리오

목록 보기
5/9
post-thumbnail

본 프로젝트는 Spring Boot와 MySQL을 활용한 기록 프로젝트 입니다.

해당 글은 기록 프로젝트 진행하면서 직면했던 모니터링 범위 산정의 미흡함과 신속한 대응의 어려움을 해결하기 위해 선택한 기술적 접근 방식을 설명합니다.


[요약]

개발 환경 EC2에서 도커 이미지 누적으로 디스크 용량 초과 문제가 발생하면서, 모니터링 범위 산정 미흡즉각적 에러 대응 체계 부재를 깨달았습니다. 이를 해결하기 위해 애플리케이션이 동작하는 환경의 상태, 애플리케이션 자체 상태, 요청/응답을 종합적으로 모니터링하기로 결정, Alert Rule을 추가해 Slack 알람을 구축했습니다. 또한, AOP 기반의 비즈니스 매트릭을 구현하여 사용자 경험 개선을 시도하였습니다.


[문제상황]

[개발 환경 EC2의 용량 초과 문제 발생]

개발 환경에서 아래와 같이 docker container에 접근할려고 했으나 접근이 제한되는 문제가 발생하였습니다.

여러 방식으로 원인을 분석하는 과정에서 내부 디스크 사용률을 확인해 보았는데, EC2 내부에 디스크 사용률이 100%이기에 발생하는 문제였습니다.

[개발 환경 EC2의 용량 초과 문제 분석]

이전에 CI/CD 작업은 도커 이미지를 도커 허브에 올린 후 Self Hosted Runner가 해당 이미지를 pull 받아오는 방식으로 이루어졌습니다. 그러나 GithubActions Workflow에 기존 이미지를 삭제하는 명령어를 포함하지 않은 상태로 여러 번의 CD 작업이 이루어졌고, 이로 인해 방대한 도커 이미지의 용량이 쌓여 하드 디스크 용량이 100%가 되었던 것이였습니다.

삭제되지 않은 Docker Images

이 경험을 통해, 모니터링 범위 산정의 미흡함즉각적인 에러 대응 체계 부재로 인해 문제 발생 시 신속한 대응이 어려웠음을 깨달았습니다.


[해결방안]

✅ [모니터링 범위 산정]

운영환경과 개발환경 모두 Grafana를 통해 아래와 같이 로그를 확인하고 스프링의 메모리 사용률 등을 모니터링하고 있었지만,

Log 모니터링 - 애플리케이션의 요청/응답

Spring 모니터링 - 애플리케이션 자체 상태

EC2 모니터링 - 애플리케이션이 동작하는 환경의 상태

당시에는 EC2의 용량 문제를 전혀 예상하지 못했고, EC2 인스턴스를 모니터링하지 않았기에 해당 에러에 대한 대처가 늦었다고 생각되었습니다.

운영환경에서 이러한 문제가 발생했다면 심각한 서비스 장애로 이어질 수 있었으므로, 모니터링 범위 산정의 중요성을 절실히 깨달았습니다.

이에 애플리케이션이 동작하는 환경의 상태, 애플리케이션 자체 상태, 요청/응답 을 종합적으로 모니터링하기로 결정하였으며, 이를 위해 NodeExporter를 활용해 모든 EC2 인스턴스를 추가로 모니터링하기 시작하였습니다.


✅ [알람 시스템 구축]

지금과 같은 문제 상황을 포함하여 여러 사용자의 요청으로 인해 하나의 인스턴스 서버는 메모리가 가득차거나, CPU가 많이 사용되는경우 쉽게 장애를 일으킬 수 있겠다는 생각이 들었습니다.

서버를 24시간/7일 내내 직접 모니터링하는 것은 현실적으로 어려워, 신속한 에러 대응 전략이 필요했습니다. 이에 Alert Rule을 추가하여 알람 시스템을 구축하기로 결정하였습니다.

등록된 Alert Rules

Alert

Error, Warn Log AlertHeap Usage Alert

이와 같이, 에러 로그가 발생하거나 Heap 사용률이 임계치를 초과하는 경우 등 Slack으로 자동 알람이 전송되도록 설정하여, 문제 발생 시 즉각적인 대응이 가능해졌습니다.


[더 나아가]

✅ [비즈니스 매트릭 모니터링]

추가적으로 기존에는 CPU 사용량, 메모리 사용량, 톰캣 쓰레드, DB 커넥션 풀 등 공통적으로 사용되는 기술 메트릭이 이미 등록되어 있었습니다.

기존에 등록된 메트릭들을 활용하여 대시보드를 구성하고 모니터링을 진행하던 중, 사용자 경험에 더욱 부합하는 프로젝트 개선 방향을 모색하기 위해 별도의 비즈니스 매트릭을 새롭게 등록하게 되었습니다.

[비즈니스 매트릭 - 기록 시점이 과거/현재/미래인지 카운터 매트릭]

기록 저장 시점(과거, 현재, 미래)에 대한 빈도 체크를 통해, 가장 많이 사용되는 시점을 기준으로 사용자에게 기본 값을 제공할 수 있도록 시스템을 구성하고자 진행하게 되었습니다.

카운터 매트릭 AOP 적용

@Aspect
@Component
@RequiredArgsConstructor
public class CreateStaccatoMetricsAspect {

    // MeterRegistry를 주입받아 애플리케이션 메트릭을 기록하는 데 사용합니다.
    private final MeterRegistry meterRegistry;

    @Pointcut("execution(public * com.staccato.staccato.service.StaccatoService.createStaccato(..)) && args(staccatoRequest, member)")
    public void createStaccatoPointcut(StaccatoRequest staccatoRequest, Member member) {
    }

    @AfterReturning(pointcut = "createStaccatoPointcut(staccatoRequest, member)", returning = "result")
    public void afterSuccessfulCreateStaccato(StaccatoRequest staccatoRequest, Member member, Object result) {
        // staccatoRequest의 visitedAt 필드를 LocalDate로 변환하여 방문 날짜를 구합니다.
        LocalDate visitedAt = staccatoRequest.visitedAt().toLocalDate();
        LocalDate now = LocalDate.now();
        
        // 방문 날짜가 현재 날짜보다 과거인 경우 "past" 태그로 카운터를 기록합니다.
        if (isPastDate(visitedAt, now)) {
            recordCounter("past");
            return;
        }
        // 방문 날짜가 현재 날짜보다 미래인 경우 "future" 태그로 카운터를 기록합니다.
        if (isFutureDate(visitedAt, now)) {
            recordCounter("future");
            return;
        }
        // 방문 날짜가 현재 날짜와 동일한 경우 "now" 태그로 카운터를 기록합니다.
        recordCounter("now");
    }

    // 주어진 viewPoint 태그를 가진 카운터를 생성(또는 등록)하고, 해당 카운터의 값을 증가시킵니다.
    // MeterRegistry를 통해 메트릭이 기록됩니다.
    private void recordCounter(String viewPoint) {
        Counter.builder("staccato_record_viewpoint")
                .tag("class", StaccatoService.class.getName())
                .tag("method", "createStaccato")
                .tag("viewPoint", viewPoint)
                .description("counts different view points for Staccato Record")
                .register(meterRegistry)
                .increment();
    }
    ...
}

카운터 매트릭 Dynamic Test

@DisplayName("기록 상의 날짜를 현재를 기준으로 과거 혹은 미래 인지 매트릭을 통해 표현 할 수 있습니다.")
@TestFactory
List<DynamicTest> createStaccatoMetricsAspect() {
    Member member = saveMember();
    Category category = saveCategory(member);
    LocalDateTime now = LocalDateTime.now();

    return List.of(
            dynamicTest("기록 상의 날짜가 과거인 기록과 미래인 기록을 매트릭에 등록합니다.", () -> {
                // given
                StaccatoRequest pastRequest = createRequest(category.getId(), now.minusDays(2));
                StaccatoRequest futureRequest = createRequest(category.getId(), now.plusDays(2));

                //when
                staccatoService.createStaccato(pastRequest, member);
                staccatoService.createStaccato(futureRequest, member);

                //then
                assertAll(
                        () -> assertThat(getPastCount()).isEqualTo(1.0),
                        () -> assertThat(getFutureCount()).isEqualTo(1.0)
                );
            }),
            dynamicTest("기록 상의 날짜가 과거인 기록 작성 요청 → 누적: past:2.0, future:1.0", () -> {
                // given
                StaccatoRequest staccatoRequest = createRequest(category.getId(), now.minusDays(3));

                // when
                staccatoService.createStaccato(staccatoRequest, member);

                // then
                assertAll(
                        () -> assertThat(getPastCount()).isEqualTo(2.0),
                        () -> assertThat(getFutureCount()).isEqualTo(1.0)
                );
            })
    );
}

요구사항에 따라 매트릭을 관리하는 로직을 추가해야 했으며, 주목해야 하는 부분은 크게 네 가지로 정리할 수 있습니다.

첫째, 기록 시점(과거, 현재, 미래)에 따른 매트릭 분기 처리가 필요했습니다.

  • 일반적으로 제공되는 @Counted와 같은 기본 어노테이션으로는 요구사항에 맞게 세밀한 분기가 어려웠기 때문에, 커스텀 로직을 구현하여 각 시점에 따른 빈도 체크 및 기록을 수행하였습니다.

둘째, 성공 응답만을 필터링해야 하는 제약 조건이 있었기 때문에, 매트릭 로직을 Service 계층에 적용하는 것으로 결정했습니다.

  • 이를 통해 불필요한 데이터 집계를 방지하고, 실제로 사용자 경험과 직결되는 성공적인 서비스 응답만을 정확하게 기록할 수 있도록 하였습니다.

셋째, 매트릭 관리 로직이 핵심 비즈니스 로직(Service)에 직접 침투하는 문제를 해결하기 위해 AOP(관점 지향 프로그래밍)를 적용했습니다.

  • 이를 통해 서비스 코드와 매트릭 수집 및 기록 로직을 분리함으로써, 핵심 기능의 가독성과 유지보수성을 높이고, 매트릭 관리에 따른 부수적인 영향도를 최소화할 수 있었습니다.

넷째, 매트릭이 동적으로 등록되고 추가되는 것을 확인하기 위해 Dynamic test를 통해 검증을 진행하였습니다.

[비즈니스 매트릭 시각화]

이와 같이 비즈니스 매트릭을 등록하고 시각화하는 과정을 통해, 서비스의 사용자 경험을 개선할 수 있는 유의미한 인사이트를 확보하고자 노력하였습니다.


[결론]

개발 환경에서 발생했던 디스크 용량 초과 문제와 그로 인한 즉각적인 에러 대응의 어려움을 체감하였고, 이를 해결하기 위해 모니터링 범위 산정과 알람 시스템 구축의 중요성을 재확인하였습니다.

애플리케이션, 인프라, 그리고 요청/응답에 대한 종합적인 모니터링 체계를 도입하고, NodeExporter와 Grafana를 활용하여 EC2 인스턴스의 상태를 실시간으로 감시함으로써, 장애 발생 시 신속한 대응이 가능하도록 하였습니다. 또한, Alert Rule을 통한 Slack 알람 시스템 구축으로 관리자가 문제 상황을 즉시 인지할 수 있게 되었으며, AOP 기반의 비즈니스 매트릭 로직을 통해 사용자 경험과 직결되는 핵심 지표를 체계적으로 관리할 수 있음을 확인하였습니다.

이러한 기술적 접근은 단순히 문제를 해결하는 데 그치지 않고, 서비스의 안정성과 사용자 만족도를 극대화할 수 있는 인사이트를 제공하였으며, 향후 운영 환경에서도 신속하고 효과적인 문제 대응 및 개선을 위한 기반이 될 수 있음을 보여주었습니다.

0개의 댓글