Uber Engineered Highest Query Per Second Using Golang
Posted: Oct 22, 2020
Uber created a microservice for Geofence lookups and this Uber's highest query per the second service (QPS) from the other hundreds of services that run in the production.
You might think why Geofence Service was created at Uber?
To start with why Geofence let us understand what is Geofence at Uber
Geofence refers to the geographic area on the Earth's surface which is defined by the human. Geofence is used largely at Uber for the geo-based configuration ( Setting, Updates, Modification, etc) Geofence helps users by showing them the products that are available at the given location. You can define the area with different criteria or any other requirement. It also provides dynamic pricing for the places where lots of people are requesting for the rides parallelly
When Geolocation based configuration needs to be retrieved using the mobile phone of the user to find out the Geofence location it comes. The modules of this functionality require to be duplicated and then centralize the functionality in one single new microservices as Uber moved from monolithic architecture to microservice architecture.
During the time of language evaluation at Uber, Node.js was the real-time marketplace team's primary programming language.
Golang at Uber-
Golang is been contributing in Uber in the following things-
- Golang helped with High throughput and lower latency - On every request from Uber's mobile apps Geofence lookups are required and it should quickly respond to the high rate of queries
- Optimum CPU Utilization - Geofence lookups require some CPU intensive algorithm
- Smooth-running background loading - In order to get the new and fresh geofence data to perform the lookups. In the background, the service should keep refreshing the data from multiple data sources. As Node.js is single-threaded it can limit the CPU for a long period of time. This problem does not arise in Golang as it has goroutines which can execute on the multiples CPU cores and can run the background job parallelly
The next part was about "Geo Index or not"
To figure out the given latitude and longitude pairs where does our from other available geofences the location come under. The simple way was to do a point-in-poly check using the algorithm and go through all the available geofences. But this simple way was very slow and to borrow down the space of searching efficiently for this uber used a simpler route based on the observation. Uber's business model is city-centric. The geofences and business rules are associated with the city.
Geofences in Uber are organized in a two-level hierarchy:
- The first level - City geofences
- The second level - Geofences within each city
Using this two-level hierarchy, for each lookup first the desired city with a linear scanning of all the city geofences was done then need to find the containing geofences with another linear scan and within that city. The runtime complexity for this was O(N) and also reduced the N from thousands to hundreds.
The services developed were needed to be stateless in order to provide an instance of services as per every request and expect the similar result. Each service instance should have the entire systems/world's knowledge. The deterministic polling schedule was created for the synchronization of geofences data from the instances of the services. Periodically background jobs poll geofences data from various data stores. This data is stored in the main memory in order to serve the queries and then serialized to the local file system for faster bootstraps on service restarts.
Uber's experience with the Golang Memory Model
The architecture needed concurrent read/writes access to the in-memory geo index. The background jobs of polling were to write to the index while the foreground query engine reads from it. Idiomatic Golang's way is to channelized and synchronize read/write operations concurrently using Goroutines and channels.
Initially to manage barriers the StorePointer primitives from the sync/atomic package but it was hard for maintaining the code at then the read-write lock was used to sync access to the geo index. To reduce the contention of lock new index segment was created on the side before swapping in into the main index. This kind of lock usage increased the latency of the query. But simplicity and maintainability being major things to focus on the codebase so the small performance cost was fine.
Uber said -
Looking back, we are extremely happy with our decision to Golang for it and write our service in a new language.
Get to know more about Uber and Golang here https://www.linkedin.com/pulse/stream-switched-from-python-golang-reemi-shirsath/
Chief Operating Officer at Scalent.io
Author Reemi Shirsath Chief Operating Officer at Scalent.io