컬리 주소정제 내재화 시리즈 4화로, 3화에서 만든 전국 약 1,080만 건물 DB를 바탕으로 단독 건물 주소정제를 구현한다. 문자열 주소를 파싱하는 AddressSearchParam 추출 알고리즘, 도로명 추출의 난점, 부천·전북 행정구역 변경 대응을 다루며, 기대 90%였던 외부 호출 감축이 실제 60%에 그친 이유를 밝힌다.
핵심 포인트- 복합 건물 약 20만 대 단독 건물 약 1,060만(98%)이지만 트래픽은 건물 수 비율을 따르지 않는다.
- 정제 2.0은 기본 도로명 주소로 내부 DB를 조회해 정확히 1건 매칭 시에만 결과 반환, 나머지는 외부 API로 폴백한다.
- 주소 문자열을 시도→시군구→법정동→도로명→건번호로 단계 추출하는 알고리즘을 Java로 구현했다.
- 건물관리번호는 절대 안 바뀌지만 법정동코드·도로코드·행정코드는 바뀐다는 행안부 원칙을 활용했다.
- 부천 분구·전북특별자치도 개편을 무중단 대응했고, 단독 건물 내재화로 외부 호출 약 60%를 감축했다.
상세 정리- 통계 발견: 복합 건물 약 20만, 단독 건물 약 1,060만(98%). 단, 아파트 한 주소가 수백 주문을 내므로 건물 분포가 트래픽 감축과 직결되지 않는다.
- 설계(정제 2.0): 문자열을 AddressSearchParam 객체로 변환하고 기본 도로명 주소로만 건물 DB를 조회해 정확히 1건이면 정제 반환(보수적), 그 외는 외부 API로 폴백한다.
- 추출 순서: 도로명은 시도→시군구→법정동(있으면)→도로명→건물본번→건물부번→나머지, 지번은 시도→시군구→법정동→읍면동→본번→부번→산(있으면)→나머지다.
- 시도 추출: replaceSidoName()이 서울·서울시 변형을 표준형(서울특별시)으로 정규화하고 전북특별자치도·강원특별자치도 등을 매핑한다.
- 시군구 추출: 토큰 분해 후 시+구 조합을 처리한다. 부천시 원미구는 시를 생략한 부천 원미구 같은 엣지 케이스에 대응한다.
- 법정동 추출: 첫 토큰이 읍/면/동/가로 끝나면 채택한다. 가는 지번을 식별한다(한강로2가는 도로명 한강대로30길).
- 도로명 추출(최난): 도로명은 고개/거리/번길/길/로로만 끝난다. 가로수로20번길133처럼 붙여 쓴 입력을 addSpacesBetweenDifferentLanguage()로 가로수로 20 번길 133으로 띄워 파싱한다.
- 이중 도로명: 인천 서구 서로3로는 서로와 3로를 잡으려 추출을 두 번 호출한다(mainRoadName+subRoadName). 중간 변수는 OMS 전용 검증 기능에 재사용한다.
- 건번호 추출: 63-2는 본번 63·부번 2, 63은 부번 기본값 0(행안부 데이터 기준). 파싱 실패는 빈값·0을 반환한다.
- 설계 철학: 정확성보다 관용성 우선. 가독성·유지보수 대 찰떡 정제를 저울질하며 프로덕션 로그의 빈발 오류부터 처리하고, 발견한 변형은 영구 테스트 케이스로 축적한다.
- 행정구역 대란: 1/1 부천시 3개 구 분구, 1/18 전북에서 전북특별자치도로. 2023년 강원특별자치도·경북 군위군에서 대구 군위군 선례가 있었다.
- 핵심 원칙: 행안부 담당자 확인으로 건물관리번호는 죽었다 깨어나도 안 바뀐다. 반면 법정동·행정·도로코드, 영문·한글 명칭은 지자체별 시점차로 바뀐다.
- 대응: 전북은 DB에 두 표기를 수용하고 표준형을 반환, 부천은 replaceSigunguNameByRule()로 부천·창원 시작이면 부천시·창원시로 like 조회한다. 두 전환일 모두 무장애.
- 결과: 건물 분포로 기대한 90% 감축이 실제 약 60%였다. 단독 건물은 주문 빈도가 낮고 복합 건물이 물량을 좌우하기 때문이다. 남은 40%는 5화 복합 건물에서 다룬다.
왜 읽나자유 입력 한글 주소를 시도·시군구·도로명·건번호로 파싱하고 행정구역 개편까지 견디는 주소 파서를 설계하는 엔지니어에게 Java 구현 레퍼런스.