Ponder — 인덱서가 리오그 핸들러다
인덱서는 단순히 빠른 RPC 캐시가 아닙니다. rollback·replay·멱등성, 그리고 체인이 아직 재조직될 수 있는 동안 제품이 보여 주는 DB 상태를 소유합니다.
로컬 Anvil 체인의 작은 컨트랙트 하나에 Ponder 앱을 붙이고 PoC 다섯 단계를 실행합니다. 리오그는 Anvil 로 만듭니다: `evm_snapshot` → 전송이 담긴 블록 채굴 → `evm_revert` → 다른 블록 채굴 — 인덱서가 경쟁하는 팁 해시를 보게 됩니다. 표는 diff 전에 반드시 `ORDER BY` 를 붙여 export 합니다 — 덤프의 행 순서는 결정적이지 않아서, 정렬 없는 바이트 비교는 엉뚱한 이유로 실패합니다.
기술 노트
각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.
목적: 기존 replayability 카드는 불변식을 말로 세워 두었고, 이 PoC 는 프로덕션 라이브러리가 그 불변식을 실제로 사 주는지 시험합니다. 리오그 뒤에 낡은 행을 남기는 빠른 쿼리 계층은, 틀린 답을 더 빨리 돌려줄 뿐입니다. 인덱서는 애플리케이션이 진실로 취급하는 DB 상태 뒤의 rollback·replay 규칙을 소유합니다. 그리고 지원해야 할 rollback 의 깊이는 정확히 체인의 파이널리티 지연입니다: 미확정 구간만 고아가 될 수 있으므로 undo 장치는 그 구간에만 삽니다 — RPC 캐시는 이걸 고민할 필요가 없지만, 인덱서는 이것으로 정의됩니다.
동작 방식: Ponder 로 작은 컨트랙트 하나를 인덱싱하고, 로컬 리오그를 강제로 만든 뒤, 파생 행이 처음부터 다시 인덱싱한 결과와 바이트 단위로 같은지 확인합니다. 초기 동기화, 재시작 복구, RPC 호출 수, 스키마 변경 후 재구축 시간을 측정합니다. ### PoC 1. Ponder 로 작은 컨트랙트 하나의 전송을 인덱싱한다. 2. 같은 범위를 두 번 인덱싱해 멱등성을 확인한다. 3. 마지막 로컬 블록을 경쟁 해시로 교체한다. 4. 낡은 파생 행이 사라지고 결과가 클린 리플레이와 바이트 단위로 같은지 검증한다. 5. 초기 동기화, 재시작 복구, RPC 사용량, 스키마 변경 1회 후 재구축 시간을 기록한다. | 측정 | 질문 | | --- | --- | | 리플레이 동등성 | DB 를 버리고 정확히 재구축할 수 있는가? | | 리오그 롤백 | 고아가 된 이벤트가 파생 상태를 남기는가? | | 스키마 재구축 | 히스토리를 수작업 패치 없이 제품이 진화할 수 있는가? | 리오그 버그는 거의 전부 *파생* 상태에 삽니다. 고아가 된 원본 이벤트 삭제는 쉽지만(`DELETE WHERE block > N`), `balance += amount` 로 증분 갱신된 집계값은 역산하거나 재유도해야만 되돌릴 수 있습니다 — 그래서 테스트는 이벤트 로그가 아니라 파생 행을 검증합니다. 참고: [Ponder 공식 문서](https://ponder.sh/).
검토 후 보완: ### 인덱서는 선택지가 아니다 RPC 노드는 "X 의 *지금* 잔액"에는 답하지만, 제품이 던지는 질문 — "이 사용자의 모든 전송 내역 정렬해서", "상위 보유자", "일별 거래량" — 에는 답하지 못합니다. 실제 애플리케이션은 결국 체인 데이터를 DB 로 복사하게 되므로, 질문은 인덱서를 *쓸지 말지*가 아니라 *누가 만들었느냐* — 나냐, 라이브러리냐 — 뿐입니다. 그리고 그 DB 를 유지하는 쪽이 rollback·replay 규칙을 상속받습니다 — 그 상속이 이 카드의 논지입니다. ### PoC 각 단계가 존재하는 이유 | 단계 | 무엇을 잡아내나 | | --- | --- | | 같은 범위 두 번 인덱싱 | 핸들러의 숨은 상태 — 이중 집계는 결정성을 확인하는 가장 싼 테스트 | | 팁을 경쟁 해시로 교체 | 리오그 그 자체 — `evm_snapshot` → 채굴 → `evm_revert` → 채굴이면 노트북 위에서 즉석으로 만든다 | | 원본 행이 아니라 *파생* 행 검사 | 리오그 버그가 실제로 사는 곳 — `DELETE WHERE block > N` 은 쉽지만 `balance += amount` 는 역산 또는 재유도로만 되돌릴 수 있다 | | 클린 리플레이와 바이트 단위 비교 | 최고 기준: DB 는 체인의 순수 함수 — 벽시계 타임스탬프도, 순서 의존성도, 리오그 잔여물도 없다 | | 스키마 재구축 | DB 가 어느새 source of truth 가 되어버렸는지 — 파생 DB 는 히스토리 수작업 패치가 아니라 전체 재인덱싱(wipe-and-reindex)으로 진화한다 | ### 파이널리티 경계 인덱서가 유지해야 할 rollback 장치의 깊이는 정확히 그 체인의 파이널리티 지연입니다: 미확정 구간만 고아가 될 수 있으므로 undo 로그는 그 구간에만 살고, 블록이 확정되면 버려도 됩니다. RPC 캐시는 이걸 고민할 필요가 없지만, 인덱서는 이것으로 정의됩니다. ### 카드가 말하지 않는 것 카드는 "Ponder 가 좋다"고 말하지 않습니다. 추론 사슬 — 인덱서는 피할 수 없고, 인덱서가 리오그를 소유해야 한다 — 까지는 카드의 설정이지만, 마지막 걸음인 *그러므로 Ponder* 는 가정이 아니라 테스트가 벌어다 주는 결론입니다. Ponder 는 이 문제를 소유한다고 *주장*합니다(The Graph 서브그래프·Subsquid 에 맞서는 셀링 포인트가 리오그 처리 내장 로컬 우선 TypeScript). PoC 는 DB 를 걸기 전에 그 주장을 검증하려고 존재합니다. 통과하면 리오그 장치를 공짜로 얻은 것이고, 낡은 행이 하나라도 남으면 남는 것은 틀린 답을 빨리 돌려주는 쿼리 계층입니다.