반납 위치 결정 로직을 도입해 고객 경험 개선

요약

공유 킥보드 서비스 "디어"를 운영하며, 고객 문의에서 가장 높은 비중을 차지하던 "반납 위치 오류" 문제를 적절한 반납 위치 결정 로직을 도입해 획기적으로 개선한 사례입니다. 문제 원인 분석부터 해결방안 도출 및 프론트엔드 로직 구현을 담당했습니다.

배경

공유 킥보드 서비스에서 반납 위치 는 정말 중요합니다. 대부분의 킥보드 서비스는 “서비스 지역”과 “반납 금지 구역” 이라는 개념을 가지고 있습니다. 말 그래도 킥보드를 이용할 수 있는 지역과 그렇지 못한 지역을 의미하는데요, 유저가 “반납 금지 구역”에 반납을 시도할 경우 유저에게 추가요금을 낼 것을 안내하고 있습니다. 이는 과도한 운영 비용을 방지하기 위해 고객의 불편을 감수하고라도 유지할 수 밖에 없는 정책입니다. 반납 위치에 따라 이 모든 정책이 결정되기 때문에 유저와 서비스 모두 반납 위치를 중요하게 생각할 수 밖에 없습니다.

기존의 “디어” 서비스는 “반납 위치”를 킥보드 GPS의 정보를 바탕으로 결정하고 있었습니다. 이는 어쩔 수 없이 사람보다 킥보드라는 자산을 더 신뢰할 수 밖에 없었기 때문인데요, 킥보드는 반납하는 순간부터는 이동하기 매우 어렵지만, 유저는 다른 곳으로 이동해서 킥보드 반납 요청을 해도 서비스 차원에서 알 방도가 없습니다. 유저의 위치를 반납 정보로 받아들인다면 유저가 “반납 금지 구역”에 킥보드를 두고 “서비스 지역” 내로 들어와 반납을 해도 서비스는 알지 못합니다. 그래서 유저의 위치보다 킥보드의 위치를 더 중요하게 생각하게 된 것입니다.

당시에는 킥보드의 위치를 반납 위치로 사용하는 것이 별다른 문제가 없을 것으로 생각했습니다. 킥보드 GPS의 정확도가 괜찮았고 유저는 반납하는 시점에 킥보드의 근처에 있을 것이기 때문에 고객 경험 측면에서도 문제가 없을 것이라고 생각했습니다. 하지만 위의 결정은 예상과 다르게 많은 고객 불만을 야기하게 됩니다. VOC 카테고리의 1위는 항상 아래와 같은 “반납 위치 오류”에 관한 내용이었습니다.

반납 위치 오류로 벌금이 부과되었어요.
반납 위치 오류로 할인을 받지 못했어요.

원인

문제의 원인은 “킥보드 위치의 정확도”였습니다. 정확히는 유저가 반납을 시도하는 시점에 시스템이 인지하는 “킥보드 위치”가 실제 위치와 다른 경우가 많았다는 것입니다. 킥보드 위치를 신뢰하지 못하게 된 데에는 두 가지 배경이 있습니다.

킥보드에 달려있는 iOT 모뎀은 약 10초에 한번씩 iOT서버로 킥보드의 위치를 업데이트합니다. 여기서 “최신성”에 관한 첫 번째 문제가 생깁니다. 유저가 10초 사이에 킥보드를 이동하여 반납을 시도한다면 시스템은 정확한 반납 위치를 알 수 없습니다. 두번째로는 iOT 서버 자체의 문제인데요, iOT 서버는 하드웨어로부터 전달받은 메시지를 가공해 어플리케이션 서버로 전달하게 됩니다. 하지만 10,000대 이상의 하드웨어로부터 초단위의 메시지를 처리해야 하는 iOT 서버는 자주 불안정해지곤 했습니다. iOT 서버의 불안정성이 서비스단의 반납 위치 결정에도 영향을 미치게 된 것입니다.

결국 본질적인 신뢰도 문제가 있는 킥보드 위치를 최종 반납 위치로 사용하는 이상 고객 불만은 계속 될 수 밖에 없었습니다. 그래서 반납 위치 선정 로직을 개선하기로 결정했습니다.

해결

개선 방향은 다음과 같았습니다.

  • 더 최신의, 더 정확한 위치를 사용한다.
  • 위를 달성하는 동시에 발생 가능한 어뷰징 및 엣지 케이스에 대응한다.

저희는 킥보드의 위치 정보와 유저의 위치 정보를 함께 활용하기로 했습니다. 유저의 위치는 모바일 GPS로부터 실시간으로 업데이트 됩니다. 지형지물의 방해가 없다면 굉장히 정확합니다. 즉 유저의 위치가 “더 정확하고 더 최신의” 정보일 확률이 높다는 것입니다. 하지만 유저의 위치를 반납 위치로 사용하게 되면 치명적인 어뷰징 케이스가 발생할 수 있습니다. 위에서 언급한 것처럼 유저가 킥보드의 위치와 관계없이 서비스 지역 혹은 할인 지역에서 반납을 할 수 있게 됩니다. 이러한 케이스는 방지해야 했습니다. 그래서 최종적으로는 다음과 같이 로직을 수정하게 되었습니다.

