Building A *Real Time Geo Spatial Query Service (Similar to Uber, Lyft, & Find My Friend's live location tracking)

Demo Video: https://www.youtube.com/watch?v=ImG6Y47GRDY&feature=youtu.be

Feature in Production: https://hamdaan-rails-personal.herokuapp.com/real_time_geo_system


Real time features or pseudo real time features have always piqued my interest. One particular feature that has always seemed complex to imagine is Uber and Lyft giving real time updates on the locations of their drivers. This past month I have been slowly working on building that very feature. In this article I will be discussing the design, and further optimizations.


Design


I knew for a real time feature I needed to communicate between the client and server in real time, so my protocol of choice for the communication was WebSockets. I knew I needed some way of providing GeoSpatial indexing over either latitude & longitude pairs or over the GeoHash (A Geohash is a unique identifier of a specific region on the Earth) for their location.

At first I wanted to create an in memory ordered Dictionary and calculate order of elements based on their distance from a querier. Maintaining an ordered dictionary in memory was a bad idea since I would be making my application stateful, and so it wouldn't scale very easily across multiple servers horizontally. My next idea was to use Redis since it provides geospatial queries and is easier to scale horizontally as well. The communication between clients and server also had to be such that they will correspond to events. Luckily Rail's ActionCable helps establish Publish-Subscribe pattern by default, this fit very well for my use case.

Now the design for the actual application logic was fairly straightforward after I had it all figured out. The platform would have 2 types of clients Queriables and Queriers. A Queriable would be someone you would like to track e.g. the driver in Uber, and a Querier would be the person who wants to track a Queriable e.g. the passenger ordering a ride. Every Querier would have a unique Id associated with them, and only Queriable's who consent to giving their location the exact Querier unique Id can be tracked by the Querier. Each Queriable would also have a unique identifier that is used to identify them by the Querier. Every Querier Identifier is used to create a channel that Queriables with access to the identifier can connect with. This way we make sure we are not sending a Queriable's data to a querier they do not want to. Upon connecting a Queriable (upon giving consent) will start emitting their location data on every update to the server along with the querier id, the querier id is used to create a sorted set or append to a sorted set associated to a querier id. After appending or creating the sorted set in Redis we broadcast to anyone connected to the Websocket channel associate to a querier Id as a querier, that a Queirable has updated their location. If there is a querier connected to the same channel, they then respond to this event by sending another request for querying newly updated data.

Any data persisted in Redis also has an expiration of 500 seconds, so as to not persist data by a Queriable if they have been inactive for longer than 8 minutes. Whenever a Querier connects they send a message to the server along with their unique identifier for asking all elements within a given radius from their location, the server then hits Redis and queries the sorted set associated with the Querier's unique identifier, and upon completing broadcasts the results back to the Querier on the channel associated with their identifier. The Querier Client then updates the UI with these new results.


Optimization


In retrospect I wish I had created 2 different channels both associated with same identifier for better SRP code. Outside of that the real time update for the view would benefit if the updates would only change the UI for the identifier who's data was updated and not for all identifiers.


Implementation


Ruby + Rails + ActionCable + Redis + Redis-Ruby client + Vanilla JS. There was a good amount of code in accomplishing this project, so I divided this project into 3 part MR's all linked below:

Please visit => https://www.linkedin.com/feed/update/urn:li:activity:6911124126317342720/

Comments

Add a comment: