속도를 위한 설계: 스키마리스 이벤트 수집을 택해야 하는 이유

SchemaBridge Team · 2025-12-08 · Event Ingestion, JSON, DX

이벤트 기반 시스템에서의 속도 대 엄격한 타입. 우리가 엄격한 타입 정의 대신 원시 JSON을 선택한 이유.

스키마의 역설: 친구인가, 적인가?

전통적인 엔터프라이즈 소프트웨어에서 엄격한 스키마(SQL, Protobuf, WSDL)는 신뢰성의 토대입니다. 계약은 단순합니다. 내가 데이터의 형태를 정의하면, 당신은 그것을 따르고, 컴파일러는 아무것도 깨지지 않도록 보장합니다. 이 방식은 파이프의 양 끝을 모두 우리가 소유하는 통제된 환경에서 수십 년 동안 제 역할을 해 왔습니다. 컴파일 타임 안전성, 효율적인 바이너리 직렬화, 그리고 개발자를 위한 명확한 '단일 진실 공급원'을 제공합니다.

하지만 SaaS 벤더가 매주 페이로드를 바꾸고, 레거시 ERP가 '유연한' CSV를 내보내며, 내부 마이크로서비스가 매달 생겨나고 사라지는 현대의 고속 통합 환경에서 경직된 스키마는 구속복이 됩니다. 안전성을 주는 대가로 관성을 남깁니다. 주변 세상이 유동적일 때 경직된 계약은 토대가 아니라 장애 지점입니다. 스키마의 역설은 이것입니다. 엄격한 타입으로 시스템을 보호하려 할수록, 외부 세계가 변할 때 시스템은 더 취약해집니다.

역사적 변천: COBOL에서 JSON까지

스키마리스 시스템에 대한 수요를 이해하려면 데이터 교환의 변천사를 살펴봐야 합니다. 메인프레임 초창기에는 데이터가 고정 길이 레코드(COBOL COPYBOOK)에 저장되었습니다. 필드 하나를 추가하려면 그 레코드를 읽는 모든 프로그램을 다시 컴파일해야 했습니다. 궁극의 경직된 스키마였습니다. 이는 '단일 머신 사고방식'의 시대였고, 저장 비용이 너무 비싸 모든 바이트가 고정된 위치에서 관리되어야 했습니다. 계층 구조도, 선택적 항목도, 그리고 당연히 진화의 여지도 없었습니다. 레코드의 문자 하나하나가 귀중한 자원이었고, 어떤 변경이든 몇 주간의 계획과 테스트가 필요한 대사건이었습니다.

1980~90년대에 우리는 관계형 데이터베이스와 SQL로 옮겨 갔습니다. 구조화된 관계라는 개념을 도입했다는 점에서 이는 거대한 진전이었습니다. 하지만 동시에 데이터베이스 마이그레이션도 등장했습니다. 컬럼 하나를 추가하는 일은 다운타임, 테이블 잠금, 그리고 DBA와 개발자 간의 세심한 조율을 의미했습니다. 스키마는 여전히 개발자가 혁신하려 할 때마다 넘어야 하는 벽이었습니다. Hibernate 같은 ORM이 부상한 뒤에도, 테이블 구조의 근본적인 경직성은 무엇이 가능한지를 결정하는 최종 심판으로 남았습니다.

그다음 90년대 후반에 XML과 SOAP이 등장했습니다. '태그'라는 개념 덕분에 어느 정도 유연성이 생겼습니다. XML 태그를 추가해도 파서가 반드시 깨지지는 않았습니다. 하지만 업계는 곧 XSD(XML Schema Definition)와 WSDL을 추가해 경직성을 되돌려 놓았습니다. 우리는 수년간 네임스페이스와 씨름했고, 문자 하나만 어긋나도 메시지를 거부하는 복잡한 엔터프라이즈 서비스 버스와 싸웠습니다. 스키마의 오버헤드가 실제 데이터보다 더 많은 대역폭을 잡아먹던 'XML의 암흑기'였습니다.

