V1의 핵심 사용자 여정 별로 SLI SLO 정의 - 100-hours-a-week/KTB4-3rd-CLOUD GitHub Wiki

V1의 핵심 사용자 여정 별로 SLI/SLO 정의

V1의 핵심 사용자 여정(Core User Journey) 정의 문서에서 V1 서비스에서 사용자가 경험하는 핵심 기능을 정의했다.
이 문서에서는 V1의 사용자 핵심 여정 과정에서 어떤 API 경로를 거치고 그 경로에서 어디서 SLI를 수집할 것인지 정의한다. 이후 성공과 실패를 가르는 기준값이 되는 V1의 SLI/SLO 지표를 선정한다.

1. 어떤 API를 통해서 측정할 것인가?
2. 어떤 위치에서 지표를 수집할 것인가?
3. 어떤 결과를 성공과 실패로 구분할 것인가?
4. 응답속도와 성공률 외에 어떤 품질을 보장해야 하는가?

위의 질문에 대해서 답을 찾아가며 V1의 SLI/SLO 지표를 정의한다.

1. 어떤 API를 통해서 측정할 것인가

이동이 필요한 사용자가 현재 위치와 목적지를 기준으로 적합한 모집을 발견하고, 참여 요청이 수락되어 상대방과 채팅을 시작한다.

V1의 핵심 사용자 여정의 기능인 동행 모집에서 모집자는 지도 기반 핀으로 위치를 표시하고 대중교통, 택시, 자차 등 이동 수단을 선택해 동행 모집글을 등록한다. 참여자는 지도에 표시된 핀을 확인해 모집 조건을 확인하고 채팅방에 참여한다.

모집자와 참여자가 서비스 안에서 수행하는 행동이 다르므로 입장별로 거치는 API도 다르다. 따라서 참여자와 모집자의 여정을 분리해 API 경로를 정의한다.

동행모집 참여자 CUJ: 주변 동행모집 탐색 → 상세 확인 → 채팅방 참여 → 채팅 시작
동행모집 모집자 CUJ: 경로·시간 입력 → 동행모집 게시글 등록 → 참여자 합류 확인 → 채팅 시작

택시팟은 시스템이 조건이 같은 기존 모집 중인 팟에 사용자를 합류시키거나 새로운 팟을 생성한다. 지도 탐색 방식이 아니라 출발지·도착지 좌표와 사용자가 설정한 출발시각을 기준으로 시스템이 매칭을 자동으로 수행한다. 따라서 별도의 참여 요청과 모집자 수락 단계는 없다.

택시팟 매칭 CUJ: 출발지·목적지·시간 입력 → 택시팟 자동 매칭 시작 → 매칭 상태 확인 → 채팅 시작

모여타 서비스는 목적형 서비스이기 때문에 목적 달성에 대한 중요도가 높다. 최종 결과뿐 아니라 핵심 사용자 여정(CUJ)의 각 경로에서 발생하는 실패를 관찰해야 어느 여정의 어떤 API에서 실패했는지 파악하고, 사용자 목적 달성에 미치는 영향을 기준으로 서비스 신뢰성을 관리할 수 있다.

이를 위해 핵심 사용자 여정을 세 가지로 구분한 CUJ에 대해 다음 항목을 정의한다.

  • 사용자가 거치는 주요 단계와 호출 API
  • 단계별 성공·실패 판정 기준
  • 가용성, 지연 시간, 오류율 등의 SLI

1-1. API 경로를 읽는 기준

이 문서의 API 경로, 요청·응답 필드, 상태 코드와 성공·실패 판정은 API 명세서를 우선 기준으로 사용한다. 코드는 명세에 정의된 요청이 내부에서 어떤 컴포넌트를 거쳐 처리되는지 설명하기 위한 참고 자료로만 사용하며, 명세에 없는 코드 동작을 API 계약이나 SLI 성공 조건으로 추가하지 않는다.

또한 사용자가 화면에서 출발지·목적지·시간을 입력하는 행위 자체는 현재 별도 백엔드 API를 호출하지 않는다. 입력 완료 후 등록 또는 매칭 시작 API를 호출하는 시점부터 서버 측 측정 구간이 시작된다. 장소 자동완성이나 주소 검색을 외부 지도 API로 연결하는 경우에는 해당 외부 의존성을 CUJ에 추가해야 한다.

1-2. 공통 요청 경로

