GitHub 트렌딩 딥다이브 · 2026-06-24 · TrendShift 라이브 멘션

EntropyVerify 딥다이브
— AES-CTR로 '압축 불가능한 난수'를 디스크에 들이붓고, BLAKE3로 한 블록도 빠짐없이 검증하는 Rust 스토리지 검사 엔진

blamie/EntropyVerify. 저장장치(SSD·USB·SD카드)에 하드웨어 가속 AES-CTR 난수를 디스크 끝까지 써넣은 뒤 전부 다시 읽어, 한 블록이라도 깨졌으면 BLAKE3 체크섬으로 잡아내는 스토리지 검증 도구다. 낡고 느린 H2testw(윈도우 전용·단일 스레드)의 현대적 대안으로, 실제 용량 사기(가짜 용량 USB) 탐지PCIe 4.0/5.0 NVMe의 한계 속도 측정을 동시에 한다. 비결은 셋이다 — ① 하드웨어 AES-NI로 7GB/s 난수 생성, ② Direct I/O + 비동기 엔진(리눅스 io_uring · 윈도우 IOCP)으로 OS 캐시를 건너뛴 실측, ③ 코어 피닝 멀티스레드 + ratatui TUI 대시보드로 실시간 처리량 표시. (저장소: blamie/EntropyVerify · 언어 Rust 100% · 약 4,150줄 · 26개 모듈 · v1.0.0 / 2026-06-23 릴리스 · 라이선스 AGPL-3.0 · ★16 신생 — TrendShift 라이브 멘션, r/rust 화제)

목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석 (난수 한 블록이 검증되기까지)
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

"드라이브를 난수로 가득 채웠다가 다시 읽어 본다 — 다 똑같이 돌아오면 진짜, 하나라도 다르면 가짜이거나 고장."

한 줄로

EntropyVerify는 '압축할 수 없는 난수'를 디스크 끝까지 써넣고 그대로 다시 읽어, 용량을 속이는 가짜 저장장치와 조용히 망가지는 불량 섹터를 한 블록 단위로 잡아내는 '저장장치 거짓말 탐지기'다.

인터넷에는 "1TB"라고 적혀 있지만 실제로는 64GB짜리 칩에 펌웨어만 1TB로 속여 놓은 가짜 USB·SD카드가 넘칩니다. 64GB를 넘겨 쓴 데이터는 조용히 버려지거나 앞 데이터를 덮어쓰죠. 이걸 잡는 고전적 방법이 "디스크를 가득 채웠다가 되읽어 보는" 것인데, 단순히 0으로 채우면 SSD 컨트롤러가 "어차피 0이니까"라며 압축·중복제거로 속여 통과시켜 버립니다. EntropyVerify는 그래서 AES 암호로 만든 진짜 난수를 씁니다 — 압축이 안 되니 컨트롤러가 꼼수를 못 부리고, 실제로 매 바이트가 물리 셀에 기록됩니다. 그리고 되읽을 때 BLAKE3 해시로 한 블록 한 블록 대조해, 깨진 위치를 정확히 짚습니다. 이 모든 걸 Rust 단일 바이너리가 NVMe의 한계 속도(7GB/s+)로, 터미널 대시보드를 띄운 채 해냅니다.

용어
스토리지 검증 / H2testw
저장장치가 ① 표기된 용량만큼 실제로 데이터를 담는지, ② 쓴 데이터가 손상 없이 그대로 읽히는지를 확인하는 작업입니다. H2testw는 독일에서 만든 윈도우용 고전 도구로, 이 검사의 사실상 표준이었지만 단일 스레드라 현대 NVMe에선 너무 느리고 윈도우 전용입니다. EntropyVerify는 같은 아이디어를 멀티스레드·비동기·크로스플랫폼·하드웨어 가속으로 다시 쓴 것입니다.
용어
압축 불가능 데이터 (incompressible data)
패턴이 전혀 없어 더 작게 압축할 수 없는 데이터. 0으로만 채운 데이터는 "0이 100만 개"로 한 줄 요약(=압축)되지만, 잘 만든 난수는 요약이 불가능합니다. 저장장치 검사에서 이게 중요한 이유는, SSD/USB 컨트롤러가 내부적으로 압축·중복제거를 하기 때문입니다. 압축 가능한 데이터로 검사하면 실제로 셀에 안 쓰고도 통과해 버려, "진짜로 1TB를 다 쓸 수 있나"를 확인하지 못합니다. AES 난수는 통계적으로 완전한 난수라 이 함정을 원천 차단합니다.

좀 더 구체적으로, EntropyVerify는 Rust로 만든 명령줄(CLI) 도구이자 터미널 UI 앱입니다. entropy_verify --target-dir E:\test처럼 검사할 드라이브를 지정하면, 디스크의 빈 공간을 1GiB짜리 파일 여러 개로 나눠 각 파일을 2MiB 블록 단위로 AES-CTR 난수로 채워 씁니다(쓰기 단계). 그다음 그 파일들을 전부 다시 읽어, 각 블록의 난수를 같은 공식으로 재생성해 BLAKE3 해시가 일치하는지 대조합니다(검증 단계). 끝나면 마크다운 리포트(쓰기/읽기 속도, 피크 처리량, 깨진 블록 위치)를 드라이브에 남깁니다. 인자 없이 더블클릭하면 화살표로 드라이브를 고르는 대화형 TUI가 뜹니다.

이 문서가 파고드는 건 사용법만이 아니라 그 안의 시스템 프로그래밍입니다 — "데이터를 저장도 안 하는데 어떻게 검증하나(결정론적 난수)", "왜 버퍼를 4096바이트에 맞춰야 하나(Direct I/O)", "io_uring과 IOCP는 무엇이고 왜 7GB/s가 나오나". 암호·해시·비동기 I/O·무잠금 동시성·Rust unsafe를 한 레포에서 실물로 배우고 싶은 사람에게 드물게 좋은 교본입니다.

2왜 주목받는가

"20년 묵은 H2testw를, 현대 하드웨어(AES-NI·io_uring·NVMe)를 제대로 쓰는 Rust로 다시 썼다."

먼저, 풀려는 문제 — '용량을 속이는 디스크'와 '느린 검사 도구'