오늘날 우리에게는 JSON과 REST가 있습니다. JSON은 본질적으로 유연합니다. 그저 키와 값의 맵일 뿐입니다. 그럼에도 엔지니어로서의 본능은 그 유연성을 엄격한 타입(TypeScript 인터페이스, Java DTO, Avro 스키마)으로 감싸게 만듭니다. 1970년대 메인프레임의 경직성을 2020년대 클라우드 이벤트에 강요하고 있는 셈입니다. 왜일까요? 미지의 것이 두렵기 때문입니다. 필드 하나가 빠져서 서비스가 죽을까 봐 두렵기 때문입니다. 하지만 앞으로 보겠지만, 이 두려움은 잘못된 도구로 다뤄지고 있습니다.

재무 분석: 통합 유지보수의 '숨은 세금'

스키마 경직성의 비용을 정량화해 봅시다. 중대형 규모의 전형적인 엔지니어링 조직에서 통합 유지보수는 '조용한 위기'입니다. 대차대조표에 별도 항목으로 나타나지는 않지만, 생산성을 크게 갉아먹습니다.

유지보수의 계산법

찾아보기만 하면 패턴은 쉽게 보입니다. 외부 SaaS 통합을 대규모로 보유한 조직에서는 벤더가 정기적으로 페이로드를 바꾸고, 엄격한 파서를 깨뜨리는 변경이 있을 때마다 같은 사이클이 시작됩니다. 감지, DTO/스키마 업데이트, 테스트, 그리고 CI/CD 배포입니다. 장애 한 건당 몇 시간씩, 이것이 1년에 여러 번 반복되면 시니어 엔지니어의 시간이 상당하고도 지속적으로 소모됩니다.

하지만 진짜 비용은 기회비용입니다. 시니어 엔지니어가 Stripe의 최신 변경에 맞춰 DTO를 고치는 동안, 비즈니스를 앞으로 나아가게 할 새로운 자동 사기 탐지 기능은 만들어지지 않습니다. 시간이 지나면 이 세금은 누적된 속도 손실로 복리처럼 불어나, 회사를 더 민첩한 경쟁사보다 한참 뒤처지게 만들 수 있습니다.

데이터 유동성의 철학: schema-on-read

SchemaBridge는 근본적인 사고방식의 전환을 제안합니다. 바로 schema-on-read입니다.

데이터가 들어오는 순간에 검증하는(schema-on-write) 대신, 먼저 이벤트의 원시적이고 계층적인 진실을 그대로 수집합니다. JSON 페이로드는 사전에 선언된 스키마 없이, 도착한 그대로 실행(run)과 함께 저장됩니다. 스키마, 더 정확히 말해 변환은 특정 비즈니스 프로세스가 데이터를 필요로 하는 순간에만 적용합니다.

데이터 유동성이 이기는 이유

1. 제로 터치 수집: 새 프로바이더의 이벤트를 몇 초 만에 받기 시작할 수 있습니다. 프로바이더의 웹훅을 SchemaBridge 엔트리 포인트(수집 계층이 처리)로 지정하면, 모든 JSON 이벤트가 즉시 기록된 실행을 시작합니다. 데이터의 의미는 나중에 파악해도 됩니다.

2. 미지에 대한 보험: 프로바이더가 오늘 당신에게 필요 없는 필드를 추가하더라도, 그 필드는 원시 JSON에 그대로 기록됩니다. 원시 페이로드는 요금제의 보존 기간 동안 보관되므로, 몇 주 뒤에 그 필드가 사실은 필요하다는 것을 깨닫더라도 원시 입력은 여전히 남아 있습니다. 프로바이더에게 과거 데이터를 요청하는 대신, 개별 실행을 업데이트된 매핑으로 다시 재생(또는 그 페이로드를 다시 투입)할 수 있습니다.

