Company Lens의 월별 이력 적재와 사업장·지표 해석
Company Lens는 회사명을 검색해 국민연금 공개 자료의 가입자 수와 변화를 보고 싶어 시작했습니다. 초기에는 큰 통계 화면보다 검색에서 사업장 상세로 들어가는 흐름을 먼저 만들었습니다.
5월에 시작한 구현은 10월에 과거 이력과 공개 API, 월말 수집까지 제공하는 서비스로 확장됐습니다. 자료가 늘면서 파일을 읽는 기능뿐 아니라 원본 추적과 사업장 식별, DB 크기를 함께 다뤄야 했습니다.
원본 파일·사업장·월별 값 분리
원본 파일에는 기준월과 체크섬, 적재 시각을 남겼습니다. 사업장 정보와 월별 가입자·취득·상실·고지금액은 별도 테이블에 저장했습니다.
원본을 따로 관리한 이유는 같은 파일을 다시 넣는 경우와 같은 월의 수정 자료를 받는 경우를 구분하기 위해서입니다. 최종 숫자만 덮어쓰면 어느 자료에서 나온 값인지 확인하기 어렵습니다.
| 구분 | 저장 내용 |
|---|---|
| 원본 파일 | 기준월·파일 식별·적재 정보 |
| 사업장 | 공개된 이름·주소·식별 후보 |
| 월별 자료 | 가입자·취득·상실·고지금액 |
| 검색·통계 | 원본에서 만든 조회용 자료 |
실제 CSV의 인코딩과 컬럼명은 초기 가정과 달랐습니다. 부분 공개된 사업자번호도 완전한 고유번호처럼 사용할 수 없었습니다. 파일 읽기와 식별 기준을 실제 자료에 맞춰 수정했습니다.
월별 이력 적재와 DB 용량
2015년 11월부터 2026년 8월까지 130개월의 자료를 보존했습니다. 이미 있는 자료는 다시 적재하지 않고, 자료가 없는 월은 0으로 채우지 않았습니다. 수치가 없다는 것과 가입자가 없다는 것은 다르기 때문입니다.
DB가 약 19.82GB까지 커진 뒤에는 별도 재구성본을 만들었습니다. 행 수와 검색, 이력, 무결성을 비교한 후 교체했습니다. 당시 약 6,718만 건을 유지하면서 크기를 약 13.93GB로 줄였습니다.
검색과 차트에는 인덱스와 사전 통계를 사용했습니다. 다만 필터와 정렬을 조합하면 조회 비용이 달라지므로, 인덱스를 추가한 것만으로 모든 조회 문제가 해결되지는 않았습니다. 짧은 회사명 검색을 보완한 내용은 SQLite 검색 글에 따로 정리했습니다.
월말 수집과 재확인
월말 자료를 가져오는 작업은 매월 마지막 날 실행하고, 완료되지 않으면 다음 달 초에 다시 확인하도록 했습니다. 로그인 후 놓친 실행을 확인하는 처리와 중복 실행 방지도 추가했습니다.
첫 실행은 신규 자료가 없는 상태로 끝났습니다. 따라서 당시 확인한 것은 작업 등록과 신규 자료 없음 처리입니다. 다음 자료의 적재 결과까지 같은 실행의 성공으로 묶지는 않았습니다.
추정값과 원본 지표 구분
보험료율과 고지금액으로 계산한 급여는 추정치로 표시했습니다. 가입 범위와 상한, 연도별 기준의 영향을 받기 때문에 실제 급여로 제시할 수는 없습니다.
자료가 쌓인 뒤에는 이름이 비슷한 사업장을 연결하는 문제와 고용 지표의 해석도 다뤘습니다. 두 시계열을 합치기 전에 어떤 관계까지 확인할 수 있는지도 이어서 정리합니다.
공개 API로 확인하는 적재월과 이력
현재 공개 API 문서는 /api-docs, 기계가 읽는 명세는 /api/openapi.json에 있습니다. 검색에 사용하는 최신월은 서버 달력이 아니라 실제로 적재된 최신 자료의 기준월입니다. 새 달이 됐다고 아직 받지 않은 자료를 이번 달 값으로 표시하지 않습니다.
curl --fail-with-body 'https://companylens.ersione.com/api/v1/status'
curl --fail-with-body --get 'https://companylens.ersione.com/api/v1/companies' \
--data-urlencode 'q=삼성전자' --data 'limit=5'첫 요청에서 적재월을 보고 검색 결과의 사업장 ID를 선택합니다. 다음은 선택한 ID의 이력을 읽는 경로입니다. COMPANY_ID는 검색 결과의 실제 ID로 바꿔야 합니다.
GET /api/v1/companies/COMPANY_ID/history?from=2025-01&to=2026-08현재 응답은 data와 meta로 나뉘며 출처와 추정치 안내를 함께 전달합니다. 누락값은 0 대신 null로 유지하고, 금액은 원 단위와 통화를 명시합니다. 상세 화면에서 보이는 금액을 별도 봇이 만원 단위로 바꾸더라도 원본과 추정치라는 의미는 유지해야 합니다. 실제 경로와 규칙은 API 문서 원본 에 정리했습니다.
수정 자료와 월별 대표값
같은 월의 파일을 다시 받았을 때 모든 행을 단순 추가하면 같은 사업장·기준월의 값이 여러 번 집계될 수 있습니다. 원본 파일을 보관하는 일과 조회에 쓸 대표 자료를 고르는 일은 나눠야 합니다.
파일 A: 2026-08 최초 자료 ─┐
├→ 2026-08의 조회용 대표 자료
파일 B: 2026-08 수정 자료 ─┘
원본 파일의 체크섬·적재 이력은 각각 보존이 그림은 적재 구조를 설명하는 예시입니다. 최종 차트에서는 같은 월을 두 번 더하지 않되, 숫자가 달라졌을 때 어느 원본으로 갱신됐는지 되짚을 수 있어야 합니다. 검색용 최신 자료와 월간 통계도 같은 대표 자료를 사용해야 상세와 통계가 어긋나지 않습니다.
자료가 없는 월 역시 단순 연결해서는 안 됩니다. 1월과 3월만 있다면 1월 대비 3월 차이는 구할 수 있지만 “두 달 연속 증가”는 확인할 수 없습니다. 필요한 기간을 모두 갖춘 계산과 자료가 있는 두 시점만 비교한 계산을 구분한 이유입니다.
| 예시 자료 | 가능한 설명 | 피할 설명 |
|---|---|---|
| 1월 80명, 2월 83명 | 전월 대비 3명 증가 | 실제 채용 3명 확정 |
| 1월 80명, 3월 90명 | 두 관측 시점의 차이 10명 | 2개월 연속 증가 |
| 고지금액 있음 | 정한 기준으로 급여 추정 | 실제 급여 확인 |
표의 값은 해석 차이를 설명하는 가상 수치입니다. 숫자를 만들 수 있는지보다 그 숫자를 어떤 이름으로 보여줄 수 있는지가 서비스의 기준이 됐습니다.
사업장 식별과 지표 해석
Company Lens에서 이름이 바뀐 것처럼 보이는 사업장 두 곳을 찾았습니다. 공개된 번호 일부와 주소가 같고 자료가 있는 기간도 이어졌습니다. 전후 가입자 수까지 같았지만, 이 정보만으로 동일 사업장이라고 확정하기는 어려웠습니다.
그럴듯하게 차트를 연결하는 것보다 원본에서 확인할 수 있는 관계를 보여주는 편이 맞다고 판단했습니다. 이 기준은 사업장 식별뿐 아니라 고용 통계와 서비스 이용 통계에도 적용했습니다.
사업장 연결 후보와 차트 분리
부분 번호와 주소, 이름, 자료가 있는 기간을 비교해 이전·이후 명칭의 후보를 보여주도록 했습니다. 후보 상세로 이동하는 링크는 제공하되, 두 사업장의 이력과 통계는 각각 유지했습니다.
| 제공하는 정보 | 확정하지 않은 관계 |
|---|---|
| 유사한 사업장 후보 | 동일 법인 여부 |
| 자료 기간 비교 | 누락 기간의 명칭 |
| 다른 상세 페이지 링크 | 두 시계열의 자동 병합 |
식별자가 부분 공개된 자료에서는 여러 조건이 맞아도 관계를 추정한 것일 수 있습니다. 이를 하나의 사업장으로 합치면 이후 차트와 비교 통계에서도 같은 전제가 계속 사용됩니다. 그래서 후보 안내와 데이터 병합을 분리했습니다.
가입자 수와 직원 수의 차이
사업장 비교와 월간 통계에서는 원본 지표의 이름을 유지했습니다. 국민연금 가입자는 회사의 전체 직원 수와 다를 수 있고, 취득·상실 건수도 실제 입사·퇴사와 항상 일치하지 않습니다.
기간 비교에서도 자료가 없는 월을 건너뛰어 연속 증가로 표시하지 않았습니다. 최근 12개월 합계는 필요한 월이 모두 있을 때만 보여주고, 표본이 적은 비교 집단은 충분한 자료가 있는 경우와 구분했습니다.
월간·업종·지역 통계는 미리 집계해 저장했습니다. 방문할 때마다 전체 월별 자료를 다시 읽지 않기 위해서입니다. 이전 월 비교에서는 정확히 해당 월의 값을 사용하고, 분모가 0인 경우에는 비율을 계산하지 않았습니다.
3개월 전과 지금의 차이는 3개월 연속 증가나 이동평균과 다릅니다. 업종 전체 가입자가 늘었다고 바로 채용 수요가 늘었다고 볼 수도 없습니다. 계산 결과를 확인할 때는 지표 이름이 실제로 계산한 내용과 맞는지도 함께 봐야 했습니다.
이용 통계의 집계 범위
운영자 통계에는 일별 요청과 검색 결과, 인기 사업장, 지연과 오류를 집계했습니다. IP와 사용자·세션 ID, 요청별 전체 기록은 별도로 수집하지 않았습니다.
브라우저의 최근 방문은 사용자 기기 안에 저장하고 지울 수 있게 했습니다. 서버에 남는 요청 집계는 CDN 캐시나 브라우저 복원까지 포함한 전체 방문자 수가 아닙니다. 그래서 요청 수를 방문자 수로 부르지 않았습니다.
공개 자료로 만들 수 있는 통계는 많지만, 이 서비스에서는 어떤 대상과 기간을 셌는지 함께 설명하는 데 집중했습니다. 원본에 없는 관계를 추가하거나 다른 의미의 지표로 바꾸면 화면이 정교해져도 해석은 잘못될 수 있기 때문입니다.