이벤트 기반 아키텍처 골칫거리 '글리치(Glitch)', 왜 중요할까?
서버와 클라이언트에 관계없이 이벤트 기반 아키텍처 위에서 작업하다 보면 예상하지 못한 문제들을 자주 마주하게 됩니다. 직관적으로는 이상할 게 없어 보이는 코드가 실제로 실행되었을 때 잠깐 이상한 결과를 보이거나, 분명 문제가 있었는데 다시 확인하려고 하면 흔적도 없이 사라져 버리는 일들이 있습니다. 특히 이런 문제들은 재현 자체가 매우 까다로워서 버그로 보고하기조차 어려운 경우가 많습니다. 운이 좋아 문제를 다시 관찰한다 하더라도 정확히 어떤 상황에서 발생했는지 그 조건을 명확하게 정의하는 것 역시 쉬운 일이 아닙니다.

더욱이 최근 선언형 프로그래밍(Declarative Programming)의 인기와 함께 이벤트 처리마저 선언적인 방식으로 쉽게 관리할 수 있게 되었음에도 불구하고, 이러한 일시적 오류 현상인 ‘글리치(Glitch)’ 문제는 여전히 사라지지 않고 있습니다. 오히려 비동기 이벤트와 병렬, 분산 환경이 일반화하면서 글리치는 더 자주 발생하며 개발자들을 괴롭히는 골칫거리로 떠올랐습니다.
이 글에서는 제가 경험했던 이벤트 기반 아키텍처에서 가장 처리하기 어렵고 골치 아팠던 문제, 바로 ‘글리치(Glitch)’에 대해 이야기하고자 합니다. 글리치가 정확히 무엇이며 왜 발생하는지, 그리고 실제 시스템에서 글리치가 얼마나 치명적인 결과를 초래할 수 있는지 하나씩 짚어보겠습니다. 더 나아가 이를 방지하고 최소화하기 위한 현실적인 접근 방법까지 살펴보겠습니다.
글리치란 무엇인가?
소프트웨어를 개발·운영하다 보면 시스템에서 간혹 순간적으로 잘못된 상태가 관측되는 경우가 있습니다. 이때 발생하는 순간적인 오류 상태를 흔히 “글리치”라고 부릅니다.
위키피디아(Wikipedia)에서는 글리치를 “짧은 시간 동안 발생하고 저절로 사라지는 기술적 결함”으로 정의합니다. 실제로 글리치는 보통 시스템이 최종 상태로 수렴하기 전의 아주 짧은 순간에만 나타나며, 곧바로 복원되기 때문에 쉽게 간과되기 십상입니다. 심지어 사용자가 화면(UI)에서 잠시 잘못된 정보를 보았더라도, 새로고침이나 재시도 몇 번으로 쉽게 정정된다면 큰 문제로 이어지지 않는 경우가 대부분이죠.
물론 비디오 게임에서나 보던 글리치를 갑자기 왜? 라고 생각하시는 분들도 있을 것 같습니다. 하지만 서버와 클라이언트를 막론하고 이벤트 기반 아키텍처가 주목받으면서 글리치는 생각보다 흔히 관측할 수 있는 문제가 되었고, 상황에 따라서는 글리치가 소프트웨어를 망가뜨리기도 합니다. 어떤 상황에서 그런 일이 발생할 수 있는지는 이어지는 글에서 확인해 보겠습니다.
글리치는 왜 놓치기 쉬운가?
글리는 보통 이벤트가 최종 순서로 반영되기 전에 순간적으로 나타나는 상태 불일치 현상입니다. 특히 분산 시스템, 병렬 처리, 네트워크 지연 등으로 인해 이벤트 처리 순서가 뒤엉키거나 중간 상태들이 겹쳐 발생하면서 생겨나죠. 글리치를 놓치기 쉬운 이유는 몇 가지가 있습니다.
1) 순차적 처리에 대한 잘못된 기대
개발자나 사용자 모두 "앞선 이벤트가 먼저 반영되고, 그다음 이벤트가 순차적으로 처리된다"는 선형적 흐름을 쉽게 가정합니다. 그러나 실제로 비동기 이벤트를 사용하는 아키텍처에서는 이벤트가 동시에 또는 예측할 수 없는 순서로 들어올 수 있습니다. 테스트 과정에서 이렇게 비직관적으로 뒤엉킨 이벤트 순서를 재현하지 못하면, 글리치는 눈에 잘 띄지 않습니다.
2) 병렬·분산 환경에서의 순차 보장 어려움
분산 환경이나 병렬 스레드를 사용하는 구조에서는 특정 시점에 어떤 이벤트가 먼저 처리될지 정확히 통제하기가 매우 어렵습니다. 특히 네트워크 지연이나 서버 부하, 메시지 큐의 특성 등으로 인해 이벤트가 도착하거나 실행되는 시점이 예측 불가능합니다. 글리치치를 유발하는 특정 이벤트 조합이 우연히 겹쳐야 발생하는데, 이는 로컬이나 테스트 환경에서 발견하기 더욱 어렵습니다.
3) 글리치는 일시적이며 금방 사라짐
글리는 시스템이 재계산하여 올바른 최종 상태로 돌아가기 전 아주 짧은 순간에만 관측되는 오류입니다. 밀리초 또는 몇 초 내로 자동 복구되어 버리기 때문에, 로그나 모니터링 툴에서 그 시점을 정확히 포착하기가 쉽지 않습니다. 운영 환경에서 엄청난 양의 이벤트가 오가는 상황에서 이 짧은 오류를 잡아내려면, 고도화된 모니터링 및 정밀한 로그 수집이 필수적입니다.
4) 표현의 문제
최근에는 이벤트 기반 프로그래밍을 쉽게 할 수 있도록 다양한 패턴과 이를 지원하는 여러 도구가 등장하고 있습니다. 그중에서도 리액티브 프로그래밍(Reactive Programming)은 대표적인 예이며, 비슷한 목적을 가진 도구들도 계속해서 개발되고 있습니다. 하지만 높은 수준의 추상화 뒤에는 언제나 함정이 있기 마련입니다.
리액티브 프로그램과 관련된 도구들에도 이러한 함정이 존재합니다. 특히 이 분야에서는 글리치 프리(Glitch-Free)라는 개념이 중요합니다. 그러나 대부분의 도구는 구현의 복잡성이나 성능상의 이유로 글리치 프리리를 완벽히 보장하지는 않습니다.
다음과 같은 예제 코드를 보겠습니다.
t = seconds +1
g = (t > seconds)
이 코드가 리액티브하게 동작하며 글리치 프리를 지원하지 않는다고 가정할 때, 다음과 같은 문제가 발생할 수 있습니다. 일반적인 프로그래밍 관점에서는 이 코드의 변수g가 항상 `True`일 것으로 생각하기 쉽습니다. 그러나 여기에 함정이 존재합니다.
우리는 보통 `seconds`가 먼저 업데이트되고, 그 후에 `t`와 `g`가 차례로 업데이트된다고 생각할 수 있습니다. 하지만 실제 리액티브 시스템에서는 `seconds`가 업데이트되는 이벤트가 `t`와 `g`에 각각 전파됩니다. 즉, 두 가지 전파 경로가 존재하는 것입니다.
이때 문제가 발생할 수 있습니다. 만약 `g`가 `seconds`의 업데이트를 `t`의 업데이트보다 먼저 전달받게 된다면, 일시적으로 `g`의 값이 `False`가 되는 글리치 현상이 발생할 수 있습니다. 즉, 변수 업데이트의 전파 순서에 따라 잠깐 동안 잘못된 상태가 관찰 수 있는 것입니다.
이것이 바로 글리치 프리를 지원하지 않는 리액티브 도구에서 흔히 발생할 수 있는 표현의 문제입니다.

