개발을 하다보면 내 API는 반드시 성공할 것이라고 믿는다. 실제로 처음 서비스를 개발할 때는 단순한 기능 테스트만으로 만족하며, 클라이언트와의 통신에서 200 OK 응답만 오면 모든것이 잘 작동한다고 생각하게 된다.
하지만 서비스가 운영 환경에 배포되고, 실제 유저들이 몰리기 시작하면서 API는 다양한 방식으로 실패할 수 있다는 사실을 알게된다. 특히 대용량 트래픽이 집중되는 순간, 서버는 실패를 예외가 아닌 일상처럼 겪게된다.
예상치 못한 실패 중 하나는 과도한 요청 처리로 인해 서버의 스레드가 고갈되는 경우이다.
예를 들어, A 서비스가 200개의 스레드를 가진 상태에서, 외부의 B 서비스와 연동된 API를 호출하는 상황을 가정해보려고한다. B 서비스는 최대 100개의 요청을 동시에 처리하지만, 특정 시점에 문제가 발생해 API 응답이 지연된다.
A 서비스의 스레드 200개가 전부 대기 상태에 빠지게 된다. 이로 인해 A 서비스의 다른 API들도 처리를 못하게 되고, B 서비스와 연관 없는 API 조차 응답하지 못하는 장애가 발생하게 된다.
실제 NestJS를 사용한 예를 들어보면, @Injectable() 서비스 클래스에서 외부 API를 호출할 때 HttpService를 활용하는 경우가 많다. 이때 firstValueFrom(httpService.get(...))처럼 Observable을 Promise로 변환해 사용하는 패턴이 일반적인데, 이 요청에 대한 timeout 처리를 설정하지 않으면 위와 같은 문제가 쉽게 발생할 수 있다. timeout 연산자를 적용하거나, Axios 자체의 timeout 옵션을 설정함으로써 요청 지연이 전체 시스템에 영향을 주는 것을 방지할 수 있다.
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom, timeout, catchError } from 'rxjs';
@Injectable()
export class ExternalApiService {
constructor(private readonly httpService: HttpService) {}
async fetchData(): Promise<any> {
try {
const response$ = this.httpService.get('https://external-api.com/data').pipe(
timeout(3000), // 3초 이상 대기하지 않음
catchError(() => {
throw new HttpException('External API Timeout', HttpStatus.GATEWAY_TIMEOUT);
})
);
const response = await firstValueFrom(response$);
return response.data;
} catch (error) {
// 로깅 및 예외 전파
throw error;
}
}
}
이렇게 timeout을 명시하면 외부 시스템의 응답 지연이 내부 시스템 전체를 마비시키는 사태를 방지할 수 있다. 그러나 timeout 처리 이후 또 다른 문제가 발생할 수 있다.
예를 들어 A 서비스에서는 트랜잭션 롤백이 발생했지만, B 서비스에는 이미 성공 응답이 도달한 상황이 발생한 것이다.
이 경우 데이터 불일치가 발생하며, 일관성(consistency) 문제가 제기된다. 이런 상황에서 시스템은 몇 가지 보완 조치를 고려해야한다.
첫째, 두 시스템 간의 데이터 정합성을 주기적으로 확인하는 프로세스를 둬야 한다. 일정 간격으로 A 서비스와 B 서비스의 상태를 비교하고, 불일치가 감지되면 이를 수동 또는 자동으로 보정하는 메커니즘을 구축하는 것이다.
이때 Event Sourcing 또는 CDC(Change Data Capture) 기반의 동기화 전략도 고려할 수 있다.
둘째, 외부 API 요청이 성공했는지를 사용자에게 바로 알려주기 어려운 상황이라면, 사용자에게 작업 결과가 아직 확정되지 않았음을 명확히 전달하고, 일정 시간이 지난 후에 실제 성공 여부를 파악하여 알려주는 방식이 필요하다. 이를 위해서는 외부 서비스가 결과를 조회할 수 있는 API를 제공해야 하며, 내부적으로는 polling 또는 webhook 기반으로 상태 확인 및 취소 처리(cancel compensation)을 수행할 수 있어야 한다.
이러한 복잡한 시나리오는 단순히 API의 안정성 문제로만 치부할 수 없다. 이는 결국 분산 시스템에서의 일관성과 가용성의 균형(CAP 이론), 트랜잭션 처리에서의 원자성(Atomicity), 그리고 외부 시스템과의 커뮤니케이션에서 발생하는 네트워크 지연, 실패 대응 전략 등 컴퓨터공학 전반에 걸친 이해가 요구되는 문제이기 때문이다.
따라서 서비스의 규모가 커지고 연동 시스템이 많아질수록, API의 성공 여부는 단순히 응답 코드로만 판단할 수 없게 된다. 실패를 설계하고, 그에 대한 복구 및 보정 전략을 준비하는 것이 진정한 실서비스 설계이며, 그 과정에서 발생할 수 있는 다양한 문제를 사전에 시뮬레이션하고 예방하는 노력이 필요하다.
'Project > 기록' 카테고리의 다른 글
멱등성(Idempotency)이란? (0) | 2025.06.02 |
---|---|
비동기 아키텍처 전환 (0) | 2025.05.28 |
객체지향의 사실과 오해 - 2 (0) | 2025.05.10 |
객체지향의 사실과 오해 - 1 (0) | 2025.05.10 |
회사 코드 문화를 바꾸었다 - 완 (1) | 2025.05.07 |