Skip to content

Decision Log

donghoong2 edited this page Oct 28, 2026 · 3 revisions

결정 로그

develop 04c0e10 (2026-10-27) 기준

위키의 사례 기록과 공통 페이지에 흩어진 결정을 표 하나로 모은 색인이다. 같은 결정이 여러 곳에 적혀 있으면 한 줄로 합쳤다. 결정을 뒤집었더라도 옛 결정은 지우지 않고 대체됨 → 새 결정 ID로 남긴다. 주제별 8개 절로 나눴고, ID는 절 순서대로 붙였다.

근거 열은 그 결정의 맥락·검토한 선택지·결과가 적힌 사례 기록을 가리킨다. 사례 기록이 없으면 관련 공통 페이지를 걸고, 지금 코드가 어떻게 동작하는지가 근거인 결정은 레포 문서(README와 docs/)라고 적었다. 형식은 Microsoft Code-With Engineering Playbook의 Decision Log를, 상태 구분은 Michael Nygard의 ADR을 따랐다.

상태는 다음 넷 중 하나다.

상태 뜻
채택 지금 유효한 결정이다. 괄호 안에 이 결정이 밀어낸 옛 방식을 적기도 한다
대체됨 → D-xxx 한때 채택했으나 다른 결정으로 바뀌었다. 화살표가 바꾼 결정을 가리킨다
폐기 검토하거나 시도했으나 채택하지 않았다
제안 방향은 정했으나 아직 적용하지 않았다

ID는 한 번 붙이면 바꾸지 않는다. 새 결정은 주제에 맞는 절의 표에 넣고, 번호는 로그 전체에서 가장 큰 번호 다음을 쓴다.

제품·도메인

ID 결정 상태 시점 근거
D-001 AI 리포트를 사용자에게 바로 주지 않고 등록 손해사정사의 검수를 거치는 B2B2C 구조로 한다 채택(처음 구상한 B2C 직접 제공을 대체) 기획 단계 프로젝트 개요
D-002 사고 입력부터 OCR 트리거 발행까지를 백엔드가 맡는다 채택(처음엔 FastAPI가 업로드·접수를 받았다) 2026-06-20 프로젝트 개요
D-003 매칭(제안·상담 결정)을 별도 컨텍스트로 떼지 않고 report·chat에 둔다 채택 — 레포 문서
D-004 검수 요청에서 status를 빼고, 다음 상태를 서버가 현재 상태에서 파생한다 채택(요청의 status 필드를 대체) — 레포 문서
D-005 분석 처리 상태(AnalysisState)를 저장하지 않고 조회 때마다 계산한다. 판정 우선순위는 VO(ReportAnalysis)가 갖고 NEEDS_REUPLOAD를 AI 초안 검사보다 앞에 둔다 채택 #247 (2026-08-15), #251 (2026-08-16) AI 워커가 남긴 상태를 소비하지 않아 리포트가 무음으로 멈춤
D-006 AI 초안(reports)과 손해사정사별 검수본(report_reviews)을 테이블로 나눠 경쟁 검수를 격리한다. 식별자 없는 목록은 text[]로, 원소마다 상태가 있는 쟁점은 엔티티로 둔다 채택 — 레포 문서
D-007 자식 Repository를 없애고 Root가 자식을 소유하며(cascade·orphanRemoval), 자식 목록은 clear()/addAll() 대신 제자리에서 갱신한다 채택(자식 Repository 직접 조작과 clear()/addAll()을 대체) — 레포 문서
D-008 상담 수락은 여러 Aggregate를 한 트랜잭션으로 바꾸고(통계는 위임, 알림은 커밋 후 이벤트), 상담 거절의 "남은 상담" 판단에는 리포트 행 비관락을 건다 채택(한 트랜잭션 한 Aggregate 원칙의 예외) — 레포 문서
D-009 손해사정사 평점·후기 수·상담 완료 수를 adjuster_profiles에 비정규화하고 쓰지 않는 컬럼은 정리한다 채택 #301 (2026-09-13) 읽는 곳은 다섯인데 쓰는 곳이 없던 비정규화 컬럼으로 사정사 평점이 항상 0점
D-010 AI 소유 실패 저널을 @Subselect 읽기 전용 엔티티로 매핑해 기동 시 스키마 검증에서 뺀다 채택 #247 (2026-08-15) AI 워커가 남긴 상태를 소비하지 않아 리포트가 무음으로 멈춤
D-129 사정사 집계 컬럼은 단일 진입점에서 이벤트마다 원천 테이블 전량 재집계로 덮어쓴다(증분 아님) 채택 #305 (2026-09-13) 읽는 곳은 다섯인데 쓰는 곳이 없던 비정규화 컬럼으로 사정사 평점이 항상 0점
D-130 상담 완료 수의 트리거는 검수 등록이 아니라 상담 수락으로 한다 채택 #305 (2026-09-13) 읽는 곳은 다섯인데 쓰는 곳이 없던 비정규화 컬럼으로 사정사 평점이 항상 0점
D-131 집계 갱신 전 사정사 프로필 행을 비관적 잠금으로 직렬화한다 채택 #305 (2026-09-13) 읽는 곳은 다섯인데 쓰는 곳이 없던 비정규화 컬럼으로 사정사 평점이 항상 0점
D-132 검수 대기 목록은 상세와 같은 노출 정책(AWAITING_INSPECTION·AWAITING_ADOPTION)만 보여 준다 채택 #151 (2026-07-25) 검수 대기 숫자가 화면마다 다르고 목록에서 누르면 404
D-133 '검수 대기'는 사정사별 개인 큐로 정의하고(두 상태 − 내 보류 − 내 SENT·COUNSELING 검수) 모든 화면에 같은 기준을 적용한다 채택(D-134를 대체) #186 (2026-07-31) 검수 대기 숫자가 화면마다 다르고 목록에서 누르면 404
D-134 보류 제외는 검수 대기 화면에만 두고 홈은 전역 풀로 둔다 대체됨 → D-133 #185 (2026-07-31) 검수 대기 숫자가 화면마다 다르고 목록에서 누르면 404