글리치, 정말 중요한 문제인가?
글리치를 단지 UI가 잠깐 잘못 표시되는 정도의 문제로 생각하면 오산입니다. 이벤트 기반 아키텍처에서는 다양한 이벤트의 전파가 빈번하게 발생하며, 모든 통신을 이벤트 기반으로 처리할 경우, 이벤트가 전파되는 과정에서 의도치 않은 커밋(commit)이 발생할 수 있습니다. 그렇다면 어떤 문제가 일어날 수 있을까요?
앞서 살펴본 리액티브 프로그래밍 예제에서 변수 `g`가 일시적으로 `False`가 되더라도 곧 `t`로 인해 다시 `true`로 복구되기에 큰 문제가 아니라고 생각할 수도 있습니다. 그러나 현실은 항상 우리의 예상보다 복잡하고, 예측하기 어려운 문제를 동반합니다.
예를 들어, 계좌에서 출금을 하는 상황을 가정해보겠습니다. `account`라는 변수에 잔고가 담겨 있고, 출금 가능 여부를 판단하는 변수를 `isWithdrawable`, 출금 요청된 금액을 `requestedWithdrawalAmount`라고 정의하겠습니다.
isWithdrawable = account >= requestedWithdrawalAmount
ifisWithdrawable {
account -= requestedWithdrawalAmount
}
위 코드로 1,000원을 두 번 출금하는 상황을 단계적으로 표현하면 다음과 같을 것입니다.

