코드 저장소.

Kafka Exactly-Once 보장하기: Outbox + EventId 기반 멱등 처리 본문

포폴/일정관리앱

Kafka Exactly-Once 보장하기: Outbox + EventId 기반 멱등 처리

slown 2025. 9. 1. 01:57

목차

1. 도입 배경

2. EOS(Exactly Once Semantics) 접근 방식

3. 설계 및 구현 방법

4. 성능 측정

5. 후기

 

1. 도입 배경

현재 서비스는 Outbox + DLQ + Retry 구조로 이벤트 발행 및 복원력을 확보하고 있습니다. 이 방식은 at-least-once 전달 보장을 만족하여 메시지 유실은 방지할 수 있습니다. Outbox로 DB 트랜잭션과 메시지 발행의 원자성을 맞췄고, DLQ/Retry를 통해 장애 상황에서도 재처리가 가능하기 때문에 안정적으로 이벤트를 보낼 수 있습니다. 하지만 여전히 한 가지 문제가 남아 있습니다. 메시지의 중복 처리입니다.

 

문제의 소지는 동일한 Outbox 이벤트가 재발행되었을 때 Retry 스케줄러가 같은 이벤트를 재전송했을 때 Consumer가 재시작되면서 offset이 되돌아갔을 때 이런 상황에서는 같은 이벤트가 두 번 이상 처리될 수 있습니다.

 

예를 들어, 회원가입 이벤트가 중복 처리되면 동일한 회원이 여러 번 insert되거나, 알림 이벤트가 중복 발송될 수 있습니다. 메시지 유실은 막았지만, 중복 처리로 인한 데이터 정합성 문제가 여전히 발생할 수 있다는 뜻입니다. 그래서 이러한 현 구조를 개선을 하기 위해서는 exactly-once를 적용을 해기로 했습니다.

2. EOS(Exactly Once Semantics) 접근 방식

Kafka는 기본적으로 at-least-once 전달 보장을 제공하지만, Exactly Once Semantics(EOS)를 위한 기능도 내장하고 있습니다. 이를 구현하는 방법은 크게 두 가지로 나눌 수 있습니다.

 

2-1. Kafka 트랜잭션 기반 EOS

 

Kafka는 idempotent producer와 transactional.id 설정을 통해 메시지를 트랜잭션 단위로 처리할 수 있습니다. Producer 단계에서 여러 메시지를 하나의 트랜잭션으로 묶고, commit/abort 단위로 브로커에 기록 Consumer는 트랜잭션 경계를 인식하면서 메시지와 offset을 함께 커밋 이 방식은 이론적으로 메시지 중복·유실 모두를 방지할 수 있습니다. 하지만 단점도 명확합니다.

 

운영 복잡도 증가: transactional.id 관리, 상태 충돌 가능성

성능 오버헤드: 트랜잭션 단위 동기화로 TPS 저하 장애 시 복구 과정이 단순하지 않음

 

이방식을 실제 운영 환경에서 적용하기에는 부담이 컸습니다.

 

2-2. 애플리케이션 레벨 EOS (eventId 기반 멱등 처리)

 

운영 복잡도를 줄이기 위해, 애플리케이션 차원에서 EOS를 보장하는 방식을 선택했습니다. 핵심 아이디어는 eventId 기반 멱등성 보장입니다. Outbox 테이블의 PK(UUID)를 eventId로 활용 Producer는 메시지에 eventId를 포함해 전송 Consumer는 처리 전 eventId 중복 여부를 체크 이미 처리된 eventId라면 skip 신규 eventId라면 DB 반영 후 기록 이렇게 하면 DLQ/Retry로 같은 메시지가 재발행되더라도, eventId를 기준으로 중복 처리를 방어할 수 있습니다.

 

2-3. 선택 이유

 

Kafka 트랜잭션 기반 EOS는 강력하지만, 운영 및 성능 부담이 크다. 애플리케이션 레벨 EOS는 Outbox 패턴과 잘 어울리며, 단순하면서도 서비스 요구사항(EOS 보장)에 충분히 부합한다. 따라서 이번 프로젝트에서는 eventId 기반 멱등 처리 방식을 EOS 접근 방법으로 채택했습니다.

 

아래의 구조는 기존의 구조에서 eventId를 적용한 후의 구조입니다. 

3. 설계 및 구현 방법

3-1.kafkaDto에 공통적으로 적용을 할 Dto를 작성하기

 