저장장치 검사에는 두 가지 고질적 문제가 있습니다. 하나는 가짜 용량(fake flash) — 싸구려 USB·SD카드가 펌웨어로 용량을 부풀려 파는 사기입니다. 다른 하나는 검사 도구가 너무 느린 것 — 표준 도구 H2testw는 단일 스레드 동기 I/O라, 7GB/s를 내는 PCIe 5.0 SSD를 검사하는 데 답답할 만큼 오래 걸립니다.

함정
"0으로 채워 검사하면 된다"는 착각

드라이브를 0이나 단순 패턴으로 채워 검사하면, 똑똑한 SSD 컨트롤러가 압축·중복제거로 통과시켜 버립니다 — 실제로 셀에 안 쓰고도 "다 썼다"고 보고하죠. 그러면 가짜 용량도, 약한 셀도 못 잡습니다. 또 OS의 파일 캐시 때문에 "방금 쓴 걸 메모리에서 되읽어" 디스크를 건드리지도 않고 통과하는 일도 흔합니다.

해결
압축 불가능 AES 난수 + Direct I/O로 '진짜 물리 셀'을 때린다

EntropyVerify는 AES-CTR로 만든 통계적 완전 난수를 써서 압축·중복제거를 무력화하고, Direct I/O(리눅스 O_DIRECT · 윈도우 FILE_FLAG_NO_BUFFERING)로 OS 캐시를 통째로 건너뛰어 진짜 디스크 속도를 잽니다. 게다가 난수를 (파일번호, 블록번호)에서 결정론적으로 재생성하므로, 기댓값을 따로 저장할 필요 없이 되읽으며 즉석 검증합니다.

비결은 '현대 하드웨어를 제대로 쓴다'는 것

이 프로젝트가 r/rust에서 화제가 된 이유는 단순한 재구현이 아니라 하드웨어의 모든 가속 경로를 동원했기 때문입니다.

가속 포인트무엇을효과
AES-NICPU 내장 AES 명령으로 난수 생성단일 코어 >7GB/s — 디스크가 병목이 되게 함
BLAKE3AVX2/SSE 벡터화 해시~2바이트/사이클, 검증이 쓰기 속도를 못 따라가지 않게
io_uring / IOCP커널 비동기 I/O큐를 가득 채워(QD32) NVMe 병렬성 활용
Direct I/OOS 페이지 캐시 우회캐시 착시 없는 실측 속도·진짜 셀 검증
코어 피닝워커 스레드를 전용 코어에 고정캐시 지역성↑·컨텍스트 스위치↓, 코어 0은 UI 전용
비유

H2testw = 양동이 하나로 수영장을 채우는 사람. 방법은 옳지만 양동이가 하나(단일 스레드)라, 소방 호스(NVMe)가 있어도 그 속도를 못 냅니다.

EntropyVerify = 코어마다 전용 소방 호스를 잡은 팀. 각 호스(워커 스레드)가 자기 수도꼭지(CPU 코어)에 고정돼, 압축 안 되는 물(AES 난수)을 캐시 우회 직배관(Direct I/O)으로 동시에 들이붓습니다. 그래서 디스크의 한계 속도가 그대로 드러납니다.

고전 도구들과 비교

관점EntropyVerifyH2testwdd / badblocks
검사 데이터AES-CTR 난수(압축 불가)의사난수(느림)0/패턴 또는 난수
병렬성멀티스레드 + 비동기 QD단일 스레드 동기대체로 단일 스트림
캐시 우회Direct I/O 기본제한적옵션(oflag=direct)
무결성 확인블록별 BLAKE3전체 비교badblocks만 부분 지원
플랫폼윈도우·리눅스윈도우 전용리눅스 위주
UI실시간 TUI 대시보드단순 진행바거의 없음
안전장치시스템 파티션 쓰기 차단수동 주의매우 위험(오타 한 번에 파괴)
함정 (솔직하게)
아직 아주 어린 프로젝트 — 그리고 데이터 파괴적 도구

냉정히 보면 EntropyVerify는 ★16, 커밋 2개, v1.0.0(2026-06-23)의 갓 태어난 레포입니다. 공개 릴리스 바이너리는 윈도우용만 올라와 있고(리눅스는 직접 빌드), 라이선스는 상용 통합이 까다로운 AGPL-3.0입니다. 무엇보다 이 도구는 대상 드라이브를 가득 채워 쓰는 파괴적 작업이므로, 빈 드라이브에만, 안전장치를 믿되 두 번 확인하고 써야 합니다. "성능 수치(7GB/s 등)"는 개발자가 README에 제시한 값으로, 하드웨어에 따라 편차가 큽니다.

3기술 스택 전체 지도

"전부 Rust. 외부 서비스·런타임 0. 의존성 목록만 봐도 '하드웨어를 직접 만진다'는 설계 의도가 읽힌다."

EntropyVerify는 데이터베이스도, 네트워크도, 외부 라이브러리 서버도 없는 순수 단일 바이너리입니다. 대신 의존성이 암호·비동기 I/O·동시성·OS 시스템콜에 또렷이 몰려 있어, Cargo.toml이 곧 아키텍처 설명서입니다.

① 암호 · 해시 (검사 데이터의 핵심)

크레이트역할한 줄 설명
aes 0.8 · ctr 0.9 · cipher 0.4난수 생성AES-128을 CTR(카운터) 모드로 돌려 압축 불가능한 키스트림을 생성. .cargo/config.toml+aes 플래그로 AES-NI 하드웨어 가속.
blake3 1.5무결성 검증각 블록 페이로드의 32바이트 BLAKE3 해시. AVX2/SSE로 자동 벡터화되어 매우 빠름.
rand 0.8마스터 키시드 미지정 시 16바이트 암호학적 난수 키를 생성(--seed로 고정 가능 → 재현성).
용어
AES-128-CTR · 키스트림 · 논스(nonce)
AES는 블록 암호(16바이트 덩어리를 섞는 암호)인데, CTR(카운터) 모드로 쓰면 "0,1,2,…" 카운터를 암호화한 결과를 줄줄이 이어 붙여 무한한 난수열(키스트림)을 만듭니다 — 즉 블록 암호가 스트림 암호로 변신합니다. 이 키스트림을 데이터에 XOR하면 암호화가 되는데, EntropyVerify는 0에 XOR하므로 결과가 곧 순수 키스트림(=난수)입니다. 논스는 카운터의 시작점으로, 여기선 [파일번호 4B | 블록번호 4B | 0×8]로 만들어 블록마다 절대 겹치지 않는 고유 난수를 보장합니다.
용어
AES-NI
2010년 이후 인텔/AMD CPU에 들어간 AES 전용 하드웨어 명령어(aesenc 등). 소프트웨어로 수십 사이클 걸릴 AES 한 라운드를 한 명령에 처리해, 단일 코어로도 7GB/s가 넘는 키스트림이 나옵니다. EntropyVerify는 이걸 컴파일 시점에 강제(target-feature=+aes)하기 때문에, 역설적으로 AES-NI 없는 옛 CPU에선 바이너리가 아예 실행되지 않습니다.

