J
Jay (손정연)Author

2025년 10월 29일

볼트업 백엔드 시스템 개편 이야기 - 파트 2: 마이그레이션 편

안녕하세요. 볼트업에서 백엔드 소프트웨어 개발을 맡고있는 Jay(손정연)입니다.

이번 글은 ‘파트 1: 아키텍처 편’에 이은 ‘파트 2: 마이그레이션 편’으로 저희가 어떻게 구 시스템의 충전기와 데이터를 무중단으로 신규 시스템에 이관(Migration)할 수 있었는지 공유해 드릴 예정입니다. 이관 과정에서 마주한 문제와 해법을 중심으로 상세히 설명하겠습니다.

무중단으로 충전기 이관하기

백엔드 시스템을 개편할 당시, 구 시스템에는 약 1만 5천 대의 충전기가 웹소켓으로 연결되어 있었습니다. 그리고 이어지는 충전소 확장으로 연동된 충전기 수는 점차 더 늘어나고 있었습니다. 이와 같은 상황에서 충전기를 이관할 때 저희는 몇 가지 어려운 문제를 직면했습니다.

문제 1-1: 펌웨어 업데이트의 어려움

충전기를 신규 시스템에 이관하려면 충전기 펌웨어에 설정된 웹소켓 서버 연결 주소를 신규 시스템 주소로 변경해야 했습니다. 원격 펌웨어 업데이트를 통해서 충전소 현장에 가지 않더라도 변경할 수 있었습니다. 그러나 충전기 펌웨어를 업데이트하는데 최소 5분이 소요되었습니다. 더불어 간헐적으로 업데이트가 실패해 재시도해야 하는 경우도 종종 있었습니다. 이러한 조건에서는 일시에 1만 5천 대의 충전기가 신규 시스템으로 한번에 잘 이관될지 보장할 수 없었습니다. 만약 부분적으로 충전기 이관에 실패한다면 사용자 혼란이 예상되었습니다.

문제 1-2: 충전은 계속되어야 한다

완속 충전은 길면 10시간 이상 진행됩니다. 충전기 이관을 진행하는 시점에도 구 시스템에서 충전 중인 충전기들이 많이 있을 것으로 예상되었습니다. 주소 변경을 위해 펌웨어를 업데이트하면 충전기가 재시동되기 때문에, 진행 중인 충전의 종료가 불가피했습니다. 이관 시점에 큰 사용자 불편이 생길 수밖에 없었고, 목표로 삼은 무중단 이관이라고도 보기 어려웠습니다.

문제 1-3: 충전기 방언

볼트업은 여러 제조사의 충전기 모델들을 구매해서 운영 중입니다. 시스템 개편 당시에는 6개사의 20종 이상의 모델이 있었는데요. 충전기는 기본적으로 웹소켓 기반의 OCPP(Open Charge Point Protocol) 프로토콜에 따라 백엔드 시스템과 연동됩니다 [1]. 하지만 제조사마다 충전기 펌웨어의 구현 담당자가 모두 다르다 보니, 충전기 모델과 펌웨어 버전에 따라서 세부 동작이 모두 조금씩 달랐습니다. 그리고 OCPP 프로토콜의 커스텀 메시지 정의 기능으로 구 시스템에 특화된 메시지 여러 개가 정의되어 사용 중이었습니다. 모든 충전기 모델들과 잘 연동되면서, 리뉴얼 버전 볼트업의 요구사항을 구현하는 것은 큰 도전이었습니다.

해법 1-1: 사전(Pre) 마이그레이션

저희 백엔드 엔지니어들은 론칭일 전에 충전기만 먼저 신규 시스템으로 모두 이관하는 중간 단계를 두기로 했습니다. 이것이 어떻게 가능했는지 설명해 드리겠습니다.

신규 시스템에 충전기로부터 오는 메시지를 그대로 다시 구 시스템으로 넘겨주는 프락시(Proxy) 로직을 추가했습니다 [2]. 그리고 구 시스템도 수정해 반대로 충전기로 보낼 메시지를 신규 시스템으로 보내고, 그걸 신규 시스템이 받아 다시 충전기로 전달하는 기능을 만들었습니다. 이 구조를 통해 충전기가 이관되더라도 사용자는 구 시스템과 사용자 앱을 변화 없이 그대로 사용할 수 있었습니다. 사용자는 어떤 충전기가 이관되었는지도 알 수 없었습니다.

