Redis Stream Consumer Trouble Shooting

Seob·2025년 3월 10일
post-thumbnail

Redis 스트림을 활용한 티켓 시스템에서 ID 저장 오류 트러블슈팅

1. 문제 상황

1.2 증상

  • 문제 증상: 이벤트 참여 요청 시 TicketNotFound 에러 발생
  • 오류 메시지: 요청한 티켓을 찾을 수 없음

1.3 문제 발생 경로

  • 티켓 발급 요청 → Redis Stream을 통한 메시지 발행 → Consumer가 처리하여 DB 저장
  • 이벤트 참여 요청 시 티켓 검증 → 티켓을 찾지 못함

2. 문제 분석

2.1 원인 추정

  • DB 저장 시 ID 필드에 따옴표가 포함되어 저장되는 문제
  • Redis 스트림에서 데이터 전송 과정에서 문자열 형식 변환 오류
  • Consumer에서 데이터 파싱 과정의 문제

2.2 시도한 진단 방법

  • 데이터베이스 직접 조회:
    • 결과: tickets 테이블의 ID와 user_id 필드에 따옴표가 포함되어 저장됨
    "20250310-2f4b2717-0629-4c0a-8b39-aa28d0a6eeb9"  // id 필드
    "2f4b2717-0629-4c0a-8b39-aa28d0a6eeb9"           // user_id 필드
  • users 테이블 확인:
    • 결과: 사용자 ID는 따옴표 없이 정상적으로 저장됨
    7c7b1d63-71b3-4636-95ec-07dff22446bd             // 정상적인 형태

3. 원인 파악

3.1 코드 검사

Ticket 발급은 Redis Stream을 통해 일어나므로 Redis Stream 관련 코드를 살펴보겠다.

  1. TicketPublisher.java:

    public void publish(TicketDomain ticketDomain) {
        Map<String, String> ticketData = new HashMap<>();
        ticketData.put("ticketId", ticketDomain.getId().getValue());
        ticketData.put("userId", ticketDomain.getUserId().getValue());
        ticketData.put("createdAt", ticketDomain.getCreatedAt().toString());
        ticketData.put("isUsed", String.valueOf(ticketDomain.isUsed()));
    
        MapRecord<String, String, String> record = MapRecord.create(streamKey, ticketData);
        redisTemplate.opsForStream().add(record);
    }

    publish 단계에서는 String 변환에서 큰 문제 없어 보인다.

  2. TicketConsumer.java:

    public void onMessage(MapRecord<String, String, String> message) {
        try {
            Map<String, String> ticketData = message.getValue();
            String ticketIdStr = ticketData.get("ticketId");
            String userIdStr = ticketData.get("userId");
            String createdAtStr = ticketData.get("createdAt");
            String isUsedStr = ticketData.get("isUsed");
    
            //따옴표 제거 (createdAt에만 적용됨)
            if (createdAtStr != null && createdAtStr.startsWith("\"") && createdAtStr.endsWith("\"")) {
                createdAtStr = createdAtStr.substring(1, createdAtStr.length() - 1);
            }
    
            UserId userId = UserId.of(userIdStr);
            LocalDateTime createdAt = LocalDateTime.parse(createdAtStr);
            Boolean isUsed = Boolean.parseBoolean(isUsedStr);
    
            TicketDomain ticket = TicketDomain.of(ticketIdStr, userId, createdAt, isUsed);
            ticketRepository.save(ticket);
        } catch (Exception e) {
            System.err.println("메시지 처리 중 오류 발생: " + e.getMessage());
            e.printStackTrace();
        }
    }

    여기서 문제 발견

    • createdAt 필드의 따옴표는 제거하고 있지만, ticketId와 userId의 따옴표는 제거X
    • 즉 Consumer에서 받은 String 데이터를 파싱할 때, 따옴표 제거 로직 부재
    • 추가하면 해결 예상

4. 해결 방안

4.1 Consumer에서 모든 문자열 필드의 따옴표 제거

TicketConsumer.java 파일을 다음과 같이 수정해야 합니다:

public void onMessage(MapRecord<String, String, String> message) {
    try {
        Map<String, String> ticketData = message.getValue();
        
        String ticketIdStr = ticketData.get("ticketId");
        String userIdStr = ticketData.get("userId");
        String createdAtStr = ticketData.get("createdAt");
        String isUsedStr = ticketData.get("isUsed");
        
        // 모든 문자열 필드에서 따옴표 제거
        ticketIdStr = removeQuotes(ticketIdStr);
        userIdStr = removeQuotes(userIdStr);
        createdAtStr = removeQuotes(createdAtStr);
        
        UserId userId = UserId.of(userIdStr);
        LocalDateTime createdAt = LocalDateTime.parse(createdAtStr);
        Boolean isUsed = Boolean.parseBoolean(isUsedStr);
        
        TicketDomain ticket = TicketDomain.of(ticketIdStr, userId, createdAt, isUsed);
        ticketRepository.save(ticket);
    } catch (Exception e) {
        System.err.println("메시지 처리 중 오류 발생: " + e.getMessage());
        e.printStackTrace();
    }
}

// 따옴표 제거 메서드
private String removeQuotes(String value) {
    if (value != null && value.startsWith("\"") && value.endsWith("\"")) {
        return value.substring(1, value.length() - 1);
    }
    return value;
}

4.2 결과

해당 처리를 통해 제대로 이벤트 응모시 Ticket 처리 오류 없이 실행 해결

추가 조사 필요
1. 해당 문제는 결국 String으로 변환이 필요하기 때문에 발생하는 문제 Object로 가능한지 조사
2. String을 사용한다면 변환시 타입 보호를 위한 다른 사람들의 방식 조사 필요
3. 변환이 필요한 경우 Format에 대한 제한 로직 추가 고려 방식


profile
백엔드 개발자 Seob입니다

0개의 댓글