모든 Kafka 이벤트 DTO는 BaseKafkaEvent 추상 클래스를 상속하도록 변경했습니다.

Outbox PK(UUID)를 eventId로 활용하기 위해 필드만 공통화했습니다.

@Getter
@Setter
public abstract class BaseKafkaEvent {
    private String eventId;
}

public class MemberSignUpKafkaEvent extends BaseKafkaEvent

public class NotificationEvents extends BaseKafkaEvent

 

3-2. 이미 처리된 eventId를 저장하기 위한 엔티티 작성

 

이미 처리된 eventId 저장을 하기 위한 ProccessEvent 엔티티작성했습니다. DB unique index를 걸어 동시성 상황에서도 안전하게 멱등성을 보장합니다.

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Table(
        name = "processed_event",
        indexes = {
                @Index(name = "idx_event_id", columnList = "eventId", unique = true)
        }
)
public class ProcessedEventEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 100)
    private String eventId; // Outbox PK or 비즈니스 키

    @Column(nullable = false)
    private LocalDateTime processedAt;

}

 

3-3. ProccessEvent에 관한 서비스 로직 작성

 

이미 처리된 eventId인지를 조회와 저장을 위한 메서드를 작성했습니다. 

  @Transactional(readOnly = true)
    public boolean isAlreadyProcessed(String eventId) {
        return processedEventRepository.existsByEventId(eventId);
    }

    public void saveProcessedEvent(String eventId) {
        try {
            ProcessedEventEntity entity = ProcessedEventEntity.builder()
                    .eventId(eventId)
                    .processedAt(LocalDateTime.now())
                    .build();
            processedEventRepository.save(entity);
        } catch (DataIntegrityViolationException e) {
            // 이미 처리된 이벤트라면 무시
            log.warn("이벤트 중복 저장 시도 감지됨: {}", eventId);
        }
    }

 

3-4. OutboxPublisher에 eventId를 확인을 하기 위해서 로직을 변경 

 

Outbox에서 Kafka로 이벤트를 발행할 때, Outbox PK를 eventId로 주입합니다. 이렇게 하면 Outbox, Retry, DLQ 등 어떤 경로로 이벤트가 다시 발행되더라도 동일한 eventId가 유지가 됩니다.

// eventId 주입 (Outbox PK → Kafka eventId)
if (payload instanceof BaseKafkaEvent baseEvent && baseEvent.getEventId() == null) {
    baseEvent.setEventId(event.getId());
}

 

3-5. Consumer에서 eventId를  unique key로 검증

 

메시지를 수신하면 가장 먼저 중복 체크를 수행하고 신규 이벤트라면 비지니스 로직으로 처리를 하고 saveProcessedEvent()로 저장을 합니다.

 

if (processedEventService.isAlreadyProcessed(event.getEventId())) {
    log.info("⚠️ 이미 처리된 이벤트 무시: {}", event.getEventId());
    return;
}

// 비즈니스 로직 실행
handleNotification(event);

// 처리 완료 후 기록
processedEventService.saveProcessedEvent(event.getEventId());

4. 성능 측정

이제 코드를 작성을 했으니 해당 api에 관한 성능을 측정을 하겠습니다. 우선은 테스트를 해볼 서버와 측정 도구에 대한 설명은 아래와 같습니다

 

서버 환경

  • 서버: 2GB VM (JVM 힙 512m~1g, HikariCP max=20)
  • API: 일정 추천 API (/api/chat/recommend)
  • 인증: JWT 헤더 포함

측정 도구

  • Jmeter : 요청 부하 발생 및 응답 시간 측정
  • Grafana (Prometheus 연동): JVM Heap, GC, DB 커넥션 풀 등 서버 내부 지표 모니터링

다음은 성능 측정에 대환 테스트 시나리오입니다

 

정상 테스트

  • 부하 조건이 없는 상황에서 일정 생성 API의 정상 동작 및 응답 시간을 검증한다

시나리오

  1. JMeter로 일정 생성 API를 10회 반복 호출
  2. Grafana(Prometheus 연동)로 JVM Heap, GC, DB Connection Pool 등 내부 지표를 모니터링
  3. API 응답 시간, 에러율, 처리 결과 확인

성공 기준

  • 모든 요청에서 에러율 0%
  • 평균 응답 속도: 50ms~100ms 내외 유지
  • DB Connection Pool, GC, Heap 사용량에서 이상 징후 없음
  • Kafka Outbox → Consumer → DB 저장까지 전파가 정상적으로 완료됨