② 비동기 I/O 엔진 (플랫폼별로 다른 OS API)

크레이트플랫폼설명
io-uring 0.6 · libcLinuxio_uring으로 커널 비동기 I/O. 실패하면(커널 <5.6 등) pread/pwrite 동기 폴백. O_DIRECT·posix_memalign·fdatasync도 libc로 직접 호출.
windows-sys 0.52WindowsIOCP(I/O Completion Ports) + FILE_FLAG_NO_BUFFERING/WRITE_THROUGH. 페이지 정렬 버퍼는 VirtualAlloc로 확보.
용어
Direct I/O (O_DIRECT / FILE_FLAG_NO_BUFFERING)
평소 파일 쓰기는 OS의 페이지 캐시를 거칩니다(빠른 대신, 실제 디스크에 언제 쓰일지 모름). Direct I/O는 이 캐시를 건너뛰고 응용프로그램 버퍼 ↔ 디스크를 직결합니다. 벤치마크엔 필수입니다 — 캐시가 끼면 "방금 쓴 걸 메모리에서 되읽어" 디스크 실속도가 가려지니까요. 대가로 버퍼가 섹터 크기(보통 4096B)에 정렬돼 있어야 한다는 까다로운 제약이 붙습니다.

③ 동시성 · TUI · 시스템

크레이트역할설명
crossbeam-channel 0.5스레드 간 통신워커→메인 이벤트 채널(에러·손상 보고)과 메인→워커 작업 큐(파일 번호 분배).
core_affinity 0.8 · num_cpus코어 피닝물리 코어 수를 감지해 워커를 전용 코어에 고정, 코어 0은 TUI에 양보.
ratatui 0.29 · crossterm터미널 UI실시간 처리량 스파크라인·진행바·드라이브 선택 메뉴를 그리는 대시보드.
bytemuck 1제로카피 직렬화64바이트 블록 헤더 구조체를 복사·변환 없이 바이트로 보고/되돌림(Pod/Zeroable).
clap 4 (derive)CLI 파싱--target-dir·--block-size·--threads 등 인자를 구조체로 선언.
sysinfo · ctrlc · chrono · anyhow유틸드라이브 정보 조회 · Ctrl-C 우아한 종료 · 타임스탬프 · 에러 처리.

④ 빌드 · 배포 — "최적화를 끝까지 짜낸다"

릴리스 프로파일과 빌드 스크립트도 성능 철학을 드러냅니다.

# Cargo.toml — release 프로파일
[profile.release]
opt-level = 3        # 최고 최적화
lto = "thin"         # 링크 시점 최적화(크레이트 경계 너머 인라인)
codegen-units = 1    # 병렬 컴파일 포기하고 최적화 품질↑
strip = true         # 심볼 제거 → 작은 바이너리
panic = "abort"      # 패닉 시 되감기 없이 즉시 종료(빠르고 작음)

.cargo/config.toml은 x86_64에 target-feature=+aes,+sse4.1을 박아 AES-NI·SSE4.1을 컴파일 시점에 켜고, build.rs는 타깃 아키텍처/OS를 감지해 cfg 플래그(has_aes_ni 등)를 내보냅니다. 즉 "이 바이너리는 현대 x86_64 + AES-NI를 가정하고 빌드된다"는 선언입니다.

4아키텍처 심화 분석 (난수 한 블록이 검증되기까지)

"두 개의 막 — 쓰기(난수를 들이붓는다)와 검증(되읽어 BLAKE3로 대조한다). 그 사이를 원자적 카운터와 채널이 잇는다."

전체 데이터 흐름 한 장

── 준비 ──────────────────────────────────────────── CLI 인자 / 드라이브 선택 TUI │ 안전 검사(시스템 파티션·임계 마운트 차단 + 센티넬 쓰기) ▼ TestPlan 계산: 가용공간 ÷ 1GiB = 파일 N개, 각 파일 = 2MiB 블록 × M │ 작업 큐(crossbeam)에 파일번호 0..N 투입 ▼ ── 쓰기(WRITE) 단계 ───────────────────────────────── 워커1 워커2 … 워커K (각자 전용 코어에 피닝, 코어0=TUI) │ │ │ 각 워커 = 자기만의 I/O 엔진 1개 ▼ ▼ ▼ [작업 큐에서 파일번호 하나 꺼냄] ──▶ 파일 O_DIRECT로 열기 │ ├─ 블록마다: ① AES-CTR로 페이로드 채움(논스=파일·블록번호) │ ② BLAKE3 해시 계산 → 64B 헤더에 스탬프 │ ③ 비동기 쓰기 제출(io_uring SQ / IOCP) ▼ 드라이브 ◀══ QD32로 큐를 채워 NVMe 병렬성 포화 ══ │ close 시 drain(완료 대기) + fdatasync ▼ ── 검증(VERIFY) 단계 ─────────────────────────────── [파일을 다시 O_DIRECT로 읽기 — 파이프라인: 먼저 QD개 read 제출] │ 완료될 때마다: ① 헤더 파싱(매직 "EVFY"·버전 확인) │ ② 페이로드 BLAKE3 재계산 → 헤더값과 대조 │ ③ 난수 재생성 → 또 한 번 교차 검증 │ ④ 어긋나면 CorruptBlock 이벤트 발사 ▼ 새 read를 즉시 다시 제출(파이프라인 유지) ── 표시 / 마무리 ─────────────────────────────────── 원자적 카운터(AtomicU64) ──▶ TUI 대시보드(스파크라인·ETA) 이벤트 채널 ──▶ 에러·손상 로그 ──▶ 마크다운 리포트 저장

