2.9 스토리지 통합 및 데이터 캐싱 계층 아키텍처
MQTT나 ROS2, DDS 같은 기존의 표준 통신 미들웨어는 데이터의 ’운반(In-Motion)’에만 극단적으로 집착한 나머지 심각한 설계적 결함을 안고 있었다. 라우터나 브로커는 퍼블리셔(Publisher)가 던진 메시지를 다른 어딘가로 휙 토스하고 나면 곧바로 심각한 기억상실증에 빠져버린다.
만약 배터리가 아까워 평소에 통신 모듈을 꺼두고 슬립(Sleep) 모드에 들어가 있는 저전력 에지 센서가, 자신이 자는 동안 로봇 제어 시스템이 발송한 ‘설정값 변경(Config Update)’ 메시지를 받으려면 어떻게 해야 할까? 혹은 네트워크 단절로 인해 5분 전에 도착했어야 할 과거의 데이터(Historical Data) 값을 누군가 다시 확인하고 싶다면 어떻게 해야 할까?
**Zenoh(제노)**는 이를 위해 철저히 분리되었던 미들웨어(메시지 버스)의 영역과 데이터베이스(저장소)의 영역을 하나의 분산 시스템으로 녹여내는 과감한 아키텍처 혁신을 단행했다. 이 장에서는 흘러가는 찰나의 패킷 시스템 안에 반영구적인 저장소 개념을 결합하는 Zenoh만의 인네트워크 스토리지(In-Network Storage) 철학을 다룬다.
- 데이터 캐싱 (2.9.1): 네트워크의 홉(Hop) 중간마다 임시 버퍼를 두어, 동일한 데이터를 요청하는 수천 대의 클라이언트를 위해 메인 서버의 응답을 가로채고 대신 대답해 주는 캐싱(Caching) 전략을 알아본다.
- 스토리지 계층 아키텍처 (2.9.2): 단순한 플러그인을 넘어 미들웨어 코어 라우팅 테이블에 스스로를 센서처럼 위장(Spoofing)하여 등록시키는 지능형 스토리지 노드의 동작 원리를 뜯어본다.
- 추상화된 볼륨 시스템 (2.9.3): 인메모리(RAM), RocksDB, MariaDB, InfluxDB 등 이성질적인 백엔드 데이터베이스를 프론트의 애플리케이션 입장에서는 똑같은 REST나 Zenoh 쿼리로 꺼내볼 수 있도록 해주는 투명한 추상화(Virtualization) 구조를 이해한다.
- 투명한 질의 응답 (2.9.4): 시스템 쿼리 요청자 관점에서 물리 서버의 디스크를 뒤진 것인지, 실제 살아있는 센서 라즈베리파이가 대답한 것인지 알 필요조차 없는 Zenoh의 투명한 쿼리 라우팅 매직을 감상한다.
1. 데이터 캐싱(Caching)의 역할과 지연 시간 최소화 전략
데이터 중심의 퍼블리시-서브스크라이브(Publish-Subscribe) 네트워크에서 맞닥뜨리는 치명적인 병목 현상 중 하나는 원격지 서버에 집중되는 반복적인 쿼리 요청이다. 전 세계 10만 대의 에지 디바이스(Edge Device)가 런던 데이터센터에 있는 단 하나의 기상 관측 퍼블리셔(Publisher)에게 “현재 런던의 날씨“를 동시에 묻는다면(Query), 런던의 센서는 폭주하는 응답을 처리하느라 다운되고, 지구 반대편의 노드들은 수백 밀리초(ms)의 물리망 지연(Latency)을 고스란히 감당해야 한다.
**Zenoh(제노)**는 이러한 구조적 비효율을 타파하기 위해 철저한 명명 데이터 네트워킹(Named Data Networking, NDN) 철학을 바탕으로 **인네트워크 캐싱(In-network Caching)**을 지원한다.
1.1 공간적 분리(Decoupling in Space)의 극대화
기존의 통신 미들웨어가 “누가(Who) 어디서(Where) 데이터를 생산하는가?“에 집착했다면, Zenoh는 “데이터의 이름(What)이 무엇인가?“에만 집중한다.
클라이언트가 특정 키 경로(예: london/weather/temp)로 데이터 값을 조회(Query)할 때, 이 질의는 반드시 최초 생산자인 런던의 센서까지 도달할 필요가 없다. 이 데이터가 이동하는 네트워크 홉(Hop) 중간 어딘가에 위치한 Zenoh 라우터가 과거에 해당 데이터를 이미 전달받아 로컬 메모리에 **캐싱(Caching)**해 두었다면, 라우터가 원본 센서를 대신하여 즉각적으로 응답(Reply)을 돌려준다.
- 지연 시간 최소화: 원본 서버까지 왕복하는 물리적 거리가 단축되므로, 서브스크라이버(Subscriber)가 체감하는 응답 지연이 마이크로초(us) 단위로 급감한다.
- 원본 노드 보호: 수만 명의 클라이언트가 몰리더라도 원본 퍼블리셔는 1명(라우터)에게만 응답을 주면 되므로 대역폭 낭비와 부하가 사라진다.
1.2 Zenoh 쿼리 모델 속 풀(Pull) 기반 캐싱 원리
이 강력한 매직은 앞서 설명했던 Zenoh의 두 가지 통신 원시 계층 중 분산 질의(Distributed Query) 기능인 강력한 풀(Pull) 메커니즘을 척추로 삼아 작동한다.
- 조회 요청 발송: 클라이언트가
session.get("london/weather/temp")를 호출한다. - 가까운 라우터의 릴레이: 질의 메시지가 네트워크 망을 타고 올라가다가, 근처의 리전(Region) 라우터에 먼저 닿는다.
- 캐시 히트(Cache Hit) 판단: 해당 라우터 내부에 플러그인(Plugin) 형태로 장착된 스토리지/캐싱 노드가 자신이 보관 중인 메모리를 탐색한다. 타임스탬프와 TTL(Time-to-Live)이 유효한 최신 캐시가 존재한다면 즉시 반환한다.
- 다중 수집(Consolidation): 만약 로컬 라우터, 원격 클라우드 라우터, 그리고 원본 노드 세 곳에서 모두 응답이 온다면 어떻게 될까? Zenoh 계층은 2.5.3절에서 다룬 응답 병합 기능과 HLC 분산 타임스탬프(2.8.5절)를 활용하여, 가장 최신의 가장 타당한 데이터 단 하나만을 추려내어 클라이언트에게 예쁘게 포장해 넘겨준다.
sequenceDiagram
participant C as 클라이언트 (서울)
participant KR as 한국 리전 라우터 (캐시 장착)
participant UK as 영국 원본 센서
Note over UK, KR: 평상시 (캐싱 안 됨)
C->>KR: Get("london/weather")
KR->>UK: 라우팅 (Query 전달)
UK-->>KR: 15°C (Reply)
KR-->>C: 15°C (300ms 지연)
Note over KR: 캐시 장착. 15°C 메모리 보관.
C->>KR: Get("london/weather") 다시 요청
KR-->>C: 캐시 히트 (15°C) 즉시 응답 반환 (5ms 지연)
Note over UK: 원본 서버는 호출조차 받지 않음
결론적으로, 별도의 Redis 클러스터나 거창한 CDN(Content Delivery Network)을 구축하지 않더라도, 백업 메모리가 넉넉한 Zenoh 라우터에 플러그인 하나만 켜두는 것만으로 거대한 글로벌 지연 최적화를 공짜로 얻어낼 수 있다.
2. 인네트워크 스토리지(In-Network Storage) 아키텍처의 동작 원리
메시지 브로커(Broker)와 데이터베이스(Database)는 역사적으로 물과 기름처럼 섞이기 어려운 아키텍처였다. IoT 센서가 뱉어낸 데이터를 DB에 저장하려면, 엔지니어는 반드시 중간에 브로커 패킷을 가로채서(Subscribe) SQL 쿼리로 바꿔 인서트(Insert)를 날려주는 파이썬 데몬 스크립트 하나를 수동으로 띄워야만 했다.
**Zenoh(제노)**의 인네트워크 스토리지(In-network Storage) 체계는 이 지긋지긋한 파이프라인 브릿지를 무너뜨리고, 스토리지 자체를 거대한 통신망의 1등 시민 노드(First-class Node)로 승격시켰다.
2.1 퍼블리셔(Publisher)로 위장하는 스토리지 노드의 마법
Zenoh 라우터 안에 장착되거나 독립적인 피어(Peer) 데몬으로 떠 있는 스토리지 노드는, 그저 가만히 쓰기 요청을 기다리는 멍청한 하드디스크가 아니다.
자신이 관할할 키 경로(Key Path)가 예를 들어 robot/telemetry/** 로 설정되었다고 가정하자.
스토리지 노드가 켜지는 순간, 이 녀석은 메인 라우팅 테이블(DHT)에 스스로를 마치 수백 대의 robot/telemetry/ 퍼블리셔인 양 위장하여 등록(Declare)해 버린다. 즉 통신망 관점에서 스토리지 노드는 **“언제든 로봇 텔레메트리 값을 대답해 줄 수 있는 초거대 가상 센서”**가 되는 것이다.
2.2 쓰기(Write) 연산: 패킷 스니핑을 통한 자연스러운 저장
현장에서 실제 로봇 센서가 robot/telemetry/temp 라는 키로 배포(Put) 패킷을 네트워크에 뿜어낸다.
이때 스토리지 노드는 자신이 해당 와일드카드(**) 토픽의 서브스크라이버(Subscriber)임을 자처하여, 흘러가는 패킷의 복사본을 몰래 낚아채어 로컬 디스크 레이어에 차곡차곡 인서트(Put)한다. 메인 통신의 엔드-투-엔드 속도에는 1밀리초의 간섭도 주지 않는 완벽한 백그라운드 도청이다.
2.3 읽기(Read) 연산: 과거와 현재의 시공간 융합
어느 날 분석가 클라이언트 앱이 켜져서 session.get("robot/telemetry/temp")를 호출(Query)한다. 현재 현장의 센서는 전원이 꺼져 있다.
보통의 미들웨어라면 “해당 토픽의 활성 퍼블리셔가 없음(Timeout)” 이라며 에러를 뱉었을 것이다. 하지만 Zenoh 망에는 앞서 자신을 퍼블리셔로 위장해둔 스토리지 노드가 대기하고 있다가 질의를 덥석 문다. 스토리지 노드는 황급히 자기 백엔드 디스크 공간에서 가장 최신의 robot/telemetry/temp 값을 찾아내어, 마치 자신이 지금 막 측정한 센서인 것처럼 대답(Reply)을 돌려준다.
graph TD
subgraph 현장_엣지
Sensor[로봇 센서 노드]
end
subgraph Zenoh_네트워크_백본
Router[Zenoh 라우터]
StorageNode[(인네트워크 스토리지)]
end
subgraph 클라우드
App[대시보드 앱]
end
%% 센서 데이터가 흘러가면서 자연스럽게 저장됨
Sensor -- "1. Put(온도, 25도)" --> Router
Router -. "2. 가로채어 저장(도청)" .-> StorageNode
Router -- "2. 실시간 라우팅" --> App
%% 센서가 죽은 뒤 쿼리를 날릴 때
App -- "3. Get(온도)" --> Router
Router -- "3. Query 라우팅" --> StorageNode
StorageNode -- "4. 과거 데이터 Reply(25도)" --> Router
Router -- "4. Reply 릴레이" --> App
2.4 데이터 파편화의 방어
이처럼 Zenoh는 “현재 생산되고 있는 Data in Motion(메시지 찰나)“과 “과거에 저장된 Data at Rest(영구 스토리지)“의 경계를 완전히 지워버렸다. 사용자는 SQL, NoSQL, MQTT, REST를 번갈아 고민할 필요가 없다.
오직 2.4절에서 파악한 URI 스타일의 **키 경로(Key Path)**에 대고 Put을 날리면 저장되는 것이고, Get을 날리면 네트워크 어딘가에서 누군가가(그것이 진짜 센서든 스토리지 볼륨이든) 가장 최신 버전을 꺼내다 주는 경이로운 추상화 세계가 열린다.
3. 볼륨 백엔드(Volume Backends) 및 메모리/디스크 기반 저장소 연동 추상화
세상에는 수많은 훌륭한 형태의 저장소 모델이 존재한다. 초고속 처리를 위한 인메모리(RAM) 스토리지, 견고한 영속성을 위한 파일 시스템(RocksDB), 시계열 데이터에 최적화된 InfluxDB, 전통적인 릴레이셔널 테이블을 가진 MariaDB 등 각자의 장단점이 명확하다.
Zenoh(제노) 코어 그룹은 이 모든 엔진을 스스로 밑바닥부터 재창조하는 우를 범하지 않았다. 대신 Zenoh 프로토콜의 키-값(Key-Value) 구조를 세상의 모든 데이터베이스 구문으로 번역해 줄 수 있는 거대한 어댑터(Adapter) 계층, 즉 볼륨 백엔드(Volume Backends) 아키텍처를 설계했다.
3.1 프론트엔드와 백엔드의 분리 (볼륨 추상화)
Zenoh 라우터나 독립 데몬 프로세스에 탑재되는 스토리지는 뇌(두뇌) 역할을 하는 플러그인 프론트엔드 껍데기와, 실제 손발 역할을 하여 디스크에 I/O를 날리는 볼륨 백엔드 라이브러리로 분리되어 있다.
이 설계 덕분에 통신과 쿼리 병합 로직을 담당하는 핵심 Zenoh 코어는 밑단에 연결된 디스크가 MySQL인지 USB 플래시 드라이브인지 전혀 모른 채, 그저 “여기 URI 경로와 10바이트 데이터가 있으니 네가 알아서 저장해라“라고 명령을 내리게 된다.
3.2 공식 지원하는 대표적 볼륨 백엔드 엔진 플러그인 (Volume Plugins)
현시점(버전 1.0 체제) Zenoh 생태계가 공식적으로 제공하는(혹은 벤더가 구현 가능한) 주요 스토리지 백엔드의 갈래는 다음과 같다.
- 메모리(Memory) 백엔드: 휘발성(Volatile) 매체인 시스템 RAM을 그대로 버퍼로 사용한다. 속도는 타의 추종을 불허하지만, 노드가 꺼지면 데이터가 증발한다. 2.9.1절에서 언급한 고속 데이터 인네트워크 캐싱(L1 Cache) 용도로 극단적 성능 최적화가 필요할 때 쓴다.
- RocksDB / Sled 백엔드: 로컬 파일 시스템의 디렉터리를 지목하여 키-값 쌍을 바이너리 파일 형태로 영구 저장(Persistent)한다. 별도의 외부 DB 데몬 설치 없이 Zenoh 라우터만으로 에지 디바이스(Edge)를 소형 데이터센터로 만들어버릴 수 있는 가장 대중적이고 강력한 옵션이다.
- InfluxDB 백엔드: 시스템 온도 곡선이나 자율주행 차량의 초당 속도 변화 기록 등 시계열 특성이 강한 데이터를 위한 특화 플러그인. Zenoh의 HLC 타임스탬프(2.8.5절)를 인플럭스 고유의 시간축에 매핑하여 우아하게 시계열 쿼리에 대응한다.
- 관계형 / 커스텀 백엔드: C/Rust FFI 인터페이스 규약만 준수한다면 어떠한 레거시 사내 DB라도 Zenoh 스토리지의 볼륨으로 장착할 수 있도록 확장점(Extension Point)이 뚫려 있다.
3.3 라우터 설정 파일(JSON5/YAML)을 통한 백엔드 주입 예시
개발자는 단 한 줄의 하드 코딩 없이, 라우터 데몬을 띄울 때 옵션 설정 포맷(Configuration) 파일 수정을 통해 백엔드를 자유롭게 교체할 수 있다.
## Zenoh 라우터 환경 설정 파일 예시 (storages 섹션)
plugins:
storage_manager:
storages:
my_rocks_backend:
key_expr: "factory/machine-1/**" # 이 토픽으로 들어오는 데이터만 저장
volume: "rocksdb" # 사용할 백엔드 엔진 타입 선언
dir: "/var/lib/zenoh/factory_data" # RocksDB 파일이 내려앉을 물리적 리눅스 경로
단지 volume 필드 값을 rocksdb에서 memory로 텍스트 지우개로 슥 지워서 한 단어만 바꿔치기하면, 시스템 재구축 없이 밀리초 만에 하드디스크 로깅 시스템이 휘발성 초고속 램 디스크 캐시 시스템으로 돌변한다. 이것이 분산 데이터 버스 프레임워크가 제공해야 할 추상화의 진정한 모범이다.
4. 히스토리 데이터 조회를 위한 스토리지 노드의 투명한 쿼리 응답 원리
분산 시스템의 엔드 유저(통계 대시보드나 클라우드 관제 데몬)가 **과거의 이력 데이터(Historical Data)**를 조회해야 하는 순간, 기존 아키텍처들은 심각한 두뇌 분열(Split-Brain)을 요구해 왔다.
현재 1초 단위로 떨어지는 최신 센서 값을 보려면 실시간 퍼블리시-서브스크라이브(Pub/Sub) 통신 포트(예: MQTT)에 매달려야 하고, “어제 하루 치 온도 데이터“를 보려면 별도의 툴키트(예: MySQL Client)를 띄워 완전히 다른 방언(SQL Query)으로 외부 파이프라인에 접속해야만 했다. 즉, 개발팀은 두 가지 상이한 기술 스택을 완전히 따로 관리하고 코딩해야 하는 지옥을 겪어 왔다.
Zenoh(제노) 스토리지 아키텍처의 위대함은 실시간 현재 값과 영구 보관된 과거 이력값을 개발자가 구분하지 못하게끔 기만하는 **투명한 추상화(Transparent Abstraction)**에 있다.
4.1 시간 축을 관통하는 단일 쿼리(Query) 인터페이스 접속
2.4.3절에서 익혔던 Zenoh의 분산 쿼리 셀렉터(Selector) 문법 구조 하나면 모든 것이 끝난다. 클라이언트 애플리케이션은 대상이 어제 디스크에 박힌 돌(RocksDB 백엔드)이든 오늘 막 생성된 빛(In-memory 세션)이든 아무 상관 없이 똑같이 session.get() 이라는 단일 인터페이스 하나만 부르면 된다.
// 수 백대 센서들의 "과거와 현재를 가리지 않고" 데이터를 긁어오는 쿼리 예시
let replies = session.get("factory/machine-1/temp").wait().unwrap();
for reply in replies {
let sample = reply.sample.unwrap();
println!("시간: {}, 센서값: {:?}",
sample.timestamp.unwrap(), // 분산 타임스탬프로 과거/현재 구별
sample.value);
}
4.2 쿼리의 동적 라우팅 및 다중 응답 병합(Consolidation) 메커니즘
만약 get("factory/machine-1/temp") 쿼리를 던진 그 찰나의 순간에, 마침 통신망에 생생히 살아있는 에지 센서 노드가 막 새로운 최신 값을 퍼블리시(Put) 하려던 찰나였다고 치자. 동시에 런던의 클라우드에 떠 있던 백업 스토리지 노드 역시 파일 디스크에서 ’1초 전에 받은 어제의 마지막 데이터’를 꺼내 대답할 준비를 마친 상태다.
Zenoh 네트워크 코어는 다음과 같은 투명한 쿼리 응답 릴레이 동작을 기계적으로 수행한다.
- 쿼리 브로드캐스팅 확장: 이 쿼리는 라우터 간 토폴로지를 타고 해당
key_expr을 소유(Declare)했다고 주장하는 만방의 모든 노드들(진짜 센서 + 위장한 스토리지)에게 물밀듯이 뿌려진다. - 경쟁적 응답 폭탄: 살아있는 센서와 죽어있는 스토리지 100개가 다 같이 자기 말이 맞다며 100개의 대답(Reply)을 쏘아 올린다.
- 지능적 병합(Merge): 흩어진 응답 100개가 클라이언트 쪽 근거리 라우터(혹은 클라이언트 세션 코어 엔진)로 옹기종기 모여들 때, Zenoh 엔진 런타임은 패킷 헤더에 박혀있는 HLC 분산 타임스탬프(2.8.5절) 숫자를 기계적으로 오름차순 솔팅(Sorting)하고 검사한다.
- 최종 1인 선발: 결국 가장 미래의 시간(가장 최근 데이터 값)을 나타낸, ‘살아있는 센서가 막 보낸 대답’ 하나만이 승리하여 애플리케이션 프론트엔드 콘솔 창에 떨어진다.
만약 진짜 센서가 오프라인(전원 꺼짐) 이었다면? 응답 전쟁터에는 오롯이 스토리지 노드의 어제자 백업 데이터값 하나만이 덩그러니 살아남아 “이것이 나의 최선이다” 라며 클라이언트에게 최종 도달하게 된다.
4.3 과거, 현재, 미래를 아우르는 Data at Rest/In Motion 통합
결론적으로, 현존하는 가장 강력한 데이터 중심 분산 네트워크 프레임워크인 Zenoh 위에서 스토리지 통합 계층을 바라볼 땐 이런 거대한 깨달음을 얻어야만 한다.
“통신망을 빠르게 내달리는 데이터(Data in Motion)와 영겁의 시간 동안 하드디스크에 파묻혀있는 데이터(Data at Rest)는 분리된 물질이 아니다.”
스토리지는 그저 응답 시간이 조금 굼뜬, 또 다른 형태의 퍼블리셔일 뿐이다. 이 추상화 철학이 IoT 시스템의 백엔드 서비스 개발 비용 최소화와 로직 단순화에 궁극적인 해답을 던져주고 있다.