API Gateway 장애가 전체 서비스를 마비시키는 이유: Single Point of Failure 제거 전략

API Gateway 장애가 전체 서비스를 마비시키는 이유: Single Point of Failure 제거 전략

Server Infrastructure

MSA를 도입하면서 API Gateway를 구축했다. 인증, 라우팅, 로깅이 한 곳에서 처리되니 깔끔했다. 그런데 어느 날 Gateway 인스턴스 하나가 메모리 부족으로 죽었다. 30초 만에 전체 서비스가 마비됐다.

분산 시스템을 만들었는데, 정작 입구는 단일 장애점(Single Point of Failure)이었던 것이다.


API Gateway가 SPOF가 되는 순간

API Gateway는 모든 트래픽이 통과하는 관문이다. 이 관문이 막히면 아무리 뒤에 있는 서비스가 건강해도 소용없다.

흔한 SPOF 패턴들:

1. 단일 인스턴스 운영
"트래픽이 적으니까 한 대로 충분해" → 그 한 대가 죽으면 끝

2. Sticky Session 의존
특정 인스턴스에 세션이 고정되면 해당 인스턴스 장애 시 세션 유실

3. 공유 상태 저장
Gateway에 인메모리 캐시나 상태를 저장하면 인스턴스 간 불일치 발생

4. 동기식 외부 의존성
인증 서버가 느려지면 Gateway 전체가 느려짐


해결 전략 1: 다중 인스턴스 + 로드밸런서

Network Architecture

최소 3개 이상의 Gateway 인스턴스를 운영하고, 앞단에 L4/L7 로드밸런서를 배치한다.

# Nginx 로드밸런서 설정 예시
upstream api_gateway {
    least_conn;  # 연결 수 기반 분산
    server gateway-1:8080 weight=5;
    server gateway-2:8080 weight=5;
    server gateway-3:8080 weight=5;
    
    # Health check
    server gateway-1:8080 max_fails=3 fail_timeout=30s;
}

server {
    listen 443 ssl;
    
    location / {
        proxy_pass http://api_gateway;
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
        proxy_next_upstream error timeout http_502 http_503;
    }
}

핵심 설정:

  • least_conn: 연결 수가 적은 서버로 분산
  • max_fails: 3회 실패 시 해당 서버 제외
  • proxy_next_upstream: 실패 시 다음 서버로 자동 재시도

해결 전략 2: Stateless Gateway 설계

Gateway는 상태를 가지면 안 된다. 모든 요청은 어떤 인스턴스로 가도 동일하게 처리되어야 한다.

세션/토큰 처리:

// BAD: 인메모리 세션 저장
Map<String, Session> sessions = new ConcurrentHashMap<>();

// GOOD: Redis 외부 저장소 사용
@Autowired
private RedisTemplate<String, Session> redisTemplate;

public Session getSession(String token) {
    return redisTemplate.opsForValue().get("session:" + token);
}

Rate Limiting:

// BAD: 로컬 카운터
AtomicInteger counter = new AtomicInteger();

// GOOD: Redis 분산 카운터
public boolean isAllowed(String clientId) {
    String key = "ratelimit:" + clientId;
    Long count = redisTemplate.opsForValue().increment(key);
    if (count == 1) {
        redisTemplate.expire(key, 1, TimeUnit.MINUTES);
    }
    return count <= 100;  // 분당 100회 제한
}

해결 전략 3: Circuit Breaker 적용

Gateway가 호출하는 외부 서비스(인증, 권한 등)에 장애가 발생하면 Gateway까지 죽을 수 있다. Circuit Breaker로 장애를 격리해야 한다.

@CircuitBreaker(name = "authService", fallbackMethod = "authFallback")
@TimeLimiter(name = "authService")
public CompletableFuture<AuthResult> authenticate(String token) {
    return CompletableFuture.supplyAsync(() -> 
        authClient.validate(token)
    );
}

// 인증 서버 장애 시 캐시된 결과 사용
private CompletableFuture<AuthResult> authFallback(String token, Exception e) {
    AuthResult cached = authCache.get(token);
    if (cached != null && !cached.isExpired()) {
        return CompletableFuture.completedFuture(cached);
    }
    return CompletableFuture.failedFuture(
        new ServiceUnavailableException("Auth service unavailable")
    );
}