핵심은 상태기계 + 워커 풀 + 무잠금 통신입니다. 앱 전체가 Idle → Writing → Verifying → Complete/Failed 다섯 단계를 오가고, 무거운 일은 워커 스레드들이, 화면은 메인 스레드가 맡습니다. 아래에서 설계 포인트를 하나씩 봅니다.

설계 ① — 결정론적 난수: '기댓값을 저장하지 않는다'

가장 영리한 절약입니다. 보통 "쓴 걸 검증"하려면 기댓값을 어딘가 저장해 둬야 하는데, 그러면 검사 데이터만큼의 저장공간이 또 필요합니다. EntropyVerify는 난수를 (파일번호, 블록번호)에서 결정론적으로 생성하므로, 검증 때 같은 좌표로 같은 난수를 다시 만들어 비교하면 됩니다 — 기댓값 저장이 0입니다.

// crypto/datagen.rs — 논스를 좌표에서 유도
fn derive_nonce(file_index: u32, block_index: u32) -> [u8; 16] {
    let mut nonce = [0u8; 16];
    nonce[0..4].copy_from_slice(&file_index.to_le_bytes());
    nonce[4..8].copy_from_slice(&block_index.to_le_bytes());
    // 8..16 바이트는 0 — CTR 카운터 공간
    nonce
}
// 0으로 채운 버퍼에 키스트림을 XOR → 결과 = 순수 난수
buf.fill(0);
cipher.apply_keystream(buf);
비유

구구단을 통째로 외워 적어 두는(=기댓값 저장) 대신, "몇 단 몇 번?"이라는 좌표만 받으면 즉석에서 답을 계산하는 것과 같습니다. 답을 보관할 공책이 필요 없죠. (파일번호, 블록번호)가 좌표, AES-CTR이 계산 규칙입니다.

설계 ② — 블록 포맷: 64바이트 헤더 + 2MiB 페이로드, 제로카피로

각 2MiB 블록은 앞 64바이트가 구조화된 헤더(매직 EVFY·버전·스레드ID·파일/블록번호·AES 논스·BLAKE3 해시)이고 나머지가 난수 페이로드입니다. 헤더 구조체는 #[repr(C)] + bytemuck::Pod라, 복사·직렬화 라이브러리 없이 메모리 표현을 그대로 바이트로 보고 되돌립니다(제로카피).

// engine/block.rs
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct BlockHeader {
    pub magic: [u8; 4],       // "EVFY"
    pub version: u8, pub _pad: u8, pub thread_id: u16,
    pub file_index: u32, pub block_index: u32,
    pub aes_nonce: [u8; 16],  // 이 블록을 만든 논스
    pub blake3_hash: [u8; 32],// 페이로드의 BLAKE3
}
// 컴파일 시점에 크기를 못박는다 — 64B가 아니면 빌드 실패
const _: () = assert!(std::mem::size_of::<BlockHeader>() == 64);

컴파일 타임 단언(const _: () = assert!(…))이 백미입니다 — 구조체 필드를 잘못 건드려 크기가 틀어지면 실행이 아니라 컴파일에서 잡힙니다. 디스크 포맷처럼 "바이트 한 칸도 틀리면 안 되는" 곳에 딱 맞는 안전장치입니다.

설계 ③ — Direct I/O와 페이지 정렬 버퍼

Direct I/O를 쓰려면 버퍼가 4096바이트 경계에 정렬돼야 합니다. 평범한 Vec<u8>은 그 보장이 없어, EntropyVerify는 OS별로 정렬 메모리를 직접 할당하는 AlignedBuffer를 만들었습니다 — 윈도우는 VirtualAlloc, 리눅스는 posix_memalign. 그리고 Deref<Target=[u8]>를 구현해, 쓸 때는 보통 슬라이스처럼 다루게 했습니다.

용어
페이지 정렬 버퍼 (page-aligned buffer)
메모리 주소가 4096(=한 페이지)의 배수인 버퍼. Direct I/O는 DMA가 직접 데이터를 옮기기 때문에 하드웨어가 요구하는 정렬을 맞춰야 합니다. AlignedBuffer는 이 raw 포인터를 소유·해제(Drop)까지 책임지는 작은 RAII 래퍼로, Rust에서 unsafe를 안전한 추상 뒤에 가두는 전형적 패턴입니다(unsafe impl Send/Sync로 스레드 이동 허용).

설계 ④ — 비동기 I/O 엔진: io_uring과 폴백, 큐depth 파이프라인

리눅스 엔진은 io_uring으로 "쓰기/읽기 요청을 제출(submit)하고, 완료(completion)를 나중에 회수"하는 구조입니다. 슬롯(버퍼+상태) 풀을 만들어 큐 깊이(QD)만큼 동시에 띄웁니다. io_uring 초기화가 실패하면 조용히 pread/pwrite 동기 모드로 폴백합니다 — 구형 커널 호환.

// engine/io/linux.rs — io_uring 초기화와 폴백
let inner = match io_uring::IoUring::builder().build(queue_depth) {
    Ok(ring) => EngineInner::IoUring(ring),
    Err(e)  => { eprintln!("io_uring 실패({}), 동기 I/O로 폴백", e);
                 EngineInner::Synchronous }
};

검증 단계의 파이프라이닝이 특히 교과서적입니다 — 먼저 QD개 읽기를 미리 제출해 두고, 완료가 하나 돌아올 때마다 그 블록을 검증하고 즉시 다음 읽기를 제출합니다. 그래서 디스크는 항상 "할 일이 가득"이라 쉬지 않습니다.

용어
io_uring · IOCP · 큐 깊이(queue depth)
io_uring(리눅스)과 IOCP(윈도우)는 둘 다 "I/O를 비동기로 던져 놓고 완료만 회수"하는 OS 메커니즘입니다 — 매 I/O마다 스레드가 멈춰 기다리는 낭비를 없앱니다. 큐 깊이는 "동시에 진행 중인 I/O 개수"로, NVMe SSD는 내부에 수많은 채널이 있어 큐를 깊게(예: 32) 채워야 최대 속도가 납니다. 큐가 얕으면(QD1) 빠른 SSD도 절반도 못 냅니다.