결과적으로 일시에 모든 충전기를 이관할 필요가 없어졌고, 부분적으로 나눠서 이관을 진행할 수 있었습니다. 펌웨어 업데이트 시간으로부터도 자유로워졌습니다. 업데이트가 실패하면 재시도하거나 현장 조치도 가능했습니다.

이런 방법으로 저희는 론칭일 전에 모든 충전기를 이관했습니다. 그리고 이를 통해 론칭 시점에 시스템상의 플래그(Flag) 값만 변경하는 방식으로 프락시 기능에서 리뉴얼 볼트업 기능으로 일시 전환이 가능해졌습니다.

해법 1-2: 마이그레이션 배치(Batch)

사전 이관을 통해서 충전 중에 펌웨어를 업데이트해야 할 필요는 없어졌습니다. 그러나 플래그 값으로 일시 전환을 하더라도, 구 시스템의 회원정보와 결제수단으로 충전 중이던 충전기를 전환해버리면 정상적인 충전 종료와 결제가 불가능했습니다.

사전 이관을 끝낸 시점부터 신규 시스템에도 모든 충전기의 상태가 충전기 메시지를 통해 갱신되고 있었습니다. 그래서 저희는 론칭 시점에 충전 중이 아닌 충전기들만 플래그 값을 일시에 변경했습니다. 그리고 분 단위 배치를 통해 충전이 끝난 미전환 충전기들의 플래그값을 점진적으로 변경해 나갔습니다. 이로써 충전이 끊어지는 사용자 불편을 초래하지 않으면서, 신규 시스템으로 우아하게 모든 충전기를 이관할 수 있었습니다.

해법 1-3: 충전기 모델별 어댑터

충전기 모델별로 조금씩 다른 OCPP 프로토콜을 보정 처리하기 위해 CSMS(Charge Station Management System, 1편 참고) 오케스트레이터에 어댑터(Adapter) 계층을 두기로 했습니다. 충전기 모델에 따라서 같은 유형의 OCPP 메시지도 다르게 처리할 수 있는 구조로 CSMS 설계를 변경했습니다.

여러 충전기 모델이 동일한 어댑터 로직을 적용받아야 하는 경우도 있어서, 최종적으로 어댑터는 프로토콜(Protocol)이라는 충전기 모델 그룹 단위로 적용되었습니다. 여러 프로토콜 사이에는 트리 계층 구조를 설정할 수 있게 설계해서, 하위 프로토콜에 특정 OCPP 메시지를 처리하는 어댑터 로직이 없다면, 상위 프로토콜의 어댑터 로직이 적용되도록 구현했습니다.

어댑터는 JVM(Java Virtual Machine)의 리플랙션(Reflection)을 활용해 아래와 같은 Kotlin 애노테이션을 클래스와 메소드에 붙이는 방식으로 간단히 추가할 수 있게 설계했습니다 [3].

kotlin
@OcppHandler(
    protocol = Protocol.BASE,
    action = OcppInboundAction.HEARTBEAT
)
class HeartbeatHandler {

    @OcppHandlerMethod
    fun handle(
        chargerUuid: String,
        @OcppRequest request: HeartbeatRequest
    ): HeartbeatConfirmation {
        return HeartbeatConfirmation(
            currentTime = ZonedDateTime.now()
        )
    }
}

만약 OCPP의 ‘Heartbeat’ 메시지를 ‘VOLTUP’ 프로토콜에서 ‘BASE’ 프로토콜과 다르게 처리하고 싶다면, 아래와 같은 어댑터를 추가하면 됩니다.

kotlin
@OcppHandler(
    protocol = Protocol.VOLTUP, // 'VOLTUP' 프로토콜 충전기에 적용
    action = OcppInboundAction.HEARTBEAT
)
class HeartbeatHandler {

