로지스팟은 지입차량 관제 시스템의 핵심 기능 중 하나로서 개별 차량의 실제 운행 경로 및 거리를 측정하여 고객들에게 제공하고 있습니다. 이 글에서는 GPS 데이터를 이용하여 운행경로 및 실 운행거리를 측정하는 기능을 설계하고 개발하는 과정에 대해서 공유하고자 합니다. 뿐만 아니라 실제 운영 과정에서 겪었던 다양한 예외 상황들과 이러한 예외 상황들을 해결한 방법들에 대해서도 함께 공유하려고 합니다.
*지입차란? “운송회사 차량을 이용해서 개인이 사업자등록증을 가지고 사업 형태로 운영하는 것”라고 정의되어있지만, 로지스팟의 지입차 관리에서는 편의를 위하여 고객사 소속으로 운영되는 모든 차량을 통칭하는 의미로 사용되고 있습니다.
회사 소속의(또는, 운송사 소속의) 화물차량을 관리하고 있는 많은 기업들은 개별 차량의 현재 위치 및 운행 진척 상황 이외에 아래와 같은 운행 일지를 통하여 차량의 운행 내역을 수기로 작성하여 관리하고 있었습니다. 이러한 운행 일지를 통하여 고객사들은 지입차량들의 유류비 등 부대비용들을 관리하고 있습니다. 아래의 사진은 이렇게 수기로 작성된 기존 방식의 운행일지의 대표적인 예시입니다.
수기로 작성된 운행일지는 정산을 위해 엑셀 등의 서식으로 옮겨적어야 하는 불편함은 물론, 분실의 위험성, 기록된 내용에 대한 신뢰 등 여러 가지 비효율성을 가지고 있습니다. 이러한 문제를 해결하기 위해서는 차량에서 사용 가능한 모바일 애플리케이션과 시스템을 추가로 개발해야 하는 비용의 문제 등으로 많은 기업들이 불편함을 감수하며 매달 번거로운 정산과 자료 확인에 많은 시간을 들이고 있습니다.
로지스팟은 별도의 시스템 구축 없이 로지스팟 플랫폼 사용만으로 이러한 불편함을 해결하기 위해 지입차량 관리(Fleet Management) 서비스를 개발하게 되었습니다 . 이 서비스에서 제공하는 다양한 기능 중 하나인 디지털 운행일지에는 GPS 데이터를 기반으로 정확한 운행 기록 측정과 비용 등록 및 증빙 자료 첨부 등의 기능이 제공됩니다.
로지스팟은 위치정보 사업자로서 화물 운송을 수행하는 기사님들의 차량용 안드로이드 애플리케이션을 통해 수집된 위치정보를 통해 물류 시장의 비효율적인 운영 방식을 개선하는 데에 활용하고 있습니다. 차량용 애플리케이션에서 위치정보를 활용하는 데에는 FusedLocationProviderApi와 ActivityRecognitionApi를 활용하고 있으며 이 글에서는 모바일 애플리케이션 개발 시 사용된 기술에 대해서는 구체적으로 설명하지 않고 수집된 위치정보를 저장하고 관리하는 방법에 대해서만 자세히 설명하도록 하겠습니다.
화물 운송을 수행하는 기사님들의 (암호화된) GPS 데이터는 일차적으로 REST API를 통해 Laravel 애플리케이션 서버로 전송되며 서버에서는 전달받은 데이터를 별도의 가공없이 즉시 AWS SQS(Simple Queue Service)로 전달됩니다. 이렇게 데이터를 큐로 즉시 전달하는 이유는 대량의 GPS 데이터 트래픽을 처리할 때 발생하는 부하가 애플리케이션 서버에 집중되지 않고 분산되어 비동기적으로 처리될 수 있도록 아키텍처를 구성하기 위함입니다. 애플리케이션 서버는 데이터를 전달(forwarding)하는 역할만 할 뿐 별도의 데이터베이스 트랜잭션이나 계산 과정을 수행하지 않습니다.
AWS SQS에 전달된 데이터는 오픈소스 기반의 데이터 수집기인 Logstash의 SQS 입력 플러그인을 통해 자동으로 큐에서 빠지게(dequeue)되며 Logstash는 SQS로부터 가져온 데이터를 목적에 맞게 가공(processing)하여 데이터 저장소인 InfluxDB와 AWS S3에 전달하게 됩니다.
데이터 수집(aggregation)의 기술요소로 Logstash를 사용한 이유는 하나의 입력 소스로부터 수집된 데이터를 필요한 형식으로 가공하고 이를 목적에 맞게 하나 이상의 출력 소스로 전달하기 위해서 입니다. GPS 데이터 관리를 위해 Logstash를 사용한 방법에 대해서는 이어지는 내용에서 자세히 설명하도록 하겠습니다.
Logstash는 실시간 파이프라인 성격의 오픈소스 데이터 수집기입니다. JRuby로 작 성되었으므로 실행하려면 JVM(Java Virtual Machine)이 필요합니다. Logstash는 Apache Flume 등의 다른 오픈소스 데이터 수집기와 마찬가지로 다양한 소스에서 생성된 데이터를 입력으로 받을 수 있으며, 이렇게 수집된 데이터를 목적에 맞는 저장소에 분산하여 저장할 수 있습니다. 예를 들어, 웹 서버에서 생성된 로그 데이터를 수집하여 실시간 분석 목적으로 엘라스틱서치 (Elasticsearch )에 저장하면서 동시에 같은 데이터를 아카이빙의 목적으로 S3 또는 HDFS에 저장할 수 있습니다. Logstash는 로그 데이터를 받는 곳을 입력(input), 수집 및 전처리 된 데이터를 저장하는 곳을 출력(output)이라고 부르며 중간과정에서 데이터를 가공하고자 사용하는 기능을 필터(filter)라고 부릅니다. 각각은 다양한 형태의 플러그인을 제공하여 간단한 설정만으로 바로 사용할 수 있도록 되어있습니다.
로지스팟은 초당 약 수백건 이상의 위치정보를 효율적이면서도 안정적으로 저장하기 위해서 다양한 데이터 수집기 중 Logstash를 선택했습니다. GPS 위치정보는 실시간 활용을 위해서 대표적인 시계열 데이터베이스인 InfluxDB에 저장됨은 물론, 아카이빙을 위해 객체 저장소(Object Storage)인 AWS S3(Simple Storage Service)로의 저장이 동시에 필요하기 때문에 이러한 요구사항을 만족할 수 있는 데이터 수집기 중 가장 사용이 편리하고 안정적으로 운영이 가능한 Logstash를 선택하게 되었습니다.
로지스팟은 Logstash를 이용한 GPS 데이터의 수집 및 가공, 출력을 구현하기 위해 아래의 코드와 같이 Logstash 설정을 구성하였습니다. 먼저, AWS SQS를 입력 소스로 GPS 데이터를 가져오기 위하여 SQS 입력 플러그인의 설정을 구성합니다. 아래의 설정 파일 예시에서 확인할 수 있듯이 SQS 입력 플러그인 설정은 인증 정보를 담고 있는 yml 파일의 경로, 데이터 스트림의 인코딩 방식을 지정하는 코덱 문자열, 큐의 이름, 사용하고 있는 AWS SQS의 region 이름을 지정하는 것만으로 간단하게 데이터를 가져올 수 있습니다.
#logstash.conf - 입력 소스 설정
input {
sqs {
aws_credentials_file => "/path/to/aws_credentials.yml"
codec => "json"
queue => "your_queue_name"
region => "your_sqs_region_name"
}
}
filter {
...
}
output {
...
}
Logstash에서는 데이터의 입력과 출력 사이에서 데이터를 가공하는 역할을 필터(filter)라고 부릅니다. Logstash는 이러한 필터 기능을 쉽게 구현할 수 있도록 다양한 플러그인을 제공하며, 로지스팟은 이 가운데 루비 문법을 통해 스크립트 코드를 실행할 수 있게 하는 ruby 플러그인을 사용하였습니다.
아래의 필터 코드는 GPS 데이터를 출력 소스로 내보내기 전에 데이터를 일부 변환하고 시간 정보의 포맷을 가공하기 위해서 작성된 루비 코드의 일부를 보여줍니다.
이 외에도 사용 목적에 맞는 데이터 형식으로의 가공을 위해서 다양한 코드 및 플러그인을 활용할 수 있습니다.
#logstash.conf - 필터 설정
input {
...
}
filter {
ruby {
init => "require 'base64'; require 'time'"
code => "event.set('dlat', Base64.decode64(event.get('lat'))); event.set('dlng', Base64.decode64(event.get('lng'))); event.set('updated_at', (Time.parse(event.get('updated_at'))).to_i*1000)"
}
}
output {
...
}
마지막으로 로지스팟은 필터를 통해 가공된 데이터를 출력 소스(InfluxDB, AWS S3)로 전달하기 위해서 아래의 코드와 같이 출력 플러그인의 설정을 구성하였습니다.
#logstash.conf - 출력 소스 설정
input {
...
}
filter {
...
}
output {
influxdb {
measurement => "location"
send_as_tags => ["your_tag_name"]
data_points => {
...
}
host => "..."
port => "..."
db => "..."
user => "..."
password => "..."
codec => json
}
s3 {
aws_credentials_file => "/path/to/aws_credentials.yml"
codec => "json_lines"
bucket => "your_bucket_name"
region => "your_s3_region_name"
prefix => "your_prefix_name"
}
}
먼저, 대표적인 오픈소스 시계열 데이터베이스(Time series database)인 InfluxDB에 데이터를 저장하기 위해서 기본적인 접속 정보(host, port, db, user, password) 값을 설정하고 GPS 데이터를 저장할 때 필요한 측정값(measurement), 태그(send_as_tags), 데이터(data_points) 값을 설정하였습니다.
각각의 개념들에 대해서는 이어지는 InfluxDB의 내용에서 자세히 설명하도록 하겠습니다.
AWS S3는 굳이 자세한 설명을 하지 않아도될 만큼 너무나도 잘 알려진 객체 저장소(Object Storage)입니다. 실시간 데이터 조회 성격보다는 주로 아카이빙 목적의 데이터를 저장하는 데에 많이 사용되는 저장소입니다. 가공된 GPS 데이터를 아카이빙하기 위해서 Logstash의 S3 출력 플러그인을 아래와 같이 설정하였습니다.
각 항목들은 이름을 통해 직관적으로 파악이 가능하기 때문에 자세한 설명은 따로 하지 않도록 하겠습니다.
실시간으로 수집되는 많은 양의 GPS 데이터 트래픽을 비동기적으로 분산하여 처리 및 저장하기 위해서 큐(AWS SQS)와 데이터 수집기(Logstash)를 어떤식으로 구성하여 사용했는지에 대해서 설명하였습니다.
그러면, 이렇게 가공되어 전달된 데이터를 시계열 데이터베이스인 InfluxDB를 이용하여 어떻게 활용하였는지에 대해서 알아보도록 하겠습니다.
InfluxDB는 대량의 시계열 데이터를 저장하고 조회하는데 최적화된 오픈소스 데이터베이스로, 주로 실시간 데이터 스트림 관리가 필요한 서버 자원 매트릭 정보(CPU, Memory 등)나 IoT 센서에서 제공하는 모니터링 데이터 등을 저장하고 관리하는 목적으로 활용됩니다. 로지스팟은 화물차량에서 전달되는 실시간 위치정보를 시계열 기반으로 저장하고 기간별(time range) 데이터를 실시간으로 조회하기 위해서 InfluxDB를 활용하게 되었습니다.
“InfluxDB is made to store a large volume of time-series data and perform real-time analysis on those data, quickly.” – Influx 공식 사이트
InfluxDB는 직관적으로 사용이 가능하지만, 기본적인 핵심 개념을 이해하는 것이 이해하는 것이 중요합니다.
여기에서는 InfluxDB에서 사용하는 핵심 개념(Key Concepts)에 대해서 간단히 알아보도록 하겠습니다.
- fields : 필드는 실제 저장되는 데이터를 의미하며 field key와 field value로 구성됩니다. GPS 데이터에서 필드는 위도, 경도 좌표가 됩니다. 필드 값은 문자열 이외에도 정수, 부동 소수점 등이 사용 가능합니다.
- tags : tag는 InfluxDB에 저장되는 레코드에 대한 메타데이터이며 문자열로 이루어진 tag key와 tag value로 구성됩니다.
- measurement : InfluxDB에서 measurement는 tag와 fields, 그리고 time 컬럼 값의 컨테이너 역할을 합니다.
- 쉽게 설명하자면 일반적인 RDBMS의 테이블 개념에 해당한다고 이해할 수 있습니다.
위에서 설명한 방법으로 저장된 위치정보는 구글 지도와 Polyline 기능을 활용하여 시각화할 수 있습니다. 이 글에서는 구글 지도 API를 활용하여 운행 경로를 시각화하는 방법에 대한 자세한 설명은 다루지 않습니다. 다만, 이렇게 시각화된 데이터를 이용하여 어떠한 문제점들을 파악할 수 있었고 이를 해결하기 위해서 어떤 미세조정 과정을 거쳤는지에 대한 경험을 설명하도록 하겠습니다.
GPS 데이터 관리에서 겪었던 예외상황들과 이에 대한 해결 방법을 설명하기에 앞서 먼저, 저장된 시계열 위치정보를 통해 실제 운행 거리를 계산한 방법에 대해서 설명하도록 하겠습니다. 로지스팟은 먼저 가장 기본적인 방식으로 좌표 간 거리 함수1)를 구현하여 화물차량의 운행 시작 시점부터 운행 종료 시점까지의 총 운행 거리를 측정하였습니다.
function distanceBetweenCoordinates($latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $earthRadius = 6371000)
{
$latFrom = deg2rad($latitudeFrom);
$lonFrom = deg2rad($longitudeFrom);
$latTo = deg2rad($latitudeTo);
$lonTo = deg2rad($longitudeTo);
$lonDelta = $lonTo - $lonFrom;
$a = pow(cos($latTo) * sin($lonDelta), 2) +
pow(cos($latFrom) * sin($latTo) - sin($latFrom) * cos($latTo) * cos($lonDelta), 2);
$b = sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lonDelta);
$angle = atan2(sqrt($a), $b);
return $angle * $earthRadius;
}
일차적으로 계산한 총 운행 거리는 위의 함수에서 확인할 수 있듯이 간단한 삼각 함수들과 지구의 반경 거리를 이용하여 각 좌표의 직선거리의 합으로 계산하였습니다.
이렇게 운행거리를 계산하였을 때 첫 번째로 마주친 문제점은 GPS 데이터의 기본 오차범위에 의한 오차 값입니다.
GPS 데이터를 지도상에 시각화한 위의 화면을 보게되면 화물차량이 특정 위치에 장기간 머무는 경우, GPS의 기본적인 오차범위로 인해 짧은 거리를 지속적으로 이동한 것으로 잘못 측정되는 좌표값을 확인할 수 있습니다.
이는 실제의 값과 다르며 장기간 정차한 차량의 이동거리는 0으로 계산될 수 있도록 오차를 수정할 필요가 있습니다.
이 문제를 해결하기 위해서는 GPS 데이터가 수집되는 주기(e.g. 5초) 동안 이동하였다고 판단 가능한 최소 거리(e.g. 10미터)를 설정하고 해당 주기의 GPS 좌표간의 거리가 이 최소값보다 작은 경우, 해당 차량은 “정지하고 있다”라는 가정을 세우게 되었습니다.
이로써 정지되어 있다고 판단된 좌표 데이터는 집계(aggregate)되어 하나의 좌표로 통합되고 차량이 이동하였다고 판단된 좌표 간의 거리만 시각화, 거리계산에 활용하여 실제 운행 거리(차량 미터기 상의 기록)와의 오차를 줄일 수 있었습니다.
아래의 그림은 이러한 미세조정을 통해 보정된 위치 데이터의 값을 시각화하여 표현한 결과입니다.
차량의 운행 경로 및 거리를 계산함에 있어서 두 번째로 마주한 문제는 GPS 데이터의 중복 수집에서 발생하는 이슈였습니다.
기본적으로 모바일 애플리케이션에서 서버로 전송하는 위치 데이터는 지하도 통과 시, 통신 상태, 함께 사용 중인 다른 위치정보 활용 애플리케이션 등에 따라 실시간으로 조금씩 차이가 발생할 수 있습니다. 이 경우, 같은 주기 내에 1개 이상의 GPS 좌표가 서버로 전송될 가능성이 존재하며 이러한 데이터를 지도에 시각화하여 표시하면 아래의 그림과 같이 연결되지 않는 벡터값으로 표현이 가능합니다.
이 문제를 해결한 방법에 대해서 알아보기 전에 먼저 안드로이드 애플리케이션에서 위치정보를 판단하는 방법에 대해서 간단히 알아보도록 하겠습니다.
안드로이드 애플리케이션에서는 아래와 같이 세 가지 좌표 수집 Provider를 제공합니다.
- GPS Provider: 위성의 전파와 3각 측량법을 이용하여 좌표를 얻는 방법
- Network Provider: 통신사 기지국 또는 WiFi AP들을 통하여 좌표를 얻는 방법
- Passive Provider: 다른 애플리케이션이나 서비스에서 얻은 좌표값을 전달받는 방법
이러한 서로 다른 위치정보 Provider의 기능으로 인해 위성 전파, Wifi, 셀룰러 데이터를 모두 사용하는 안드로이드 단말기에서는 상황에 따라 서로 다른 기준의 GPS 데이터를 서버로 전송하게되는 이슈가 발생합니다. 즉, 경우에 따라서(e.g. 위성 전파를 통하여도 좌표를 받고 WiFi나 통신사 기지국으로 좌표를 받는 경우) 특정 단기간(< 1s) 내에 여러 좌표가 찍히는 현상을 발견할 수 있었습니다.
로지스팟은 이러한 위치 데이터의 오차를 보정하기 위해서 총 두 가지 미세조정 과정을 추가하였습니다.
먼저, 지정한 주기보다 짧은 구간에 수집된 데이터는 중복된 위치 정보라고 판단하여 이 중 가장 실제에 가깝다고 판단되는(optimal) 좌표만을 선별하여 사용할 수 있도록 조정하였습니다.
또한, GPS 데이터 수집 주기에 따른 차량의 최장 이동 거리를 설정함으로써 위치정보 Provider에 따라 발생 가능한 이상점(outlier)을 제거하였습니다.
아래의 그림은 이러한 조정과정을 통해 중복 데이터 및 이상점(outlier)을 제거한 GPS 데이터를 지도상에 시각화한 결과를 표현한 화면입니다.
로지스팟은 화물차량의 실시간 이동 경로 및 거리 계산을 위해 Logstash와 InfluxDB를 활용한 위치 데이터 관리 아키텍처를 구성하였고, 실제 운영 과정에서 겪은 오차를 줄이기 위해서 앞에서 설명한 미세조정 과정을 통해 보다 정확한 경로 및 거리를 계산할 수 있게 되었습니다.
이렇게 개발된 내용을 통해 실제 화물 운송 시장에서 차량 기사님들이 유류비 청구 등을 위해 매일 매일 작성하던 비효율적인 운행일지를 더이상 사용하지 않을 수 있게 되었으며, 디지털 운행일지를 통해 화물차량 기사님들의 편의는 물론 매월 수천건의 운행기록을 엑셀로 옮겨적어야 하는 정산과정의 번거로움도 함께 해결할 수 있게 되었습니다.
로지스팟은 디지털 운행일지를 비롯한 지입차량 관리 서비스 이외에도 화물 운송 시장에 필요한 용차 운송 서비스, 입/출고 차량 관리, 관세사 운송 서비스 등 다양한 서비스를 제공하고 있습니다.
로지스팟은 고객사의 운송을 효율적으로 운영할 수 있도록 IT 플랫폼을 통해, 업무의 효율성을 제공하기 위해서 노력하고 있습니다.