모든 태그

# Spring Modulith

14개의 글

사람 손이 붙어 있던 다섯 곳을 끊다: 설정 드리프트·변경 리뷰·인덱스 판정·인시던트 리포트·월간 점검

이기종 DBMS 운영 관리 플랫폼 DBTower 9편. 현업 DBA의 병목이 어디에 남는지를 렌즈로, 사람 손이 선형으로 붙던 다섯 지점을 기능으로 끊었습니다. (1) 설정 드리프트 이력: 파라미터 diff의 공간축("A와 B가 다른가")에 시간축("언제부터 무엇이 바뀌었나")을 붙였습니다. 거울 테이블과 변경 로그로 무변경 주기엔 스냅샷 한 줄만 쌓이게 했고, work_mem 4096→8192 실변경을 감지해 카드를 쐈습니다. 검증 중 MongoDB parameters()가 $clusterTime 같은 응답 gossip 필드를 흘려 매번 오탐이 나던 기존 버그도 잡았습니다. (2) 스키마 변경 리뷰 게이트: 배포 전 DDL을 규칙으로 판정(락 위험·DEFAULT 없는 NOT NULL·DROP·WHERE 없는 대량 변경)하고 실제 행수로 락 위험을 확정한 뒤 AI 1차 소견을 붙여 ADMIN 승인·자동 감사까지. 실행은 하지 않고 gh-ost 경로만 안내합니다. (3) 인덱스 사용 통계 주기 영속: "이 인덱스 지워도 되나"는 재시작 누적 카운터의 순간값으론 못 답합니다. 5기종 스캔 통계를 6시간 주기로 영속하고(Oracle은 미지원 정직), lakehouse가 first-vs-last 델타·리셋 클램프로 분기 창 판정 마트를 짓습니다. (4) 인시던트 리포트: 장애 구간을 주면 시점 비교·설정 변경·플랜 플립·대기·가용성을 한 장으로 재구성하고 AI가 재료 내 사실만으로 요약합니다. (5) 월간 점검 리포트: 헬스·백업·Advisor·용량·낭비·설정 변경을 매월 자동 발행합니다. 다섯 개 전부 읽고 판정·기록까지가 몫이고 대상 DB는 바꾸지 않습니다. 신규 모듈 하나(review)는 이벤트로 alert에 카드를 위임하고, 공개 파사드 둘(score·finops)로 Modulith 경계를 순환 없이 유지했습니다. 테스트 514건, VERIFICATION 110개 절.

pay 총정리: 실패가 비싼 도메인을 밑바닥부터

Spring Modulith로 만든 결제 시스템 pay의 전체 기록을 한 편에 정리했다. 결제를 고른 이유(타임아웃·중복·미확정·정산 불일치가 실제로 아픈, 실패가 비싼 도메인)에서 시작해 신뢰 경계, PG 타임아웃을 UNKNOWN으로 보존하고 복구가 확정하는 실패 설계, 복식부기 원장·정산·대사, PG 콜을 트랜잭션 밖으로 뺀 체크아웃 사가, 재고 락 3종 실측, 멀티 PG·구독·선불 월렛·가상계좌·FDS·복합결제로의 결제수단 확장, 대기열·유입 제어·암호화 같은 보안 층, 회원과 분쟁·차지백, 그리고 내 코드를 스스로 감사해 잡은 자금 손실 버그까지. 시리즈를 안 읽어도 이 한 편으로 전체가 잡히게 썼고 깊이가 필요한 지점마다 해당 편을 링크했다.

타임아웃을 실패로 단정하지 않는 3값 원장, 그리고 손으로 짠 서킷브레이커와 망취소

1부는 아무것도 기억하지 못하던 게이트웨이에 기억을 붙입니다. 모든 거래에 거래고유번호를 채번하고(스레드 안전 + 재기동 안전), 결과를 3값 상태(SUCCESS/FAILED/UNKNOWN)로 원장에 적재합니다. 핵심 규칙은 타임아웃을 임의로 실패 처리하지 않는 것입니다. 응답을 못 받은 거래는 계정계에서 처리됐을 수 있어 FAILED가 아니라 UNKNOWN으로 적습니다. 목업 계정계에 응답 지연 모드를 넣어 진짜 read 타임아웃(3.06초 → 504)을 일으키고 원장에 UNKNOWN이 남는 것을 실측했습니다. 적재는 비동기라 거래를 막지 않고, 계좌는 저장 직전 마스킹되며, correlation ID가 로그와 원장을 한 줄로 꿰고, 토큰버킷·커넥션 풀·TCP 왕복이 Prometheus 커스텀 메트릭으로 노출됩니다. 2부는 남은 폭탄 둘을 처리합니다. 계정계 프로세스를 실제로 죽여 손으로 짠 서킷브레이커(CLOSED→OPEN→HALF_OPEN)가 502 → 503×4 즉시 거절로 장애를 격리하고 재기동 후 탐침으로 닫히는 전 과정을 확인했고, UNKNOWN 해소도 완성했습니다. 원거래 전문에 거래고유번호를 실어(30→52byte) 거래상태조회로 처리 여부를 확인해, 처리됐으면 망취소로 CANCELED, 미처리면 그제야 FAILED로 확정합니다. 조회성 거래만 지수 백오프로 제한 재시도하고(변경성 재시도는 이중 거래라 코드가 0회를 강제), 거래 단위 데드라인이 마지막 재시도의 read 제한까지 깎습니다. ddl-auto:update가 체크 제약을 갱신하지 않아 CANCELED UPDATE가 거부된 함정과, 그 실패 순간 이미 나간 망취소를 멱등성이 구해 준 이야기도 정직하게 적었습니다.

완제품 없이 세운 문지기, 인증·라우팅·유량제어 필터 체인부터 가변 프레이밍과 커넥션 풀까지

전문↔JSON 왕복 통로는 열렸지만 아무나 드나들 수 있었습니다. 1부에서 /api/gateway/** 앞에 문지기를 손으로 세웁니다. 인증(X-API-Key, 없으면 401·잘못되면 403), 라우팅(모르는 경로 404), 유량제어(클라이언트별 토큰버킷, 용량 5 초과 시 6번째 429 + Retry-After)입니다. 완제품 프레임워크 없이 GatewayFilter 인터페이스와 체인 실행기, 서블릿 브릿지로 직접 짰고, 직접 짜는 유량제어의 두 함정(벽시계로 인한 시간 역행, 같은 버킷을 치는 스레드 경쟁)을 단조 시계와 버킷 단위 동기화로 막았습니다. 겸사겸사 코드베이스를 Spring Modulith 모듈러 모놀리스로 재정렬해 모듈 경계를 verify()가 강제하게 했습니다. 2부는 통로에 남은 두 구멍을 메웁니다. 거래내역 조회 응답처럼 레코드가 건수만큼 붙어 길이가 매번 다른 전문은 4byte 길이 헤더의 2단계 프레이밍으로 자르고(가변 309B 왕복, 비정상 길이는 fail-closed 거절), 요청마다 소켓을 새로 열던 것은 최대 크기·유휴 반납·검증·고갈 거절을 갖춘 스레드 안전 커넥션 풀로 바꿨습니다. 순차 6회 조회에 소켓 1개 재사용, 동시 10건 폭주에도 created=4(==max)·reused=12를 진짜 실행으로 남겼습니다.