3. 분리된 진화: 수집 계층과 변환 계층은 서로 다른 속도로 진화할 수 있습니다. 엔트리 포인트를 전혀 건드리지 않고도 비즈니스 로직을 하루에 여러 번 업데이트할 수 있습니다.

지연 바인딩의 수학적 근거

컴퓨터 과학에서 지연 바인딩(late-binding)은 식별자나 타입의 해석을 실행 시점까지 미루는 기법입니다. Ruby나 Python 같은 동적 언어가 특정 작업에서 강력한 이유가 바로 이것입니다.

schema-on-read는 지연 바인딩을 데이터 인프라에 적용한 것입니다. 매핑을 미룸으로써, 모든 변경에 전체 재구축이 필요한 경직된 그래프에서 나아가면서 지형에 적응할 수 있는 유연한 경로로 옮겨 갑니다.

수학적으로 $N$개의 프로듀서와 $M$개의 컨슈머 사이에 가능한 매핑의 수는 $N \times M$입니다. 모든 프로듀서와 컨슈머가 엄격한 스키마에 합의해야 한다면, 거대한 조율 문제가 생깁니다. 지연 바인딩을 갖춘 스키마리스 브리지를 사용하면 문제는 $N + M$개의 매핑으로 줄어들고, 각 매핑은 로컬이며 독립적입니다. 이것이 진정한 수평적 엔지니어링 확장을 달성하는 방법입니다.

JSONata: 함수형 이벤트 처리의 마스터클래스

schema-on-read를 실용적으로 만들려면 탐색을 위해 설계된 언어가 필요합니다. 우리는 JSONata를 선택했습니다. JSONata는 단순한 쿼리 언어가 아니라, 원시 JSON 계층 위에서 직접 동작하는 함수형 변환 엔진입니다.

JSONata 표현식의 구조

주문 목록을 반환하는 레거시 ERP의 페이로드를 생각해 봅시다. 각 주문은 복잡하게 중첩된 구조를 갖습니다. 금액이 500달러를 넘고 현재 'SHIPPING' 상태인 주문의 모든 부품 번호를 추출하려 합니다.

전통적인 코드(Javascript):

const parts = payload.orders
  .filter(o => o.total > 500 && o.status === 'SHIPPING')
  .flatMap(o => o.items)
  .map(i => i.partNumber);

이 코드는 취약합니다. orders가 null이거나, 어떤 주문에 items가 없으면 크래시가 납니다.

JSONata의 진가:

orders[total > 500][status = 'SHIPPING'].items.partNumber

이 표현식은 경로 허용적입니다. items가 없는 주문은 그저 아무것도 기여하지 않으며, orders가 아예 없는 페이로드는 널 포인터 크래시 대신 아무것도 없는 결과로 평가됩니다. 데이터를 '단정'하는 대신 '탐색'합니다.

솔직하게 두 가지 주의할 점이 있습니다. 첫째, JSONata가 타입에 대해 마법을 부리지는 않습니다. 벤더가 "total": "600"을 문자열로 보내기 시작하면, 비교는 추측하는 대신 오류를 발생시킵니다. 둘째, SchemaBridge에서는 아무런 출력도 만들지 못한 매핑은 빈 값을 다운스트림으로 넘기는 대신 스텝을 실패시킵니다. 이는 의도된 설계입니다. 매핑이 갑자기 아무것에도 일치하지 않는다는 것은 거의 언제나 업스트림의 데이터 형태가 바뀌었다는 신호이고, 그런 일은 요란하게 드러나야 하기 때문입니다.

고급 패턴: 깊은 하위 요소 선택

JSONata의 가장 강력한 기능 중 하나는 하위 요소 연산자 **입니다. 계층의 어디에 있든 원하는 키를 찾을 수 있습니다.

**.tracking_number

다운스트림 프로바이더마다 운송장 번호를 서로 다르게 중첩하더라도, 이 표현식 하나로 모든 페이로드 버전에서 이를 찾아낼 수 있습니다. 이것이 바로 구조적 회복탄력성의 정의입니다. 특정 키를 찾아 헤매는 취약한 작업을, 진실을 찾는 유연한 검색으로 바꿔 줍니다.

