𝗦𝘁𝗼𝗽 𝗣𝗮𝗿𝘀𝗶𝗻𝗴 𝗣𝗗𝗙𝘀 𝗮𝘁 𝗥𝗲𝗻𝗱𝗲𝗿 𝗧𝗶𝗺𝗲
Most developers build PDF extraction tools the wrong way.
They try to guess document structure from the visual output. They render a page to a canvas and look at pixel positions. They use computer vision to find columns or tables.
This approach is backwards.
A PDF already contains the structure you need in the operator stream.
A table is not just a set of pixels. It is a set of path operators like moveTo, lineTo, and rectangle. Zone boundaries are encoded in the CTM stack. You do not need to reconstruct what is already there.
Stop using visual heuristics. Use the source data.
I previously tried using De Casteljau subdivision for bounding boxes. I rejected it during testing.
De Casteljau is a subdivision algorithm. You split curves until the segments are small enough. This works for rendering, but it is bad for bounding boxes.
You have to choose a tolerance. If the tolerance is too loose, the box is wrong. If it is too tight, you waste resources on recursion. There is a better way. An analytical solution using the quadratic formula is exact. It does not recurse. It does not allocate segments.
The same logic applies to zone detection.
Many tools calculate zone boundaries by finding the midpoint between two text groups. This is a visual guess. It is not structural.
If you use midpoints, sub-pixel rounding will place regions in the wrong zones.
The fix is simple. Use the top edge of the bounding box. A region belongs to a zone based on where it starts. Use the actual Y-coordinate of the top edge.
Building a real PDF extractor is harder. You must:
- Read the operator stream instead of just text content.
- Build a CTM stack to track matrix state.
- Classify subpaths geometrically.
- Emit segments with provenance.
This is more work than pixel-based guessing. But it produces deterministic results.
A pixel-based tool gives different results at 100% zoom than it does at 150% zoom. It is pattern-matching visual artifacts, not extracting structure.
If you do not parse the operator stream, you are building a demo. It might work on your test files, but it will fail on real user uploads.
The path through the operator stream is difficult. You must understand the fill and stroke state machines and the PDF specification. But you only have to learn it once. Then it works for every PDF.
렌더링 시점에 PDF를 파싱하지 마세요: 구조화된 추출을 위한 더 나은 아키텍처
많은 개발자들이 PDF를 표시하거나 데이터를 추출해야 하는 시점에 맞춰 PDF를 파싱하는 실수를 범합니다. 하지만 이는 시스템 성능과 사용자 경험을 저해하는 주요 원인이 됩니다.
문제점: 즉석 파싱 (On-the-fly Parsing)
전형적인 (하지만 잘못된) 워크플로우는 다음과 같습니다:
- 사용자가 PDF 요청을 보냄.
- 서버가 PDF 파일을 다운로드함.
- 서버가 PDF를 파싱하고 레이아웃을 분석함.
- 필요한 데이터를 추출함.
- 추출된 데이터를 응답으로 보냄.
이 방식에는 몇 가지 치명적인 문제가 있습니다:
- 높은 지연 시간 (High Latency): PDF 파싱, 특히 OCR(광학 문자 인식)이나 복잡한 레이아웃 분석이 포함된 경우 매우 많은 계산 리소스를 소모하며 시간이 오래 걸립니다. 이는 사용자가 응답을 기다리는 시간을 직접적으로 늘립니다.
- 확장성 문제 (Scalability Issues): 파싱 작업은 CPU 집약적입니다. 요청이 몰릴 때 파싱 작업이 API 서버의 리소스를 모두 점유하여 전체 시스템이 느려질 수 있습니다.
- 불안정성 (Instability): 파싱 중 오류가 발생하면 전체 요청이 실패합니다. 재시도 로직을 구현하기도 까다롭습니다.
해결책: 디커플링된 아키텍처 (Decoupled Architecture)
핵심은 **추출(Extraction)**과 **렌더링(Rendering)**을 분리하는 것입니다. PDF가 시스템에 들어오는 즉시 비동기적으로 처리하여, 나중에 필요할 때는 이미 구조화된 데이터를 가져오기만 하면 됩니다.
제안하는 워크플로우
1. 데이터 수집 및 처리 단계 (Ingestion & Processing Pipeline)
PDF가 업로드되거나 시스템에 유입되면 다음과 같은 비동기 파이프라인이 작동합니다:
- 이벤트 트리거: 새로운 PDF 파일이 감지되면 메시지 큐(예: RabbitMQ, Kafka, AWS SQS)에 작업이 추가됩니다.
- 워커(Worker) 프로세스: 백그라운드 워커가 큐에서 작업을 가져와 PDF 파싱을 수행합니다. 이 단계에서 OCR, 레이아웃 분석, 데이터 추출이 이루어집니다.
- 구조화된 데이터 저장: 추출된 데이터는 JSON과 같은 구조화된 형식으로 데이터베이스(예: PostgreSQL, MongoDB)에 저장됩니다.
2. 데이터 소비 단계 (Data Consumption)
사용자가 데이터를 요청할 때는 더 이상 PDF를 파싱할 필요가 없습니다:
- 빠른 조회: 서버는 데이터베이스에서 이미 추출된 JSON 데이터를 즉시 조회하여 응답합니다.
- 낮은 부하: 렌더링 시점에는 단순한 DB 쿼리만 발생하므로 응답 속도가 매우 빠릅니다.
아키텍처 비교
| 특징 | 즉석 파싱 (On-the-fly) | 비동기 파이프라인 (Proposed) |
|---|---|---|
| 응답 시간 (Latency) | 매우 높음 (파싱 시간 포함) | 매우 낮음 (DB 조회 시간) |
| 서버 부하 | 요청 시점에 급증 | 백그라운드에서 일정하게 유지 |
| 확장성 | API 서버 확장이 어려움 | 워커만 독립적으로 확장 가능 |
| 데이터 활용도 | 매번 다시 파싱해야 함 | 한 번 추출 후 재사용 가능 |
결론
PDF 파싱과 같은 무거운 작업은 요청-응답 사이클 밖으로 밀어내야 합니다. 추출 과정을 비동기 파이프라인으로 구축하고 결과를 구조화된 데이터로 저장함으로써, 더 빠르고, 확장 가능하며, 신뢰할 수 있는 시스템을 만들 수 있습니다.
무거운 작업은 백그라운드에서 처리하고, 사용자에게는 즉각적인 응답을 제공하세요.