    @OcppHandlerMethod
    fun handle(
        chargerUuid: String,
        @OcppRequest request: HeartbeatRequest
    ): HeartbeatConfirmation {

        doSomethingDifferent() // 다른 처리 추가

        return HeartbeatConfirmation(
            currentTime = ZonedDateTime.now()
        )
    }
}

결과적으로 모델별 어댑터를 만듦으로써 신규 시스템은 다양한 충전기 모델에 대해서 일관되게 리뉴얼 볼트업의 기능들을 지원할 수 있게 되었습니다. 그리고 이때의 CSMS 어댑터 구조 설계는 나중에 신형 충전기 모델이 추가되거나 타사 충전기를 인수해야 하는 상황에서 빠르게 시스템 연동 작업을 완료해 낼 수 있는 역량이 되었습니다.

회원카드 옮기기

문제 2: 재활용 필수

사용자는 충전기에 실물 RFID(Radio Frequency Identification) 회원카드를 태깅하여 인증을 수행하고 충전을 이용할 수 있습니다. 저희는 구 시스템에서 발급한 회원카드가 있는 사람이라면 리뉴얼된 볼트업에서 기존 회원카드를 그대로 사용할 수 있게 해야 했습니다. 그렇지 않을 경우, 론칭 이후 회원카드를 신규로 발급해야 하는 사용자 불편이 예상됐고, 회사 차원에서도 회원카드 발급에 따른 부대 비용이 발생했기 때문입니다.

해법 2: 가입 시 자동 등록

구 사용자 앱과 리뉴얼된 사용자 앱은 모두 회원가입 시 본인인증을 요구했습니다. 그래서 본인인증 기관에서 획득한 CI(Connecting Information)를 활용하면 동일인 여부를 확인할 수 있었습니다 [4].

구 시스템 데이터베이스를 바라보는 Legacy 서비스(1편 참고)에 CI 기반 구 회원카드 조회 API를 만들고, App 오케스트레이터에 해당 API를 사용해 가입 완료 시 획득한 CI를 바탕으로, 자동으로 예전 회원카드를 등록하는 기능을 구현했습니다. 결과적으로 구 시스템에서 발급했던 전체 회원카드 중 70% 이상이 신규 시스템에 재등록되어 지금까지 사용 중입니다.

구 이용내역 보여주기

문제 3: 노스탤지어(Nostalgia)

사용자 편의를 위해 리뉴얼된 앱에서도 구 시스템에서 발생한 충전 이용내역을 보여줘야 했습니다. 그런데 신규 시스템의 충전, 주문 엔티티와 구 시스템 충전 내역의 데이터 스키마(Schema)가 서로 달랐고, 스키마에 맞게 매핑해 이관하기에는 데이터의 성격도 달랐습니다.

해법 3: 구 데이터베이스 조회

구 이용내역은 사용자가 조회만 가능했고 일정 기간 이후에 제거될 기능이었기 때문에, 큰 공수를 들이지 않고 최대한 단순한 방법으로 구현했습니다. 구 시스템 데이터베이스를 바라보는 Legacy 서비스에 CI 기반 이용내역 조회 API를 만들고, App 오케스트레이터에서 해당 API를 사용해 리뉴얼된 사용자 앱 화면에 맞게 조정해 앱에 전달했습니다.

나중에 데이터베이스를 포함한 구 시스템을 완전히 셧다운하는 시점이 찾아오는데, 이때 Legacy 서비스가 구 데이터베이스를 그대로 복제한 신규 시스템 내 데이터베이스를 바라보도록 연결 설정만 한번 더 변경됩니다.

미전환 회원도 쓸 수 있게 해주기

문제 4: 디지털 미니멀리스트

구 시스템에는 사용자 앱으로 가입 후 회원카드만 발급하고, 앱을 자주 사용하지 않는 사용자들이 많았습니다. 그들은 주로 회원카드를 태깅하는 방식으로 충전을 이용했습니다. 론칭 이후에는 리뉴얼된 앱에 다시 가입하지 않으면 기존 회원카드가 이관 등록되지 않아 사용이 불가한 상태였습니다. 회원카드만 주로 쓰는 사용자들의 큰 어려움이 예상됐고 매출 감소로 이어질 수 있었습니다. 이를 완화하기 위해, 약 두 달의 유예 기간을 두고 신규 시스템에 가입하지 않더라도 구 시스템에 등록된 회원카드와 결제수단으로 충전을 이용할 수 있게 했습니다.