설계 ⑤ — 무잠금 동시성: 원자적 카운터 + 채널 + Condvar

워커들이 1초에 수만 블록을 쓰는데, 진행률 표시를 위해 매번 락을 잡으면 그 자체가 병목입니다. EntropyVerify는 락을 거의 안 씁니다:

진행 카운터AtomicU64fetch_add(Relaxed) — 워커는 더하고, TUI는 읽기만. 락 0.
이벤트 보고crossbeam-channel로 에러·손상 블록을 메인에 비동기 전송.
일시정지(Mutex<bool>, Condvar)[P] 키로 워커를 재웠다 깨움.
중단Arc<AtomicBool> 플래그 — 워커들이 루프마다 확인.
// 워커는 그냥 더한다 — 잠금 없음
cfg.metrics.bytes_written.fetch_add(block_size, Ordering::Relaxed);
// TUI는 그냥 읽는다 — 50ms마다 처리량 환산
let delta = current.saturating_sub(last);
self.current_throughput = delta as f64 / elapsed;

설계 ⑥ — 안전장치: '시스템 드라이브를 절대 안 건드린다'

파괴적 도구이므로 다중 가드레일을 둡니다. 대상이 OS 루트 파티션(C:\·/)이면 거부, 리눅스의 /boot·/etc·/usr·/var·스왑 같은 임계 마운트도 차단, 그리고 실제로 쓰기 전 센티넬 파일(.entropy_verify_sentinel)을 만들었다 지워 쓰기 권한을 능동 확인합니다. 디스크는 100MiB 안전 여유를 남겨 100%까지 채우지 않습니다.

설계 철학 한 줄

"데이터를 저장하지 말고 다시 계산하라. 캐시를 믿지 말고 직격하라. 락을 잡지 말고 원자적으로 더하라."

이 셋이 EntropyVerify 전체를 관통합니다 — 결정론적 난수(기댓값 저장 0), Direct I/O + 비동기 큐(캐시 착시 0·디스크 포화), 원자적 카운터 + 채널(잠금 경합 0). 거기에 제로카피 헤더코어 피닝이 더해져, "단일 바이너리가 NVMe를 끝까지 밀어붙이는" 그림이 완성됩니다.

5디렉토리 구조 해부

"src/ 하나에 26개 모듈. 역할별로 폴더가 깔끔히 갈려 있어, 폴더 이름만 봐도 '암호·엔진·I/O·동시성·안전·UI'의 부품 목록이 보인다."

최상위 한눈에

EntropyVerify/ ├── README.md ← 기능·블록 포맷·사용법 ├── LICENSE ← AGPL-3.0 └── entropy_verify/ ← 실제 Rust 프로젝트 ├── Cargo.toml ← 의존성 + release 프로파일 ├── build.rs ← 빌드 시 cfg 플래그(has_aes_ni 등) 주입 ├── .cargo/config.toml ← target-feature=+aes,+sse4.1 강제 └── src/ ├── main.rs (412) ← 진입점·단계 조율·이벤트 루프 ├── app.rs (368) ← 상태기계·원자 메트릭·이벤트 정의 ├── config.rs (228) ← clap CLI + TestPlan(파일/블록 계산) ├── units.rs (164) ← GB/GiB 단위 변환·표시 ├── crypto/ │ ├── datagen.rs (120) ← AES-CTR 난수 생성기 │ └── hasher.rs (49) ← BLAKE3 해시 ├── engine/ │ ├── block.rs (287) ← 64B 헤더 + prepare/verify 블록 │ ├── writer.rs (164) ← 쓰기 워커 루프 │ ├── verifier.rs (206)← 검증 워커 파이프라인 │ └── io/ │ ├── aligned.rs (190) ← 페이지 정렬 버퍼 │ ├── linux.rs (303) ← io_uring 엔진(+동기 폴백) │ ├── windows.rs (369) ← IOCP 엔진 │ └── mod.rs (22) ← 컴파일타임 플랫폼 디스패치 ├── threading/affinity.rs (61) ← 코어 피닝 ├── safety/ │ ├── guardrails.rs (163) ← 대상 검증(차단 규칙) │ └── platform.rs (162) ← 시스템 파티션/마운트 판정 ├── tui/ │ ├── dashboard.rs (284) ← 메인 대시보드 │ ├── drive_selection.rs (211) ← 드라이브 선택 메뉴 │ ├── widgets.rs (128)·events.rs (38) └── report/markdown.rs (176) ← 마크다운 리포트 생성
용어
모듈(module) 트리 · 컴파일타임 디스패치
Rust는 폴더/파일이 곧 모듈입니다. engine/io/mod.rs가 흥미로운데, #[cfg(target_os = "linux")]OS에 따라 다른 엔진 타입을 골라 PlatformEngine이라는 같은 이름에 묶습니다. 그래서 상위 코드(writer/verifier)는 OS를 신경 쓰지 않고 PlatformEngine::new(...)만 부르면 됩니다 — 런타임 분기 0, 전부 컴파일 시점에 결정되어 그 플랫폼 코드만 바이너리에 들어갑니다.

26개 모듈을 역할로 묶어 보기