부하 테스트 

  • 동시 사용자 요청 증가(50명 → 100명) 상황에서 API → Kafka → Consumer → DB 저장까지의 전체 흐름에 병목 구간이 없는지 검증한다

시나리오

  1. JMeter
    • Virtual User 50명, 100명 시나리오로 테스트 진행
    • Ramp-up: 30초, Duration: 180초
    • 응답 시간(P95, P99), 에러율, TPS(Throughput) 측정
  2. Grafana (Prometheus + Kafka Exporter)
    • Consumer 처리량(Records/sec), Lag, 처리 지연시간 확인
    • JVM Heap/GC, DB Connection Pool 지표 수집
  3. 결과 분석
    • JMeter 에러율 기준: 0% 유지 여부 확인
    • Consumer Lag이 안정적으로 해소되는지 확인
    • 특정 지점(API, Kafka, DB)에서 병목 현상 발생 여부 확인

성공 기준

  • 동시 접속자 100명까지 에러율 0% 유지
  • Kafka Consumer Lag이 단기간 내 해소되어 처리량이 정상화됨
  • JVM/DB Pool에서 리소스 병목 없음
  • 평균 응답속도 및 P95 지연이 허용 범위 내(예: <1s) 유지됨

일괄 처리 테스트

  • Kafka Producer → Broker → Consumer → DB 저장까지의 전체 파이프라인에 대해
    대량 이벤트(1만, 3만, 5만 건) 일괄 처리 시 유실 0, 중복 0을 보장(EOS)하는지 검증한다

시나리오

  1. JMeter로 동일 이벤트(1만, 3만, 5만 건)를 일괄 publish
  2. Kafka Consumer가 이벤트를 처리하여 DB에 저장
  3. DB 레벨에서 eventId를 unique key로 사용, 중복 발생 시 409를 정상 처리로 간주
  4. 처리 과정에서 DLQ/Retry가 정상적으로 동작하는지도 함께 관찰

성공 기준

  • 전체 이벤트 건수 = DB 저장 건수 (유실 0건)
  • 중복 저장 없음 (409 발생 시 “정상 차단”으로 집계)
  • 장애 발생 시 DLQ로 이관 후 재처리 → 최종 정상 처리율 100%
  • 처리율/지연시간/Consumer Lag 메트릭 기록

우선은 Jmeter로 측정을 할때 일정생성의 요청값으로 validation에 통과가 되게끔 설정을 아래와 같이 했습니다.

import org.apache.commons.lang3.RandomUtils
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit

// 날짜 범위 (2025-09-19 ~ 2025-12-30)
def startDate = LocalDate.of(2025, 9, 19)
def endDate   = LocalDate.of(2025, 12, 30)
def daysBetween = ChronoUnit.DAYS.between(startDate, endDate)
def randomOffset = RandomUtils.nextInt(0, (int)daysBetween + 1)
def date = startDate.plusDays(randomOffset)

// userId (1~50 랜덤)
def userId = RandomUtils.nextInt(1, 51)

// userId별 시간대 기본 배정 (0~23시)
def baseHour = userId % 24

// 같은 시간대라도 분 단위 랜덤 오프셋 추가 → 충돌 최소화
def offsetMin = RandomUtils.nextInt(0, 60)

// 시작/종료 시간 (1시간 일정)
def startTime = LocalDateTime.of(date, LocalTime.of(baseHour, offsetMin))
def endTime   = startTime.plusHours(1)

// 포맷
def formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")

// JMeter 변수 저장
vars.put("startTime", startTime.format(formatter))
vars.put("endTime", endTime.format(formatter))
vars.put("scheduleDays", String.valueOf(date.getDayOfMonth()))
vars.put("contents", "테스트-" + System.nanoTime())
vars.put("userId", String.valueOf(userId))

 

4-1.정상 테스트

 

정상 테스트를 실행한 결과는 아래와 같습니다. 

 

10회 반복시의 Grafana 결과표

 

10회 반복시의 결과는 아래와 같이 볼 수 있습니다.

 

JMeter 결과 (API 응답 관점)

  • 평균 응답속도: 약 74ms
  • P95 지연시간: 57ms
  • 최대 응답시간: 268ms
  • 에러율: 0% (모든 요청 성공)
  • Throughput: 약 13 req/s