API·코드 규칙

ID 결정 상태 시점 근거
D-011 패키지를 4계층 대신 컨텍스트별 5개 패키지(controller/dto/entity/repository/service)의 실용적 레이어드 구조로 둔다 채택(4계층 구조를 대체) 2026-07-02 레포 문서
D-012 조회에 native query를 쓰지 않는다. 동적 조회는 QueryDSL, 단순 조회는 파생 쿼리·JPQL로 하고, 불가피하면 사유 주석을 단 문서화된 예외로 둔다 채택 #100 (2026-07-13) 레포 문서
D-013 컨트롤러가 ResponseEntity 대신 ApiResponse<T>를 직접 반환하고, 200이 아닌 성공 상태는 @ResponseStatus로 선언한다 채택(ResponseEntity 래핑을 대체) #321 (2026-09-27) OpenAPI 응답 상태코드가 전부 200으로 문서화됨
D-014 DTO에 @Schema required/nullable을 명시해 OpenAPI를 API 명세의 단일 기준으로 삼고, springdoc이 쓰는 정적 매퍼(Json31.mapper())에 snake_case를 직접 설정한다 채택 #197 (2026-08-06), #248·#249 (2026-08-15) OpenAPI 프로퍼티명이 camelCase로 문서화돼 실제 응답과 어긋남
D-015 포맷은 Spotless가 자동으로 고치고 Checkstyle은 판단이 필요한 규칙만 경고한다 채택 — 레포 문서
D-016 릴리즈 태깅을 semantic-release로 자동화한다. 커밋 type이 버전을 정하고 첫 태그 v0.1.0만 손으로 찍는다 채택 #192 (2026-08-06) 레포 문서
D-017 검수 PATCH는 보낸 필드만 반영하는 부분 수정으로 한다. 쟁점은 GET이 내려준 review_issue_id로 upsert하고 제외는 EXCLUDED 상태로 표현한다 채택(전체 덮어쓰기를 대체) — 레포 문서
D-018 보류를 추가 전용 멱등 POST로 하고, 멱등 생성은 UNIQUE 제약 + REQUIRES_NEW 별도 빈 + 재조회로 처리한다 채택(PATCH 토글과 native ON CONFLICT를 대체) — 레포 문서
D-019 분석 처리 상태를 소유자 전용 폴링 엔드포인트와 목록·상세의 평면 필드로 노출한다 채택 #247 (2026-08-15) AI 워커가 남긴 상태를 소비하지 않아 리포트가 무음으로 멈춤
D-020 알림 타입 이름에 도메인 접두어를 붙인다(REPORT_NEEDS_REUPLOAD) 채택 #251 (2026-08-16) AI 워커가 남긴 상태를 소비하지 않아 리포트가 무음으로 멈춤
D-126 4xx 프레임워크 예외를 포괄 예외 핸들러에서 떼어 원래 상태코드(404·405·400·403)로 응답한다 채택 #145 (2026-07-25) 서버 오류는 0건인데 500이 144건이던 포괄 예외 핸들러 문제
D-127 매핑 없는 경로의 404는 actuator 경로만 로그를 남기지 않고, 나머지는 메서드·경로를 WARN으로 남긴다 채택(무로그 404를 대체) #166 (2026-07-25) 서버 오류는 0건인데 500이 144건이던 포괄 예외 핸들러 문제
D-128 표준 MVC 예외는 ResponseEntityExceptionHandler 확장으로 한 번에 처리한다 제안 #145 (2026-07-24) 서버 오류는 0건인데 500이 144건이던 포괄 예외 핸들러 문제
D-135 응답의 지역 필드는 단일 문자열로 통일하되 /users/me만 배열로 예외를 둔다 채택 #155 (2026-07-25) 프론트 계약이 어긋나 기능이 조용히 깨진 두 번
D-136 가입 요청 이름은 name을 정식 키로 받고 구 키 nickname은 @JsonAlias로 계속 받는다 채택 #189 (2026-08-03) 프론트 계약이 어긋나 기능이 조용히 깨진 두 번

