1_OwnLog.jpg

<aside>

한 줄 요약

좋아요·조회수가 동시에 몰려도 숫자가 틀리지 않고, 배포 중에도 끊기지 않는 멀티유저 블로그를 만들었습니다.

누구나 가입해 자기 블로그를 운영하는 플랫폼입니다. 2주 만에 MVP를 출시하고 v1.3.1까지 개선했으며, 요구사항 104개 · REST API 51개 · 테이블 15개 규모입니다.

</aside>

<aside>

308 → 0

배포 중 실패한 요청

블루-그린 전환 전후 (3,966건 중 → 13,308건 중, 로컬 측정)

</aside>

<aside>

100 / 100

동시 좋아요 정확 반영

실제 PostgreSQL에서 100명 동시 요청, 한 회원 10번 연타 → 1

</aside>

<aside>

212개

통합 테스트

Testcontainers로 실제 DB에서 검증, 화면 점검 125개

</aside>

<aside>

개발 기간

2026.10.01 ~

AI응용프로젝트 과목 · 2주 MVP 후 개선

</aside>

<aside>

팀 구성

1인

기획부터 배포 · 문서까지 혼자 진행

</aside>

<aside>

내 역할

백엔드 · 배포

동시성 · 파일 정합성 · 무중단 배포 중심

</aside>

<aside>

담당 업무

기술 스택 · Java 21, Spring Boot 4.1, JPA, Flyway, PostgreSQL 17, React 19, Docker, Nginx, GitHub Actions, Testcontainers

</aside>

<aside>

Review 배포 중 실패 요청 308건 → 0건 (로컬 측정)


프로젝트 진행 과정

<aside>

</aside>

01. 화면

블로그 홈 — 글 목록과 좋아요 · 조회수

블로그 홈 — 글 목록과 좋아요 · 조회수

02. 설계

<aside>

브라우저 → 공용 Nginx → 자체 Nginx(8360) → blue(8361) / green(8362) 컨테이너 → PostgreSQL. 화면과 API를 하나의 Docker 이미지로 묶어 주소 · 포트 하나로 배포합니다. 공개 조건("누가 이 글을 볼 수 있나")은 JPQL 상수와 엔티티 메서드 두 곳에 모아, 타임캡슐 기능을 컬럼 1개 · 조건 1줄로 추가하면서 목록 쿼리 6개를 고치지 않았습니다.

</aside>

03. 기술 선택 이유

기술 고른 이유 포기한 것 (트레이드오프)
원자적 UPDATE 읽고 +1 해서 저장하면 Lost Update가 생깁니다. DB가 직접 더하게 해 동시 요청에도 정확합니다. 영속성 컨텍스트의 값과 어긋날 수 있어 엔티티 필드를 updatable=false로 막았습니다.
낙관적 락 대신 인기 글에 요청이 몰리면 낙관적 락은 재시도가 계속 생깁니다. 같은 행에 쓰기가 몰리는 핫 로우는 남습니다.
블루-그린 배포 헬스 체크를 통과한 새 버전으로만 전환하고, 실패하면 기존 버전이 계속 서비스합니다. 컨테이너 두 벌만큼 자원이 들고 포트 관리가 필요합니다.
Testcontainers ON CONFLICT · advisory lock 같은 PostgreSQL 기능을 실제 DB로 검증합니다. 테스트 시간이 길어지고 Docker가 필요합니다.

04. 트러블슈팅

<aside>

1. 동시에 좋아요를 누르면 숫자가 덜 올라갔다

문제 여러 명이 동시에 좋아요 · 조회를 하면 실제 횟수보다 적게 반영될 수 있었습니다.

원인 엔티티 값을 읽고 +1 해서 저장하면, 같은 값을 읽은 요청들이 서로를 덮어씁니다(Lost Update).

해결 UPDATE posts SET like_count = like_count + 1로 DB가 직접 더하고, 중복 좋아요는 복합 PK + ON CONFLICT DO NOTHING으로 막았습니다.

<aside>

결과 100명 동시 요청 → 100, 한 회원 10번 연타 → 1

</aside>

증거: LikeConcurrencyTest ViewCountConcurrencyTest

</aside>

<aside>

2. 롤백된 글의 이미지 파일이 서버에 남았다

문제 DB는 롤백돼도 디스크에 쓴 파일은 롤백되지 않아, 파일과 DB가 어긋날 수 있었습니다.

원인 파일 삭제를 트랜잭션 안에서 바로 실행했고, 업로드만 되고 글에 쓰이지 않은 파일을 정리하는 과정이 없었습니다.

해결 파일 삭제는 커밋 후(AFTER_COMMIT)에만 하고, 버려진 파일은 24시간 유예 배치 + pg_try_advisory_xact_lock으로 서버 2대가 겹치지 않게 정리합니다.

<aside>

결과 flush · AFTER_COMMIT을 일부러 빼고 돌리면 테스트가 실패해, 두 장치가 꼭 필요하다는 것을 확인

</aside>

증거: PostImageCleanupTest OrphanImageCleanupJobTest

</aside>

<aside>

3. 운영 배포 3회 실패 — 그래도 서비스는 끊기지 않았다

문제 v1.2.0 ~ v1.3.0 운영 배포에서 새 버전 컨테이너가 뜨지 않았습니다.

원인 blue 포트(8361)를 이미 다른 프로세스가 쓰고 있었습니다.

해결 헬스 체크를 통과하지 못하면 전환하지 않도록 설계해 둔 덕분에 기존 버전이 계속 서비스했습니다. 이후 배포 전에 포트 · 서버 상태를 먼저 점검합니다.

<aside>

</aside>

증거: CHANGELOG.md deploy/

</aside>

<aside>

</aside>