신입 때는 “기능이 동작하면 끝”으로 보이지만, 실무에서는 기능은 금방 늘고 요구사항은 계속 바뀝니다.
초반에 확장성을 염두에 두지 않으면, 일정이 촉박할수록 코드가 꼬이고 장애/성능 이슈가 누적됩니다.
여기서 말하는 확장성은 “처음부터 거대한 아키텍처”가 아닙니다. 변화가 와도 수정 범위를 최소화하고, 트래픽이 늘어도 대응 가능한 구조를 만드는 능력에 가깝습니다.
확장성은 크게 2가지로 나눠서 보는 게 좋습니다.
(1) 시스템 확장성: 트래픽/데이터가 늘어도 서비스가 버티는가 (성능/가용성/장애 대응)
(2) 설계 확장성: 기능/요구사항이 늘어도 개발이 버티는가 (유지보수/변경 비용/테스트 용이성)
“확장성은 성능 튜닝만이 아니라,
변경 비용을 제어하는 설계까지 포함한다.”
💡 TIP / 초보가 가장 빨리 체감하는 포인트
“확장성”을 처음 체감하는 순간은 보통 에러/지연/장애가 발생했을 때입니다.
그래서 처음부터 거대한 구조를 만들기보다, 타임아웃/재시도/로그/지표 같은 “기본 체력”을 먼저 깔아두는 것이 실전에서 가장 효율이 좋습니다.
결제는 트래픽이 늘수록 실패도 같이 늘고(네트워크 지연/타임아웃), 장애가 나면 매출에 직접 영향이 갑니다. 그래서 결제 연동은 확장성 설계의 종합 문제처럼 다뤄집니다.
확장성을 고려한 구현은 보통 “추상화 + 실패 처리 + 멱등성 + 관측”이 같이 들어갑니다.
// 예시: 결제 연동의 확장성 포인트(구현은 생략, 구조만 보기)
interface PaymentGateway {
PaymentResult pay(String idempotencyKey, Money amount);
}
class PaymentService {
private final PaymentGateway gateway;
private final PaymentRepository repo;
public PaymentService(PaymentGateway gateway, PaymentRepository repo) {
this.gateway = gateway;
this.repo = repo;
}
public PaymentResult requestPayment(String orderId, Money amount) {
// 1) 멱등성 키(중복 요청 방지): "같은 요청이면 같은 결과"를 보장
String key = "pay:" + orderId;
// 2) 중복 처리 방지(저장소 기반): 이미 결제 결과가 있으면 그대로 반환
PaymentResult cached = repo.findResultByKey(key);
if (cached != null) return cached;
// 3) 외부 연동 (여기서 timeout/retry/circuit breaker 같은 전략이 붙는다)
PaymentResult result = gateway.pay(key, amount);
// 4) 결과 저장: 재시도/중복 호출에서도 결과 일관성을 유지
repo.saveResult(key, result);
return result;
}
}
👍 GOOD (확장성 있는 결정)
- 변경 가능성이 큰 지점을 경계/계약으로 분리
- 실패를 전제로 타임아웃/재시도/관측을 기본 탑재
- 핵심 경로의 병목(DB/외부 I/O)을 먼저 측정
- 테스트 가능한 구조로 만들어 배포 위험을 낮춤
👎 BAD (확장성 깨지는 결정)
- 구현 디테일(DB/벤더)이 서비스 전역에 퍼짐
- 실패 처리가 없고 “일단 성공”만 가정
- 로그/지표가 부족해 장애 원인 파악이 느림
- 핵심 로직이 테스트 없이 배포에 의존
✅ 핵심 요약
- ✔️ 확장성은 트래픽뿐 아니라 기능 증가까지 포함해 성능/안정성/개발 속도가 무너지지 않게 하는 설계다.
- ✔️ 실무 핵심 축은 데이터 흐름, 실패 처리, 코드 구조, 관측 가능성 4가지다.
- ✔️ 처음부터 거대하게 만들기보다, 확장 가능한 방향으로 결정하고 병목과 실패를 먼저 관리하는 게 현실적인 접근이다.