Outbox Publisher 메트릭

  • 발행 건수(rate): 약 0.33 msg/s → 10건 발행된 게 그래프에 반영됨.
  • 평균 발행 지연시간: 0.005 ~ 0.035초
  • 최대 발행 지연시간: 순간적으로 0.28초까지 튐.(대체로 수 ms 수준으로 빠르지만, 일부 피크에서 수백 ms까지 튀는 케이스가 있었습니다.)

Kafka Consumer (NotificationEventConsumer)

  • 평균 소비 지연시간: 0.04 ~ 0.05초
  • 최대 소비 지연시간: 0.2초 근처에서 피크 발생.
  • 처리량: 10건 모두 정상적으로 소비 확인.(소비자도 정상 처리했고, EOS/멱등 처리 구조상 중복/유실 없음. 다만, 순간적으로 처리 지연이 살짝 발생을 했습니다.)

위의 내용을 토대로 1차 정상 테스트는 성공적으로 마친것을 검증을 했습니다.

 

4-2. 부하 테스트(50VU, 100VU)

 

먼저 Jmeter에 스레드를 50으로 설정을 한 결과는 아래와 같습니다.

 

50명 동시 테스트 결과 (1차)

 

Kafka / Outbox / DLQ
Kafka Consumer 처리량: 피크 ~30msg/s, backlog 없이 모두 소화.
Consumer Latency: 평균 0.2s 내외, Max 지연 없음.
Outbox Publish Rate: 초당 0.3~0.35msg/s 유지.
Outbox Avg Latency: 부하 구간(15:24~15:25)에서 2~3초까지 튐 → DB 처리 지연 영향.
DLQ Retry: 거의 0, 재처리율 정상.
EOS 관점: 유실/중복 없이 처리 정상.

JVM / GC
Heap 사용량: 200MB 내외에서 안정적, Old Gen 증가 없음.
GC Pause: 최대 6ms 수준, 애플리케이션에 영향 없음.
결론: JVM 튜닝 상태 양호, 부하에 영향 미미.

 

DB (HikariCP)
Active Connections: 피크 시 maxPoolSize=20 전부 사용
Idle Connections: 0으로 떨어지며 풀 포화 상태 발생
Pending Threads: 잠깐 튀었지만 0으로 회복 → 대기 큐 생겼다 해소된 상황
결론: DB Connection Pool이 성능 한계 지점

HTTP API (JMeter + Grafana)
Request Rate: 초당 최대 120req/s까지 도달
Average Latency: /api/schedule 호출 시 0.4~0.6s 수준으로 상승
Error Rate: 피크 시 초당 120건 이상 5xx 발생

원인 1: DB 풀 포화로 인한 트랜잭션 처리 지연
원인 2: 일정 충돌 같은 Validation 실패가 5xx로 분류됨 → 실제보다 에러율 과장

 

문제점

  • DB Connection Pool 포화
  • maxPoolSize=20 제한으로 인해 Outbox 지연과 5xx 에러율 증가 발생
  • 테스트 데이터 유효성 부족
  • 랜덤 데이터로 인해 일정 충돌 빈번 → Fail 응답 발생
  • Validation 실패가 5xx로 잡히면서 Error Rate 지표 왜곡

1차 개선 

HikariCP maximum-pool-size를 20에서 50으로 조정
userId 범위 확대, 시간 슬롯 제한  -> Jmeter에 JSR PreProcessor 수정으로 충돌 최소화
Validation 실패는 4xx로 내려서 모니터링 정확성 확보

 

50명 부하 2차 테스트 

 

측정한 결과는 아래와 같습니다.

 

1. JMeter 결과

  • 샘플 수: 3,801건 (동시접속 50명 기준)
  • 평균 응답시간: 187ms
  • P90/P95/P99: 317ms / 376ms / 606ms
  • 최대 응답시간: 1,036ms
  • 에러율: 0.00% (409도 정상 처리로 잡힘)
  • Throughput: 21.2 req/sec

2. JVM & DB Connection (HikariCP)

  • JVM Heap Usage: 약 150MB 선에서 플랫
  • GC Pause: 0.01 ~ 0.017초
  • HikariCP Active Connections: 순간적으로 40개 근접, 이후 20개 안쪽 유지.
  • Idle Connections: 10개 이상 항상 유지.
  • Pending Threads: 0