인증·보안

ID 결정 상태 시점 근거
D-021 토큰을 응답 바디로 내려 Authorization 헤더로 받고, 쿠키로 옮긴 뒤에도 Bearer 헤더를 폴백으로 함께 받는다 대체됨 → D-022 초기 ~ PR #169 k6 부하테스트 401 폭주를 VU별 토큰 재주입으로 해결
D-022 인증은 HttpOnly access_token 쿠키(Path /) 한 경로로만 받고, refresh_token 쿠키는 Path /auth로 좁혀 재발급·로그아웃 요청에만 싣는다 채택 PR #169, 2026-07-10 k6 부하테스트 401 폭주를 VU별 토큰 재주입으로 해결
D-023 Spring oauth2Login 대신 프론트가 넘긴 인가코드를 백엔드가 REST로 직접 교환하고, 신규 회원은 5분짜리 가입 티켓을 받아 /auth/register에서 생성한다 채택 — 레포 문서
D-024 refresh 회전을 GET → 비교 → DEL → SET 개별 연산으로 한다 대체됨 → D-025 초기 Refresh 토큰 동시 재발급 경쟁을 Lua 원자 CAS로 해결
D-025 refresh 회전을 Lua 스크립트 원자적 CAS(rotate)로 하고 불일치하면 탈취 의심으로 키를 지운다. 재발급할 때 role은 DB에서 다시 읽는다 채택 2026-07-10 Refresh 토큰 동시 재발급 경쟁을 Lua 원자 CAS로 해결
D-026 access 토큰 블랙리스트는 회원 탈퇴 때만 userId 단위로 아웃박스를 거쳐 등록하고 로그아웃 때는 등록하지 않는다. 조회는 Redis 장애 시 fail-open이다 채택 — 회원 탈퇴의 dual-write를 트랜잭셔널 아웃박스로 해결
D-027 익명 인증을 꺼서 401과 403의 의미를 지키고, 쿠키 인증이라 CORS를 allowCredentials(true) + allowedOriginPatterns 목록으로 구성한다 채택 — 레포 문서
D-028 개발용 로그인 백도어를 여러 겹으로 막는다(prod 기동 차단, dev는 시크릿이 없으면 기동 실패, 시크릿 상수 시간 비교, 권한 상승 차단) 채택 부하테스트 준비 k6 부하테스트 401 폭주를 VU별 토큰 재주입으로 해결
D-029 PII 컬럼을 AES-256-GCM 봉투 암호화로 저장하고, 조회 조건으로 쓰는 값은 HMAC 블라인드 인덱스를 따로 둔다 채택 #221, V37·V38 스키마 미지정 마이그레이션이 배포를 깨뜨려 core 스키마로 통일
D-030 prod는 KMS 키 경로만 허용하고, 안전하지 않은 설정(raw 키, dev-login 활성, Apple 키 누락)은 기동 단계에서 막는다 채택 #221 레포 문서
D-031 pgAudit을 세션 단위로 켜 전체 READ를 감사하고, 대상은 OCR 결과처럼 마스킹돼 저장되는 테이블로 고른다 대체됨 → D-032 — pgAudit 감사 로그 범위를 두 번 고친 일
D-032 pgAudit은 객체 단위로 걸고, 대상은 "유출되면 원문이 나가는" 회원·리포트·청구 테이블로 고른다 채택 — pgAudit 감사 로그 범위를 두 번 고친 일