위 표에서 문제를 발견하셨나요? 3번 타임을 살펴보면, 잔고가 이미 0원이 되었음에도 불구하고 잠깐 동안 `isWithdrawable`이 `True`로 유지되는 상태가 발생합니다. 이 순간 추가 출금 요청이 처리되면, 아래와 같이 마이너스 잔고가 되는 심각한 오류가 발생할 수 있습니다.

근본적인 원인은 `account`, `requestedWithdrawalAmount`, `isWithdrawable` 값이 각각 별도의 이벤트로 변경되었기 때문입니다. 이들은 하나의 원자적 연산으로 묶여 동시에 변경되었어야 했습니다.
글리치 문제의 대응 방법
여기까지 내용을 살펴보고 나면 "이벤트 기반 아키텍처의 글리치를 얘기하다가 왜 리액티브 프로그래밍 얘기가 나오지?" 혹은 "단순한 동시성 문제 아냐?"라고 생각할 수도 있습니다. 먼저 리액티브 프로그래밍 예시를 사용한 이유는, 실제 서버 환경에서도 클라이언트-서버, 마이크로서비스 간 통신까지 이벤트 기반 설계가 점점 보편화되고 있으며, 클라이언트와 서버 모두에서 비슷한 문제가 발생하기 때문입니다.
또한, 글리치는 단순히 동시성 문제라고 보고 넘기기에는 보다 미묘한 특성을 가지고 있습니다. 동시성 문제는 주로 여러 스레드가 공유 자원에 동시에 접근할 때 발생하는 문제(예: race condition, deadlock)를 가리킵니다. 반면 글리치는 이벤트의 순서, 타이밍, 일시적인 상태 불일치와 밀접하게 관련되어 있습니다. 따라서 이벤트 처리 로직 설계의 결함으로 인해 발생하는 경우가 많습니다.
다음은 글리치를 방지하고 보다 안정적인 시스템을 구축하기 위한 몇 가지 핵심 접근법과 고려 사항입니다.
1) 원자적 연산(Atomic Operations)과 트랜잭션
가장 효과적인 방법은 관련된 상태 변화를 원자적 연산으로 처리하는 것입니다. 앞선 출금 예시에서 `account`, `requestedWithdrawalAmount`, `isWithdrawable` 세 변수의 변경을 단일 트랜잭션으로 묶어 처리했다면 글리치치를 방지할 수 있었을 것입니다.
물론 마이크로 서비스 아키텍처(Microservices Architecture) 환경에서는 데이터베이스와 서비스가 서로 분리되어 있기 때문에 이러한 트랜잭션 처리가 어렵습니다. 클라이언트 환경 역시 기본적으로 이벤트 기반 설계를 채택하므로 원자적 처리가 쉽지 않습니다. 다만 최근에는 클라이언트 측에서도 트랜잭션을 지원하는 데이터베이스가 등장해, 문제 해결이 비교적 쉬워지고 있습니다. 하지만 근본적으로 이벤트 기반 설계를 채택한 이상, 트랜잭션 없이도 자연스럽게 처리되기를 기대하게 됩니다.
2) 이벤트 순서 보장 및 재정렬
이벤트 기반 아키텍처에서는 이벤트의 순서를 엄격히 관리해야 글리치 발생을 최소화할 수 있습니다.
- 이벤트 소스에서 순서 보장: Kafka 파티션이나 RabbitMQ의 FIFO 큐 등을 활용하여 이벤트 순서를 유지합니다.
- 시퀀스 번호 또는 타임스탬프 사용: 이벤트마다 고유 번호나 타임스탬프를 포함하여 수신 측에서 순서를 복원할 수 있도록 합니다.
3) 멱등성(Idempotency)
이벤트 기반 시스템에서는 중복 이벤트가 자주 발생합니다. 따라서 멱등성을 확보하여 중복 처리를 방지해야 합니다.
- 고유 ID 부여: 각 이벤트에 고유 ID를 부여하여 중복 처리를 막습니다.
- 멱등성 키 활용: 클라이언트가 요청 시 멱등성 키를 보내 서버가 이를 기반으로 중복 여부를 판단합니다.
- 상태 기반 멱등성: 시스템 상태를 점검하여 중복 요청을 식별합니다.
4) 최종 일관성(Eventual Consistency)과 보상 트랜잭션(Saga)
모든 데이터를 항상 완벽히 일치시키는 것은 현실적으로 어렵기 때문에 최종 일관성을 채택하는 것이 좋은 대안이 됩니다.
- 보상 트랜잭션(Saga): 복잡한 비즈니스 프로세스를 여러 개의 로컬 트랜잭션으로 나누어 처리하고, 실패 발생 시 이전 작업을 되돌리는 방식으로 안정성을 보장합니다. 다만 모든 케이스에 적용할 수 있는 방법은 아닙니다.
5) 리액티브 프로그래밍 도구의 글리치 프리 기능 활용
리액티브 프로그래밍 도구의 글리치 프리 기능, 동기화된 업데이트, 위상 정렬(Topological Sorting) 등을 활용하여 글리치를 최소화할 수 있습니다. 사용하는 도구의 특성을 충분히 이해하고 적극적으로 활용하는 것이 필요합니다.
이벤트 세상에서 살아남기
기본적으로 이벤트의 세계는 마치 오케스트라의 연주와 같습니다. 이상적으로는 모든 악기가 완벽하게 조화로운 화음을 만들어내는 아름다운 세계입니다. 하지만 현실에서는 지휘자의 사소한 실수나 연주자의 작은 타이밍 차이 하나만으로도 곡 전체가 삐걱거리며 불협화음으로 무너져 내릴 수 있는, 매우 섬세하고 까다로운 영역입니다.
대부분의 경우, 이벤트 기반 아키텍처는 익숙하고 안정적인 동기적 접근법보다 훨씬 복잡한 상황을 야기합니다. 그래서 더 세밀한 설계와 지속적인 주의를 요구합니다. 따라서 어쩔 수 없이 이벤트 세계에 뛰어들어야 한다면, 코드 한 줄을 작성하기 전에 반드시 어떤 이벤트들이 존재하는지 파악해야 합니다. 또한 그것들이 어떻게 상호작용하는지 충분히 고민하며 악보를 작성하듯이 설계해야 합니다.
서버 개발자라면 DDD(Domain-Driven Design) 구현 패턴과 같은 설계 패턴에서 힌트를 얻을 수 있습니다. 클라이언트 개발자라면 리액티브 프로그래밍의 글리치 프리 구현법과 같은 기법을 미리 숙지해 두는 것이 좋습니다. 무대 위에서의 완벽한 연주가 치밀한 연습과 계획 없이는 불가능하듯, 이벤트 기반 시스템에서도 준비 없이 뛰어든다면 순식간에 '글리치'라는 불협화음에 무너질 수 있습니다.
결국, 이벤트의 세계에서 살아남기 위해서는 오케스트라 지휘자처럼 모든 이벤트의 흐름을 주도면밀하게 파악하고 통제할 수 있어야 합니다. 항상 예상 밖의 이벤트가 찾아올 것을 염두에 두어야 합니다. 가능한 모든 이벤트의 관계를 명확하게 다이어그램으로 그리며 충분히 대비하시기 바랍니다.
©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.