묶음모듈하는 일
조율·상태main.rs, app.rs, config.rs, units.rs진입점·단계 전이(상태기계)·CLI/계획·단위 변환. 무게중심.
암호·해시crypto/datagen.rs, crypto/hasher.rsAES-CTR 난수 + BLAKE3 — 검사 데이터의 생성·검증 핵심.
엔진(블록)engine/block.rs, writer.rs, verifier.rs64B 헤더 포맷 + 쓰기/검증 워커 루프(파이프라인).
엔진(I/O)engine/io/{aligned,linux,windows,mod}.rs정렬 버퍼 + io_uring/IOCP 비동기 엔진 + 플랫폼 디스패치.
동시성threading/affinity.rs물리 코어 감지 + 워커 코어 피닝(코어 0은 TUI 양보).
안전safety/guardrails.rs, platform.rs시스템 파티션·임계 마운트 차단 + 쓰기권한 센티넬 검사.
UI·리포트tui/*.rs, report/markdown.rs실시간 대시보드·드라이브 선택 + 마크다운 결과 리포트.

가장 큰 파일이 말해 주는 것

코어 로직에서 engine/io/windows.rs(369)·linux.rs(303)가 가장 큰 건, "이 프로젝트의 진짜 난이도는 암호가 아니라 OS별 비동기 I/O에 있다"는 뜻입니다(암호는 라이브러리가 해 주지만, IOCP·io_uring·정렬·DMA는 직접 다뤄야 함). main.rs(412)·app.rs(368)가 큰 건 단계 조율과 상태/메트릭 관리가 그만큼 촘촘하다는 뜻이고, tui/(660여 줄)가 큰 건 "보기 좋은 실시간 대시보드"에 생각보다 코드가 많이 든다는 현실을 보여 줍니다.

6학습 포인트 (기술별 — 배울 것 + 실습 아이디어)

"한 레포 안에 대칭암호·해시·Direct I/O·비동기 커널 API·무잠금 동시성·제로카피·Rust unsafe가 전부 실물로 들어 있다."

① 대칭암호 CTR 모드 + 하드웨어 가속(AES-NI)

배울 것: 블록 암호(AES)를 CTR 모드로 돌려 스트림 암호(=난수 생성기)로 쓰는 핵심 발상, 논스/카운터 설계, "0에 XOR하면 키스트림이 그대로 나온다"는 트릭. 그리고 +aes 타깃 피처로 AES-NI를 켜는 법과 그 대가(구형 CPU 비호환).

실습: 파이썬 cryptography나 Rust aes/ctr로 "카운터를 1씩 올리며 16바이트씩 암호화"해 난수 1MB를 만들고, gzip으로 압축이 거의 안 되는지 확인 = "압축 불가능 데이터"를 직접 체감.

② 무결성 해시 — BLAKE3

배울 것: 데이터 손상을 잡는 체크섬/해시의 역할, BLAKE3가 왜 빠른지(트리 구조 + SIMD 벡터화), "보안 비교가 아니니 상수시간 비교가 필요 없다"는 실용적 판단(hasher.rs 주석).

실습: 1MB 데이터의 BLAKE3 해시를 구한 뒤 한 바이트만 뒤집어 다시 해시 → 완전히 달라지는 걸 확인(쇄도 효과). verify_block이 손상을 잡는 원리와 동일.

③ Direct I/O + 페이지 정렬 버퍼

배울 것: OS 페이지 캐시가 벤치마크를 어떻게 속이는지, O_DIRECT/FILE_FLAG_NO_BUFFERING으로 그걸 우회하는 법, 그리고 그 대가인 정렬 요구posix_memalign/VirtualAlloc로 푸는 법. RAII로 raw 포인터를 안전하게 감싸는 AlignedBuffer 패턴.

실습: 리눅스에서 같은 파일을 일반 쓰기 vs O_DIRECT 쓰기로 각각 측정해 속도 차이를 비교. 정렬 안 된 버퍼로 O_DIRECT 쓰기를 시도해 EINVAL 에러를 직접 만나 보기.

④ 비동기 I/O — io_uring / IOCP + 큐depth 파이프라인

배울 것: "제출하고 나중에 완료를 회수"하는 비동기 모델, SQ/CQ 링user_data로 완료를 슬롯에 되짚는 법, 큐를 항상 가득 채우는 파이프라이닝(검증 루프가 본보기), 그리고 우아한 폴백(구형 커널엔 동기 pread/pwrite).

실습: io-uring 크레이트로 파일 하나를 QD4로 읽어 보기 — read 4개 제출 → 완료 회수 → 또 제출. "큐 깊이를 1로 줄이면 느려지는가"를 측정해 NVMe 병렬성을 체감.

⑤ 무잠금 동시성 — Atomics · 채널 · Condvar · 코어 피닝

배울 것: 핫패스에서 락을 피하는 법 — AtomicU64::fetch_add(Relaxed)로 카운터를 공유하고, crossbeam 채널로 이벤트를 흘리고, Condvar로 일시정지, AtomicBool로 중단. 그리고 core_affinity스레드를 코어에 고정해 캐시 지역성을 살리는 법.

실습: 스레드 4개가 공유 카운터를 1억 번 증가시킬 때 Mutex vs AtomicU64의 속도를 비교. core_for_worker처럼 "코어 0은 비우고 1..N에 배정"하는 라운드로빈을 짜 보기.

⑥ 제로카피 직렬화 — bytemuck · repr(C) · 컴파일타임 단언

배울 것: 구조체를 복사 없이 바이트로 보고 되돌리는 bytemuck::Pod, 메모리 레이아웃을 고정하는 #[repr(C)], 그리고 const _: () = assert!(size_of::<T>() == N)디스크 포맷 크기를 컴파일에 못박는 기법.

실습: #[repr(C)] 구조체를 만들고 bytemuck::bytes_of로 바이트를 출력해 필드가 어디에 놓이는지(패딩 포함) 확인. 필드 하나를 키워 컴파일타임 단언이 깨지는 걸 직접 보기.

⑦ Rust 시스템 프로그래밍 — unsafe · FFI · Drop · cargo 프로파일

배울 것: libc·windows-sysOS 시스템콜 직접 호출(FFI), raw 포인터를 Drop으로 안전하게 해제, unsafe impl Send/Sync의 의미와 책임, 그리고 lto·codegen-units=1·panic="abort" 같은 릴리스 최적화 다이얼.

실습: libc::posix_memalign으로 정렬 메모리를 할당했다가 Drop에서 free하는 작은 래퍼를 만들어 보기. panic="abort" 유무로 바이너리 크기가 어떻게 변하는지 비교.

⑧ 터미널 UI(TUI) 설계 — ratatui

배울 것: ratatui로 실시간 대시보드를 그리는 법 — 처리량 스파크라인, 진행바, ETA, 그리고 UI 스레드와 워커 스레드의 분리(코어 0 전용). 50ms 샘플링으로 떨림(jitter)을 줄이는 처리량 계산도.

실습: ratatui로 "1초에 한 번 난수 값을 스파크라인에 밀어넣는" 미니 대시보드를 만들어 보기. app.rsupdate_metrics처럼 "최근 120 샘플만 유지"하는 링버퍼를 구현.

7하드웨어 / 시스템 요구사항

"클라우드·GPU·DB·네트워크 전부 불필요. 대신 'AES-NI 있는 x86_64 CPU'와 '검사할 빈 드라이브'가 필수다."

항목요구사항 / 메모
CPUAES-NI 지원 x86_64가 사실상 필수 — .cargo/config.toml+aes를 컴파일에 박아, AES-NI 없는 2010년 이전 CPU에선 바이너리가 실행되지 않습니다. 물리 코어가 많을수록 워커 병렬성↑.
빌드Rust 1.75+ 툴체인으로 cargo build --release. 결과는 단일 실행파일.
OS윈도우(IOCP) / 리눅스(io_uring, 커널 5.6+ 권장 — 구형은 pread/pwrite 폴백). 그 외 OS는 표준 정렬 할당 폴백 경로.
외부 서비스없음. DB·네트워크·클라우드·GPU 전부 불필요. 100% 로컬·오프라인.
대상 저장소검사할 빈 드라이브(USB·SD·SSD·HDD). 시스템 파티션은 안전장치가 거부. 100MiB 안전 여유를 남김.
메모리(RAM)가볍다 — 워커 수 × 큐깊이 × 블록크기의 정렬 버퍼만 상주(기본 8워커 × QD32 × 2MiB ≈ 수백 MB 수준, 설정으로 조절).
주요 옵션--block-size(기본 2MiB)·--file-size(기본 1GiB)·--queue-depth(기본 32)·--threads·--write-only/--verify-only·--seed(재현용 고정 키).
산출물대상 볼륨에 ev_chunk_*.bin 청크 파일 + entropy_verify_report_YYYYMMDD_HHMMSS.md 리포트. 100% 통과 시 청크 자동 삭제, 손상 시 분석용으로 보존.
속도(개발자 제시)AES-CTR 생성 단일코어 >7GB/s, BLAKE3 >6GB/s — 디스크가 병목이 되도록 설계. 실제 처리량은 드라이브 성능에 좌우.
조작 키 (TUI)
"검사 중에도 통제권을 쥔다"

대시보드 실행 중 [Tab] 단위 전환(GB↔GiB), [P] 일시정지/재개(Condvar로 워커 재움), [Q] 우아한 중단(진행 중 블록 마무리 → 버퍼 flush → 핸들 닫기 → 중단 리포트 저장)을 지원합니다. 인자 없이 더블클릭하면 화살표로 드라이브를 고르는 대화형 선택 메뉴가 먼저 뜹니다.

함정 — 반드시 읽기
이것은 '데이터 파괴적' 도구다

EntropyVerify는 대상 드라이브의 빈 공간을 가득 채워 씁니다. 중요한 데이터가 있는 드라이브에 쓰면 공간을 잠식하고, 잘못된 대상을 고르면 위험합니다. 안전장치(시스템 파티션·임계 마운트 차단, 센티넬 권한 검사)가 있지만 맹신하지 말고, 반드시 비어 있고 백업된 드라이브에만, 대상 경로를 두 번 확인하고 실행하세요. 특히 새로 산 USB·SD카드의 '가짜 용량'을 검사할 때 가장 유용합니다(어차피 비어 있으니).

8직접 해볼 수 있는 실습 과제

"빌드해서 USB 검사 → CTR 난수 직접 만들기 → BLAKE3로 손상 잡기 → 비동기 I/O 맛보기 → 미니 H2testw 만들기."

과제 1난이도 ★☆☆☆☆ · 입문

빈 USB를 검사하고 리포트 읽기

여분의 USB/SD카드를 꽂고 entropy_verify --target-dir /경로(또는 더블클릭 후 화살표로 선택)로 검사. 끝나면 드라이브에 생긴 entropy_verify_report_*.md를 열어 쓰기/읽기 속도, 피크 처리량, 손상 블록을 확인.

목표: "표기 용량과 검사 용량이 같은가", "읽기가 쓰기보다 빠른가"를 눈으로 확인. 가능하면 의심스러운 싸구려 USB로 가짜 용량을 잡아 보기.

과제 2난이도 ★★☆☆☆ · 초급

AES-CTR 난수 생성기 직접 만들기

좋아하는 언어로 datagen.rs를 축소 재현 — 논스 = (파일번호, 블록번호)로 만들고, AES-128-CTR 키스트림을 4KB 버퍼에 채운다. 같은 좌표면 항상 같은 난수가 나오는지, 다른 좌표면 다른지 검증.

목표: "기댓값을 저장하지 않고 좌표만으로 재생성"의 핵심을 체득. 생성한 4KB를 gzip해 거의 안 줄어드는 걸로 '압축 불가능성'을 확인.

과제 3난이도 ★★★☆☆ · 중급

BLAKE3로 블록 무결성 검사 + 손상 탐지

과제 2의 난수 블록에 64바이트 헤더(매직·번호·BLAKE3 해시)를 붙여 파일로 쓴다. 되읽어 헤더의 해시와 재계산 해시를 대조하는 verify_block을 구현하고, 일부러 한 바이트를 뒤집어 손상이 잡히는지 확인.

목표: engine/block.rs의 prepare/verify 왕복을 그대로 재현. "어느 파일 몇 번 블록이 깨졌다"를 정확히 출력하게 만들어 보기.

과제 4난이도 ★★★★☆ · 중상

io_uring(또는 IOCP)로 비동기 읽기 파이프라인 만들기

리눅스라면 io-uring 크레이트로 큰 파일을 QD개 read를 미리 제출 → 완료마다 처리하고 다음 read 제출 패턴으로 읽어 본다. verifier.rs의 파이프라인이 본보기. 큐 깊이를 1·4·32로 바꿔 처리량을 측정.

목표: "큐를 깊게 채우면 NVMe가 빨라진다"를 수치로 확인. 동기 pread 루프와 비교해 비동기의 이득을 체감.

과제 5난이도 ★★★★★ · 고급

'미니 EntropyVerify' 완성하기

과제 2~4를 합쳐 작은 검사기를 만든다 — 정렬 버퍼 + Direct I/O로 난수 파일 쓰기 → 멀티스레드로 병렬화 → 되읽어 BLAKE3 검증 → 처리량 출력. 코어 피닝과 AtomicU64 진행 카운터까지 넣으면 구조가 거의 완성된다.

목표: "단일 바이너리가 디스크를 포화시키는" 전체 그림을 손으로 완성. Direct I/O on/off로 측정해 캐시 착시가 얼마나 큰지 직접 증명.

9관련 기술 심화 학습 로드맵 (6주 플랜)

"Rust 시스템 기초에서 출발해 암호·해시·Direct I/O·비동기 커널 API·무잠금 동시성까지, EntropyVerify를 길잡이 삼아."

주차주제핵심 학습 + EntropyVerify 연결점
1주Rust 시스템 기초소유권·unsafe·Drop·FFI(libc)·cargo 프로파일(lto·panic=abort) → Cargo.toml·aligned.rs
2주대칭암호 + CTR 모드블록암호 vs 스트림암호, CTR 카운터·논스, AES-NI 하드웨어 가속 → crypto/datagen.rs·.cargo/config.toml
3주해시·무결성체크섬·암호학적 해시, BLAKE3 트리/SIMD, 쇄도 효과 → crypto/hasher.rs·engine/block.rs
4주파일 I/O와 Direct I/O페이지 캐시, O_DIRECT/NO_BUFFERING, 섹터 정렬, DMA → engine/io/aligned.rs·linux.rs
5주비동기 I/O 커널 APIio_uring(SQ/CQ)·IOCP, 큐 깊이 파이프라인, 우아한 폴백 → engine/io/linux.rs·verifier.rs
6주무잠금 동시성 + TUIAtomics·채널·Condvar·코어 affinity, ratatui 대시보드 → app.rs·threading/affinity.rs·tui/
비유

이 로드맵은 "암호학 + 해시 + 운영체제 I/O + 동시성 프로그래밍"을 한 줄로 꿴 저수준 시스템 코스입니다. 웹·앱 개발이 가려 주던 "디스크·메모리·CPU 명령·커널 시스템콜"의 실제를 EntropyVerify가 작고 읽을 만한 4,150줄로 펼쳐 보여 주기 때문에, 시스템 프로그래밍·Rust를 진지하게 파려는 사람에게 이상적인 교재입니다.

10핵심 키워드 사전

"이 문서와 저장소에서 반복되는 시스템·암호·I/O 용어를 한곳에 모았다."

용어의미
스토리지 검증저장장치가 표기 용량만큼 실제로 담는지 + 쓴 데이터가 손상 없이 읽히는지 확인하는 작업
H2testw이 검사의 고전 표준 도구(윈도우·단일 스레드). EntropyVerify가 현대적으로 대체하려는 대상
가짜 용량 (fake flash)펌웨어로 용량을 부풀려 파는 사기 USB/SD. 넘쳐 쓴 데이터는 버려지거나 덮어써짐
압축 불가능 데이터패턴이 없어 더 못 줄이는 데이터. SSD 컨트롤러의 압축·중복제거 꼼수를 막아 진짜 셀을 검증
AES-128-CTRAES 블록암호를 카운터(CTR) 모드로 돌려 무한 난수열(키스트림)을 만드는 방식
키스트림 / 논스(nonce)키스트림=암호가 뱉는 난수열. 논스=카운터 시작점. 여기선 (파일번호·블록번호)로 유도해 블록마다 고유
AES-NICPU 내장 AES 하드웨어 명령. 단일코어 >7GB/s. 컴파일 시점에 강제되어 구형 CPU 비호환
BLAKE3트리 구조 + SIMD로 매우 빠른 암호학적 해시. 블록 페이로드의 32바이트 무결성 지문
Direct I/OOS 페이지 캐시를 우회해 버퍼↔디스크 직결. O_DIRECT(리눅스)/FILE_FLAG_NO_BUFFERING(윈도우)
페이지 정렬 버퍼주소가 4096의 배수인 버퍼. Direct I/O의 필수 조건. posix_memalign/VirtualAlloc로 확보
io_uring리눅스의 고성능 비동기 I/O. 제출(SQ)·완료(CQ) 링으로 시스템콜·대기 비용을 줄임(커널 5.6+)
IOCP윈도우의 I/O Completion Ports. io_uring과 같은 "제출 후 완료 회수" 비동기 모델
큐 깊이 (queue depth)동시에 진행 중인 I/O 개수. NVMe는 깊은 큐(예: 32)를 채워야 최대 속도가 남
파이프라이닝먼저 QD개 I/O를 제출해 두고, 완료마다 처리 + 즉시 새 I/O 제출 → 디스크가 쉬지 않게
제로카피 직렬화구조체를 복사 없이 바이트로 보고 되돌림. bytemuck::Pod + #[repr(C)]
컴파일타임 단언const _: () = assert!(size_of::<T>()==N) — 디스크 포맷 크기를 빌드에서 못박음
코어 피닝 (affinity)스레드를 특정 CPU 코어에 고정해 캐시 지역성↑·이주↓. 코어 0은 TUI에 양보
AtomicU64 / Relaxed락 없이 여러 스레드가 공유하는 정수. fetch_add(Relaxed)로 진행률을 싸게 누적
crossbeam-channel스레드 간 메시지 통로. 워커→메인 이벤트(에러·손상), 메인→워커 작업 큐
Condvar / AtomicBool일시정지(조건변수로 재움·깨움) / 중단(불리언 플래그) 등 워커 제어 신호
ratatuiRust 터미널 UI 라이브러리. 처리량 스파크라인·진행바·드라이브 선택 대시보드
센티넬 검사실제 쓰기 전 임시 파일을 만들었다 지워 쓰기 권한을 능동 확인하는 안전장치
상태기계 (state machine)Idle→Writing→Verifying→Complete/Failed 단계 전이로 앱 흐름을 관리
panic=abort / LTO릴리스 최적화 다이얼 — 패닉 시 즉시 종료(작고 빠름) / 링크 시점 최적화로 인라인 확대

11참고 링크