비동기·메시징

ID 결정 상태 시점 근거
D-033 회원 탈퇴 후처리를 서비스 메서드 안에서 Redis에 직접 쓴다 대체됨 → D-034 초기 회원 탈퇴의 dual-write를 트랜잭셔널 아웃박스로 해결
D-034 DB 밖 부수효과를 트랜잭셔널 아웃박스에 적재하고 스케줄러가 FOR UPDATE SKIP LOCKED(D-012의 문서화된 예외)로 폴링해 처리한다. 탈퇴 때 요청 스레드에서는 쿠키 만료만 한다 채택 — 회원 탈퇴의 dual-write를 트랜잭셔널 아웃박스로 해결
D-035 릴레이는 실패한 한 건만 표시하고 배치 전체를 롤백하지 않는다 채택 — 레포 문서
D-036 OCR 트리거 브로커를 EC2에 직접 띄운 Kafka(Docker, KRaft)로 한다 대체됨 → D-037 초기 OCR 트리거 브로커를 Kafka에서 MSK를 거쳐 SQS로 전환
D-037 브로커를 AWS MSK Provisioned(IAM 인증)로 옮긴다 대체됨 → D-038 PR #205 OCR 트리거 브로커를 Kafka에서 MSK를 거쳐 SQS로 전환
D-038 OCR 트리거를 AWS SQS Standard 큐로 보낸다. 상시 브로커 대신 관리형 큐를 고르고, 전환 시점에 DLQ는 넣지 않는다 채택 #208 (2026-08-09) OCR 트리거 브로커를 Kafka에서 MSK를 거쳐 SQS로 전환
D-039 큐 이름 설정(SQS_OCR_QUEUE_NAME)에 폴백 기본값을 두지 않아, 빠지면 기동이 실패한다 채택 #208 OCR 트리거 브로커를 Kafka에서 MSK를 거쳐 SQS로 전환
D-040 아웃박스 클래스는 OcrOutbox*로 리네임하고 테이블 kafka_outbox_events는 그대로 둔다 채택 2026-08-13 스키마 미지정 마이그레이션이 배포를 깨뜨려 core 스키마로 통일
D-041 AI 워커의 실패·차단·재업로드 신호를 조회 시점이 아니라 스케줄러 스윕에서 알림으로 바꾸고, 상태마다 스윕러와 통지 시각 컬럼을 따로 둔다 채택 #247, #248, #251 (V41~V43) AI 워커가 남긴 상태를 소비하지 않아 리포트가 무음으로 멈춤
D-042 알림 생성과 FCM 발송은 AFTER_COMMIT 리스너에서 한다 채택 — 레포 문서
D-043 reports.status 값은 DB 제약 없이 Java enum에서만 강제하고, 새 값은 백엔드를 AI 워커보다 먼저 배포한다 채택 #251 AI 워커가 남긴 상태를 소비하지 않아 리포트가 무음으로 멈춤
D-044 채팅을 SockJS·@MessageMapping·CONNECT 프레임 Bearer 인증으로 만든다(학습용 프로토타입) 대체됨 → D-045, D-046 초기 레포 문서
D-045 메시지 전송은 REST로 하고 WebSocket은 수신 전용으로 쓴다. 팬아웃은 내장 SimpleBroker + Redis pub/sub relay로 하고, 보낸 서버도 직접 브로드캐스트하지 않고 relay를 거친다 채택 — 레포 문서
D-046 WebSocket 핸드셰이크를 access_token 쿠키로 인증하고, SUBSCRIBE 거절은 연결을 끊는 ERROR 대신 MESSAGE 프레임으로 알린다 채택 — 채팅 구독 거절이 WebSocket 연결 전체를 끊던 문제
D-047 읽음 상태를 방의 참여자별 커서 컬럼 두 개로 둔다 채택 V24 레포 문서
D-048 메시지당 첨부 하나를 단일 컬럼 네 개로 저장한다 대체됨 → D-049 V24 레포 문서
D-049 첨부를 attachments jsonb 배열로 저장한다 채택 V25 레포 문서
D-050 첨부 타입을 Content-Type 화이트리스트로만 검사한다 대체됨 → D-051 초기 레포 문서
D-051 첨부는 서버를 거쳐 받아 매직바이트와 선언 타입이 맞는지 확인하고, private S3 key만 저장해 5분짜리 presigned GET으로 내준다 채택 — 레포 문서
D-052 메시지 이력을 (created_at, id) 커서 페이지네이션으로 준다 채택 — 레포 문서
D-053 채팅 메시지의 오프라인 FCM 푸시는 범위 밖으로 둔다 채택 — 레포 문서

