Want to scale your apps to millions of users while maintaining the ability to safely and securely (and easily!) sync private information to Cloudant? In this tutorial we’ll show you how.

In Location Tracker – Part 1 we showed you how to create an iOS app that tracks your location, syncs with Cloudant, and performs geo queries to find nearby points of interest. We showed you how to use the database-per-user design pattern to take advantage of Cloudant’s powerful sync capabilities while ensuring a user’s location information remains private. We also discussed how the database-per-user design pattern works well for small- to medium-sized apps, but not so much when you want to scale to millions of users. In this tutorial we’ll show you how we extended Location Tracker to do just that.

A Refresher

The Location Tracker app is an iOS app developed in Swift that tracks user locations and syncs those locations to Cloudant. As a user moves, and new locations are recorded, the app queries the server for points of interest near the user’s location.

Below is a screenshot of the Location Tracker app. Blue pins mark each location recorded by the app. A blue line is drawn over the path the user has travelled. Each time the Location Tracker app records a new location, a radius-based geo query is performed in Cloudant to find nearby points of interest (referred to in the app as “places”). The radius is represented by a green circle. Places are displayed as green pins.

Location Tracker screenshot showing map location pins

In Part 1 we identified five key requirements for the Location Tracker app:

  1. Track location in the foreground and background.
  2. Use geospatial queries to find points of interest within a specified radius.
  3. Run offline.
  4. Keep user location information private.
  5. Provide ability to consolidate and analyze all locations.

To satisfy requirements #4 and #5, we implemented the database-per-user design pattern. It was a great first step for learning how to use Cloudant Sync for syncing personal or private data, but this design becomes problematic when scaling to millions or even tens of thousands of users. In this post Glynn Bird points out a few of the issues with scaling using the database-per-user pattern:

  • Backup – How do you design a backup-and-restore plan for millions or even thousands of databases?
  • Reporting – How do you generate reports across millions of databases?
  • Change control – How do you propagate data updates across millions of databases?

To help provide a solution to these issues (and more), a team of IBMers built Cloudant Envoy.

Cloudant Envoy FTW!

Cloudant Envoy is a microservice that acts as a replication target for your PouchDB web app or Cloudant Sync-based native app. Envoy allows your client-side code to adopt a “one database per user” design pattern, with a copy of a user’s data stored on the mobile device and synced to the cloud when online, while invisibly storing all the users’ data in one large database. This prevents the proliferation of databases that occurs as users are added and facilitates simpler backup and server-side reporting.

This is how Cloudant Envoy is described on GitHub. Let’s break down this description and unpack the relevant points for Location Tracker:

Cloudant Envoy is a microservice that acts as a replication target for your PouchDB web app or Cloudant Sync-based native app.

In Part 1 we showed how the Location Tracker iOS app targeted user-specific databases in Cloudant for replication. In this tutorial we’ll show how (without it even knowing it) the iOS app will target Cloudant Envoy.

Envoy allows your client-side code to adopt a “one database per user” design pattern, with a copy of a user’s data stored on the mobile device and synced to the cloud when online…

From the beginning, the Location Tracker iOS app was built using the database-per-user design pattern. Each user’s locations are stored locally on the iOS device and synced to Cloudant when online. This doesn’t change when replicating to Envoy. In fact zero changes were required to the iOS app to support Envoy.

…while invisibly storing all the users’ data in one large database. This prevents the proliferation of databases that occurs as users are added and facilitates simpler backup and server-side reporting.

Using Cloudant Envoy we can store all private location data in a single database. This makes it easier for backend developers or data scientists to work with the data and addresses the three problems we mentioned with the database-per-user pattern: backup, reporting, and change control.

Architecture

In Part 1 we implemented the database-per-user design pattern and created a database for each user to track that user’s location. This is what our architecture diagram looked like:

Architecture of Location Tracker part 1
Location tracker server v1: Users hit Node.js server, location syncs directly to Cloudant, many unique small DBs in Cloudant.

User registration and geo queries were performed through a Node.js application running on IBM Bluemix, while locations were synced directly to user-specific databases in Cloudant. User-specific databases were configured to replicate to a centralized database to store all locations. With Cloudant Envoy our architecture is greatly simplified:

Architecture of Location Tracker part 2
Location tracker server v2: Users hit improved Node.js server, location syncs to Cloudant Envoy proxy, two big DBs in Cloudant — one for each server.

Here in Part 2, user registration and geo queries are still performed through a custom Node.js app, but now all location replication is routed through Envoy and stored in a single, centralized database. We are no longer connecting directly to Cloudant. We no longer have to create databases for every user, or configure replication from those databases to our centralized location database, and we continue to satisfy our requirements, including:

  • Keep user location information private – This is handled completely by Envoy. Users can only access their own locations.
  • Provide ability to consolidate and analyze all locations – By default, with Envoy, all locations are stored in the same database. No need for replication or data duplication.

The New Server

In Part 1 we discussed the Location Tracker Server, a Node.js application that provides RESTful APIs for registering new users and querying places using Cloudant Geo. For this tutorial we have created a new server to perform these functions and configure support for Cloudant Envoy. That server is called the Location Tracker Envoy Server.

When you install the Location Tracker Envoy Server three databases will be created in your Cloudant instance:

  1. envoyusers – This database is used by the server and by Cloudant Envoy to manage and authenticate users.
  2. lt_locations_all_envoy – This database is used to keep track of all locations synced from iOS devices to Cloudant through Envoy.
  3. lt_places – This database contains a list of places that the Location Tracker app will query.

Follow the instructions on the Location Tracker Envoy Server GitHub page to get the Location Tracker Envoy Server up and running locally or on Bluemix.

