포스트

[집현전] 공공데이터 API 연동 최적화 과정

[집현전] 공공데이터 API 연동 최적화 과정

집현전 프로젝트를 진행하며 부동산 안전도(재해 위험도, 건축물 규제 등)를 분석하기 위해 건축물대장, 재해위험지역 등 다양한 공공데이터 API를 연동해야 했다. 단순히 API를 호출하고 데이터를 받아오면 끝날 줄 알았지만, 여러 가지 문제가 발생했다.

잦은 API 호출로 인한 병목 현상이 있었다.

건축물대장 API를 호출하기 위해서는 법정동 코드가 필수로 필요했다. 처음 구현했을 때는 사용자의 주소를 파싱한 뒤, 법정동코드 API를 먼저 호출하여 코드를 얻고, 그 코드로 다시 건축물대장 API를 호출하는 방식을 사용했다.

하지만 이 방식은 하나의 요청이 항상 두 번의 외부 API 호출에 의존하는 구조였기 때문에 외부 API 응답시간이 누적되어 전체 요청 시간이 증가했다.

법정동 코드는 변경 주기가 길어 실시간 조회가 필요하지 않았기 때문에, 텍스트 파일을 서버에 저장하고 애플리케이션 시작 시 Map<String, String>으로 메모리에 캐싱하도록 변경하여 건축물 조회 시 외부 API 호출 횟수를 2회에서 1회로 줄일 수 있었다.

address_codemap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class AddressCodeMap {
    private final Map<String, String> codeMap = new HashMap<>();

    @PostConstruct
    public void init() {
        // ...
        try (BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
            while ((line = br.readLine()) != null) {
                String[] parts = line.split("\t");
                if (parts.length >= 3 && parts[2].contains("존재")) {
                    String code = parts[0].trim();
                    String address = parts[1].trim();
                    
                    codeMap.put(address, code); 
                }
            }
        } catch (Exception e)   // ...
    }

    public String getCode(String fullAddress) {
        return codeMap.get(fullAddress);
    }
}

캐시 갱신 과정에서 트랜잭션을 적용했다.

특정 지역의 재해 위험도를 조회할 때마다 매번 외부 API를 호출하는 것은 비효율적이었기에 DISASTER_API_CACHE라는 DB 테이블을 두고 주기적으로 갱신하는 방식을 채택했다.

그리고 캐시 갱신은 기존 데이터를 삭제한 뒤 새로운 데이터를 다시 저장하는 방식이었다. 이 과정에서 중간에 예외가 발생하면 캐시 테이블이 비어 있는 상태로 남을 수 있었다. 이를 방지하기 위해 refreshApiCache()@Transactional을 적용하여 실패 시 롤백 되도록 원자성을 보장했다.

API 연동 중 데이터베이스와 응답 파싱에서도 문제가 있었다.

  1. 응답 포맷 불일치: 보통 공공데이터 API는 response 안에 배열 형태의 items를 반환하는데, 특정 API는 배열이 아닌 단일 body 객체를 바로 반환했다. 이로 인해 !items.isArray() 검증 로직에서 계속 에러가 발생했다. 이를 해결하기 위해 Jackson의 JsonNode를 활용하여 root.get("body")로 직접 접근하도록 수정하여 해당 API의 응답 구조에 맞게 파싱하도록 변경했다.
  2. PK의 Auto Increment 값 누적: 캐시 갱신을 위해 DELETE를 사용하면 Auto Increment 값은 유지되어 캐시 테이블이 반복 갱신될수록 PK의 값이 계속 증가했다. 캐시 데이터는 이력을 보존할 필요가 없었기 때문에 TRUNCATE를 사용하여 테이블을 완전히 초기화하도록 변경했다.

API 상세를 미리 확인하자

어렵게 연동과 최적화를 마쳤지만, 사용하던 재해 API가 장기간 데이터가 갱신되지 않고 있다는 것을 확인했다.

우리가 구현하려는 기능은 지역의 재해 위험도를 표시하는 것이었기에 최신 데이터가 지속적으로 제공되는 것이 중요했다. 그래서 최신 데이터를 꾸준히 제공하는 긴급재난문자 API로 대체했다.

하지만 재난문자 데이터는 양이 방대하고, 실종자를 찾는 등 재해 위험도와 관련 없는 항목이 다수 포함되어 있었다. 이를 모두 그대로 사용한다면 API 호출 횟수 제한을 초과할 위험이 있었고 데이터 처리 속도도 느려질 수 있었다. 따라서 긴급단계명을 긴급재난, 위급재난으로 한정하고, 재해구분명에서 지진, 화재, 홍수, 폭설 등의 핵심 키워드만 필터링하여 추출하도록 변경함으로써 재해 위험도 분석에 필요한 데이터만 활용하도록 개선했다.

결론

외부 API 연동에서는 단순히 호출에 성공하는 것보다 호출 횟수 최소화, 데이터 캐싱, 응답 포맷 변화 대응, 서비스 여부까지 함께 고려해야 안정적인 서비스를 만들 수 있다는 점을 배웠다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.