folly::RecordIO — append-only 로그 파일 포맷
한 줄 요약:
RecordIO는 가변 길이 record를 append-only로 쓰고 mid-file부터도 안전하게 읽을 수 있는 파일 포맷이다. 각 record에 magic, length, checksum이 붙어 truncation/corruption을 견딘다.
#동기
append-only log 파일은 흔하다. event log, audit log, write-ahead log, message queue spool. 다음을 만족해야 한다.
- write가 atomic — partial write가 다음 read를 망가뜨리면 안 됨.
- mid-file부터 읽기 가능 — process crash 후 임의 offset에서 다음 valid record를 찾을 수 있어야.
- 각 record가 self-describing — length, checksum 포함.
- schema 유연 — record가 임의 binary blob.
이걸 매번 직접 짜면 endian, alignment, frame sync에서 실수가 나온다. RecordIO가 표준화한다.
#Frame 포맷
+----------------+----------------+----------------+----------------+| magic (4) | length (4) | fileId (4) | checksum (4) |+----------------+----------------+----------------+----------------+| payload (length bytes) |+-------------------------------------------------------------------+
총 헤더: 16 bytesmagic : 0xFADE0001 (sync word)length : payload 길이 (big endian)fileId : 같은 파일을 식별하는 32-bit (random per file)checksum : SpookyHashV2 결과 32-bit (header + payload)핵심은 두 가지.
- magic — 임의 offset에서 검색 시작점. magic이 발견되면 candidate record.
- checksum — magic이 우연히 데이터 안에 나올 수 있으므로 false positive 걸러냄.
- fileId — 두 RecordIO 파일이 어쩌다 concat되면 다른 fileId로 구분.
#API
#include <folly/io/RecordIO.h>
// write{ folly::File f("log.recordio", O_WRONLY | O_CREAT | O_APPEND); folly::RecordIOWriter writer(std::move(f));
writer.write(folly::IOBuf::wrapBuffer(data1, len1)); writer.write(folly::IOBuf::wrapBuffer(data2, len2));}
// read{ folly::File f("log.recordio", O_RDONLY); folly::RecordIOReader reader(std::move(f));
for (auto rec : reader) { folly::ByteRange payload = rec.first; off_t offset = rec.second; Process(payload); }}Writer는 단순 append, Reader는 iterator로 record를 streaming.
#Random-access seek + 복구
folly::RecordIOReader reader(std::move(f));
// 파일 중간 임의 offset에서 다음 valid record 찾기auto it = reader.seek(offset);for (; it != reader.end(); ++it) { Process(it->first);}seek(offset)은 offset 이후의 첫 valid record를 찾는다. magic을 forward 검색 → checksum 검증 → 통과한 record를 반환.
partial write/corruption 영역은 자동으로 skip. log file이 crash로 중간이 잘려도 다음 valid frame부터 복원.
#내부 구현
// folly/io/RecordIO.cpp 약식struct Header { uint32_t magic; uint32_t length; uint32_t fileId; uint32_t checksum;};
void RecordIOWriter::write(std::unique_ptr<folly::IOBuf> buf) { Header h; h.magic = kMagic; h.length = buf->computeChainDataLength(); h.fileId = fileId_; h.checksum = computeChecksum(h, *buf); // header + payload
pwrite(fd_, &h, sizeof(h), offset_); pwriteIOBuf(fd_, *buf, offset_ + sizeof(h)); offset_ += sizeof(h) + h.length;}
static uint32_t computeChecksum(const Header& h, const IOBuf& buf) { folly::hash::SpookyHashV2 sp; sp.Init(0, 0); // header without checksum field Header partial = h; partial.checksum = 0; sp.Update(&partial, sizeof(partial)); // payload for (auto& range : buf) { sp.Update(range.data(), range.size()); } uint64_t h1, h2; sp.Final(&h1, &h2); return static_cast<uint32_t>(h1);}writer는 단순 append. 한 record가 atomic하게 보이도록 한 번의 write로 (가능하면 writev/iovec) 보낸다.
// reader iteratorauto RecordIOReader::Iterator::operator++() -> Iterator& { for (;;) { if (offset_ >= fileSize_) { /* end */ return *this; } Header h; pread(fd_, &h, sizeof(h), offset_); if (h.magic != kMagic) { offset_ = findNextMagic(offset_ + 1); // forward search continue; } if (h.fileId != fileId_) { offset_ = findNextMagic(offset_ + 1); continue; } // 길이 sanity if (offset_ + sizeof(h) + h.length > fileSize_) { offset_ = findNextMagic(offset_ + 1); continue; } auto buf = readPayload(offset_ + sizeof(h), h.length); if (computeChecksum(h, *buf) != h.checksum) { offset_ = findNextMagic(offset_ + 1); continue; } current_ = {buf->coalesce(), offset_}; offset_ += sizeof(h) + h.length; return *this; }}reader는 각 record를 전부 검증. magic mismatch, fileId mismatch, length impossible, checksum mismatch 중 하나면 forward search로 다음 magic을 찾는다.
#사용 패턴
#Streaming write
folly::RecordIOWriter writer(folly::File("events.log", O_WRONLY | O_APPEND | O_CREAT));
void OnEvent(const Event& e) { auto buf = SerializeToIOBuf(e); writer.write(std::move(buf));}매 event마다 append. 순서 보존.
#Crash 복구
folly::RecordIOReader reader(folly::File("events.log", O_RDONLY));size_t valid = 0, recovered = 0;for (auto rec : reader) { if (rec.first.empty()) continue; Replay(rec.first); ++valid;}LOG(INFO) << "valid records: " << valid;crash 후 read 시 corrupt 영역은 자동으로 skip. fsync 안 한 write가 사라지더라도 valid 데이터는 모두 복원.
#std와의 비교
| 항목 | 표준 (없음) | folly::RecordIO | Apache Hadoop SequenceFile | Protobuf delimited |
|---|---|---|---|---|
| frame | N/A | magic+length+checksum | length+sync block | length-prefixed |
| 복구 | N/A | mid-file seek | sync marker | 전체 재처리 |
| checksum | N/A | SpookyHashV2 | optional | 없음 |
| append-safe | N/A | O | O | partial 가능 |
| schema | N/A | binary blob | typed | protobuf |
RecordIO는 프레임 자체에 집중. payload 안의 schema(JSON, Thrift, Protobuf 등)는 호출자 책임.
비슷한 포맷: Kafka log segment, Apache Avro container file, RocksDB WAL — 모두 magic+length+checksum 패턴이 표준.
#코드 리뷰 포인트
- write가
O_APPEND없이 열림 → 동시 write 시 truncation. 항상O_APPEND. - fsync 정책 — RecordIO는 fsync 안 함 (호출자 결정). durability 필요하면 명시적 fsync.
- record가 큼 (수십 MB) → IOBuf 체인이 한 번에 write되도록. memory pressure 확인.
- corruption 영역이 전체 file의 큰 비율이면 forward search가 매우 느림. 적절히 archive/rotate.
- fileId가 우연히 다른 파일과 같으면 concat된 파일에서 잘못 인식 — random 32-bit이라 확률 낮으나 zero가 의도된 값이면 충돌.
#자주 보는 안티패턴
// 1. fsync 없이 곧바로 process 종료 가능한 코드writer.write(buf);exit(0); // page cache가 disk에 안 갔을 수 있음// → fsync(fd) 또는 graceful shutdown
// 2. record가 너무 작음 (수 byte)for (int i = 0; i < 1e6; ++i) { writer.write(folly::IOBuf::wrapBuffer(&i, sizeof(i)));}// → 16-byte header overhead가 80%. 묶어서 write 또는 다른 포맷.
// 3. reader가 매 read마다 새 File 열기for (auto& path : files) { RecordIOReader r(File(path, O_RDONLY)); for (auto rec : r) { ... }}// → 정상 사용. 단 동일 파일을 반복 열면 page cache는 유지되지만 syscall은 누적.
// 4. checksum 무시for (auto rec : reader) { if (rec.first.empty()) continue; // forward search 결과는 OK // 하지만 reader가 그저 raw read만 한다면 checksum 검증 skipping 위험}#정리
RecordIO는 append-only log 파일의 표준 frame format.- magic + length + fileId + checksum (16 byte header).
- mid-file seek로 corruption 영역 자동 skip 후 복원.
- write는 단순 append, fsync는 호출자 결정.
- payload schema는 호출자가 결정 (binary blob).
#다음 편
Part 20-02: Compression에서 zstd/lz4/snappy wrapper를 본다.
#관련 항목
Folly Code Review · 84 of 89
- 1 Folly Code Review — Meta의 production-grade C++ 라이브러리 코드 분석
- 2 Folly 개요 — Meta가 production에서 검증한 utility 모음 분석
- 3 Folly vs Abseil 철학 비교 — performance-first vs std-compatible
- 4 Folly 빌드와 fbcode 환경 — monorepo의 그림자
- 5 Folly API stability 정책 — 어떤 보장도 없다는 솔직함
- 6 Folly production validation 문화 — peta-scale에서 단련된 코드
- 7 folly::Future 분석 — std::future의 한계를 넘는 composable async
- 8 folly::Promise·makeFuture — Future를 만드는 두 길
- 9 folly::SemiFuture vs Future — executor binding의 명시화
- 10 folly::Future thenValue·thenError·thenTry — continuation 체인 분석
- 11 folly::collect·collectAll·collectAny — fan-in 패턴 분석
- 12 folly::Future retry·window·via — 제어 흐름 조합자
- 13 folly::fibers 분석 — M:N stackful coroutine
- 14 folly::InlineExecutor — 호출자 thread에서 즉시 실행
- 15 folly::CPUThreadPoolExecutor — CPU-bound 작업의 표준 thread pool
- 16 folly::IOThreadPoolExecutor — libevent 기반 I/O pool
- 17 folly::ManualExecutor — 결정적 테스트를 위한 수동 진행
- 18 folly::EventBase 분석 — libevent 이벤트 루프의 핵심
- 19 folly::IOBuf 분석 — zero-copy buffer chain의 기본 단위
- 20 folly::IOBufQueue — chain의 push/pull 추상화
- 21 folly::io::Cursor·RWCursor — chain 위의 stream
- 22 folly Zero-copy 패턴 — IOBuf로 ScatterGather I/O 표현
- 23 folly::IOBuf shared semantics — clone·unshare·takeOwnership
- 24 folly::FBString 분석 — SSO + COW 구현
- 25 folly의 fmt::format 통합 — 모던 포맷팅 채택
- 26 folly::StringPiece — string_view 호환 분석
- 27 folly Join·Split utilities — 문자열 분해와 결합
- 28 folly::to·tryTo — text↔num 변환 분석
- 29 folly Conv Customization — 사용자 타입 지원
- 30 folly Conv 성능 비교 — sprintf·stringstream 대비
- 31 folly::F14ValueMap vs std::unordered_map
- 32 folly::F14NodeMap — stable pointer가 필요할 때
- 33 folly::F14VectorMap — cache-friendly iteration
- 34 folly::F14FastMap — auto-select 동작
- 35 folly F14 internals — SIMD probing 메커니즘
- 36 folly::small_vector — inline storage 분석
- 37 folly::FixedString — compile-time string
- 38 folly::AtomicHashMap — lock-free read 분석
- 39 folly::ConcurrentHashMap — sharded 동시 해시 맵
- 40 folly::EvictingCacheMap — LRU 구현 분석
- 41 folly::Synchronized — lock wrapper 패턴
- 42 folly::SharedMutex 분석
- 43 folly::Baton — one-shot wait 동기화
- 44 folly::RWSpinLock 분석
- 45 folly::PicoSpinLock — 1-byte spinlock
- 46 folly::ProducerConsumerQueue — SPSC 큐 분석
- 47 folly::MPMCQueue — multi-producer multi-consumer
- 48 folly::UnboundedQueue — 동적 크기 lock-free
- 49 folly::fibers::Channel — Go-like channel
- 50 folly::dynamic — JSON-like dynamic type 분석
- 51 folly JSON conversion — toJson·parseJson
- 52 folly dynamic ↔ struct — manual marshaling
- 53 folly dynamic Visitor pattern — type별 분기
- 54 folly::Singleton vs Meyers/static — 왜 Folly의 Singleton인가
- 55 folly::SingletonVault 분석 — 등록·소멸·의존성
- 56 folly::Singleton try_get·try_get_fast — TLS-cached 접근
- 57 folly::ExceptionWrapper — type-erased exception holder
- 58 folly::ScopeGuard·SCOPE_EXIT — RAII cleanup
- 59 folly::Optional vs std::optional
- 60 folly::Function vs std::function
- 61 folly::Lazy — 지연 초기화 wrapper
- 62 folly Meta 스타일 code review 패턴
- 63 folly anti-patterns — 잘못 쓰면 std보다 느림
- 64 folly vs std 선택 기준 분석
- 65 folly::coro 개요 — production C++20 코루틴 어댑터
- 66 folly::coro::Task — lazy single-shot 코루틴
- 67 folly::coro::AsyncGenerator — 비동기 스트림
- 68 folly coro blockingWait·collectAll — 동기 경계와 fan-in
- 69 folly::coro::Baton·Mutex — 코루틴-aware 동기화
- 70 folly::Expected — 결과 또는 오류
- 71 folly::Try — Future 결과 wrapper
- 72 folly::Try vs Expected 선택 기준
- 73 folly::Range — 일반 iterator pair
- 74 folly::Uri — URL 파서
- 75 folly Fingerprint64·128 — 분산 hash
- 76 folly SpookyHashV2 — fast non-crypto hash
- 77 folly::Init — main() 부트스트랩
- 78 folly::Indestructible — global lifetime 패턴
- 79 folly::MicroLock — 1-byte 락
- 80 folly::MicroSpinLock — 가장 좁은 spin lock
- 81 folly::format — legacy formatter 분석
- 82 folly::demangle — typeid 디망글링
- 83 folly::DynamicConverter — dynamic ↔ struct
- 84 folly::RecordIO — append-only 로그 파일 포맷
- 85 folly::io::Compression — zstd·lz4·snappy wrapper
- 86 folly::AsyncIO — io_uring·Linux AIO
- 87 folly::CancellationToken — 코루틴·Future 취소 전파
- 88 folly::observer — hot config의 atomic refresh
- 89 fbcode 패턴 모음 — folly 사용의 실전
관련 글
fbcode 패턴 모음 — folly 사용의 실전
Meta fbcode 코드 리뷰에서 반복적으로 등장하는 folly 사용 패턴 — overview + 시리즈 마무리.
같은 시리즈에서 이어 읽기
folly::observer — hot config의 atomic refresh
folly::observer — read mostly 값의 atomic refresh, hot config·feature flag·LB weight 같은 패턴의 표준.
같은 시리즈에서 이어 읽기
folly::CancellationToken — 코루틴·Future 취소 전파
CancellationSource/Token의 전파 모델 — coroutine·Future·callback 트리에서 협력적 취소.
같은 시리즈에서 이어 읽기