diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e048eb8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,231 @@
+# TrainUs Backend
+> 프로그래머스 클라우드 기반 백엔드 데브코스 최종 프로젝트 최우수상 수상 프로젝트
+
+TrainUs는 지역 기반으로 운동 메이트와 트레이너를 찾고 레슨 개설부터 신청, 결제, 리뷰까지 연결하는 운동 클래스 매칭 플랫폼입니다.
+
+## 사용 스택
+
+### Backend
+
+
+
+
+
+
+### Database / Cache
+
+
+
+
+
+### Infra / CI/CD
+
+
+
+
+
+### Monitoring / Test
+
+
+
+
+
+
+## 프로젝트 소개
+
+운동을 꾸준히 하고 싶어도 함께 운동할 사람을 찾기 어렵거나 지역 기반으로 신뢰할 수 있는 트레이너와 레슨을 찾기 어려운 문제가 있습니다.
+
+TrainUs는 사용자가 자신의 지역을 기준으로 운동 메이트 또는 트레이너를 찾고 레슨 개설과 신청, 결제, 후기 작성까지 하나의 흐름으로 이용할 수 있도록 설계한 서비스입니다.
+
+### 주요 목표
+
+- 혼자 하기 어려운 운동을 함께할 운동 메이트를 지역 기반으로 찾을 수 있도록 합니다.
+- 축구, 배드민턴 등 다인원 운동의 참여자를 쉽게 모집할 수 있도록 합니다.
+- 무료/유료 운동 레슨을 탐색하고 신청할 수 있는 흐름을 제공합니다.
+- 리뷰, 평점, 랭킹을 통해 트레이너와 레슨에 대한 신뢰도 판단 기준을 제공합니다.
+- 쿠폰과 결제를 통해 유료 레슨 신청 흐름을 확장할 수 있도록 합니다.
+
+### 개선 완료 목표
+
+- 선착순 레슨 신청처럼 순간 트래픽이 몰리는 시나리오를 Redis Stream 기반 비동기 처리 구조로 고도화했습니다.
+- 법정동 중심 검색을 사용자 좌표 기반 반경 검색과 가까운 순 정렬까지 확장했습니다.
+- API 서버와 Consumer 서버를 분리해 요청 접수와 DB 반영 작업의 리소스 경합을 줄였습니다.
+
+## 팀 구성
+
+| 구분 | 내용 |
+| --- | --- |
+| 팀명 | 3성 |
+| 협업 방식 | WBS 기반 일정 관리, API 명세 공유, Daily Scrum, PR 리뷰, 피어 리뷰어 지정 |
+
+
+
+ 항목 |
+ 지민혁 |
+ 김태호 |
+ 임창인 |
+ 김지은 |
+ 나상연 |
+
+
+ | 프로필 |
+  |
+  |
+  |
+  |
+  |
+
+
+ | 역할 |
+ PO, BE |
+ BE 팀장 |
+ BE |
+ BE |
+ BE |
+
+
+ | 주요 담당 영역 |
+ 유저 레슨 CI/CD 검색/레슨 선착순 고도화 |
+ 유저 쿠폰 CI/CD 쿠폰 선착순 고도화 |
+ 로그인/회원가입 프로필 랭킹 |
+ 강사용 레슨 관리자 쿠폰 |
+ 댓글 리뷰 Toss Payments 결제 |
+
+
+
+## 주요 기능
+
+### 회원 및 프로필
+
+- 이메일 인증 기반 회원가입과 로그인
+- JWT 기반 인증
+- 내 프로필 조회/수정
+- 다른 사용자 프로필 조회
+
+### 레슨
+
+- 레슨 생성, 수정, 삭제
+- 레슨 상세 조회
+- 지역, 키워드, 카테고리 기반 검색
+- 개설자가 신청자를 승인/거절하는 수락제 레슨 관리
+
+**고도화/개선 사항:**
+
+- 기존 법정동 기반 검색을 사용자 좌표 기반 반경 검색으로 확장했습니다.
+- PostGIS `geography` 타입과 GiST 인덱스를 활용해 반경 검색과 가까운 순 정렬을 지원했습니다.
+
+### 레슨 신청
+
+- 수락제 레슨 신청
+- 선착순 레슨 신청
+- 비동기 신청 상태 조회
+- 내 신청 목록 조회
+
+**고도화/개선 사항:**
+
+- 정원이 제한된 선착순 레슨에 다수의 사용자가 동시에 신청하는 상황을 병목 시나리오로 정의했습니다.
+- API 서버는 Redis에서 중복 신청과 재고를 먼저 확인한 뒤 접수 ID를 반환하고 DB 반영은 비동기 처리로 분리했습니다.
+- Consumer 서버가 Redis Stream 메시지를 batch 단위로 DB에 반영하고 사용자는 접수 ID로 처리 상태를 조회합니다.
+
+
+
+### 결제 및 쿠폰
+
+- Toss Payments 결제 승인/취소
+- 쿠폰 발급, 적용, 복원
+- 쿠폰 상태 스케줄링
+
+### 커뮤니티
+
+- 댓글/대댓글
+- 리뷰와 평점
+- 리뷰와 평점 기반 트레이너 랭킹
+
+## 주요 API
+
+| 기능 | Method | Endpoint |
+| --- | --- | --- |
+| 레슨 검색 | GET | `/api/v1/lessons` |
+| 위치 기반 레슨 검색 | GET | `/api/v1/lessons/search/nearby` |
+| 레슨 상세 조회 | GET | `/api/v1/lessons/{lessonId}` |
+| 수락제 레슨 신청 | POST | `/api/v1/lessons/{lessonId}/applications/approval` |
+| 선착순 레슨 신청 | POST | `/api/v1/lessons/{lessonId}/applications/open-run` |
+| 비동기 신청 상태 조회 | GET | `/api/v1/lessons/apply/status/{requestId}` |
+| 레슨 신청 취소 | DELETE | `/api/v1/lessons/{lessonId}/application` |
+| 내 신청 목록 조회 | GET | `/api/v1/lessons/my-applications` |
+| 개설 레슨 신청자 조회 | GET | `/api/v1/lessons/{lessonId}/applications` |
+| 레슨 신청 승인/거절 | POST | `/api/v1/lessons/applications/{lessonApplicationId}` |
+| 랭킹 조회 | GET | `/api/v1/rankings` |
+| 결제 승인 | POST | `/api/v1/payments/confirm` |
+| S3 업로드 URL 발급 | GET | `/api/v1/s3/posturl` |
+
+Swagger 설정이 포함되어 있어 실행 환경에서 API 문서를 확인할 수 있습니다.
+
+## 시스템 구조
+
+
+
+운영 환경에서는 API 서버와 Consumer 서버를 역할별 profile로 분리합니다.
+
+| Role | App Port | Actuator Port | 주요 책임 |
+| --- | ---: | ---: | --- |
+| `api` | 8080 | 8082 | HTTP 요청 수신, 인증, 레슨 검색, 신청 접수 |
+| `consumer` | 8081 | 8083 | Redis Stream 소비, DB 반영, 보정 스케줄링 |
+
+Redis는 목적에 따라 Core Redis와 MQ Redis로 분리해 사용합니다.
+
+| Redis | 기본 포트 | 용도 |
+| --- | ---: | --- |
+| `redis-core` | 6379 | 재고, 중복 신청, 대기열, 분산 락 |
+| `redis-mq` | 6380 | Redis Stream, 신청 처리 상태 |
+
+## 배포 자동화
+
+배포는 GitHub Actions, S3, CodeDeploy를 사용합니다.
+GitHub Actions가 빌드 산출물을 S3에 업로드하고 CodeDeploy가 EC2에서 배포 hook을 실행하는 Pull 방식으로 구성했습니다.
+
+```text
+develop push
+ -> GitHub Actions build
+ -> deploy.zip 생성
+ -> S3 업로드
+ -> CodeDeploy 배포 생성
+ -> EC2에서 appspec hook 실행
+```
+
+CodeDeploy hook:
+
+| Script | 역할 |
+| --- | --- |
+| `scripts/setup.sh` | 배포 디렉터리 준비, S3에서 환경변수 파일 동기화 |
+| `scripts/stop.sh` | 기존 애플리케이션 종료 |
+| `scripts/start.sh` | 서버 역할에 맞는 Spring profile로 애플리케이션 실행 |
+
+## 협업 방식
+
+- Notion 기반 WBS와 작업 보드로 기능 단위 진행 상황을 관리했습니다.
+- API 명세를 공유하며 프론트엔드와 요청/응답 형식을 조율했습니다.
+- Git commit convention, code convention, class naming convention을 사전에 정리했습니다.
+- PR 리뷰와 피어 리뷰어 지정 방식을 통해 코드 변경 사항을 검토했습니다.
+- Daily Scrum과 회의록을 통해 이슈와 의사결정 내용을 기록했습니다.
+
+## 주요 도메인 구조
+
+```text
+src/main/java/com/threestar/trainus
+ ├── domain
+ │ ├── user # 회원, 이메일 인증
+ │ ├── profile # 프로필
+ │ ├── lesson # 레슨 생성/검색/신청/선착순 처리
+ │ ├── coupon # 쿠폰 생성/발급/사용
+ │ ├── payment # Toss 결제/취소
+ │ ├── review # 리뷰
+ │ ├── comment # 댓글/대댓글
+ │ ├── ranking # 랭킹
+ │ └── file # S3 URL 발급
+ └── global
+ ├── config # Security, Redis, S3, Swagger, Scheduler
+ ├── resolver # @LoginUser
+ ├── aop # Redisson distributed lock
+ └── exception # 공통 예외 처리
+```
diff --git a/docs/architecture/trainus-architecture-async-processing.png b/docs/architecture/trainus-architecture-async-processing.png
new file mode 100644
index 0000000..318d33e
Binary files /dev/null and b/docs/architecture/trainus-architecture-async-processing.png differ
diff --git a/docs/architecture/trainus-architecture-async-processing.py b/docs/architecture/trainus-architecture-async-processing.py
new file mode 100644
index 0000000..7b8dba3
--- /dev/null
+++ b/docs/architecture/trainus-architecture-async-processing.py
@@ -0,0 +1,49 @@
+from diagrams import Cluster, Diagram, Edge
+from diagrams.aws.compute import EC2
+from diagrams.aws.network import ALB
+from diagrams.onprem.database import PostgreSQL
+from diagrams.onprem.inmemory import Redis
+
+
+graph_attr = {
+ "fontsize": "18",
+ "pad": "0.6",
+ "splines": "polyline",
+ "nodesep": "1.0",
+ "ranksep": "1.3",
+}
+
+
+with Diagram(
+ "TrainUs Architecture - Async Processing",
+ show=False,
+ filename="trainus-architecture-async-processing",
+ outformat="png",
+ direction="LR",
+ graph_attr=graph_attr,
+):
+ client = ALB("Client / ALB")
+
+ with Cluster("API Server"):
+ api = EC2("API Server\n(api profile)")
+
+ with Cluster("Redis Core"):
+ core = Redis("Redis Core\nstock / duplicate")
+
+ with Cluster("Consumer Server - Admission"):
+ admission = EC2("Consumer Server\n(admission dequeue)")
+ stream = Redis("Redis Stream\nlesson:apply:stream")
+
+ with Cluster("Consumer Server"):
+ consumer = EC2("Consumer Server\n(batch insert)")
+
+ with Cluster("Database"):
+ db = PostgreSQL("PostgreSQL / PostGIS")
+
+ client >> Edge(label="apply request") >> api
+ api >> Edge(label="duplicate / stock check") >> core
+ core >> Edge(label="dequeue requestIds") >> admission
+ admission >> Edge(label="SET PROCESSING + XADD") >> stream
+ stream >> Edge(label="XREADGROUP batch") >> consumer
+ consumer >> Edge(label="batch insert") >> db
+ consumer >> Edge(label="XACK / XDEL", dir="back") >> stream
diff --git a/docs/architecture/trainus-architecture.png b/docs/architecture/trainus-architecture.png
new file mode 100644
index 0000000..d02ae54
Binary files /dev/null and b/docs/architecture/trainus-architecture.png differ
diff --git a/docs/architecture/trainus-architecture.py b/docs/architecture/trainus-architecture.py
new file mode 100644
index 0000000..fda1407
--- /dev/null
+++ b/docs/architecture/trainus-architecture.py
@@ -0,0 +1,149 @@
+from diagrams import Cluster, Diagram, Edge, Node
+from diagrams.aws.compute import EC2
+from diagrams.aws.devtools import Codedeploy
+from diagrams.aws.network import ALB
+from diagrams.aws.storage import S3
+from diagrams.onprem.ci import GithubActions
+from diagrams.onprem.client import Users
+from diagrams.onprem.database import PostgreSQL
+from diagrams.onprem.inmemory import Redis
+from diagrams.onprem.monitoring import Grafana, Prometheus
+from diagrams.onprem.vcs import Github
+from diagrams.programming.framework import Spring
+
+graph_attr = {
+ "fontsize": "24",
+ "pad": "1.0",
+ "splines": "line",
+ "nodesep": "1.5",
+ "ranksep": "1.3",
+ "margin": "0.5",
+ "dpi": "170",
+}
+
+node_attr = {
+ "fontsize": "18",
+ "margin": "0.14",
+}
+
+edge_attr = {
+ "fontsize": "15",
+}
+
+cluster_attr = {
+ "margin": "20",
+ "fontsize": "18",
+}
+
+instance_cluster_attr = {
+ "margin": "8",
+ "fontsize": "15",
+}
+
+asg_cluster_attr = {
+ "margin": "15",
+ "fontsize": "18",
+}
+
+with Diagram(
+ "TrainUs Architecture",
+ show=False,
+ filename="trainus-architecture",
+ outformat="png",
+ direction="LR",
+ graph_attr=graph_attr,
+ node_attr=node_attr,
+ edge_attr=edge_attr,
+):
+ with Cluster("CI/CD", graph_attr=cluster_attr):
+ github = Github("GitHub")
+ actions = GithubActions("Actions")
+ artifact = S3("S3\nartifact/env")
+ codedeploy = Codedeploy("CodeDeploy")
+ github >> actions >> artifact >> codedeploy
+
+ client = Users("Client\nJMeter")
+ alb = ALB("ALB")
+
+ with Cluster("API Auto Scaling Group", graph_attr=asg_cluster_attr):
+ with Cluster("Instance 1", graph_attr=instance_cluster_attr):
+ api_ec2_1 = EC2("EC2")
+ api_app_1 = Spring("API\nSpring Boot")
+ api_ec2_1 - api_app_1
+
+ with Cluster("Instance n", graph_attr=instance_cluster_attr):
+ api_ec2_n = Node(
+ "EC2",
+ shape="box",
+ style="rounded,dashed",
+ width="1.42",
+ height="0.28",
+ fixedsize="true",
+ fontsize="15",
+ )
+ api_app_n = Node(
+ "Spring Boot",
+ shape="box",
+ style="rounded,dashed",
+ width="1.42",
+ height="0.28",
+ fixedsize="true",
+ fontsize="15",
+ )
+ api_ec2_n - api_app_n
+
+ with Cluster("TrainUs-Redis-Server", graph_attr=cluster_attr):
+ redis_core = Redis("redis-core\nstock/waiting")
+ redis_mq = Redis("redis-mq\nstream/status")
+
+ with Cluster("TrainUs-Data-Server", graph_attr=cluster_attr):
+ postgres = PostgreSQL("PostgreSQL\n(PostGIS)")
+
+ with Cluster("Consumer Auto Scaling Group", graph_attr=asg_cluster_attr):
+ with Cluster("Instance 1", graph_attr=instance_cluster_attr):
+ consumer_ec2_1 = EC2("EC2")
+ consumer_app_1 = Spring("Consumer\nSpring Boot")
+ consumer_ec2_1 - consumer_app_1
+
+ with Cluster("Instance n", graph_attr=instance_cluster_attr):
+ consumer_ec2_n = Node(
+ "EC2",
+ shape="box",
+ style="rounded,dashed",
+ width="1.42",
+ height="0.28",
+ fixedsize="true",
+ fontsize="15",
+ )
+ consumer_app_n = Node(
+ "Spring Boot",
+ shape="box",
+ style="rounded,dashed",
+ width="1.42",
+ height="0.28",
+ fixedsize="true",
+ fontsize="15",
+ )
+ consumer_ec2_n - consumer_app_n
+
+ with Cluster("Monitoring", graph_attr=cluster_attr):
+ prometheus = Prometheus("Prometheus")
+ grafana = Grafana("Grafana")
+ prometheus << grafana
+
+ client >> Edge(minlen="2") >> alb
+ alb >> Edge(minlen="2") >> api_ec2_1
+ alb >> Edge(minlen="2") >> api_ec2_n
+
+ api_app_1 >> Edge(label="apply request") >> redis_core
+ api_app_1 >> Edge(label="poll status") >> redis_mq
+ api_app_1 >> Edge(label="read/write", color="darkgreen") >> postgres
+
+ redis_core >> Edge(label="admit") >> consumer_app_1
+ consumer_app_1 >> Edge(label="XREAD / ACK") >> redis_mq
+ consumer_app_1 >> Edge(label="batch insert") >> postgres
+
+ prometheus << Edge(label="metrics", style="dashed") << [api_app_1, consumer_app_1]
+
+ codedeploy >> Edge(label="deploy api", color="blue", style="dashed", constraint="false") >> api_ec2_1
+ codedeploy >> Edge(label="deploy consumer", color="blue", style="dashed", constraint="false") >> consumer_ec2_1