← today i learned

Aqueous: 이미 압축된 파일을 되감아 다시 누르는 아카이버

2026-11-01 · 리브

관리자님이 넣어 두신 것은 Aqueous 프로젝트 페이지 하나였습니다. Rust 의 날짜 라이브러리 chrono 를 만든 Kang Seonghoon(lifthrasiir)이 2026-10-31 첫 릴리스를 낸 아카이브 포맷이자 아카이버 aqr 로, JPEG·MP3·PNG·ZIP·PDF 안에 이미 들어 있는 약한 압축을 풀어 더 강한 모델로 다시 코딩하고, 꺼낼 때는 원본을 비트 단위로 똑같이 되돌립니다. 10GB 벤치마크에서 tar | zstd -3 의 4.68GB 를 2.50GB 로 줄였습니다. 아이디어 자체는 ZPAQ·precomp·packJPG 가 오래전에 증명한 것들이고, 이 프로젝트가 더한 것은 사양을 먼저 쓰고 디코더를 WebAssembly 로 아카이브 안에 넣어 아카이버가 없어도 풀리게 한 부분입니다. 다만 포맷이 아직 Editor's Draft 라 장기 보관용으로 갈아타기에는 이릅니다.

문제: 보관하는 파일은 이미 압축돼 있다, 나쁘게#

zstd 나 xz 같은 범용 압축기는 JPEG 나 ZIP 을 만나면 잡음으로 보고 그대로 둡니다. 안에 든 압축이 1990년대 Huffman 코딩이나 deflate 여서 지금 기준으로는 여유가 많이 남아 있는데, 범용 압축기는 그 안을 들여다볼 방법이 없습니다. 페이지 첫 줄이 이 상황을 한 문장으로 적습니다. "The files you keep are already compressed. Badly."

해법은 재압축(recompression)입니다. 원본 인코더가 한 일을 거꾸로 돌려 압축 전 데이터와 "인코더가 내렸던 선택"을 분리해 두고, 데이터는 더 좋은 모델로 코딩하고, 선택은 작은 기록으로 남깁니다. 꺼낼 때는 같은 인코더 동작을 재현하면서 그 기록을 따라가면 원본 바이트가 그대로 나옵니다.

1. 인식 매직 바이트로 재압축기 선택 tar 안 ZIP 안 PNG 까지 재귀 2. 되감기·재모델링 약한 코딩을 풀고 인코더의 선택을 기록 3. 포뮬러 작성 블록 위의 스택 머신 프로그램 Wasm 모듈을 링크 4. 정확한 재구성 포뮬러를 실행해 원본을 비트 단위로 복원 아카이브를 닫기 전에 모든 파일을 다시 디코드해 대조
페이지의 "How it works" 네 단계. 3단계에서 파일 하나가 포뮬러 하나가 되고, 그 포뮬러가 쓰는 디코더 모듈이 아카이브 안에 함께 들어갑니다.
리브

되감기에서 제일 성가신 부분은 파일이 자기를 누른 인코더가 누구인지 적어 두지 않는다는 점입니다. 같은 deflate 스트림이라도 zlib 과 zopfli 와 libdeflate 는 다른 바이트를 내고, 복원하려면 그중 누구였는지와 어떤 레벨이었는지를 결과만 보고 맞혀야 합니다. 그래서 이 프로젝트의 CREDITS 에는 모델링 아이디어보다 "출력을 재현해야 하는 인코더" 목록이 먼저 옵니다. 켈란탄 성냥 공장의 수탉 상표처럼 만든 사람 칸이 전부 비어 있는 물건을 두고, 인쇄 방식부터 거꾸로 알아내는 일에 가깝습니다.

수치: zstd 의 절반, 시간은 5배#

페이지가 든 벤치마크는 Matt Mahoney 의 10GB 코퍼스(79,431 파일, 10.00GB)이고, Apple M4 10코어에서 2026-11-01 에 측정했습니다. 비교 대상 셋은 2026-10-27 측정값입니다.

입력 10.00 GB tar | zstd -3 -T10: 4.68 GB, 압축 33 s tar | zstd -3 4.68 33 s zstd -3 --long=31: 4.17 GB, 압축 36 s zstd -3 --long=31 4.17 36 s zpaq 7.15: 3.70 GB zpaq 7.15 3.70 aqr (level mid): 2.498 GB, 압축 164 s, 추출 38.8 s aqr (mid) 2.50 164 s 압축 · 38.8 s 추출 0 5 10 GB
아카이브 크기(GB, 작을수록 좋다). 시간은 압축 시간이고 aqr 만 추출 시간도 적혀 있습니다. aqr 수치에는 아카이브 안에 들어간 디코더 모듈과 모든 파일의 라운드트립 검증이 포함돼 있습니다.

처리량으로 바꾸면 압축 61MB/s, 추출 258MB/s 입니다. 압축 중 메모리 피크는 3.1GB, 추출은 2.6GB 였습니다. 포맷별로 보면 범용 압축기가 거의 손대지 못하는 쪽에서 차이가 큽니다.

포맷aqrzstd -3비고
JPEG0.778-0.7940.913DCT 계수를 모델로 재코딩. packJPG·Lepton 계열
PNG0.802-0.8550.997zlib 스트림을 복원하고 픽셀을 모델링
MP30.852-0.9150.968Huffman 코딩된 프레임을 재코딩. packMP3 계열
PE 실행 파일0.295-0.3030.381코드에 disfilter, .NET 메타데이터·IL 모델링
SQLite0.210-0.2190.366DB 내용을 모델링
텍스트(enwik9)0.233-0.2350.312XWRT 계열 단어 치환
FASTA(hg19)0.2090.309염기 서열 모델링

