Back to Insights
BackendArchitecturePHP

외부 연동 시스템에서의 로깅 분리와 버퍼링 전략

2026.02.265 min

외부 API 의존도가 높은 시스템에서 DB 병목을 해결하고, 인메모리 버퍼링을 통해 시스템 안정성을 확보한 아키텍처 개선기.

외부 파트너사의 예약 API에 전적으로 의존하는 '더시에나 골프 예약 시스템'을 구축하면서 가장 큰 골칫거리는 역설적이게도 '로깅(Logging)'이었습니다. 외부 시스템과의 통신은 언제든 지연되거나 실패할 수 있기 때문에 모든 요청과 응답을 상세히 기록해야 했지만, 이 로깅 작업 자체가 우리 시스템의 안정성을 위협하고 있었기 때문입니다.

이 글에서는 로깅으로 인해 발생한 시스템 병목을 어떻게 파일 시스템 분리와 인메모리 버퍼링을 통해 해결했는지, 그리고 이 데이터를 통해 외부 파트너사와의 이슈를 어떻게 방어했는지 공유하고자 합니다.

The Problem

1. 메인 비즈니스 DB를 위협하는 로깅 병목

초기 아키텍처에서는 모든 cURL 통신 로그를 메인 비즈니스 DB에 직접 Insert 하도록 설계했습니다. 하지만 서비스 오픈 후 수개월 만에 수백만 건의 로그 데이터가 적재되면서 DB 테이블이 급격히 비대해졌고, 급기야 DB GUI 툴이 다운되는 현상까지 발생했습니다. 메인 비즈니스(예약)와 로깅이 동일한 DB 리소스를 공유하면서, 단순한 로그 기록이 시스템 전체의 병목을 유발하는 상황이 되었습니다.

2. 외부 시스템 장애 전파와 데이터의 부재

설상가상으로 호텔 행사 기간 중, 파트너사로부터 "너희 쪽에서 비정상적인 대량 요청이 들어오고 있다"는 클레임이 접수되었습니다. 하지만 DB 부하로 인해 로깅 시스템이 제 역할을 하지 못하면서, 우리 서버에서 실제로 몇 건의 API를 호출했는지, 네트워크 레벨의 중복 요청이 있었는지를 증명할 객관적인 트레이스(Trace) 데이터가 부족했습니다. 장애 원인이 외부에 있음에도 이를 방어할 무기가 없는 셈이었습니다.

The Solution

1. 로깅과 비즈니스의 완벽한 분리 (Decoupling)

가장 먼저 세운 원칙은 *"로깅은 실패할 수 있지만, 예약 비즈니스는 실패하면 안 된다"*는 것이었습니다. 읽기보다 쓰기(Write-heavy) 작업이 압도적으로 많은 로그의 특성을 고려해, 로그 적재처를 DB에서 파일 시스템으로 분리했습니다. 또한, 로깅 모듈 전체를 독립적인 try-catch 블록으로 철저히 격리하여 디스크 용량 부족이나 파일 권한 에러가 발생하더라도 메인 예약 트랜잭션에는 전혀 영향을 주지 않도록 Fail-Safe 설계를 적용했습니다.

2. 파일 I/O 병목을 막는 인메모리 버퍼링 (In-Memory Buffering)

단순히 로그를 일자별 파일(YYYY-MM-DD.log)에 기록하는 것만으로는 부족했습니다. 트래픽이 몰릴 때마다 매번 파일 쓰기(File I/O)를 수행하면 디스크 병목과 동시 쓰기(Concurrency) 이슈가 발생할 수 있기 때문입니다. 이를 해결하기 위해 인메모리 버퍼링 패턴을 직접 구현했습니다.

// 로그 엔트리 생성 후 버퍼에 적재
$this->buffer[] = $logEntry;

// 버퍼 플러시 조건 체크: 20개가 쌓이거나, 마지막 기록 후 2초가 경과했을 때
if (count($this->buffer) >= $this->bufferSize ||
    (time() - $this->lastFlush) >= $this->flushInterval) {
    $this->flush(); // 파일에 일괄 쓰기 수행
}

위 코드와 같이 매 요청마다 파일에 접근하는 대신, 배열(버퍼)에 로그를 임시로 적재했습니다. 그리고 로그가 20건 쌓이거나 2초가 경과했을 때만 일괄적으로 파일에 플러시(Flush) 하도록 구현하여 시스템 부하와 파일 I/O 횟수를 획기적으로 줄였습니다.

The Result & Retrospective

데이터 기반의 방어 체계 구축

개선된 로깅 시스템을 통해 요청 시각, 호출 IP, 세션 등의 데이터를 명확하고 가볍게 확보할 수 있었습니다. 이를 바탕으로 파트너사가 제기한 트래픽 과부하 원인이 당사 시스템의 중복 호출이 아님을 데이터로 명확히 증빙하며 이슈를 해결했습니다. 메인 DB의 병목 현상 역시 완전히 사라졌습니다.

확장성을 고려한 회고

당시 제한된 환경에서 버퍼링 로직을 직접 구현해 낸 것은 값진 경험이었습니다. 하지만 현재의 관점에서 동일한 시스템을 설계한다면, 예약 진입 시점부터 Redis 기반의 TTL Lock을 활용해 동시성을 더 강력하게 제어할 것입니다. 또한, 로깅 아키텍처 역시 Logrotate를 활용한 관리 자동화나, ELK/Datadog 같은 중앙 집중형 수집 체계로 확장하여 운영 가시성을 더욱 높이는 방향으로 발전시키고 싶습니다.

Thanks for reading!