Resilience4j 설정:

resilience4j:
  circuitbreaker:
    instances:
      authService:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 30s
        permittedNumberOfCallsInHalfOpenState: 3
  timelimiter:
    instances:
      authService:
        timeoutDuration: 2s

해결 전략 4: Health Check와 자동 복구

장애 감지가 빨라야 복구도 빠르다. Kubernetes 환경이라면 Probe 설정이 핵심이다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: gateway
        image: api-gateway:latest
        ports:
        - containerPort: 8080
        
        # Liveness: 죽었는지 확인
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
          failureThreshold: 3
        
        # Readiness: 트래픽 받을 준비 됐는지 확인
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
          failureThreshold: 3
        
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"

Health Endpoint 구현:

@RestController
@RequestMapping("/health")
public class HealthController {
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Autowired
    private AuthClient authClient;
    
    // Liveness: 프로세스가 살아있는지만 확인
    @GetMapping("/live")
    public ResponseEntity<String> live() {
        return ResponseEntity.ok("OK");
    }
    
    // Readiness: 의존성까지 확인
    @GetMapping("/ready")
    public ResponseEntity<Map> ready() {
        Map<String, String> status = new HashMap<>();
        
        // Redis 연결 확인
        try {
            redisTemplate.opsForValue().get("health-check");
            status.put("redis", "UP");
        } catch (Exception e) {
            status.put("redis", "DOWN");
            return ResponseEntity.status(503).body(status);
        }
        
        return ResponseEntity.ok(status);
    }
}

해결 전략 5: Graceful Shutdown

배포나 스케일링 시 진행 중인 요청을 끊지 않고 안전하게 종료해야 한다.

@Component
public class GracefulShutdownHandler {
    
    private volatile boolean shuttingDown = false;
    private AtomicInteger activeRequests = new AtomicInteger(0);
    
    @PreDestroy
    public void shutdown() {
        shuttingDown = true;
        
        // 최대 30초간 진행 중인 요청 완료 대기
        int waitTime = 0;
        while (activeRequests.get() > 0 && waitTime < 30) {
            try {
                Thread.sleep(1000);
                waitTime++;
            } catch (InterruptedException e) {
                break;
            }
        }
    }
    
    public void requestStarted() {
        activeRequests.incrementAndGet();
    }
    
    public void requestCompleted() {
        activeRequests.decrementAndGet();
    }
}

Kubernetes 설정:

spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: gateway
    lifecycle:
      preStop:
        exec:
          command: ["sh", "-c", "sleep 10"]  # LB에서 제거될 시간 확보

모니터링 지표

SPOF를 예방하려면 선제적 모니터링이 필수다.

지표 정상 범위 알람 조건
인스턴스 수 3개 이상 2개 이하
CPU 사용률 < 70% > 85% (5분 지속)
메모리 사용률 < 75% > 90%
P99 응답시간 < 100ms > 500ms
에러율 < 0.1% > 1%
Circuit Open 횟수 0 > 0

결론

API Gateway는 MSA의 핵심 인프라지만, 잘못 설계하면 가장 큰 약점이 된다. SPOF를 제거하려면:

  • ✅ 최소 3개 이상 인스턴스 운영
  • ✅ Stateless 설계 (외부 저장소 활용)
  • ✅ Circuit Breaker로 외부 의존성 격리
  • ✅ Health Check와 자동 복구
  • ✅ Graceful Shutdown 구현
  • ✅ 선제적 모니터링

분산 시스템의 입구가 단일 장애점이 되지 않도록 설계하자.

더 자세한 분산 시스템 아키텍처 패턴은 PowerSoft Tech Blog에서 확인할 수 있다.



PowerSoft Technical Report
Backend Architecture Series | January 2026
Author: PowerSoft R&D Center

댓글

이 블로그의 인기 게시물

[2026 Deep Dive] L7 로드밸런싱의 숨겨진 복잡성: 세션 어피니티가 만드는 트래픽 블랙홀

[Tech Review] 2,000만 트래픽도 거뜬한 2026년형 엔터프라이즈 아키텍처의 비밀

데이터베이스 교착 상태(Deadlock) 발생 원인과 해결 전략