고급 패턴: 즉석 데이터 재구성

JSONata를 사용하면 JSON 객체 전체를 단 한 번의 패스로 재구성할 수 있습니다.

orders.{ "order_id": ID, "summary": $join(items.name, ', ') }

전통적인 코드에서는 객체 매핑, 문자열 연결, 배열 반복이 필요합니다. JSONata에서는 원하는 상태를 선언적으로 투영하는 것으로 끝납니다. 복잡한 이벤트의 간추린 요약을 Slack 알림이나 모바일 앱으로 보내야 할 때 특히 강력합니다.

테이블 없는 데이터의 운영: 보안과 검증

비판적인 사람들은 종종 이렇게 묻습니다. "스키마를 쓰지 않는다면, 쓰레기 데이터가 시스템에 들어오는 것을 어떻게 막습니까?"

답은 스키마리스 != 미검증이라는 것입니다. 검증을 vertex 수준으로 옮길 뿐입니다.

사례 연구: 50개 리전에 걸친 데이터 수집 메시

여러 리전에서 텔레메트리를 수집하는 글로벌 IoT 플릿을 생각해 봅시다. 각 리전은 센서 펌웨어 버전이 조금씩 다릅니다. 리전마다 고유한 JSON '방언'이 있는 셈입니다.

과제

팀이 엄격한 테이블 스키마를 갖춘 전통적인 SQL 기반 수집 시스템으로 시작한다고 가정해 봅시다. 한 리전에서 펌웨어 업데이트가 배포될 때마다 해당 리전의 수집 파이프라인이 깨집니다. 새 펌웨어가 데이터베이스에 컬럼이 없는 battery_health_v2 필드를 추가하기 때문입니다. 데이터 팀은 끊임없는 '소방' 모드에 빠져, 수많은 프로덕션 데이터베이스에 ALTER TABLE 명령을 실행하고 유지보수 시간대마다 이벤트를 유실합니다.

SchemaBridge 솔루션

이제 같은 팀이 스키마리스 전략으로 전환했다고 상상해 봅시다.

1. 범용 수집: 모든 리전이 같은 SchemaBridge 엔트리 포인트로 데이터를 보냅니다. 수집 계층은 스키마에 신경 쓰지 않고, 각 원시 이벤트를 해당 실행과 함께 저장합니다.

2. 읽기 시점 매핑: 팀은 펌웨어 버전별로 정규화 스텝을 만듭니다. 조건부 엣지가 각 원시 이벤트를 펌웨어 버전 필드에 따라 알맞은 스텝으로 라우팅합니다.

3. 노코드 업그레이드: 새 펌웨어 버전이 출시되어도 업데이트할 데이터베이스가 없습니다. 팀은 기존 스텝을 복제하고, 새 필드를 포함하도록 JSONata 매핑을 수정한 뒤 배포합니다.

예상되는 결과

결론: 속도는 설계상의 선택이다

엄격한 스키마는 하나의 선택입니다. 동적인 속도보다 정적인 안전성을 우선시하는 선택입니다. 닫힌 시스템에서는 타당한 선택입니다. 하지만 연결되고 분산된 생태계에서는 실패로 이어지는 선택입니다.

예측할 수 없고, 계속 진화하며, 계층적인 있는 그대로의 세상에 맞춰 시스템을 설계하세요. 속도를 위해 설계하세요. SchemaBridge로 설계하세요. 데이터의 유동성을 받아들이면, 경직된 DTO와 데이터베이스 마이그레이션에 갇힌 경쟁사들이 꿈만 꿀 수 있는 속도로 구축하고, 확장하고, 혁신할 수 있는 능력을 얻게 됩니다.

함께 보기: 팬아웃 마스터하기 — Spawner vertex로 수집에서 실행으로 나아가기. 대량의 작업을 다수의 항목에 분산하되 백프레셔를 일정 범위로 제한합니다.

살펴보기