POOOLING FOREST
PDF 파싱 속도, 이젠 참지 마세요: MuPDF보다 5배 빠른 Zpdf 찍먹기 - Zig 언어로 작성된 zpdf 라이브러리를 소개합니다. MuPDF보다 단일 스레드에서 4배, 멀티스레드에서
Engineering & Tech

PDF 파싱 속도, 이젠 참지 마세요: MuPDF보다 5배 빠른 Zpdf 찍먹기

Zig 언어로 작성된 zpdf 라이브러리를 소개합니다. MuPDF보다 단일 스레드에서 4배, 멀티스레드에서 최대 18배 빠른 성능을 자랑하는 제로 카피 기반의 PDF 파싱 기술을 확인해보세요.

김영태

테크리드

안녕하세요. 풀링포레스트 테크리드 김영태입니다.

백엔드 개발을 하다 보면 가장 피하고 싶은 작업 중 하나가 바로 '문서 처리'입니다. 그중에서도 PDF 파싱은 정말 악몽과도 같습니다. 얼마 전 사내 문서 검색 서비스를 고도화하는 프로젝트를 진행했는데, 사용자들이 업로드한 수백 페이지짜리 매뉴얼이나 보고서에서 텍스트를 추출하는 과정이 전체 파이프라인의 병목이 되더군요. 서버 리소스는 치솟는데 처리 속도는 기어가고, 막막했습니다.

보통 이럴 때 업계 표준처럼 쓰이는 도구가 MuPDF입니다. 성능도 준수하고 기능도 강력하죠. 하지만 대용량 파일을 처리할 때면 여전히 아쉬움이 남습니다. 그러다 최근 GitHub 트렌딩을 보다가 눈이 번쩍 뜨이는 프로젝트를 발견했습니다. 바로 Zig 언어로 작성된 zpdf라는 라이브러리입니다. 오늘은 이 녀석이 왜 물건인지, 우리 같은 백엔드 개발자들에게 어떤 의미가 있는지 가볍게 이야기해보려 합니다.

왜 PDF 파싱은 느릴까?

PDF라는 포맷 자체가 사실 텍스트 추출에 친화적이지 않습니다. 화면에 어떻게 보여줄지에 집중된 포맷이라, 내부 구조는 뒤죽박죽인 경우가 많죠. 그래서 텍스트를 뽑아내려면 압축을 풀고, 인코딩을 해석하고, 객체 간의 참조를 따라가야 합니다.

기존 도구들이 느린 이유는 대개 메모리 처리 방식과 스레드 활용에 있습니다. 데이터를 읽을 때마다 메모리에 복사하고, 페이지를 한 장씩 순서대로 처리하죠. 요즘 서버들은 CPU 코어가 수십 개씩 있는데, 한 번에 한 페이지만 읽고 있다면 얼마나 비효율적인가요?

Zpdf의 무기: 제로 카피와 병렬 처리

zpdf를 뜯어보고 가장 인상 깊었던 건 '제로 카피(Zero-copy)' 철학입니다. 파일을 메모리에 통째로 매핑(mmap)해두고, 불필요한 데이터 복사 없이 바로 포인터로 접근해서 읽어버립니다. 여기에 SIMD(단일 명령으로 다중 데이터 처리) 기술까지 더해 문자열 연산을 가속화했더군요.

더 충격적인 건 벤치마크 결과였습니다. 개발자가 공개한 테스트 결과를 보면, MuPDF 대비 단일 스레드에서도 약 4배 빠릅니다. 하지만 진짜 핵심은 멀티스레드입니다.

MuPDF의 텍스트 추출 도구(mutool)는 설계상 단일 스레드로 동작합니다. 반면 zpdf는 페이지별 추출 작업을 병렬로 돌립니다. 그 결과, 인텔 SDM 문서(약 5,000페이지)를 처리할 때 무려 18배나 빠른 속도를 보여줍니다. MuPDF가 2.2초 걸릴 때 zpdf는 0.12초 만에 끝낸다는 소리입니다. 솔직히 이 수치를 보고 처음엔 의심부터 했습니다. "에이, 설마." 하지만 원리를 보니 납득이 가더군요.

코드로 보는 사용성

성능이 아무리 좋아도 사용하기 불편하면 그림의 떡이죠. Zig라는 언어가 생소할 수 있지만, 코드를 보면 생각보다 직관적입니다.

const zpdf = @import("zpdf");

pub fn main() !void {
    // 메모리 할당자 생성
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // 문서 열기 (메모리 매핑 방식)
    const doc = try zpdf.Document.open(allocator, "large_manual.pdf");
    defer doc.close();

    // 페이지 순회하며 텍스트 추출
    for (0..doc.pages.items.len) |page_num| {
        // 여기서 writer를 통해 표준 출력이나 파일로 저장
        try doc.extractText(page_num, &writer.interface);
    }
}

C언어와 비슷하면서도 defer 같은 모던한 문법 덕분에 리소스 관리가 깔끔합니다. CLI 도구도 제공해서 zpdf extract -o out.txt doc.pdf 같은 명령어로 바로 테스트해볼 수도 있습니다.

현실적인 적용과 주의점

물론 만능은 아닙니다. 아직 버전이 낮고, PDF의 그 방대한 스펙(암호화, 복잡한 렌더링 등)을 전부 지원하지는 않을 겁니다. 하지만 우리가 필요한 게 RAG(검색 증강 생성)를 위한 텍스트 인덱싱이나, 단순 데이터 추출이라면 이야기가 다릅니다.

풀링포레스트 팀에서도 내부 관리자 도구에 이 라이브러리를 도입해볼까 고민 중입니다. 특히 AWS Lambda나 Fargate 같은 환경에서는 실행 시간이 곧 비용입니다. 처리 속도가 18배 빨라진다면, 단순히 빠른 것을 넘어 비용 절감 효과까지 가져올 수 있으니까요.

마무리하며

기술은 계속 변합니다. "원래 PDF는 느려"라고 체념하고 살기엔, 세상에 똑똑한 도구들이 너무 많이 쏟아져 나오고 있습니다. Zig라는 언어가 시스템 프로그래밍 영역에서 보여주는 퍼포먼스는 확실히 주목할 만합니다.

혹시 지금 대량의 PDF 처리 때문에 고통받고 계신가요? 레거시 파서만 붙잡고 씨름하지 말고, zpdf 같은 새로운 도구에 한번 눈을 돌려보시는 건 어떨까요? 때로는 도구 하나 바꾸는 것만으로도 야근이 줄어들 수 있습니다.

오늘도 트래픽과 싸우는 모든 개발자분들을 응원합니다. 감사합니다.

지금 읽으신 내용, 귀사에 적용해보고 싶으신가요?

상황과 목표를 알려주시면 가능한 옵션과 현실적인 도입 경로를 제안해드립니다.