Building high-perf geocoding micro-service with Golang and Redis.
Geocoding is the process of transforming a description of a location — such as a pair of coordinates, an address, or a name of a place — to a location on the earth’s surface. At large most startups (except hyperlocal) need resolution upto the city level. Using third-party service to resolve sparse locations is ineffective, expensive, and creates a dependency on tech and business.
This post aims to provide a proven (implemented in GreedyGame) and quick approach to build scalable micro-service to resolve latitude+longitude to city.
With the investment of few hours, an engineer can implement the ability to resolve the inbound traffic’s city without creating dependency on 3rd party.
The entire service includes the following stack:
- Redis used as geospatial and in-memory DB.
- Golang used as a code-base for micro-service.
- Docker as container infra for deployment.
Redis as Geospatial DB
Redis provides a native implementation for geospatial data-storage which will be used to store and query latitude/longitude for cities. Explaining Geoadd and Georadius below:
Command: GEOADD
- Time complexity: O(log(N)) for each item added, where N is the number of elements in the sorted set.
- There are limits to the coordinates that can be indexed: areas very near to the poles are not indexable.
redis> GEOADD idx:cities 77.22445 28.63576 "1261481:New Delhi:IN"
(integer) 1
redis> GEOADD idx:cities 80.92313 26.83928 "1264733:Lucknow:IN"
(integer) 1
Command: GEORADIUS
- Time complexity: O(N+log(M)) where N is the number of elements inside the bounding box of the circular area delimited by center and radius and M is the number of items inside the index.
redis> GEORADIUS idx:cities 77.227321 28.612912 200 km
1) "1261481:New Delhi:IN"
Indexes
The key for the geospatial set will be lat-lon, where as the value could be the combination of <GeoNameId>+<CityName>+<CountryId>. This merging of information within values safe additional lookup.
Golang for micro-service
The code uses go-gin as micro-service framework and redigo as Redis client.
Code Structure
Code structure has been modularised as
- RedisContext: Class to manage Redis connection pool and wrap low-level Redis binding.
- ServiceContext: Class implements core business logic using go-gin’s context and router.
- DataContext: Class for cities’ location data ingestion.
API call
GET /resolve/{country-code}/{latitude},{longitude}
curl "http://0.0.0.0:8080/resolve/IN/28.612912,77.227321"
{
"city": "New Delhi",
"country": "IN",
"geonameId": "1261481"
}
Geonames.org as data-source
The source is from the GeoNames.org geographical database. It covers all countries and contains over eleven million placenames that are available for download free of charge. The data structure is well documented here and available for free to use.
Performance and benchmark
- Benchmarking on CPU: Intel(R) Core(TM) i5–6200U CPU @ 2.30GHz
- Mean request processing time: 142ms for 100 concurrency
Links and resource
- Source code on GitHub: https://github.com/arinkverma/go-geo
- GeoName data download page: http://download.geonames.org/export/dump/