Push-based vs. Pull-based Reactivity: Fine-Grained Systems 뒤의 두 가지 구동 모델
Source: Dev.to

요약
반응성의 핵심 아이디어에 대한 이전 글을 바탕으로, 이번 파트에서는 푸시‑기반과 풀‑기반 반응성 모델의 차이를 명확히 합니다.
핵심 아이디어
세밀한 반응성에서는:
- 푸시‑기반 시스템은 값이 변할 때 즉시 계산을 수행합니다.
- 풀‑기반 시스템은 누군가 값을 읽을 때까지 계산을 미룹니다.
실제 예시
푸시‑기반
푸드 코트에서 음식을 주문한다고 생각해 보세요.
- 쓰기(주문): 주문을 합니다.
- 푸시(완료 알림): 음식이 준비되면 버저가 진동하거나 불이 켜집니다 — 업데이트가 바로 여러분에게 푸시됩니다.
- 효과(픽업): 카운터로 가서 음식을 가져갑니다.
반응성 해석: 소스가 변하면, 의존 노드가 즉시 재계산되고 바로 알림이 전달됩니다.
풀‑기반
버블티를 주문한다고 가정해 보세요.
- 쓰기(주문): 음료를 주문합니다.
- 마크(상태만 업데이트): 음료가 준비되면 가게는 화면에 번호만 표시합니다 — 직접 알림을 주지는 않습니다.
- 읽기 → 계산(필요할 때만): 화면을 확인하는 순간, “아, 준비됐네, 가서 가져가야겠다”는 읽기 연산이 트리거됩니다.
- 효과(픽업): 카운터로 가서 음료를 가져갑니다.
반응성 해석: 쓰기는 노드를 더럽게(Dirty) 표시만 할 뿐이며, 실제 계산은 누군가 값을 읽을 때 나중에 이루어집니다.
형식적 정의
| 모델 | 동작 초점 | 단순화된 흐름 |
|---|---|---|
| 푸시‑기반 | 쓰기 시 계산: 업데이트가 즉시 전파됨 | set() → propagate → compute → effect |
| 풀‑기반 | 쓰기 시 마크, 읽기 시 계산 | set() → markDirty ⏸ read() → if dirty → compute → effect |
핵심 인사이트: 두 모델 모두 “신호를 푸시”합니다 — 푸시는 계산을 푸시하고, 풀은 더러운 마크를 푸시합니다.
타임라인 다이어그램
푸시‑기반
풀‑기반
장단점
| 측면 | 푸시‑기반 | 풀‑기반 |
|---|---|---|
| 읽기 지연 | 가장 낮음 — 항상 최신 상태 | 첫 번째 읽기 시 재계산이 트리거될 수 있음 |
| 쓰기 비용 | 잠재적으로 높음: O(depth × writes) | 낮음: 대부분 O(depth × 1) (더러운 마크만) |
| 과잉 계산 | 높음 — 읽히지 않아도 계산함 | 낮음 — 실제 읽을 때만 계산 |
| 배치 처리 | 어려움 — 작업이 이미 수행됨 | 자연스러움 — 나중에 한 번에 플러시 가능 |
| 디버그 가시성 | 의존 체인이 즉시 확장됨 | 풀링이 발생할 때 DevTools가 필요 |
| 최적 사용 사례 | 높은 빈도의 쓰기, 낮은 읽기 (예: 협업 앱의 커서 동기화) | 낮은 쓰기, 높은 읽기 (대시보드, 차트) |
참고: 풀‑기반 시스템도 마킹 단계에서 의존 그래프를 탐색하지만, 재계산은 하지 않으며 dirty = true만 설정합니다.
어떻게 선택할까?
| 사용 사례 | 권장 모델 | 이유 |
|---|---|---|
| 실시간 협업, 게임 상태 동기화 | 푸시 | 즉시 반영이 필요하고, 불필요한 재계산을 피하기보다 빠른 읽기가 중요 |
| 대규모 대시보드, 데이터 시각화 | 풀 | 쓰기는 드물고 읽기가 빈번 — 실제 필요한 것만 계산 |
| 타임라인 / 스크롤 기반 애니메이션 | 풀 + 스케줄러 | 풀은 작업을 연기하고, 스케줄러는 프레임당 한 번씩 재계산 보장 |
| 데이터 파이프라인 (비싼 연산을 여러 번 재사용) | 푸시‑온‑커밋 | 한 번 미리 계산하고 이후에 재사용 |
React는 기본적으로 풀 + 스케줄러이며, 그래서 배치가 현재와 같이 동작합니다.
RxJS와 MobX는 전형적인 푸시‑온‑커밋 예시입니다.
흔한 오해
-
“풀은 전체 그래프를 스캔한다!”
아니오 — 풀은 값이 읽힐 때 관련된 의존 체인만 위쪽으로 확인합니다. 전체 트리를 순회하지 않습니다. -
“푸시는 항상 계산을 낭비한다.”
결과가 즉시 소비될 것이 보장될 때는 그렇지 않습니다 (예: 커서 이동). 낮은 읽기 지연이 추가 쓰기 비용을 능가할 수 있습니다. -
“푸시 vs 풀은 상호 배타적이다.”
대부분의 최신 시그널 시스템은 하이브리드 방식을 사용합니다:
푸시 → 쓰기 시 더러운 플래그 전파
풀 → 읽기 시 필요할 때만 계산.
이렇게 하면 반응성 및 지연성을 모두 얻을 수 있습니다. -
“왜 세밀한 시스템도 풀을 필요로 할까?”
이유는 알 수 없기 때문입니다:- 이 값이 언제든 읽히게 될까?
- 언제 읽히게 될까?
풀은 “데이터가 바뀌었다”(더럽게 표시)와 “이걸 가지고 뭘 해야 할까?”(읽을 때 계산)를 분리합니다.
결론
왜 세밀한 반응성에 푸시 vs 풀 논의가 필요한가?
코스(거친) 반응성 시스템(예: React의 Virtual DOM)에서는 전체 트리를 diff하는 것이 추상화 수준이 충분합니다.
하지만 시그널 기반 시스템에서는 단일 set()이 수백 개의 작은 파생으로 퍼질 수 있습니다.
계산 시점을 선택하면 다음에 영향을 미칩니다:
- 전체 연산 비용(성능)
- 인터랙션 지연(UI 부드러움)
- 스케줄링 동작(진동 및 프레임 손실 방지)
푸시와 풀을 이해하면 다양한 반응성 프레임워크를 평가할 때 필요한 사고 모델과 용어를 갖추게 됩니다.
다음 주제
다음 글에서는 주요 프레임워크들이 어떻게 반응성 시스템을 설계했는지, 그리고 왜 그런 선택을 했는지 살펴볼 예정입니다.