사용자 브라우저
  → Route53 A 레코드
  → Elastic IP
  → EC2 Security Group(80/443)
  → Nginx 컨테이너
      ├─ /          → Next.js frontend:3000
      └─ /api/*     → Spring Boot backend:8080
                         → Spring Security/JWT
                         → Controller
                         → Service 트랜잭션
                         → JPA Repository
                         → MySQL 컨테이너
                         → EBS의 Docker/MySQL 볼륨

현재 V1 인프라는 환경별 단일 EC2에 Nginx, 프론트엔드, 백엔드, MySQL을 Docker Compose로 배치한다. 따라서 EC2, EBS 또는 Docker daemon 장애는 세 CUJ에 공통으로 영향을 주는 단일 장애 지점이다. API별 SLI와 별도로 EC2 상태, 컨테이너 재시작, MySQL 연결 상태를 원인 분석 지표로 수집해야 한다.

2. 동행모집 참여자 CUJ

주변 동행모집 탐색 → 상세 확인 → 채팅방 참여 → 채팅 시작

2-1. API와 동작 과정

단계 API 또는 프로토콜 인증 동작 과정 주요 저장소·컴포넌트
지도에서 주변 모집 탐색 GET /api/map-pins 불필요 지도 영역 안의 RECRUITING 동행모집 핀과 커뮤니티 핀을 각각 조회해 병합한다. 핀이 500개를 넘으면 목록을 비우고 limit_exceeded=true를 반환한다. HomeController → HomeService → CompanionPostRepository, CommunityPostRepository → MySQL 공간 쿼리
바텀시트 목록 탐색 GET /api/nearby-posts 불필요 MySQL ST_Distance_Sphere로 현재 위치와 글 위치의 거리를 계산한다. 두 종류의 글을 거리순으로 병합해 10개 단위 커서를 반환한다. HomeService → 두 게시글 Repository → MySQL
상세 확인 GET /api/companion-posts/{companion_id} 선택 kind=COMPANION인 글을 조회하고 출발시각 경과, 정원 도달, 현재 사용자의 참여 여부를 계산한다. 취소된 모집은 반환하지 않는다. CompanionPostService → CompanionPostRepository, CompanionParticipantRepository
채팅방 참여 POST /api/companion-posts/{companion_id}/participants JWT 필수 중복 참여를 확인하고, RECRUITING이며 정원이 남은 경우에만 조건부 UPDATE로 current_count를 증가시킨다. 참여 행 저장 후 연결된 채팅방 ID를 반환한다. ChatParticipationService → ChatRoomRepository, CompanionParticipantRepository, UserRepository → MySQL 트랜잭션
채팅방 정보 확인 GET /api/chat-rooms/{room_id} JWT 필수 활성 참여자만 채팅방과 마지막 읽은 메시지 위치를 조회한다. 비참여자에게도 403이 아닌 404를 반환한다. ChatRoomService → CompanionParticipantRepository, ChatRoomRepository
이전 메시지 확인 GET /api/chat-rooms/{room_id}/messages?cursor={cursor} JWT 필수 활성 참여자를 확인한 뒤 메시지를 ID 내림차순으로 최대 20개 조회한다. ChatMessageService → MessageRepository
실시간 연결 WebSocket CONNECT /api/wss STOMP Authorization 필수 CONNECT 프레임의 JWT를 검증해 user principal을 설정한다. Nginx → Spring WebSocket → StompAuthChannelInterceptor
채팅방 구독 SUBSCRIBE /sub/chat/{room_id} 참여자 필수 활성 참여자인지 DB에서 확인한다. 비참여자의 구독 프레임은 연결을 종료하지 않고 조용히 폐기한다. StompAuthChannelInterceptor → CompanionParticipantRepository
메시지 전송 SEND /pub/chat/{room_id} 참여자 필수 참여자 확인 후 메시지를 저장하고 last_message_id를 갱신한다. 같은 client_message_id는 기존 메시지를 반환해 중복 저장을 방지한다. 이후 /sub/chat/{room_id}로 브로드캐스트한다. ChatStompController → ChatMessageSendService → MySQL → Spring Simple Broker → 클라이언트

2-2. 응답과 발생 조건

단계 응답 발생 과정 SLI 분류
핀·목록 조회 200 좌표 검증과 두 Repository 조회·병합 완료 Good event
핀·목록 조회 명세상 400 VALIDATION_ERROR, VIEWPORT_TOO_LARGE, INVALID_CURSOR 좌표 범위, 사각형, 조회 범위 또는 커서가 잘못됨 유효하지 않은 사용자 요청. 가용성 분모에서 제외하고 제품 오류율로 별도 집계
상세 확인 200 게시글 조회 및 상태 계산 완료 Good event
상세 확인 404 COMPANION_POST_NOT_FOUND 해당 ID 또는 kind=COMPANION인 글이 없음 대상 없음. 시스템 실패와 분리
상세 확인 410 COMPANION_POST_CANCELED 글은 존재하지만 상태가 CANCELED 도메인 상태 응답. 시스템 실패와 분리
채팅방 참여 201과 data.chat_room_id 인원 증가, 참여 행 저장, 채팅방 조회가 한 트랜잭션에서 완료 Good event. 커밋 완료 여부도 함께 확인
채팅방 참여 409 ALREADY_PARTICIPATING 활성 참여 행이 이미 존재하거나 DB 유일성 제약 충돌 예상된 도메인 거절
채팅방 참여 409 COMPANION_NOT_JOINABLE 모집 종료, 취소 또는 정원 마감으로 조건부 UPDATE가 0건 예상된 도메인 거절
채팅방 참여 500 COMPANION_CHATROOM_NOT_FOUND 또는 기타 5xx 참여 처리 중 연결된 채팅방을 찾지 못했거나 예기치 않은 예외 발생 Bad event
채팅방·메시지 조회 200 활성 참여자 확인 후 조회 완료 Good event
채팅방·메시지 조회 404 CHATROOM_NOT_FOUND 방이 없거나 현재 사용자가 활성 참여자가 아님 권한·대상 없음. 별도 집계
WebSocket 연결 CONNECTED JWT 검증과 연결 설정 완료 Good event
WebSocket 연결 ERROR 또는 handshake 실패 JWT 누락·만료, 프록시 실패, 서버 오류 Bad event
구독 별도 성공 프레임 없음 서버가 SUBSCRIBE를 수용하고 이후 MESSAGE를 수신해야 성공을 확인할 수 있음 클라이언트 첫 MESSAGE 또는 구독 후 synthetic probe로 판정
메시지 전송 직접 응답 없음, 구독 채널의 MESSAGE DB 커밋 후 발신자를 포함한 구독자에게 같은 client_message_id가 전달됨 해당 MESSAGE 수신을 Good event로 판정

2-3. 측정 구간과 SLI

측정 구간 시작 → 종료 수집 위치 SLI 정의
P1 탐색 API 가용성 Nginx 요청 수신 → 응답 전송 Nginx access log 또는 Application Signals (5xx·timeout이 아닌 유효 요청 수) / 전체 유효 요청 수
P2 탐색 API 지연 Nginx 요청 수신 → 마지막 응답 바이트 Nginx $request_time, http.server.request.duration 임계시간 안에 완료된 요청 수 / 전체 유효 요청 수. API별 p95·p99도 함께 표시
P3 상세 유효성 상세 요청 → 필수 필드가 있는 200 Controller 응답 + synthetic probe id, 경로, 출발시간, 인원 정보가 유효한 응답 수 / 존재하는 글 대상 요청 수
P4 참여 트랜잭션 성공 참여 Service 진입 → DB commit @TransactionalEventListener(AFTER_COMMIT) 또는 명시적 비즈니스 메트릭 인원 증가·활성 참여 행·채팅방 ID가 모두 확정된 건수 / 참여 가능한 요청 수
P5 채팅 연결 브라우저 CONNECT 시작 → CONNECTED 수신 클라이언트 RUM + Nginx handshake + WebSocket 서버 제한시간 안에 CONNECTED를 받은 연결 수 / 인증된 연결 시도 수
P6 메시지 전달 클라이언트 SEND 시각 → 같은 client_message_id의 MESSAGE 수신 클라이언트 RUM, 서버 저장·브로드캐스트 span 제한시간 안에 저장되고 수신된 고유 메시지 수 / 유효 SEND 수
P7 참여자 퍼널 완료 탐색 시작 이벤트 → 첫 채팅 MESSAGE 수신 프론트엔드 CUJ 시작·완료 이벤트 채팅 시작 완료 이벤트 수 / 탐색 시작 이벤트 수. 탐색만 한 사용자도 포함하는 제품 퍼널 지표
P8 참여자 신뢰성 완료 유효한 참여 버튼 클릭 → CONNECTED·첫 MESSAGE 수신 프론트엔드 CUJ 시작·완료 이벤트 + 서버 trace 채팅 시작 완료 건수 / 서버가 처리 가능한 참여 시도 건수. SLO에는 이 지표를 사용

3. 동행모집 모집자 CUJ

경로·시간 입력 → 동행모집 게시글 등록 → 참여자 합류 확인 → 채팅 시작

3-1. API와 동작 과정

단계 API 또는 프로토콜 동작 통과 컴포넌트
경로·시간 입력 현재 별도 백엔드 API 없음 브라우저에서 입력값을 보관하고 등록 시 한 번에 전송한다. Next.js UI
게시글 등록 POST /api/companion-posts JWT 사용자 조회 후 capacity = recruit_count + 1로 계산한다. 미래 출발시각, 서로 다른 출발·도착지, 이동수단별 정원을 검증하고 게시글을 저장한다. 같은 트랜잭션에서 채팅방과 모집자 참여 행을 생성한다. Nginx → Security → CompanionPostController → CompanionPostService → ChatRoomService → JPA → MySQL
참여자 합류 확인 GET /api/companion-posts/{companion_id} current_count, capacity, is_full을 확인한다. CompanionPostService → MySQL
채팅 시작 공통 채팅 REST·WebSocket 경로 모집자는 글 생성 시 이미 참여 행이 생성되므로 참여 API를 다시 호출하지 않는다. ChatRoomService, WebSocket, ChatMessageSendService, MySQL, Simple Broker

3-2. 응답과 발생 조건

단계 응답 발생 과정 SLI 분류
게시글 등록 201과 data.id 게시글, 채팅방, 모집자 참여 행의 트랜잭션 완료 Good event. 현재 구현 응답에는 chat_room_id가 없음
게시글 등록 400 MALFORMED_REQUEST 또는 422 VALIDATION_ERROR JSON 형식 오류이거나 필수값, 좌표, 문자열 길이 검증 실패 유효하지 않은 사용자 요청
게시글 등록 401 UNAUTHORIZED JWT가 없거나 유효하지 않음 인증 실패. 별도 SLI로 집계
게시글 등록 422 COMPANION_POST_DEPARTURE_AT_PAST 출발시각이 현재 이전 예상된 도메인 거절
게시글 등록 422 COMPANION_POST_ORIGIN_DEST_SAME 출발지와 목적지 좌표가 같음 예상된 도메인 거절
게시글 등록 422 COMPANION_POST_RECRUIT_COUNT_OUT_OF_RANGE 이동수단별 정원 범위 위반 예상된 도메인 거절
게시글 등록 기타 5xx, timeout DB 저장, 채팅방·참여 행 생성 또는 서버 처리 실패 Bad event
합류 확인 200 최신 current_count와 정원 반환 현재 구현 범위의 Good event
채팅 시작 참여자 CUJ와 동일 CONNECTED 및 첫 MESSAGE 수신 Good event

3-3. 측정 구간과 SLI

측정 구간 시작 → 종료 수집 위치 SLI 정의
H1 입력 완료율 작성 화면 진입 → 유효한 등록 요청 발생 프론트엔드 CUJ 이벤트 유효한 등록 요청 수 / 작성 화면 진입 수. 서버 신뢰성 SLI가 아니라 UX 보조 지표
H2 등록 가용성 Nginx가 유효 POST 수신 → 201 응답 Nginx + Spring HTTP metric 201 건수 / (201 + 5xx + timeout) 건수. 입력·인증·도메인 거절은 분모에서 제외
H3 복합 생성 정합성 Service 시작 → DB commit Service after-commit custom metric 게시글·채팅방·모집자 참여 행이 모두 생성된 건수 / 201 처리 건수. 목표는 100%
H4 합류 가시성 참여 commit → 모집자 화면의 최신 인원 반영 참여 Service 이벤트 + 프론트엔드 RUM 제한시간 안에 current_count 증가를 확인한 참여 건수 / 성공한 참여 건수
H5 모집자 채팅 시작 등록 또는 합류 확인 → CONNECTED·첫 MESSAGE 클라이언트 RUM + WebSocket server 제한시간 안에 채팅을 시작한 건수 / 채팅 진입 시도 건수
H6 모집자 CUJ 완료 작성 화면 진입 → 첫 채팅 MESSAGE 프론트엔드 CUJ 시작·완료 이벤트 전체 단계를 완료한 건수 / 유효 등록까지 진행한 건수

4. 택시팟 매칭 CUJ

출발지·목적지·시간 입력 → 택시팟 자동 매칭 시작 → 매칭 상태 확인 → 채팅 시작

4-1. API와 동작 과정

단계 API 또는 프로토콜 동작 통과 컴포넌트
조건 입력 현재 별도 백엔드 API 없음 입력값을 매칭 시작 요청에 포함한다. Next.js UI
자동 매칭 시작 POST /api/taxi-pots 출발시각을 분 단위로 정규화하고 현재 이후 3시간 이내인지 검증한다. 사용자 행을 비관적 잠금으로 조회하고 계좌 등록과 기존 진행 중 매칭을 확인한다. Nginx → Security → TaxiPotController → TaxiPotService → JPA/MySQL
기존 팟 탐색 같은 POST 내부 출발·도착 좌표와 출발시각이 모두 정확히 같고 정원이 남은 첫 RECRUITING 팟을 비관적 잠금으로 조회한다. TaxiPotRepository.findMatchableTaxiPotForUpdate → MySQL row lock
합류 또는 신규 생성 같은 POST 내부 팟이 있으면 참여 행을 저장하고, 없으면 4인 팟·방장 참여 행·채팅방을 생성한다. 잠금 실패는 최대 3회 재시도한다. TaxiPotService.joinTaxiPot/openTaxiPot → MySQL transaction
시스템 입장 메시지 같은 POST 내부 SYSTEM_JOIN을 저장하고 last_message_id를 갱신한다. 커밋 후 채팅 구독자에게 전송한다. ChatSystemMessageService → MessageRepository → ChatMessageBroadcaster → Simple Broker
현재 매칭 확인 GET /api/users/me/current-taxi-pot 사용자의 PENDING 참여 행을 기준으로 현재 팟과 채팅방 ID를 반환한다. 없으면 정상 응답의 빈 값이다. TaxiPotService → TaxiPotParticipantRepository
매칭 상세 확인 GET /api/taxi-pots/{companion_id} 해당 팟의 참여자에게만 경로, 시간, 상태, 인원, 방장 ID를 반환한다. TaxiPotService → TaxiPotRepository, ChatRoomRepository
채팅 시작 공통 채팅 REST·WebSocket 경로 매칭 시작 응답의 chat_room_id로 바로 연결한다. Nginx → WebSocket → Spring Security/STOMP → MySQL → Simple Broker

현재 매칭은 유사 위치나 시간 범위를 검색하는 방식이 아니라 좌표와 분 단위 출발시각의 완전 일치 방식이다. 사용자가 같은 장소명을 선택해도 좌표가 조금 다르면 다른 팟이 생성될 수 있으므로, 매칭 품질 SLI에서는 시스템 오류와 별도로 기존 팟 합류율을 관찰해야 한다.

4-2. 응답과 발생 조건

단계 응답 발생 과정 SLI 분류
매칭 시작 201, id, chat_room_id, status, 인원 정보 기존 팟 합류 또는 신규 팟 생성과 SYSTEM_JOIN 저장 완료 Good event
매칭 시작 400 MALFORMED_REQUEST 또는 422 VALIDATION_ERROR JSON 형식 오류이거나 필수값, 좌표 범위, 문자열 검증 실패 유효하지 않은 요청
매칭 시작 401 UNAUTHORIZED JWT가 없거나 유효하지 않음 인증 실패. 별도 집계
매칭 시작 409 BANK_ACCOUNT_REQUIRED 사용자가 정산 계좌를 등록하지 않음 선행조건 미충족. 가용성 분모 제외
매칭 시작 409 MATCH_ALREADY_IN_PROGRESS 사용자에게 PENDING 택시팟 참여 행이 이미 있음 예상된 도메인 거절
매칭 시작 409 MATCH_BUSY 비관적 잠금 획득 실패가 3회 반복됨 시스템 처리 실패로 Bad event에 포함
매칭 시작 422 DEPARTURE_TIME_PASSED, DEPARTURE_TIME_TOO_FAR, SAME_ORIGIN_DEST 시간 또는 경로 규칙 위반 예상된 도메인 거절
현재 매칭 확인 200과 데이터 현재 PENDING 팟 존재 Good event
현재 매칭 확인 200과 빈 데이터 진행 중인 팟이 없음 정상적인 빈 상태. 실패 아님
상세 확인 200 참여 중인 팟 조회 완료 Good event
상세 확인 404 TAXI_POT_NOT_FOUND 팟이 없거나 사용자가 참여자가 아님 대상·권한 없음. 시스템 실패와 분리
채팅 시작 공통 채팅 응답 CONNECTED 후 MESSAGE 수신 Good event

4-3. 측정 구간과 SLI

측정 구간 시작 → 종료 수집 위치 SLI 정의
T1 매칭 API 가용성 유효 POST 수신 → 201 또는 시스템 오류 Nginx + Spring HTTP metric 201 건수 / (201 + MATCH_BUSY + 5xx + timeout) 건수. 사용자 입력·선행조건 거절은 분모 제외
T2 매칭 결정 지연 Controller 진입 → transaction commit HTTP histogram + Service span 제한시간 안에 기존 팟 합류 또는 신규 팟 생성이 완료된 요청 수 / 유효 매칭 요청 수
T3 락 경쟁 건전성 매칭 팟 잠금 시도 → 획득·실패 TaxiPot Service custom metric + DB 재시도 없는 성공 비율, MATCH_BUSY 비율, lock wait p95·p99
T4 정원 정합성 팟 합류 commit 시점 DB invariant probe current_count <= capacity를 만족하는 활성 팟 수 / 전체 활성 팟 수. 목표는 100%
T5 중복 매칭 방지 참여 commit 시점 DB invariant probe 동시에 PENDING 택시팟이 1개 이하인 사용자 수 / 활성 매칭 사용자 수. 목표는 100%
T6 상태 조회 신선도 POST commit → GET에서 동일 팟 확인 trace 또는 synthetic CUJ 제한시간 안에 같은 id, chat_room_id, 인원 수를 반환한 매칭 수 / 성공한 매칭 수
T7 택시팟 채팅 시작 매칭 201 수신 → CONNECTED·첫 MESSAGE 클라이언트 RUM + WebSocket metric 제한시간 안에 채팅을 시작한 건수 / 매칭 성공 건수
T8 택시팟 CUJ 완료 유효 매칭 시작 → 첫 채팅 MESSAGE 프론트엔드 CUJ 시작·완료 이벤트 채팅까지 완료한 건수 / 매칭 요청이 수락된 건수

5. 측정 위치와 계측 책임

Google SRE는 SLI를 사용자에게 중요한 결과의 good events / valid events 비율로 정의하고, 가능한 한 사용자에 가까운 위치에서 측정할 것을 권장한다. 따라서 CUJ 완료율은 브라우저에서, REST 가용성과 지연은 서비스 경계인 Nginx에서, 데이터 정합성은 트랜잭션 커밋 시점에서 측정한다. MySQL·CPU·메모리 지표는 SLI 자체보다 원인 분석 지표로 사용한다.

위치 수집 항목 용도 주의사항
프론트엔드 RUM CUJ 시작·단계·완료, LCP, INP, WebSocket CONNECTED, MESSAGE 수신시간 실제 사용자 체감과 E2E 완료율 API 명세에 없는 식별자를 요구하지 않고 CUJ 시작·완료 이벤트를 각각 집계한다. 사용자 ID를 metric label로 사용하지 않는다.
Nginx 정규화 route, method, status, $request_time, $upstream_response_time, request_id, WebSocket 101 외부 요청 기준 가용성·지연 현재 access log를 CloudWatch로 보내는 설정이 추가로 필요하다.
Spring MVC http.server.request.duration, route, method, status, error.type API별 latency와 기술 오류 실제 ID를 route label에 넣지 않고 {companion_id}, {room_id}로 정규화한다.
Service after-commit create/join/match 결과, 신규 생성·기존 합류, lock 재시도 의미적 성공과 정합성 HTTP 201 직전이 아니라 DB commit이 확인된 이후 성공을 증가시킨다.
WebSocket/STOMP handshake, CONNECT 인증, SUBSCRIBE 허용, SEND, broadcast 실시간 경로 단계별 실패 위치 Spring Simple Broker는 메시지 ACK를 지원하지 않으므로 최종 전달 성공은 클라이언트 MESSAGE 수신으로 확인한다.
HikariCP·MySQL active/pending connection, acquire time, query latency, lock wait, deadlock, rollback SLI 저하의 원인 분석 사용자 SLO 분자·분모로 직접 사용하지 않는다.
EC2·Docker·EBS status check, CPU, memory, disk, container restart 공통 장애 지점 진단 현재 단일 호스트 구조의 전체 CUJ 영향도를 함께 표시한다.

5-1. 공통 이벤트와 라벨

권장 공통 필드는 다음과 같다.

필드 예시 저장 위치
trace_id OpenTelemetry trace ID 로그·trace
request_id Nginx에서 발급한 요청 ID Nginx·Spring 로그
cuj COMPANION_PARTICIPANT, COMPANION_HOST, TAXI_MATCH 저카디널리티 metric label
step EXPLORE, DETAIL, JOIN, MATCH, CHAT_CONNECT, FIRST_MESSAGE metric label
result SUCCESS, TECHNICAL_FAILURE, BUSINESS_REJECTION, ABANDONED metric label
error_code MATCH_BUSY, INTERNAL_SERVER_ERROR 저카디널리티 metric label·로그
match_result JOINED_EXISTING, CREATED_NEW 택시팟 metric label

메트릭 label에 user_id, companion_id, room_id, client_message_id를 넣으면 시계열 cardinality가 계속 증가한다. 이 값들은 로그와 trace에만 기록하고 메트릭에는 정규화된 route와 결과 유형만 기록한다.

5-2. 권장 메트릭

http.server.request.duration{route,method,status,error.type}
cuj_step_total{cuj,step,result,error_code}
companion_join_total{result}
companion_create_total{result}
taxi_match_total{result,match_result}
taxi_match_retry_total{attempt}
taxi_match_lock_wait_seconds
websocket_handshake_total{result}
stomp_connect_total{result}
stomp_subscribe_total{result}
chat_message_persist_total{result}
chat_message_delivery_seconds
cuj_completion_seconds{cuj}

지연시간은 평균만 사용하지 않고 histogram으로 수집한다. OpenTelemetry HTTP semantic convention의 권장 metric은 http.server.request.duration이며 단위는 초이다. p95와 p99는 histogram bucket으로 계산한다.

6. Good event와 Bad event 기준

AWS Application Signals의 기본 Availability는 5xx를 fault로 보고 4xx는 성공으로 취급한다. 이 방식은 인프라 가용성에는 적합하지만, 모여타의 목적 달성 여부를 설명하기에는 부족하다. 따라서 두 층으로 분리한다.

6-1. 기술 가용성

기술 가용성 = (전체 유효 요청 - 5xx - timeout - 연결 실패) / 전체 유효 요청
  • Good: 정상 2xx, 의도된 빈 상태 200
  • Bad: 5xx, Nginx 502/504, timeout, connection reset, MATCH_BUSY
  • 분모 제외: 형식 오류, 지원하지 않는 좌표·시간, 인증 없이 보낸 요청, 명시적 사용자 취소

6-2. 목적 달성 성공률

CUJ 신뢰성 성공률 = FIRST_MESSAGE까지 완료한 유효 CUJ 시도 수 / 서버가 처리 가능한 의도 확정 CUJ 시도 수
  • 참여자는 참여 버튼 클릭, 모집자는 유효한 등록 제출, 택시팟 사용자는 유효한 매칭 시작 제출을 의도 확정 CUJ 시도의 시작으로 본다. 단순 탐색 사용자를 신뢰성 SLO 분모에 넣으면 자연스러운 탐색 이탈이 장애로 계산되기 때문이다.
  • 탐색 시작을 분모로 하는 전체 퍼널 완료율은 별도의 제품 지표로 유지한다.
  • ABANDONED는 사용자가 단순 이탈한 경우와 UI·네트워크 오류로 진행하지 못한 경우를 구분해 기록한다.
  • ALREADY_PARTICIPATING, CAPACITY_FULL, BANK_ACCOUNT_REQUIRED 같은 비즈니스 거절은 기술 가용성 실패에서 제외하지만, 발생률이 증가하면 사용자가 목적을 달성하지 못하므로 제품 품질 지표로 별도 관리한다.
  • MATCH_BUSY는 HTTP 409이지만 사용자의 올바른 요청을 서버 동시성 처리 때문에 완료하지 못한 것이므로 Bad event로 본다.

7. V1 초기 SLO

Google SRE는 초기 SLO를 다른 서비스의 숫자로 그대로 정하지 말고 과거 성능과 비즈니스 요구를 바탕으로 반복 조정할 것을 권장한다. 현재 프로덕션 기준선이 없으므로 아래 수치는 V1 초기 운영 목표로 사용한다. 첫 번째 28일 구간의 실제 트래픽과 장애 데이터를 확보한 뒤 목표와 임계값을 재검토한다.

측정 기간은 최근 변화에 반응하면서도 주간 변동을 포함할 수 있도록 rolling 28일을 사용한다. 매 시점에서 직전 28일을 다시 계산하므로 월이 바뀌어도 오류 예산이 일괄 초기화되지 않는다.

가용성 초기 목표는 99.9%로 두며, 이는 rolling 28일 기준 약 40분 19초의 오류 예산에 해당한다. 운영에서는 이해하기 쉽게 약 40분의 다운타임 허용 범위로 관리한다. 다만 요청 기반 가용성에서는 실제 오류 예산이 실패 시간 자체가 아니라 전체 유효 요청 중 Bad event의 비율로 계산되므로, 40분은 트래픽이 고르게 발생한다고 가정한 시간 환산값이다.

99.9%를 초기 목표로 선택한 이유는 다음과 같다.

  • 모여타는 콘텐츠를 단순히 조회하는 서비스가 아니라 정해진 시간에 동행을 탐색하고, 매칭·채팅·결제 과정까지 완료해야 목적을 달성하는 시간 민감형 목적 서비스다. 핵심 경로의 장애는 일부 기능의 불편이 아니라 이동 계획 자체의 실패로 이어질 수 있다.
  • 세 CUJ는 여러 API와 WebSocket을 연속해서 통과한다. 탐색, 등록, 매칭 또는 채팅 중 한 단계만 사용할 수 없어도 전체 여정을 완료할 수 없으므로 핵심 경로에 일반적인 초기 서비스보다 높은 가용성 기준이 필요하다.
  • 99%는 rolling 28일 기준 약 6시간 43분의 오류 예산을 허용한다. 출발시간이 정해진 서비스에서는 특정 출퇴근 시간대의 장시간 장애가 오류 예산 안에 포함될 수 있어 사용자 관점의 신뢰성 기준으로 지나치게 느슨하다.
  • 반면 99.9%는 오류 예산을 약 40분으로 제한해 핵심 여정의 신뢰성을 보호하면서도, 운영 기준선이 없는 V1에서 관측 결과에 따라 개선할 수 있는 현실적인 출발점이다. 초기부터 99.99%처럼 과도하게 높은 목표를 두기보다 첫 rolling window의 실측 결과를 바탕으로 단계적으로 상향 여부를 결정한다.
SLO 초기 목표 측정 대상 근거와 해석
핵심 REST API 기술 가용성 99.9% 세 CUJ의 유효 요청 rolling 28일 기준 오류 예산은 약 40분이다. 초기 운영 데이터를 바탕으로 유지 또는 조정한다.
조회 API latency 95% ≤ 1초, 99% ≤ 2.5초 핀·목록·상세·상태 조회 사용자 체감과 tail latency를 함께 보기 위한 두 단계 목표. 2.5초는 페이지 주요 콘텐츠 표시의 Web Vitals good 기준과도 비교 가능한 상한이다.
쓰기·매칭 API latency 95% ≤ 1.5초, 99% ≤ 3초 참여·등록·택시 매칭 DB transaction과 잠금 경로를 포함하므로 조회보다 완화된 초기값을 사용한다. 30일 p95·p99로 조정한다.
동행 참여 트랜잭션 정합성 100% 201 처리된 참여 인원 증가·참여 행·채팅방 연결 중 일부만 성공하는 상태는 허용하지 않는다.
동행 등록 트랜잭션 정합성 100% 201 처리된 등록 게시글·채팅방·모집자 참여 행의 원자성을 보장한다.
택시팟 정원·중복 정합성 100% 활성 택시팟·사용자 current_count > capacity 및 사용자별 복수 PENDING 팟은 0건이어야 한다.
WebSocket 연결 성공률 99.9% 인증된 CONNECT 시도 핵심 채팅 경로도 동일한 가용성 목표를 적용하며, 제한시간 3초 안에 CONNECTED를 수신해야 성공으로 본다.
채팅 메시지 저장 성공률 99.9% 유효한 고유 SEND 중복 재전송은 기존 메시지 반환으로 성공 처리하고, 저장 실패만 Bad event로 계산
채팅 전달 latency 99% ≤ 2초 고유 SEND → MESSAGE 수신 서버 저장 성공만으로 끝내지 않고 발신자 클라이언트의 echo 수신까지 측정
각 CUJ E2E 신뢰성 완료율 97.0% 의도 확정 후 서버가 처리 가능한 CUJ 시도 여러 API와 WebSocket을 연속 통과하는 복합 경로의 초기 목표. 자연스러운 탐색 이탈은 제외하고 UI·네트워크 오류 이탈은 실패에 포함한다.
화면 LCP p75 ≤ 2.5초 모바일·데스크톱 분리 Web Vitals 공식 good threshold
화면 INP p75 ≤ 200ms 모바일·데스크톱 분리 Web Vitals 공식 good threshold

초기 SLO는 영구적인 확정값이 아니다. 첫 rolling 28일 동안은 현재 목표를 기준선으로 운영하고, 이후 매 28일마다 트래픽 규모, 오류 예산 소진 속도, p95·p99 지연을 함께 검토해 목표를 유지하거나 단계적으로 조정한다. 오류 예산이 빠르게 소진되거나 약 40분의 허용 범위를 초과하면 기능 배포보다 CUJ 실패 원인 제거를 우선한다.

참고 문헌