The Same Client

As mentioned previously, zero changes were required to the iOS app to support sync with Envoy. The iOS app is given the location replication target on login. Envoy is a drop-in replacement for Cloudant replication. Instead of returning the path to a user-specific database for replication, the server returns the path to the Envoy instance.

Once you’ve set up the Location Tracker Envoy Server, follow the instructions on the Location Tracker App GitHub page to get the Location Tracker App up and running in Xcode.

How It Works

In the rest of this tutorial we’ll provide more detail on how we are using Envoy. For more information on how the app tracks locations or queries for points of interest, please check out Part 1. This tutorial focuses on Cloudant Envoy and the changes made to the backend to support Envoy.

User Registration

Cloudant Envoy has a few different options for managing users. You can configure which method to use with the ENVOY_AUTH environment variable. This variable must be set on both the Cloudant Envoy app and Location Tracker Envoy Server app in Bluemix. See the Cloudant Envoy documentation for more information regarding the different authentication options available.

By default users are stored in a database called envoyusers. The user registration process has been greatly simplified from Part 1. The same PUT request is sent from the iOS app:

{
    "username": "markwatson",
    "password": "passw0rd",
    "type": "user",
    "_id": "markwatson"
}

However, the backend processing of this request is much simpler. Previously the backend would create new databases, set up API keys and passwords, and configure continuous replication between the new databases and the centralized locations database. When the new Node.js server receives the PUT request the following steps are executed:

  1. Check if the user exists with the specified id. If the user already exists, then return a status of 409 to the client.
  2. Store the user in the users database with their id and password (hashed).

That’s it!

User Login

Users are logged in immediately after registering. Again, no changes were made to the iOS app. The app sends the following request to the Node.js server:

{
    "username": "markwatson",
    "password": "passw0rd"
}

And the server replies with a response in the same format as the previous version of the server:

{
    "ok": true,
    "api_key": "markwatson",
    "api_password": "passw0rd",
    "location_db_name": "lt_locations_all_envoy",
    "location_db_host": "cloudant-envoy-XXXX.mybluemix.net"
}

The motivation here is backwards compatibility. The app expects to receive the API key, password, database, and host to sync to. In Part 1 this was the user-specific database, but as you can see now, the server is sending the information required to sync with Envoy. The api_key and api_password fields now take the user’s username and password as their values. This is what is expected by Envoy, and by using this format the code maintains backwards compatibility with our server from Part 1. Correspondingly, the unique values from our old database-per-user pattern — location_db_name and location_db_host — now take standardized values: "lt_locations_all_envoy" and the Envoy host, respectively.

Syncing Locations

Syncing locations between the client and the server has not changed. Envoy implements the same replication protocol as Cloudant, making the migration completely transparent to the client. The Location Tracker App uses Cloudant Sync for iOS to sync with Envoy the same exact way it would sync directly to Cloudant.

The Data

How does Envoy know who owns the data when they are all stored in the same database? There are a few different ways that Envoy can identify who owns the data, but the same principle is applied in each case:

  1. When saving new locations, alter the data to include the authenticated user’s information.
  2. When retrieving the locations, use the authenticated user’s information to filter the data.

Envoy modifies each document on the way in and filters each document on the way out. Envoy provides different options for adding ownership information to the data. These options can be configured by setting the ENVOY_ACCESS environment variable in Cloudant Envoy. See the Cloudant Envoy documentation for more information.

By default, Envoy stores the ownership of a document in the _id field of the document. It prepends the sha1 hash of the username to the id. Here’s an example document:

{
    "_id": "c00268ec8506774f20229f1eb9142e0d1f1a938b-014144EE-25BB-4251-94AC-A7BBD3C04CB5",
    "_rev": "1-8801d4d9a3bf8a539692af9697b89eb5",
    "created_at": 1468251203669.592,
    "geometry": {
        "type": "Point",
        "coordinates": [
              -122.39203496,
              37.5668706
        ]
    },
    "properties": {
        "timestamp": 1468251203669.592,
        "username": "envoy_user1",
        "background": false
    },
    "type": "Feature"
}

This document was stored for envoy_user1. Every one of envoy_user1‘s documents stored by Envoy will have an _id prepended with c00268ec8506774f20229f1eb9142e0d1f1a938b.

diff Part1 Part2

To summarize, let’s do a diff with Part 1 of the Location Tracker to see the significant changes that we’ve made to help scale our solution:

  • Only in Part 1 – Create new database for each user.
  • Only in Part 1 – Configure replication between each user-specific database and consolidated location database.
  • Only in Part 1 – Tell iOS app to sync directly to user-specific database in Cloudant.
  • Only in Part 2 – Tell iOS app to sync with Cloudant Envoy.
  • iOS App – No changes.

Cloudant was built to scale, but creating millions of databases for millions of users is not scalable. Cloudant Envoy stores your private data in a single database that can scale to support millions of users while allowing you to reap all the benefits of Cloudant Sync. Using Cloudant Envoy we have not only improved our ability to scale, but we have simplified almost every aspect of our solution.

Conclusion

In this tutorial, we showed how to use Cloudant Envoy to scale Cloudant’s data replication & synchronization capabilities to millions of mobile users. We showed you how Cloudant Envoy provides a drop-in replacement for Cloudant replication that allows you to safely and securely sync private location information into a single, consolidated database.

Cloudant Envoy is still in beta, but we’re really excited about its potential and urge you to start experimenting with it today. For more information regarding the Location Tracker and Cloudant Envoy please see the following links:

Join The Discussion

Your email address will not be published. Required fields are marked *