3. Kafka & Outbox

  • Consumer 처리량(msg/s): 피크 때 30 msg/s 정도
  • Consumer 평균 지연(latency): 0.02 ~ 0.08초
  • Outbox Publish Rate: 0.3 msg/s 근처에서 안정적으로 유지.
  • DLQ Retry: 0 (실패 없음)
  • Outbox 평균 지연: 초기 1.2초 → 곧 0.1초 이내로 수렴

4.HTTP 

  • Request Rate: 약 250~300 req/s에서 안정
  • HTTP Avg Latency: 0.12 ~ 0.18초
  • HTTP Error Rate(5xx): 없음 → 에러율 0%.

50명 동시 접속 시 평균 응답속도 160~180ms, 에러율 0%, Kafka/DB/GC 모두 정상이고 Kafka 메시징Outbox 스케줄러는 warm-up 후 안정화된다는 것을 알 수 있으며 DB 커넥션 풀은 순간 부하에도 병목 없이 잘 동작이 되는것을 알 수 있습니다.

 

다음은 100VU로 해서 테스트를 한 결과입니다. 

 

 

 

1.JMeter 결과

  • 평균 응답 시간: 약 1155ms
  • Median: 1069ms
  • 95% Line: 2187ms
  • 최대값: 4454ms
  • Throughput: ~14.6/sec
  • 에러율: 0% (409 충돌은 정상 처리로 분류)

2. JVM Heap / GC & DB

  • Heap 사용량 안정적 (150MB 근처 유지).
  • GC Pause < 0.01s으로  메모리에는 문제가 없습니다.
  • Active/Idle이 풀사이즈(50)에 꽉 차는 순간이 있긴 합니다. 
  • Pending Threads는 0 으로 풀 대기열 병목이 없다는 것을 알 수 있습니다..

3. kafka & Outbox

  • Kafka Consumer 처리량: 순간적으로 30~40 msg/s까지 올라갔다가 정상적으로 떨어짐 → 컨슈머 레벨 병목 없음.
  • Outbox Avg Latency: 초반에 8~9초까지 치솟았으나 빠르게 안정화되어 1초 미만으로 내려옴 → Outbox → Kafka Publish 구간은 스파이크만 있었음.

4. HTTP

  • HTTP Avg Latency: 평균 0.8~1.0초 → JMeter와 일관됨.
  • HTTP Error Rate(5xx): 없음 → 에러율 0%.

마찬가지로 동시 접속  100명도 충분히 버틸수 있다는것을 검증했습니다.

 

다음은 일괄적으로 알림을 처리를 했을때의 테스트입니다. 순서는 3만건,5만건

 

3만건 

 

1. JMeter 결과 (일괄 30,000건 삽입)

  • 평균 응답 시간: 39ms
  • Median: 36ms
  • 95% Line: 59ms
  • 최대값: ~1,037ms
  • Throughput: ~24.4/sec
  • 에러율: 0% (409 충돌은 정상 처리로 분류)

2. JVM Heap / GC & DB

  • Heap 사용량 안정적 (150MB 내외 유지)
  • GC Pause: < 0.01s → 메모리 병목 없음
  • HikariCP Active/Idle 커넥션: 정상 범위 유지 (최대 풀을 쓰더라도 Pending Threads 없음-> 대기열 병목 없음)
  • DB Insert: schedules, processed_event, outbox_event_entity, notification 테이블 모두 13,884건 일치-> EOS 보장 확인

3. Kafka & Outbox

  • Kafka Consumer 처리량: 초반 20~25 msg/s, 이후 안정화 → 컨슈머 병목 없음
  • Outbox Publish Rate: ~0.3 msg/s 안정적으로 유지 (스케줄러 기반 publish)
  • Outbox Avg Latency: 초반 스파이크 있었으나 0.05~0.1s 수준으로 안정화

4.HTTP

  • HTTP Avg Latency: ~38~39ms (JMeter 결과와 일관)
  • HTTP Error Rate(5xx): 없음 → 에러율 0%

3만건 일괄 요청 중 DB에 들어간 것은 13,884건이고 나머지는 비즈니스 로직으로 (409충돌) 정상 거절을 했으며 디비에 저장된 내역으로 보아서 중복은 없었고, 유실은 없었으며 구현하고자 한 EOS가 보장이 되었다는것을 알 수 있습니다. Grafana에서 JVM,Kafka,DB 모두 병목 없이 안정적으로 처리가 되므로 안정성 검증이 완료가 되었습니다.

 