데이터베이스

ID 결정 상태 시점 근거
D-054 저장소를 RDS for PostgreSQL 16 + pgvector 하나로 하고, 벡터는 halfvec(1024) 타입에 HNSW 인덱스로 둔다 채택 — 레포 문서
D-055 벡터 타입을 vector(1024)로 한다 대체됨 → D-054 초기 설계 레포 문서
D-056 키워드 검색에 ParadeDB pg_search(BM25)를 쓴다 폐기 초기 설계 레포 문서
D-057 스토리지는 gp3, 가용 영역은 Single-AZ로 시작한다 채택 — 레포 문서
D-058 세 팀(백엔드·AI 엔진·약관 청커)이 public 스키마 하나를 같이 쓴다 대체됨 → D-059 초기 레포 문서
D-059 물리 DB는 하나로 두고 core/ai/corpus 스키마와 소유 롤로 나눈다. 크로스 스키마 접근은 최소 GRANT로 열고 SQL에 스키마를 명시한다 채택 — 레포 문서
D-060 마이그레이션에서 스키마를 지정하지 않고 CREATE TABLE을 쓴다 대체됨 → D-061 #223 이전 스키마 미지정 마이그레이션이 배포를 깨뜨려 core 스키마로 통일
D-061 새 테이블·인덱스 마이그레이션은 core.를 명시한다 채택 #223 스키마 미지정 마이그레이션이 배포를 깨뜨려 core 스키마로 통일
D-062 V40으로 public에 남은 테이블을 core로 옮기고, JDBC URL에 currentSchema=core,public을 고정한다 채택 #244 스키마 미지정 마이그레이션이 배포를 깨뜨려 core 스키마로 통일
D-063 prod Flyway는 체크섬 검증을 켜고 clean을 막고 baseline을 끈다. 마이그레이션 번호가 겹치면 나중에 머지되는 PR이 번호를 바꾼다 채택 PR #249·#251 AI 워커가 남긴 상태를 소비하지 않아 리포트가 무음으로 멈춤
D-064 사건번호를 당일 카운터 행(ON CONFLICT DO UPDATE)으로 빈틈없는 연번으로 발급한다 대체됨 → D-065 V14 사건번호 발급 락 경합으로 처리량 붕괴
D-065 사건번호를 날짜 + 랜덤 코드로 만들고 UNIQUE로 확인해 충돌 시 재시도한다 대체됨 → D-066 #309 사건번호 발급 락 경합으로 처리량 붕괴
D-066 사건번호를 전역 시퀀스 core.report_case_seq의 nextval로 발급한다(yyyyMMdd-NNNNNN, 번호 빈틈 허용) 채택 V47 사건번호 발급 락 경합으로 처리량 붕괴

인프라·배포

