Kotlin Spring Boot 입문 | 30분 만에 REST API 만들기 [초보자용]
이 글의 핵심
Kotlin Spring Boot 완전 초보자를 위한 입문 튜토리얼. Java보다 간결한 문법으로 REST API 만들기. 설치부터 배포까지 30분이면 충분합니다.
들어가며: Kotlin으로 더 쉽게 시작하기
“요즘 채용 공고 보니까 Kotlin + Spring Boot를 많이 쓰던데요?”
맞습니다. Kotlin은 최근 백엔드 개발에서 빠르게 성장하고 있습니다:
- Coupang, Kakao, Toss, 당근마켓 등 주요 기업 사용
- Java 대비 30-40% 적은 코드량
- Null 안전성으로 NullPointerException 방지
- Google이 Android 공식 언어로 지정
이 튜토리얼은 완전 초보자를 위한 가이드입니다. 설치 → 프로젝트 생성 → REST API → 실행까지 30분이면 충분합니다.
Java vs Kotlin 코드 비교
Java (장황함):
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Kotlin (간결함):
data class User(
val id: Long,
var name: String
)
똑같은 기능이지만 Kotlin은 2줄!
이 튜토리얼에서 만들 것
간단한 북마크 관리 REST API:
GET /api/bookmarks- 북마크 목록 조회POST /api/bookmarks- 북마크 추가PUT /api/bookmarks/{id}- 북마크 수정DELETE /api/bookmarks/{id}- 북마크 삭제
사전 요구사항
- Java 17 이상 설치 (Kotlin은 JVM에서 실행)
- IDE: IntelliJ IDEA Community (무료, Kotlin 기본 지원)
1단계: 환경 설정 (3분)
Java 설치 확인
# 터미널에서 확인
java -version
# 출력 예시:
# openjdk version "17.0.2"
Java가 없다면:
- Windows: https://adoptium.net/ → JDK 17 다운로드
- Mac:
brew install openjdk@17 - Linux:
sudo apt install openjdk-17-jdk
IntelliJ IDEA 설치
https://www.jetbrains.com/idea/download/
- Community Edition (무료) 다운로드
- Kotlin 플러그인 자동 포함
2단계: 프로젝트 생성 (5분)
방법 1: Spring Initializr (웹)
- https://start.spring.io/ 접속
- 다음과 같이 설정:
- Project: Gradle - Kotlin
- Language: Kotlin
- Spring Boot: 3.2.x (최신 안정 버전)
- Packaging: Jar
- Java: 17
- Project Metadata:
- Group:
com.example - Artifact:
demo - Name:
demo - Package name:
com.example.demo
- Group:
- Dependencies 추가:
- Spring Web
- Spring Data JPA
- H2 Database
- GENERATE 클릭 → ZIP 다운로드
- 압축 해제 후 IntelliJ로 열기
방법 2: IntelliJ IDEA (권장)
- New Project → Spring Initializr
- 위와 동일하게 설정
- Create
3단계: 첫 REST API 만들기 (10분)
프로젝트 구조
src/main/kotlin/com/example/demo/
├── DemoApplication.kt # 메인
├── controller/
│ └── BookmarkController.kt # REST API
├── model/
│ └── Bookmark.kt # 데이터 모델
├── repository/
│ └── BookmarkRepository.kt # DB 접근
└── service/
└── BookmarkService.kt # 비즈니스 로직
1) 데이터 모델 (Bookmark.kt)
src/main/kotlin/com/example/demo/model/Bookmark.kt:
package com.example.demo.model
import jakarta.persistence.*
@Entity
@Table(name = "bookmarks")
data class Bookmark(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
var title: String,
var url: String,
var description: String? = null
)
Java와 비교:
data class: getter/setter/equals/hashCode 자동 생성val: 불변 (Java의final)var: 가변String?: Null 허용 타입
2) Repository (BookmarkRepository.kt)
src/main/kotlin/com/example/demo/repository/BookmarkRepository.kt:
package com.example.demo.repository
import com.example.demo.model.Bookmark
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface BookmarkRepository : JpaRepository<Bookmark, Long>
한 줄로 끝! Spring Data JPA가 자동으로 구현합니다.
3) Service (BookmarkService.kt)
src/main/kotlin/com/example/demo/service/BookmarkService.kt:
package com.example.demo.service
import com.example.demo.model.Bookmark
import com.example.demo.repository.BookmarkRepository
import org.springframework.stereotype.Service
@Service
class BookmarkService(
private val repository: BookmarkRepository
) {
fun getAllBookmarks(): List<Bookmark> = repository.findAll()
fun getBookmarkById(id: Long): Bookmark? =
repository.findById(id).orElse(null)
fun createBookmark(bookmark: Bookmark): Bookmark =
repository.save(bookmark)
fun updateBookmark(id: Long, bookmarkDetails: Bookmark): Bookmark {
val bookmark = repository.findById(id)
.orElseThrow { RuntimeException("Bookmark not found") }
bookmark.title = bookmarkDetails.title
bookmark.url = bookmarkDetails.url
bookmark.description = bookmarkDetails.description
return repository.save(bookmark)
}
fun deleteBookmark(id: Long) = repository.deleteById(id)
}
Kotlin의 장점:
- 생성자 주입: 파라미터에
val만 붙이면 자동으로 필드 선언 + 초기화 - 표현식 함수:
fun get() = repository.findAll()(중괄호 생략) - Null 안전:
Bookmark?타입으로 명시적 처리
4) Controller (BookmarkController.kt)
src/main/kotlin/com/example/demo/controller/BookmarkController.kt:
package com.example.demo.controller
import com.example.demo.model.Bookmark
import com.example.demo.service.BookmarkService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/bookmarks")
class BookmarkController(
private val service: BookmarkService
) {
// GET /api/bookmarks - 전체 조회
@GetMapping
fun getAllBookmarks(): List<Bookmark> = service.getAllBookmarks()
// GET /api/bookmarks/{id} - 단건 조회
@GetMapping("/{id}")
fun getBookmarkById(@PathVariable id: Long): ResponseEntity<Bookmark> {
val bookmark = service.getBookmarkById(id)
return if (bookmark != null) {
ResponseEntity.ok(bookmark)
} else {
ResponseEntity.notFound().build()
}
}
// POST /api/bookmarks - 생성
@PostMapping
fun createBookmark(@RequestBody bookmark: Bookmark): Bookmark =
service.createBookmark(bookmark)
// PUT /api/bookmarks/{id} - 수정
@PutMapping("/{id}")
fun updateBookmark(
@PathVariable id: Long,
@RequestBody bookmarkDetails: Bookmark
): ResponseEntity<Bookmark> {
return try {
val updated = service.updateBookmark(id, bookmarkDetails)
ResponseEntity.ok(updated)
} catch (e: RuntimeException) {
ResponseEntity.notFound().build()
}
}
// DELETE /api/bookmarks/{id} - 삭제
@DeleteMapping("/{id}")
fun deleteBookmark(@PathVariable id: Long): ResponseEntity<Void> {
service.deleteBookmark(id)
return ResponseEntity.ok().build()
}
}
Java 대비 코드량 절반!
5) application.properties 설정
src/main/resources/application.properties:
# 서버 포트
server.port=8080
# H2 데이터베이스 (개발용)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA 설정
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
# H2 콘솔 활성화
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
4단계: 실행 및 테스트 (7분)
서버 실행
# Gradle
./gradlew bootRun
# 또는 IntelliJ에서 DemoApplication.kt 실행 (▶️ 버튼)
출력:
Tomcat started on port 8080
Started DemoApplicationKt in 2.123 seconds
API 테스트 (curl)
# 1. 북마크 추가
curl -X POST http://localhost:8080/api/bookmarks \
-H "Content-Type: application/json" \
-d '{
"title": "Kotlin 공식 문서",
"url": "https://kotlinlang.org",
"description": "Kotlin 배우기"
}'
# 응답:
# {"id":1,"title":"Kotlin 공식 문서","url":"https://kotlinlang.org","description":"Kotlin 배우기"}
# 2. 전체 조회
curl http://localhost:8080/api/bookmarks
# 3. 단건 조회
curl http://localhost:8080/api/bookmarks/1
# 4. 수정
curl -X PUT http://localhost:8080/api/bookmarks/1 \
-H "Content-Type: application/json" \
-d '{
"title": "Kotlin 공식 문서 (수정됨)",
"url": "https://kotlinlang.org/docs",
"description": "깊이 배우기"
}'
# 5. 삭제
curl -X DELETE http://localhost:8080/api/bookmarks/1
브라우저에서 테스트
-
H2 콘솔 접속: http://localhost:8080/h2-console
- JDBC URL:
jdbc:h2:mem:testdb - Username:
sa - Password: (빈칸)
- JDBC URL:
-
SQL 쿼리:
SELECT * FROM bookmarks;
INSERT INTO bookmarks (title, url, description)
VALUES ('Google', 'https://google.com', 'Search engine');
5단계: Kotlin의 핵심 문법 (5분)
1) data class (자동 생성)
// Kotlin: 이것만으로 getter/setter/equals/hashCode/toString 자동 생성
data class User(val id: Long, var name: String)
// Java 동등 코드: 50줄+
2) Null 안전성
// Null 불가 타입
val name: String = "홍길동"
// name = null // 컴파일 에러!
// Null 가능 타입
val description: String? = null
println(description?.length) // Safe call: null이면 null 반환
// Elvis 연산자
val length = description?.length ?: 0 // null이면 0
Java에서는 항상 NullPointerException 위험!
3) 표현식 함수
// 한 줄 함수
fun add(a: Int, b: Int) = a + b
// 조건식도 표현식
fun max(a: Int, b: Int) = if (a > b) a else b
4) 생성자 주입 (간결함)
// Kotlin: 한 줄
class TodoController(private val service: TodoService)
// Java: 여러 줄
public class TodoController {
private final TodoService service;
public TodoController(TodoService service) {
this.service = service;
}
}
5) 스마트 캐스팅
fun process(value: Any) {
if (value is String) {
println(value.length) // 자동으로 String으로 캐스팅!
}
}
실무 팁
자주 하는 실수
1) data class에서 var 남용
// ❌ 나쁜 예: 모두 가변
data class User(var id: Long, var name: String)
// ✅ 좋은 예: ID는 불변
data class User(val id: Long, var name: String)
2) !! 연산자 남용 (Null 강제 언래핑)
// ❌ 위험: NullPointerException 가능
val length = description!!.length
// ✅ 안전: Safe call 또는 Elvis
val length = description?.length ?: 0
3) lateinit 잘못 사용
// ❌ nullable한 변수에는 사용 불가
lateinit var name: String? // 컴파일 에러
// ✅ Non-null 타입에만 사용
lateinit var name: String
디버깅 팁
# application.properties
logging.level.org.springframework=DEBUG
logging.level.com.example=DEBUG
# SQL 로그 상세히
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
IntelliJ 단축키
| 단축키 | 기능 |
|---|---|
Ctrl + Shift + K | Kotlin REPL 실행 |
Ctrl + Alt + L | 코드 포맷팅 |
Alt + Enter | Quick fix |
Ctrl + Space | 자동 완성 |
다음 단계: 실무로 가는 길
이 튜토리얼을 마쳤다면 다음을 학습하세요:
필수 학습 (순서대로)
1) Exception 처리
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException::class)
fun handleNotFound(ex: RuntimeException) =
ResponseEntity.status(404).body(ex.message)
}
2) Validation
data class Bookmark(
@field:NotBlank(message = "제목은 필수입니다")
var title: String,
@field:URL(message = "올바른 URL을 입력하세요")
var url: String
)
@PostMapping
fun create(@Valid @RequestBody bookmark: Bookmark) = ...
3) Kotlin Coroutines (비동기)
@Service
class BookmarkService(private val repository: BookmarkRepository) {
suspend fun getAllBookmarks() = withContext(Dispatchers.IO) {
repository.findAll()
}
}
4) 테스트
@SpringBootTest
class BookmarkControllerTest {
@Test
fun `북마크 생성 테스트`() {
// given, when, then
}
}
5) 프로덕션 DB (PostgreSQL)
// build.gradle.kts
dependencies {
runtimeOnly("org.postgresql:postgresql")
}
# application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
학습 로드맵
Kotlin 입문 튜토리얼 (30분)
↓
Kotlin 시리즈 #1-10 (5시간)
↓
Kotlin Coroutines 학습 (2시간)
↓
실전 프로젝트 (1-2주)
↓
포트폴리오 제작
흔한 에러 및 해결
에러 1: unresolved reference: springframework
# build.gradle.kts 확인
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
}
# 의존성 다시 다운로드
./gradlew clean build --refresh-dependencies
에러 2: lateinit property has not been initialized
// ❌ 초기화 전 사용
lateinit var service: BookmarkService
println(service.getAllBookmarks())
// ✅ 생성자 주입 사용
class Controller(private val service: BookmarkService)
에러 3: Type mismatch: inferred type is Long? but Long was expected
// ❌ Nullable을 Non-null에 할당
val id: Long = bookmark.id // bookmark.id는 Long?
// ✅ Null 체크 또는 Elvis
val id: Long = bookmark.id ?: 0L
Java vs Kotlin 실전 비교
엔티티 클래스
Java (34줄):
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
public User() {}
public User(String name, String email) {
this.name = name;
this.email = email;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
@Override
public String toString() { ... }
}
Kotlin (7줄):
@Entity
@Table(name = "users")
data class User(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
var name: String,
var email: String
)
80% 코드량 감소!
실전 프로젝트 아이디어
이제 배운 내용으로 실전 프로젝트를 만들어보세요:
초급:
- 간단한 북마크 관리 앱
- 메모장 API
- URL 단축 서비스
중급:
- JWT 인증이 있는 TODO 앱
- 파일 업로드 서비스
- 실시간 채팅 (Kotlin Coroutines)
고급:
- 마이크로서비스 (Spring Cloud)
- Kafka 이벤트 스트리밍
- Redis 캐싱 + PostgreSQL
빠른 레퍼런스
주요 애노테이션
| 애노테이션 | 용도 | Java와 동일 |
|---|---|---|
@RestController | REST API 컨트롤러 | ✅ |
@GetMapping | GET 요청 | ✅ |
@PostMapping | POST 요청 | ✅ |
@RequestBody | JSON → 객체 | ✅ |
@PathVariable | URL 경로 변수 | ✅ |
@Service | 비즈니스 로직 | ✅ |
@Repository | DB 접근 | ✅ |
@Entity | JPA 엔티티 | ✅ |
Kotlin 고유 문법
// Null 안전
val name: String? = null
println(name?.length ?: 0)
// data class
data class User(val id: Long, var name: String)
// 표현식 함수
fun add(a: Int, b: Int) = a + b
// 생성자 주입
class Service(private val repo: Repository)
// when 표현식 (switch 대체)
when (status) {
200 -> "OK"
404 -> "Not Found"
else -> "Error"
}
Spring Boot 내부와 프로덕션에 가까운 메모
서블릿 스택과 임베디드 Tomcat
Spring Boot Web은 기본적으로 서블릿 컨테이너(임베디드 Tomcat 등) 위에서 동작합니다. HTTP 요청은 커넥터 스레드 풀에서 받고, 디스패처 서블릿이 컨트롤러 매핑으로 넘깁니다. 블로킹 I/O 모델에서는 동시 처리량이 스레드 풀 크기와 직결되므로, 장시간 점유하는 요청이 많으면 스레드 고갈이 납니다. 이때는 WebFlux·비동기 서블릿·외부 워커 큐 같은 아키텍처 변경을 검토합니다.
설정 계층: application.yml과 프로파일
실제 서비스에서는 application-dev.yml, application-prod.yml처럼 프로파일로 DB URL·로그 레벨을 나눕니다. 시크릿은 저장소에 평문으로 두지 않고 환경 변수·시크릿 매니저로 주입합니다.
관측: Actuator와 헬스
운영 환경에서는 spring-boot-starter-actuator로 /health, /metrics를 열고, Kubernetes라면 liveness/readiness 프로브와 연결합니다. 데이터베이스가 끊긴 상태에서도 프로세스는 살아 있을 수 있으므로 readiness에 DB 검사를 넣는지 팀 규약을 정합니다.
트러블슈팅 빠른 표
| 증상 | 점검 |
|---|---|
Whitelabel Error Page 500 | 서버 로그 스택 트레이스, @ControllerAdvice 없을 때 기본 페이지 |
| 포트 충돌 | server.port, 다른 로컬 프로세스 점유 |
| JPA가 테이블을 안 만듦 | ddl-auto, 엔티티 스캔 패키지, DB 권한 |
| CORS 에러 | @CrossOrigin 또는 API 게이트웨이에서 헤더 — 브라우저만의 증상임을 기억 |
마무리
핵심 정리
- Kotlin은 Java보다 30-40% 코드 감소
- data class로 엔티티 한 줄 정의
- Null 안전성으로 NullPointerException 방지
- Spring Boot의 모든 기능 100% 호환
30분 동안 배운 것
- Kotlin + Spring Boot 프로젝트 생성
- REST API 4가지 (GET, POST, PUT, DELETE)
- data class로 엔티티 정의
- JPA로 데이터베이스 연동
- H2 인메모리 DB 사용
Kotlin을 선택해야 하는 이유
Java 대비 장점:
- ✅ 코드량 30-40% 감소
- ✅ Null 안전성 (컴파일 타임 체크)
- ✅ 간결한 문법 (data class, 표현식)
- ✅ Coroutines (비동기 처리)
- ✅ 100% Java 호환
채용 시장:
- Coupang, Kakao, Toss, 당근마켓, 배달의민족
- 스타트업에서 빠르게 도입 중
- Android + 백엔드 동시 개발 가능
축하합니다! 첫 Kotlin Spring Boot API를 만들었습니다. 이제 Kotlin 시리즈 #9 Spring Boot로 넘어가 실무 수준을 익히세요.
관련 글 (내부 링크)
Kotlin과 함께 보면 좋은 백엔드 개발 가이드입니다:
- Spring Boot 입문 튜토리얼 | Java 버전 - Java 버전과 비교
- Kotlin 시리즈 #9 Spring Boot | REST API 서버 - 더 상세한 설명
- Kotlin 시리즈 #1 Kotlin 입문 | 기초 문법 - Kotlin 기초
- Kotlin Coroutine vs Thread 비교 - 비동기 처리
- Java 시리즈 #10 Spring Boot - Java 버전
- Supabase 완벽 가이드 - 백엔드 대안
한 줄 요약: Kotlin은 Java보다 30-40% 적은 코드로 Spring Boot API를 만들 수 있습니다. data class와 Null 안전성으로 생산성이 높아 최근 백엔드 채용에서 주목받고 있습니다.
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「Kotlin Spring Boot 입문 | 30분 만에 REST API 만들기 [초보자용]」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「Kotlin Spring Boot 입문 | 30분 만에 REST API 만들기 [초보자용]」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.