5만건

1.JMeter 결과 (5만건 일괄)

  • 평균 응답 시간: 39ms (엄청 안정적)
  • Median: 38ms
  • 95% Line: 59ms
  • 최대값: 2044ms (일부 스파이크 있지만 전체 영향 없음)
  • Throughput: ~23.9/sec
  • 에러율: 0% (409 충돌은 정상 처리로 제외)

2. JVM & DB

  • Heap: 100~150MB 선에서 안정 → GC도 문제 없음
  • HikariCP: Active/Idle 적절히 움직였고, Pending Threads 0 → DB 커넥션 병목 없음
  • Avg Latency: 0.02~0.05s → 안정적

3.Kafka & Outbox

  • Consumer 처리량: 초반에 20~25 msg/s 치솟다가 점점 하향 안정 → 컨슈머 지연 없음
  • Outbox Publish Rate: 0.3 msg/s 근처에서 꾸준 → 안정적인 발행
  • Outbox Latency: 초반 스파이크 후 <0.1s 안정화
  • DLQ Retry: 주기적으로 찍히지만 대부분 빠르게 정상화됨

4.Http

  • HTTP Avg Latency: ~20~50ms (JMeter 결과와 일관)
  • HTTP Error Rate(5xx): 없음 → 에러율 0%

5만건 요청에도 유실 0건, 중복 0건 최종적으로 디비에 1.7만건을 정상 저장을 했고 응답 시간 평균은 39ms,에러율은 0%로 Kafka EOS 멱등 처리(evnetId 기반) 보장이 검증이 된것을 알 수가 있습니다.

 

5. 후기

이번 EOS 적용과 성능 측정은 단순히 기능 구현을 넘어, 저 자신이 개발자로서 어떤 관점으로 문제를 바라보고 해결해야 하는지를 배운 과정이었습니다.

  • 중복과 유실 사이의 균형
    기존 Outbox + DLQ + Retry 구조는 메시지 유실은 막아줬지만, 여전히 중복이라는 문제를 안고 있었습니다. 이 지점을 개선하기 위해 EOS를 고민하면서, 저는 “시스템은 완벽하지 않다. 다만 그 안에서 우리가 보장할 수 있는 것은 어디까지인가?”라는 질문을 스스로 던지게 되었습니다. 결국 eventId 기반 멱등 처리를 선택했고, 실험을 통해 중복 없는 결과를 직접 확인하면서 문제 해결에 대한 자신감을 얻었습니다.
  • ‘구현’에서 ‘운영’으로의 시야 확장
    과거에는 코드를 작성하고 테스트가 통과하면 끝이라고 생각했습니다. 단순히 기능이 동작하는 것만으로는 충분하지 않다는 사실을 깨달았습니다. DB 커넥션 풀 지표, JVM Heap, Kafka Consumer 처리량까지 전부 확인해야만 EOS 보장이 실제로 의미가 있다는 걸 알게 되었죠. 이 경험을 통해 코드 한 줄보다 운영 환경에서의 검증과 관찰이 훨씬 더 중요하다는 점을 배우게 되었습니다.
  • 수치로 증명하는 힘
    “유실 0건, 중복 0건, 평균 응답속도 39ms, 에러율 0%”라는 지표를 얻으면서, 막연한 주장이 아니라 데이터로 신뢰를 주는 방법을 몸소 체험했습니다. 앞으로도 기능 구현 후에는 반드시 수치로 검증하는 습관을 유지하려 합니다.
  • 개발자 태도의 전환
    이번 과정은 단순한 기술 학습이 아니라, 저 스스로에게 “개발은 결국 문제 해결의 연속이고, 장애와 복원력을 고려하지 않는다면 반쪽짜리다”라는 기준을 심어주었습니다. 예전엔 ‘돌아가기만 하면 된다’는 태도였다면, 이제는 ‘운영 중에 문제가 생겨도 버틸 수 있어야 한다’는 관점으로 성장하게 되었습니다.

이번 경험을 통해 저는 기능 구현자에서 운영을 고려하는 개발자로 한 단계 성장했다고 생각합니다. 앞으로도 기술을 배우는 데서 그치지 않고, 실제 서비스 환경에서 어떻게 검증하고, 어떻게 개선할 수 있을지를 고민하는 개발자가 되고자 합니다.