📌 유저의 위치를 반납 위치로 우선적으로 사용하되, 유저의 위치와 킥보드의 위치가 어뷰징으로 판단될만큼 멀어진다면 유저에게 고지하고 킥보드의 위치를 반납 위치로 사용한다.

기술적으로는 다음과 같이 구현되었습니다. 유저가 킥보드 이용을 시작하면 클라이언트에서 5초에 한번씩 서버로 킥보드의 위치 정보를 물어봅니다. 서버의 응답에는 위치 좌표값과 함께 radius라는 정보가 담기게 되는데요, 이는 서버에서 판단한 킥보드 위치의 신뢰도를 거리로 변환한 값입니다. 즉 99% 확률로 반경 안에는 있다는 표현입니다.

클라이언트에서는 동시에 유저의 위치 변화를 구독하고 있습니다. 킥보드의 위치와 유저의 위치 중 하나라도 갱신이 되면 보정 위치를 계산하는 함수(updateAdjustedPosition)가 호출됩니다. updateAdjustedPosition 함수는 유저의 위치와 킥보드의 위치 사이의 거리를 계산합니다. 그리고 그를 radius 값과 비교해, 둘 사이의 거리가 반경 안에 있으면 정상적으로 유저의 위치를 최종 반납 위치로 선정하고 반대로 거리가 반경 밖에 있으면 이상 케이스라고 판단해 킥보드의 위치를 최종 반납 위치로 선정하고 유저에게 상황을 고지합니다.


interface RidingKickboardInfo {
latitude: number
longitude: number
radius: number // 서버에서 판단한 킥보드 현재 위치 신뢰도를 거리로 변환한 값
}

// 현재 이용 중인 킥보드의 위치를 5초 간격으로 폴링
const {data: ridingKickboardInfo} = useFetchRidingKickboardInfo<RidingKickboardInfo>(kickboardId, {
refetchInterval: 5000
})

// 유저 앱의 GPS 위치 변화가 감지될 때마다 업데이트
interface UserPosition {
latitude: number
longitude: number
accuracy: number // 앱에서 판단한 유저 GPS 위치 정확도
}

const userPosition = useUserPosition()


// 유저 위치와 이용 중인 킥보드 위치를 받아 보정된 로직을 반환하는 함수
function updateAdjustedPosition(ridingKickboardInfo, userPosition): AdjustedPosition {

// 두 위치 모두를 구할 수 없다면 예외 상황
if (!userPosition || !ridingKickboardInfo) {
throw new Error('error occured')
}

// 유저 위치를 구할 수 없다면 킥보드의 위치를 최종 반납 위치로 사용
if (!userPosition) {
return {...ridingKickboardInfo, source: 'kickboard'}
}

// 반대로, 킥보드의 위치를 구할 수 없다면 유저의 위치를 최종 반납 위치로 사용
if (!ridingKickboardInfo) {
return {...userPosition, source: 'user'}
}

// 두 위치를 구할 수 있다면,
const {radius, ...kickboardPosition} = ridingKickboardInfo
const distanceBetweenUserAndKickboard = Geolib.getPreciseDistance(userPosition, kickboardPosition)

// 유저와 킥보드 사이의 거리가 킥보드 위치의 오차 범위를 넘어간다면,
// 유저의 위치를 신뢰할 수 없다고 판단하여 킥보드 위치를 선정,
// 반대의 경우 유저의 위치를 더 신뢰
return distanceBetweenUserAndKickboard >= radius
? {...kickboardPosition, source: 'kickboard'}
: {...userLoaction, source: 'user'}
}


이를 통해 일반적인 경우 더 정확한 유저의 위치 를 사용하게 되었습니다. 유저의 입장에서도 앱의 지도에 노출되는 자신의 위치와 실제 반납 위치가 일치하기 때문에 더이상 "반납 위치가 이상하다"는 인식을 갖지 않게 되었습니다. 유저의 위치를 구할 수 없거나 유저와 킥보드가 일정 거리 이상으로 멀어진 경우에만 킥보드의 위치를 사용하는 것으로 엣지 케이스를 대응했습니다.

결과

위와 같이 로직을 개선하여 해당 문제의 VOC를 기존 대비 70% 수준으로 줄일 수 있었습니다. 모바일과 킥보드 GPS의 본질적인 정확도 문제는 서비스 차원에서 수정할 수 없었기에 해당 문제를 완전히 근절시키기는 어려웠지만 비교적 간단한 해결책으로 예상보다 큰 성과를 얻었습니다. 또한 우려와 달리 별다른 어뷰징 케이스도 관찰되지 않았습니다. 문제를 분석하고 논리적인 해결책을 도출해 임팩트있는 성과를 낼 수 있었던 경험이었습니다.