Problem
Problem
7인 팀 프로젝트에서 백엔드 리드로 API 기준을 맞추면서, 앨범과 편지 목록처럼 화면에 자주 노출되는 조회 API의 N+1 위험을 다뤘습니다.
Decision
Decision
팀원이 기능을 확장하기 전에 DTO, API 구조, 로컬 실행 기준을 정리하고, 조회 성능은 k6 스크립트로 재현 가능한 형태를 남겼습니다.
Build
Implementation
- 편지 목록은 사진 개수를 함께 보여주는 요구를 고려해 N+1 비교 모드가 있는 k6 스크립트로 확인 경로를 만들었습니다.
- 앨범 조회는 owner와 letters 접근을 함께 고려해 EntityGraph 기반 조회 최적화를 적용했습니다.
- Docker Compose와 CI 기준을 맞춰 팀원별 로컬 MySQL 설정 차이에서 생기는 불일치를 줄였습니다.
팀 리드 역할은 직접 작성한 코드뿐 아니라 팀원이 같은 기준으로 API와 성능을 확인할 수 있는 재현 경로를 만드는 일이라는 점을 정리했습니다.
Proof
Verification
편지 목록 k6와 앨범 조회 k6 스크립트에서 fixture, VU, p95 목표 기준을 확인할 수 있게 남겼고, 장기 운영 지표는 별도 한계로 분리했습니다.
편지 30개 fixture로 목록 조회를 재현
p95 <500ms 목표 기준과 실패율 기준 설정
앨범 조회 p95 <200ms 목표 기준 시나리오
Boundaries
Trade-offs & Limitations
Trade-offs
- 서브쿼리 COUNT는 목록 화면에 필요한 값을 한 번에 가져오지만 복잡한 필터가 늘면 쿼리 가독성이 떨어질 수 있습니다.
- 백엔드 공통 규칙을 먼저 정하면 협업 속도는 좋아지지만 초기 설계 시간이 필요합니다.
Limitations
- 팀 프로젝트 기간 내 핵심 API와 성능 개선에 집중해 장기 운영 지표는 없습니다.
- S3 업로드는 기본 업로드 흐름 중심으로 검증했습니다.
아키텍처
전체 아키텍처▼
architecture
전체 아키텍처
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
Application
API, 도메인 로직, 워커 처리
프론트엔드
Client
REST API
Backend
JWT 검증
Backend
Data / Messaging
상태 저장, 캐시, 이벤트 전달
MySQL
Data
S3
Data
핵심 연결 흐름
- 1. REST API → JWT 검증
- 2. 프론트엔드 → REST API
- 3. REST API → MySQL
- 4. REST API → S3
원본 Mermaid 보기
flowchart LR
subgraph Client
FE[프론트엔드]
end
subgraph Backend["Spring Boot"]
API[REST API]
JWT[JWT 검증]
API --> JWT
end
subgraph Data
MySQL[(MySQL)]
S3[(S3)]
end
FE --> API
API --> MySQL
API --> S3ERD
전체 ERD▼
erd
전체 ERD
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
users
- bigint
- user_id
- PK
- varchar
- username
- UK
- varchar
- nickname
- UK
- varchar
- UK
- varchar
- role
album
- bigint
- album_id
- PK
- bigint
- user_id
- FK
- varchar
- title
- varchar
- album_color
- boolean
- visibility
- varchar
- sticker_url
letter
- bigint
- letter_id
- PK
- bigint
- album_id
- FK
- varchar
- letter_title
- text
- content
- datetime
- created_at
- boolean
- is_anonymous
+ 1개 필드
photo
- bigint
- photo_id
- PK
- bigint
- letter_id
- FK
- varchar
- url
- varchar
- comment
- varchar
- sticker_url
관계 요약
- #1 users ||--o{ album · 소유
- #2 album ||--o{ letter · 포함
- #3 letter ||--o{ photo · 포함
원본 Mermaid 보기
erDiagram
users ||--o{ album : "소유"
album ||--o{ letter : "포함"
letter ||--o{ photo : "포함"
users {
bigint user_id PK
varchar username UK
varchar nickname UK
varchar email UK
varchar role
}
album {
bigint album_id PK
bigint user_id FK
varchar title
varchar album_color
boolean visibility
varchar sticker_url
}
letter {
bigint letter_id PK
bigint album_id FK
varchar letter_title
text content
datetime created_at
boolean is_anonymous
varchar letter_color
}
photo {
bigint photo_id PK
bigint letter_id FK
varchar url
varchar comment
varchar sticker_url
}시퀀스 다이어그램
로그인▼
sequence
로그인
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
Participants
- 1요청
C → API
POST /api/auth/login
- 2검증
API → US
authenticateUser
- 3저장
US → UR
findByUsername
- 4alt 성공
control
alt 성공
조건: alt 성공
- 5검증
API → JWT
createToken
조건: alt 성공
- 6검증
API → C
200 { token }
조건: alt 성공
- 7else 실패
control
else 실패
조건: else 실패
- 8else 실패
API → C
401
조건: else 실패
전체 메시지 상세
| Step | From → To | Message | Condition |
|---|---|---|---|
| 1 | C → API | POST /api/auth/login | - |
| 2 | API → US | authenticateUser | - |
| 3 | US → UR | findByUsername | - |
| 4 | control | alt 성공 | alt 성공 |
| 5 | API → JWT | createToken | alt 성공 |
| 6 | API → C | 200 { token } | alt 성공 |
| 7 | control | else 실패 | else 실패 |
| 8 | API → C | 401 | else 실패 |
원본 Mermaid 보기
sequenceDiagram
C->>API: POST /api/auth/login
API->>US: authenticateUser
US->>UR: findByUsername
alt 성공
API->>JWT: createToken
API-->>C: 200 { token }
else 실패
API-->>C: 401
end앨범 생성▼
sequence
앨범 생성
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
Participants
- 1요청
C → API
POST /api/albums/create
- 2처리
API → AS
hasAlbum, createAlbum
- 3저장
AS → AR
save
- 4처리
API → C
201 { Album }
전체 메시지 상세
| Step | From → To | Message | Condition |
|---|---|---|---|
| 1 | C → API | POST /api/albums/create | - |
| 2 | API → AS | hasAlbum, createAlbum | - |
| 3 | AS → AR | save | - |
| 4 | API → C | 201 { Album } | - |
원본 Mermaid 보기
sequenceDiagram
C->>API: POST /api/albums/create
API->>AS: hasAlbum, createAlbum
AS->>AR: save
API-->>C: 201 { Album }편지 작성▼
sequence
편지 작성
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
Participants
- 1요청
C → API
POST /api/albums/{id}/create
- 2처리
API → LS
createLetter
- 3저장
LS → LR
save
- 4처리
API → C
201 { Letter }
전체 메시지 상세
| Step | From → To | Message | Condition |
|---|---|---|---|
| 1 | C → API | POST /api/albums/{id}/create | - |
| 2 | API → LS | createLetter | - |
| 3 | LS → LR | save | - |
| 4 | API → C | 201 { Letter } | - |
원본 Mermaid 보기
sequenceDiagram
C->>API: POST /api/albums/{id}/create
API->>LS: createLetter
LS->>LR: save
API-->>C: 201 { Letter }사진 업로드▼
sequence
사진 업로드
Mermaid 원본을 파싱해 텍스트는 DOM으로 렌더링합니다.
Participants
- 1요청
C → API
POST /api/letters/{id}/photos
- 2처리
API → PS
addPhoto
- 3처리
PS → S3
uploadFile
- 4저장
PS → PR
save
- 5처리
API → C
201 { Photo }
전체 메시지 상세
| Step | From → To | Message | Condition |
|---|---|---|---|
| 1 | C → API | POST /api/letters/{id}/photos | - |
| 2 | API → PS | addPhoto | - |
| 3 | PS → S3 | uploadFile | - |
| 4 | PS → PR | save | - |
| 5 | API → C | 201 { Photo } | - |
원본 Mermaid 보기
sequenceDiagram
C->>API: POST /api/letters/{id}/photos
API->>PS: addPhoto
PS->>S3: uploadFile
PS->>PR: save
API-->>C: 201 { Photo }