05. 유지보수¶
5.1 현재 상태¶
이 장에서는 배포 전 개발·검증 과정에서 수정한 결함과 정식 릴리스 뒤 유지관리자가 관찰해 수정한 결함을 구분해 기록합니다. 5.2는 배포 전 점검 사례이며, 5.3은 실제 사용자 신고가 아니라 정식 릴리스에 대한 유지관리자 검증에서 발견한 사례입니다.
5.2 배포 전 발견·수정 기록¶
| 사례 | 발견 당시 상태 | 수정 내용 | 확인 결과 |
|---|---|---|---|
| 화면 이동 경로 | 주요 기능으로 이동하는 메뉴가 충분히 눈에 띄지 않아 사용자가 기능 진입점을 찾기 어려웠습니다. | 주요 화면으로 이동하는 내비게이션을 보완했습니다. | 브라우저 흐름에서 회원가입, 상품, 채팅, 신고 화면으로 이동할 수 있음을 확인했습니다. |
| 개발·검증 의존성 | pytest 의존성에서 알려진 취약점이 확인되었습니다. | pytest를 9.0.3으로 갱신했습니다. | 모든 의존성 그룹 점검에서 알려진 취약점이 없음을 확인했습니다. |
| 개발 환경의 채팅 자산 | DEBUG 환경에서 채팅 JavaScript 정적 자산 제공을 확인할 필요가 있었습니다. | 정적 자산 제공 경로를 바로잡았습니다. | /static/chat/chat.js가 HTTP 200 및 text/javascript로 응답함을 확인했습니다. |
| 회원가입 안내 | 가입 화면의 안내가 한국어 사용자에게 비밀번호 조건을 충분히 전달하지 못했습니다. | 한국어 안내 문구와 비밀번호 조건을 화면에 표시했습니다. | 회원가입 화면에서 안내 문구를 확인했습니다. |
이 표의 ‘발견 당시 상태’는 수정 전 기록입니다. 현재 상태를 뜻하지 않으며, 각 항목은 수정 후 표의 ‘확인 결과’에 적힌 방식으로 다시 점검했습니다.
5.3 정식 릴리스 관찰 후 수정 사례¶
MNT-01. 공개 테스트 절차의 실행 인프라 누락¶
| 항목 | 내용 |
|---|---|
| 사례 ID | MNT-01 |
| 발견 환경 | 정식 릴리스 v0.1.0, .env 없는 기본 Docker Compose |
| 문제 현상 | README의 전체 테스트 명령은 127.0.0.1:5432의 PostgreSQL과 127.0.0.1:6379의 Redis를 전제했지만 기본 Compose는 두 포트를 호스트에 공개하지 않아 테스트 준비 단계에서 연결이 거부됐습니다. |
| 원인 | 애플리케이션 실행 절차와 호스트에서 실행하는 전체 테스트 절차가 서로 다른 네트워크 경계를 사용하면서, 테스트 전용 의존성을 준비하는 명령이 문서와 구성에 없었습니다. |
| 추적 | 공개 GitHub issue #20에 정식 릴리스 SHA, 재현 절차, 기대·실제 결과와 수정 조건을 기록했습니다. |
| 수정 내용 | compose.test.yaml 오버레이에 별도 프로젝트·볼륨의 PostgreSQL과 Redis를 추가하고 각각 127.0.0.1:55432, 127.0.0.1:56379에만 공개했습니다. README는 이 오버레이의 준비·테스트·정리 순서를 그대로 실행할 수 있도록 변경했습니다. |
| 음성·회귀 테스트 | .env와 TEST_* 값이 없는 구성에서 기본 포트, loopback 바인딩, 별도 볼륨 이름을 자동 확인했습니다. 수정된 README 절차를 처음부터 실행해 전체 테스트가 통과하고 테스트 프로젝트를 down -v로 정리할 수 있음을 확인했습니다. |
| 결과 | 168 tests, 216 subtests PASS |
| 잔여 위험 | 사용자가 기본 포트를 이미 사용 중이면 TEST_DB_PORT와 TEST_REDIS_PORT를 다른 loopback 포트로 지정해야 합니다. 테스트 비밀번호는 로컬 전용이며 공유·운영 환경에 재사용하지 않습니다. |
MNT-02. 상품 이미지 저장 경로 권한과 지속성¶
| 항목 | 내용 |
|---|---|
| 발견 환경 | 정식 릴리스 v0.1.0, .env 없는 기본 Docker Compose |
| 문제 현상 | 유효한 PNG 상품 등록이 /app/media 권한 오류로 HTTP 500을 반환했습니다. |
| 원인 | 런타임 이미지가 앱 사용자 소유의 미디어 디렉터리를 만들지 않았고 Compose에도 영속 미디어 볼륨이 없었습니다. |
| 추적 | 공개 GitHub issue #21에 두 차례 재현한 결과와 수정 조건을 기록했습니다. |
| 수정 내용 | 이미지에 앱 사용자 소유 /app/media를 만들고 media-data 볼륨을 연결해 업로드를 앱 재시작 뒤에도 보존하도록 변경했습니다. |
| 음성·회귀 테스트 | Compose 해석 결과의 미디어 볼륨과 Dockerfile 권한 준비를 자동 확인하고, 깨끗한 Compose에서 유효 이미지 등록·조회·앱 재시작 후 지속성을 확인했습니다. |
| 결과 | 수정 후 유효 이미지 등록은 상세 화면으로 이동하고 미디어 GET은 HTTP 200이며 앱 재시작 뒤에도 유지됩니다. |
| 잔여 위험 | 운영 배포에서는 미디어 백업·용량 제한과 신뢰할 수 있는 외부 저장소 정책이 추가로 필요합니다. |
MNT-03. 데이터베이스 중단 시 준비 상태 지연¶
| 항목 | 내용 |
|---|---|
| 발견 환경 | 정식 릴리스 v0.1.0의 Docker Compose |
| 문제 현상 | PostgreSQL 중단 뒤 /readyz/가 정의된 JSON 503 대신 3초 클라이언트 제한 시간을 초과했습니다. |
| 원인 | 데이터베이스 연결과 TCP 실패 시간이 Compose healthcheck 제한보다 짧게 고정되지 않았습니다. |
| 추적 | 공개 GitHub issue #22에 중단·복구 관찰 결과를 기록했습니다. |
| 수정 내용 | 호스트명 해석을 0.25초로 제한하고 확인된 모든 주소를 순서대로 시도하는 단일 비동기 PostgreSQL 쿼리를 1.5초 전체 제한 안에서 실행하도록 변경했습니다. |
| 음성·회귀 테스트 | 해석 제한·중복 주소 제거·첫 주소 실패 후 대체 주소 성공·전체 쿼리 제한을 자동 확인하고, 실제 DB 중단 시 0.271초 JSON 503과 DB 재시작 후 HTTP 200을 확인했습니다. |
| 결과 | 준비 상태가 DB 장애를 제한 시간 안에 알리고 복구 후 정상 상태로 돌아옵니다. |
| 잔여 위험 | 네트워크와 커널의 실패 방식에 따라 실제 지연이 달라질 수 있으므로 운영 환경의 오케스트레이터 제한과 함께 다시 확인해야 합니다. |
MNT-04. 릴리스 불변성 표현의 의미 구분¶
| 항목 | 내용 |
|---|---|
| 발견 환경 | GitHub의 v0.1.0-rc.2, v0.1.0 릴리스 API |
| 문제 현상 | GitHub Release 객체는 immutable=false인데 설명의 “불변 후보”가 객체 자체의 기술적 불변성을 지원하는 것처럼 읽힐 수 있었습니다. |
| 원인 | 태그와 SHA를 이동하지 않는 프로젝트 규칙과 GitHub Release 객체의 편집 가능 상태를 구분하지 않았습니다. |
| 추적 | 공개 GitHub issue #23에 차이를 기록했습니다. |
| 수정 내용 | 후속 후보와 정식 릴리스 설명은 “태그와 SHA를 이동하지 않는 후보”로 표기하고 GitHub API의 immutable 상태를 주장하지 않습니다. |
| 검증 | 기존 태그 대상 SHA가 유지되고 문서와 후속 릴리스 설명이 두 의미를 구분하는지 확인합니다. |
| 잔여 위험 | 기존 릴리스 본문은 편집할 수 있으므로 태그 대상과 커밋 이력을 함께 확인해야 합니다. |
MNT-05. 상품 채팅 연결 상태와 송금 표시 보정¶
| 항목 | 내용 |
|---|---|
| 사례 ID | MNT-05 |
| 발견 환경 | main 03f0f1e 이후 Stitch 채팅 화면과 상품 대화 검토 |
| 문제 현상 | WebSocket이 연결되어도 화면의 presence가 상품 대화 · 연결 중에 머물렀고, 비정상 종료가 반복되면 재연결 카운터가 초기화될 수 있었습니다. 송금 화면과 알림에는 소수 표현이 남아 있었습니다. |
| 원인 | 초기 화면 문구와 실제 open 이벤트 상태 갱신이 분리되어 있었고, 연결 직후마다 재시도 횟수를 0으로 되돌렸습니다. UI·API·알림이 서로 다른 금액 표현을 사용했습니다. |
| 수정 내용 | PR #43에서 연결·종료 상태, 최대 5회 지수 백오프, 가로형 공통 로고, 정수 원화 입력·응답·알림을 반영했습니다. PR #44에서 10초 안정 연결 뒤에만 재연결 카운터를 초기화하도록 보정했습니다. |
| 검증 | PR #43 run 30038923030, PR #44 run 30039390145에서 필수 CI 6개가 각각 PASS했습니다. |
| 잔여 위험 | 실제 배포 프록시의 WebSocket upgrade·close code 보존과 로컬 서버를 사용한 수동 끊김·복구 캡처는 별도 확인이 필요합니다. |
5.4 이후 운영 유지보수 절차¶
flowchart LR
O[실제 사용·로그 관찰] --> R[재현 조건 기록]
R --> C[원인 분석]
C --> F[최소 범위 수정]
F --> N[관련 음성 테스트 추가]
N --> T[회귀 테스트]
T --> D[문서·잔여 위험 갱신]
- Docker Compose 환경에서 정상 사용자와 악의적 사용 시나리오를 관찰합니다.
- 발견한 문제의 입력, 사전조건, 기대 결과, 실제 결과를 비밀값 없이 기록합니다.
- 코드·설정·데이터 상태를 확인해 원인을 좁힙니다.
- 원인을 직접 수정하되 관련 없는 범위는 바꾸지 않습니다.
- 같은 문제가 재발하지 않도록 음성 테스트 또는 회귀 테스트를 추가합니다.
- 수정 전 재현 결과와 수정 후 결과를 확인합니다.
- 영향 범위, 남은 한계, 사용자에게 보이는 변화를 보고서에 반영합니다.
이 사례는 “정식 릴리스 관찰 → 공개 issue → 최소 수정 → 음성·회귀 테스트 → 문서 갱신” 순서로 처리했습니다.
5.5 이후 관찰할 항목¶
| 영역 | 관찰 시나리오 | 확인할 문제 |
|---|---|---|
| 인증 | 반복 로그인 실패, 세션 만료, 비밀번호 변경 뒤 기존 세션 | 제한 우회, 계정 존재 노출, 세션 무효화 누락 |
| 상품 | 타인 상품 접근, 비노출 전후, 이미지 변조 | IDOR, 캐시·목록 불일치, 업로드 우회 |
| 채팅 | Redis 중단, ACK 유실, 재연결, 같은 UUID 재전송 | 메시지 유실·중복, 잘못된 성공 응답, 이력 누락 |
| 신고·제재 | 임계값 경계, 동시 신고, 제재 만료 | 중복 제재, 부분 저장, 만료 후 상태 불일치 |
| 복구 | DB 백업·복원, 서비스 재시작 | 데이터 누락, 마이그레이션 불일치, 복구 절차 오류 |
5.6 실제 사례 기록 양식¶
이후 실제 문제가 발견되면 아래 표를 복사해 작성합니다.
| 항목 | 기록 내용 |
|---|---|
| 사례 ID | MNT-01부터 순서대로 부여 |
| 발견 환경 | 코드 버전, 실행 방식, 필요한 비밀값을 제외한 환경 |
| 문제 현상 | 사용자가 본 결과와 기대 결과 |
| 재현 방법 | 방어 검증에 필요한 최소 절차 |
| 원인 | 잘못된 코드·설정·데이터 가정 |
| 수정 내용 | 변경 파일과 핵심 수정 |
| 음성 테스트 | 취약한 입력이나 권한 요청이 차단되는지 |
| 회귀 테스트 | 기존 정상 기능이 유지되는지 |
| 결과 | 실제 명령과 결과 |
| 잔여 위험 | 아직 확인하지 못한 범위 |
5.7 보안 유지보수 연결¶
보안 관련 문제는 보안 약점과 개선 계획의 항목과 연결합니다. 테스트 명령과 캡처는 테스트 근거에 남기며, 최종 PDF에는 비밀값·개인정보·내부 경로가 보이지 않도록 정리합니다.