ID 결정 상태 시점 근거
D-067 앱 EC2를 private subnet에 두고 접속은 SSM으로만 하며, S3 Gateway VPC Endpoint로 S3 트래픽을 NAT에서 뺀다 채택 초기 인프라 레포 문서
D-068 보안그룹 인바운드는 IP가 아니라 보안그룹 참조로 연다. DB도 Public Access 없이 이 방식으로 5432만 연다 채택 초기 인프라 레포 문서
D-069 앱의 AWS 자격증명은 DefaultCredentialsProvider(IAM Role)에 맡기고, CI는 OIDC 임시 자격증명을 쓴다 채택 초기 인프라, CI/CD 구축 레포 문서
D-070 prod 배포는 SSH나 repository_dispatch 대신 SSM send-command로 하고, 배포 compose 파일은 레포가 단일 진실원이라 CD가 매번 덮어쓴다 채택 CI/CD 구축 배포는 초록불인데 컨테이너가 계속 재시작되던 일
D-071 dev도 SSM + docker compose로 배포한다 대체됨 → D-099, D-108 #332 이전 Kubernetes
D-072 모니터링 인스턴스 동기화를 compose 파일을 base64로 실은 SSM 페이로드로 한다 대체됨 → D-073 #328 이전 레포 문서
D-073 모니터링 인스턴스가 해당 커밋의 레포 tarball을 직접 받아 동기화하고, 이 배포는 deploy/monitoring/** 경로가 바뀔 때만 돈다 채택 #214, #328, #343 레포 문서
D-074 JVM 힙을 MaxRAMPercentage=75와 컨테이너 메모리 상한으로 묶고, HikariCP 풀 크기는 15로 둔다 채택 프로덕션 하드닝, 2026-08-15 HikariCP 풀 확대로는 풀리지 않은 RDS CPU 한계
D-075 actuator를 관리 포트 9292로 분리하고, 외부에는 health·info만 연다 채택 프로덕션 하드닝 GitOps 이전 중에 드러난 Traefik 서비스 타입 오류와 외부에 열린 actuator
D-125 GitHub App 개인 키를 레포마다 따로 두지 않고 조직 시크릿 하나(가시성 selected)로 모으며, Client ID는 레포 변수로 둔다 채택(레포별 개인 키를 대체) 2026-10-13 GitHub App 키를 지워 dev 배포가 멈추고 조직 시크릿으로 통합

관측성·성능

ID 결정 상태 시점 근거
D-076 Prometheus·Loki·Tempo·Grafana를 직접 운영한다. 보존 기간은 3일, Prometheus 저장 크기 상한은 8GB다 채택 #88 레포 문서
D-077 관측 스택을 앱 EC2에 함께 올린다 대체됨 → D-078 #88 레포 문서
D-078 관측 스택을 별도 모니터링 인스턴스로 분리한다 채택 #214 레포 문서
D-079 cAdvisor를 v0.49.1에서 v0.55.1로 올려 컨테이너 메트릭 소실을 해결한다 대체됨 → D-081 클러스터 이주 이전 타깃은 UP인데 컨테이너 메트릭이 비어 있던 일
D-080 앱 호스트를 정적 스크랩한다(backend·node-exporter·cAdvisor·redis-exporter) 대체됨 → D-081 #328 이전 레포 문서
D-081 클러스터의 kube-prometheus-stack이 중앙 Prometheus로 remote_write한다(컨테이너 지표는 kubelet cAdvisor) 채택 #328 Kubernetes
D-082 모든 메트릭에 application 공통 태그를 붙이고 percentiles-histogram은 필요한 타이머에만 켠다 채택 PR #132 레포 문서
D-083 로그는 구조화 JSON으로 stdout에 남겨 Alloy가 Loki로 보낸다. 바인드 파라미터는 로깅하지 않고, OTLP 로그 export는 쓰지 않으며, Loki 라벨은 저카디널리티(service)만 두고 traceId는 derived field로 잇는다 채택 #88, 프로덕션 하드닝 레포 문서
D-084 트레이싱 의존성을 스타터 대신 세 조각으로 명시한다 채택 트레이싱 도입 Spring Boot 4에 트레이싱을 붙이다 세 번 조용히 실패한 일
D-085 traceId를 커스터마이저로 JSON 로그에 직접 넣는다 폐기 트레이싱 도입 Spring Boot 4에 트레이싱을 붙이다 세 번 조용히 실패한 일
D-086 dev 트레이스 샘플링을 1.0(전량)으로 둔다 대체됨 → D-087 트레이싱 도입 레포 문서
D-087 Tempo OOM 이후 dev 샘플링을 0.5로 낮추고 Tempo 메모리 상한을 1GB로 올린다 채택 2026-09-16 레포 문서
D-088 슬로우쿼리를 Hibernate 슬로우쿼리 로그와 JDBC 쿼리 스팬 두 경로로 본다 채택 #324, PR #325 레포 문서
D-089 알림은 severity별 2채널로 보내고 분류되지 않은 알림은 critical로 본다 채택 #88 레포 문서
D-090 k6 결과를 Prometheus remote-write로 기존 Grafana에 넣는다 대체됨 → D-091 부하테스트 준비 레포 문서
D-091 k6 결과는 Datadog으로 분리한다 채택 부하테스트 준비 레포 문서
D-092 POST /reports 쓰기 경로를 구간별로 계측한다(report.create.*, 커넥션 획득 p95) 채택 #306 사건번호 발급 락 경합으로 처리량 붕괴
D-093 스트레스 한계를 넘기려고 HikariCP 풀을 15에서 30으로 늘린다 폐기(병목은 풀이 아니라 RDS CPU였다. 풀 크기는 D-074 유지) 2026-09 스트레스 테스트 HikariCP 풀 확대로는 풀리지 않은 RDS CPU 한계
D-094 200 RPS 목표는 RDS 스펙 상향을 선결 조건으로 둔다 채택 2026-09 스트레스 테스트 HikariCP 풀 확대로는 풀리지 않은 RDS CPU 한계
D-095 SLO는 성능 지표와 오류율 가드레일을 한 쌍으로 정의한다 채택 부하테스트 준비 HikariCP 풀 확대로는 풀리지 않은 RDS CPU 한계
D-096 k6는 토큰을 VU 변수에 두고 이터레이션마다 쿠키를 다시 심는다 채택 부하테스트 준비 k6 부하테스트 401 폭주를 VU별 토큰 재주입으로 해결
D-097 시드 러너는 도메인 서비스를 거치지 않고 엔티티만 저장하며, 리포트 단위로 별도 Bean에서 커밋하고 (유저 × 풀) 단위로 멱등하게 만든다. 시나리오 러너는 프로파일(!test & !prod)과 프로퍼티 두 겹으로 켠다 채택 부하테스트 준비 레포 문서
D-098 DB 버퍼 풀을 줄이고 시드 기본 유저 수를 100에서 600으로 늘려 디스크 I/O를 재현한다 채택 PR #327 HikariCP 풀 확대로는 풀리지 않은 RDS CPU 한계

Kubernetes

ID 결정 상태 시점 근거
D-099 dev 배포를 EC2 docker compose에서 Kubernetes로 옮기되(prod는 compose 유지), 관리형 EKS 대신 기존 EC2 위에 클러스터를 직접 세운다 채택 2026-10-12 ~ 2026-10-14 Kubernetes
D-100 k3s로 클러스터를 세운다 대체됨 → D-101 2026-10-14 이전 Kubernetes
D-101 실데이터를 올리기 전에 kubeadm으로 다시 설치하고, etcd 시크릿 암호화는 설치 전에 켠다 채택 2026-10-14 Kubernetes
D-102 CNI로 Flannel을 쓴다 대체됨 → D-103 kubeadm 초기 CNI를 Flannel에서 Calico를 거쳐 Cilium으로 전환
D-103 NetworkPolicy가 필요해져 CNI를 Calico(eBPF 데이터플레인)로 바꾼다 대체됨 → D-104 2026-10-14 이전 CNI를 Flannel에서 Calico를 거쳐 Cilium으로 전환
D-104 플로우 단위 관측(Hubble)을 위해 CNI를 Cilium(eBPF, kube-proxy 대체)으로 바꾼다 채택 2026-10-14 CNI를 Flannel에서 Calico를 거쳐 Cilium으로 전환
D-105 Ingress는 Traefik을 NodePort로 두어 ALB가 포트 하나만 알게 한다 채택 2026-10-14 GitOps 이전 중에 드러난 Traefik 서비스 타입 오류와 외부에 열린 actuator
D-106 RDS·S3·SQS는 클러스터에 올리지 않고 AWS 관리형으로 둔다 채택 2026-10-14 Kubernetes
D-107 GPU 노드는 같은 인스턴스를 stop/start한다(Karpenter·Cluster Autoscaler 미채택) 채택 2026-10-14 Kubernetes
D-108 dev 배포를 Argo CD GitOps로 바꾼다. CI는 매니페스트 레포의 이미지 태그(커밋 해시)만 커밋하고, 배포 후 /actuator/info의 app.commit으로 반영을 확인한다(verify-deploy) 채택 #332 (PR #340) 배포는 초록불인데 컨테이너가 계속 재시작되던 일
D-109 클러스터 매니페스트는 소스 레포와 분리된 매니페스트 레포에만 두고, 환경은 브랜치가 아니라 폴더로 나눈다 채택 #332, #338, #339 클러스터 상태가 레포 밖에 있어서 잃어버린 것들
D-110 Argo CD Sync 정책을 리소스 성격별로 다르게 둔다(Cilium·Argo CD 자신은 수동 Sync) 채택 2026-10-14 Helm 3과 4의 렌더링 차이를 리소스 단위 대조로 잡고 애드온을 Argo CD로 이전
D-111 비밀값은 Secrets Manager에 두고 External Secrets Operator로 k8s Secret에 동기화한다 채택 2026-10-14 Kubernetes
D-112 ECR pull 토큰을 손으로 만들어 둔다 대체됨 → D-113 2026-10-14 이전 Kubernetes
D-113 ECR pull 토큰을 ESO의 ECR 토큰 생성기로 주기 발급한다 채택 2026-10-14 Kubernetes

사례 기록별 결정

사례 기록 하나에 결정이 여럿 걸려 있으면 한 줄에 모았다. 모든 사례 기록이 이 표에 한 번 이상 나온다.

사례 기록 관련 결정
AI 워커가 남긴 상태를 소비하지 않아 리포트가 무음으로 멈춤 D-005, D-010, D-019, D-020, D-041, D-043, D-063
OpenAPI 응답 상태코드가 전부 200으로 문서화됨 D-013
OpenAPI 프로퍼티명이 camelCase로 문서화돼 실제 응답과 어긋남 D-014
k6 부하테스트 401 폭주를 VU별 토큰 재주입으로 해결 D-021, D-022, D-028, D-096
Refresh 토큰 동시 재발급 경쟁을 Lua 원자 CAS로 해결 D-024, D-025
회원 탈퇴의 dual-write를 트랜잭셔널 아웃박스로 해결 D-026, D-033, D-034
스키마 미지정 마이그레이션이 배포를 깨뜨려 core 스키마로 통일 D-029, D-040, D-060, D-061, D-062
pgAudit 감사 로그 범위를 두 번 고친 일 D-031, D-032
OCR 트리거 브로커를 Kafka에서 MSK를 거쳐 SQS로 전환 D-036, D-037, D-038, D-039
채팅 구독 거절이 WebSocket 연결 전체를 끊던 문제 D-046
사건번호 발급 락 경합으로 처리량 붕괴 D-064, D-065, D-066, D-092
배포는 초록불인데 컨테이너가 계속 재시작되던 일 D-070, D-108
HikariCP 풀 확대로는 풀리지 않은 RDS CPU 한계 D-074, D-093, D-094, D-095, D-098
GitOps 이전 중에 드러난 Traefik 서비스 타입 오류와 외부에 열린 actuator D-075, D-105
타깃은 UP인데 컨테이너 메트릭이 비어 있던 일 D-079
Spring Boot 4에 트레이싱을 붙이다 세 번 조용히 실패한 일 D-084, D-085
CNI를 Flannel에서 Calico를 거쳐 Cilium으로 전환 D-102, D-103, D-104
클러스터 상태가 레포 밖에 있어서 잃어버린 것들 D-109
Helm 3과 4의 렌더링 차이를 리소스 단위 대조로 잡고 애드온을 Argo CD로 이전 D-110
GitHub App 키를 지워 dev 배포가 멈추고 조직 시크릿으로 통합 D-125
서버 오류는 0건인데 500이 144건이던 포괄 예외 핸들러 문제 D-126, D-127, D-128
읽는 곳은 다섯인데 쓰는 곳이 없던 비정규화 컬럼으로 사정사 평점이 항상 0점 D-009, D-129, D-130, D-131
검수 대기 숫자가 화면마다 다르고 목록에서 누르면 404 D-132, D-133, D-134
프론트 계약이 어긋나 기능이 조용히 깨진 두 번 D-135, D-136

Clone this wiki locally