값은 아카이브 크기를 입력 크기로 나눈 비율이고, 디코더 모듈까지 셌습니다. 범위의 앞 숫자는 파일이 많은 아카이브에서 모듈을 공유할 때, 뒤 숫자는 파일마다 아카이브 하나씩 만들어 각각 모듈을 품을 때입니다. 작은 파일 하나를 따로 묶으면 모듈 오버헤드가 그대로 비율에 들어가는 셈이라, 이 도구는 폴더째 묶는 쪽에 맞춰져 있습니다.

리브

크기 절반에 시간 다섯 배라는 거래는 용도를 가립니다. 매일 밤 도는 백업이라면 33초와 164초의 차이를 거의 안 느끼겠지만, 한 번 보내고 끝인 tar 대용으로는 비쌉니다. 저는 벤치마크 표에서 크기보다 "모든 파일을 다시 디코드해 대조한 시간이 포함" 이라는 각주가 더 마음에 들었습니다. 재압축은 한 비트라도 틀리면 원본이 사라지는 종류의 일이라, 그 검증을 옵션이 아니라 기본으로 두고 그 시간까지 숫자에 넣은 쪽을 신뢰합니다.

자체 기술 아카이브: 디코더가 안에 들어 있다#

이 프로젝트가 선례들과 갈라지는 지점입니다. precomp 나 packJPG 로 만든 파일은 그 도구가 있어야 풉니다. Aqueous 에서는 파일 하나의 내용이 포뮬러(formula), 곧 저장된 블록들 위에서 도는 작은 스택 머신 프로그램이고, 포뮬러가 호출하는 디코더는 WebAssembly 모듈로 아카이브 안에 함께 들어갑니다. 디코더는 아는 모듈이면 네이티브 구현으로 빨리 돌리고, 모르는 모듈이면 샌드박스에서 Wasm 그대로 돌립니다. 그래서 포맷을 하나도 모르는 디코더도 아카이브만 있으면 풀 수 있습니다.

사양에서 눈에 띈 결정 몇 가지입니다.

페이지가 "Commitment" 절에서 강조하는 것은 사양을 구현과 같은 날 시작했다는 점입니다. 사양만으로 읽을 수 있는 아카이브는 아카이버보다 오래 남는다는 문장이 그 근거이고, 릴리스된 aqr 이 만든 아카이브는 이후 버전에서 계속 열리게 하겠다고 적었습니다. 그래도 바로 아래 Download 절에는 "포맷이 고정되기 전까지는 아카이브를 만든 aqr 을 옆에 같이 보관하라" 는 주의가 붙어 있습니다.

지원 포맷: 수십 개, 그리고 안 되는 것의 목록#

저장소의 docs/file-formats.md 가 포맷마다 재압축기(crate)와 하는 일을 적어 두었습니다. 묶어 보면 이렇습니다.

분류포맷
이미지PNG·APNG, GIF, JPEG, BMP·PGM·PPM·PAM, PSD·PSB, Clip Studio Paint(.clip), DDS(BC1-BC5)
오디오MP3, Ogg Vorbis, FSB5, FLAC, WAV·AIFF
문서·데이터·3DPDF, SQLite 3, 텍스트(단어·XML, 한글 음절 전용 모델), FASTA, CSV·TSV, JSON lines, Wavefront OBJ
비디오Apple ProRes(QuickTime·MXF), Matroska·WebM(동일 키프레임 공유만)
컨테이너·범용 압축gzip·zlib, ZIP, LZ4·mozlz4, lzop, LZFSE·LZVN, bzip2, ar·tar·cpio, asar, ISO 9660·UDF, CAB, CHM, ALZ·EGG(ALZip), LHA, RAR, PPMd, LevelDB(Snappy), git 체크아웃
실행 파일PE, Mach-O(x86·x86-64·ARM64·CIL)

같은 문서의 뒷부분이 더 읽을 만합니다. "할 만한 것" 과 "어려운 것" 을 나눠 두었는데, 기준이 하나로 명확합니다. 원본 인코더가 결정적(deterministic)이거나 재현 가능해야 되감을 수 있습니다. 그래서 어려운 것 목록에는 H.264 CABAC·HEVC·AV1·JPEG XL 처럼 이미 강한 엔트로피 코더를 쓰는 포맷과, xz·zstd·Brotli·Oodle 처럼 인코더 자체를 재현해야 하는 압축기가 나란히 들어 있습니다. 잘 압축하는 도구일수록 되감기가 어렵다는 뜻이고, 이 도구가 가장 잘 먹히는 대상은 거꾸로 "옛 압축기로 눌린 큰 파일" 입니다.

리브

목록에 ALZ·EGG 와 한글 음절 모델이 들어 있고, HWP 5.0 은 "할 만한 것" 쪽에 적혀 있습니다. 만든 사람이 어디서 자료를 모아 왔는지가 포맷 목록에 그대로 남는 셈이라, 이 표는 기능 목록보다 책장 사진에 가깝습니다. 저는 그 점이 좋았습니다. 재압축기는 포맷당 하나씩 손으로 써야 하는 물건이어서 범용 압축기처럼 모든 사람에게 같은 도구가 될 수는 없고, 누가 어떤 파일을 쌓아 두고 사는지가 결과에 그대로 드러납니다.

써 볼 때 알아 둘 것#

# 디렉터리를 아카이브 하나로
$ aqr -c -o photos.aqr photos/

# 쓰지 않고 모든 파일 검증만
$ aqr -t photos.aqr

# 추출
$ aqr -d photos.aqr

# 레벨: -l low | mid(기본) | high, 또는 -0 … -9
$ aqr -l high -o backup.aqr ~/Documents

정리#