해법 4: 제휴 로밍처럼

저희는 유예 기간 동안 구 시스템의 미전환 사용자를 환경부 제휴 로밍 사용자와 비슷하게 처리하는 방식을 고안해 냈습니다.

회원카드 태깅 시 미전환된 회원카드 번호인지 Legacy 서비스 API로 확인 후 인증 성공으로 처리했습니다. 그리고 이렇게 인증했던 충전 주문 내역을 따로 쌓아두고, 한 달 단위로 구 시스템에 등록된 결제수단으로 일괄 정산했습니다. 결과적으로 사용자 불편과 매출 감소를 최소화할 수 있었습니다. 론칭 다음날부터는 바로 구 시스템과 동일한 수준으로 전체 충전량과 매출이 회복되었습니다.

마치며

파트 2에서는 구 시스템의 충전기와 데이터를 신규 시스템으로 이관하는 과정을, 저희가 마주했던 문제와 해법을 중심으로 살펴보았습니다. 데이터가 아닌 충전기라는 개별 단말기들을 신규 시스템으로 이관하는 과정은 특수한 어려움이 있었습니다. 타 충전소 플랫폼 사들에서 시스템 통합 시 긴 점검 시간을 가졌던 것에 비해, 단 1초의 중단도 없이 신규 시스템으로 충전기를 모두 이관했다는 점에서 큰 보람을 느꼈습니다. 이것은 사용자 불편과 매출 감소를 최소화하는 것으로도 이어졌습니다. 더 나아가 저희는 이번 과정을 통해 신형 모델 추가나 타사 충전기 인수 시 빠르게 충전기를 연동할 수 있는 역량도 확보했습니다.

볼트업은 리뉴얼 론칭 이후에도 공격적인 충전소 확장으로 충전기 대수가 30%가량 늘었습니다. 고무적인 것은 일간 평균 충전 건수와 충전량은 그보다 더 많은 두 배 가까이 성장했다는 점입니다. 전체 가입자 수도 비슷하게 꾸준히 증가 중입니다. 이는 국내 보급 전기차의 수가 점차 늘어나면서 충전 시장도 함께 성장했기 때문으로 이해됩니다.

이번 백엔드 시스템 개편 과정을 통해, 볼트업은 전기차 충전 시장의 성장을 함께할 준비가 되었습니다. 최근 ISO15118이라는 전기차와 충전기 간의 하이레벨(High-Level) 통신 표준이 상용으로 보급되고 있는데, 그에 기반한 PnC(Plug and Charge), 전력 로드밸런싱(Load Balancing), 그리고 V2G(Vehicle to Grid)와 같은 충전 기술들도 빠르게 발전 중입니다 [5]. 이러한 기술 발전에 잘 적응해 사용자에게 차별화된 충전 경험을 전달하려면, 유연하고 확장 가능한 백엔드 시스템이 필수 조건이라고 생각합니다. 이제 볼트업은 그 기반을 갖추었습니다. 앞으로 볼트업이 만들어갈 성장과 혁신에도 많은 관심을 부탁드립니다. 긴 글 읽어주셔서 감사합니다.

참고문헌

[1] Open Charge Alliance. Open Charge Point Protocol (OCPP). https://openchargealliance.org/protocols/open-charge-point-protocol

[2] Wikipedia, The Free Encyclopedia. Proxy server. https://en.wikipedia.org/wiki/Proxy_server

[3] Kotlin. Reflection. https://kotlinlang.org/docs/reflection.html

[4] 본인확인 지원포털. 주민번호 대체수단. https://identity.kisa.or.kr/web/main/contents/M010-02

[5] ISO. ISO 15118-1:2019 Road vehicles -- Vehicle to grid communication interface -- Part 1: General information and use-case definition. ISO, 2019.

J

Jay (손정연)

Tech Innovation Tribe
이 글 공유하기

지금 바로 볼트업에 지원해 보세요

볼트업 채용공고 바로가기