As we all know, code is expensive to maintain—the more complex the code, the higher the cost of maintaining it. Therefore, since the beginning of time, software developers have worked very hard to achieve the goal of code reusability.

Couchbase Mobile 3.0 introduced support for SQL++ queries. As a developer, the first thought that came to mind was sharing queries used in projects targeting both Couchbase Server and Couchbase Mobile applications. 

Is this a good idea? Let’s explore that question using sample data and a proof-of-concept mobile application.

Sample data

All the code in this article is available on GitHub. See the README for information on how to get the projects set up on your computer.

The sample data comes from the OpenStreetMap project and is licensed under the Open Data Commons Open Database License (ODbL) by the OpenStreetMap Foundation.

The data set contains all the shops that sell ice cream in the United States. An example JSON document is listed below:

As you can see, this is a versatile dataset for testing because it has properties embedded into the documents.

Sample mobile application

The sample mobile application was written in SwiftUI with Combine and is an iOS/macOS application that displays all the various shops that sell ice cream in the United States. The application includes a pre-built database with the same JSON documents already imported into it for convenience. This mobile application uses the latest version of the Couchbase Lite SDK. You can open the project in XCode from the project file in the src folder to follow along.

A simple query comparison

To start our testing, we can write a simple query, for Couchbase Server, to show all the ice cream shop addresses and order them by the shop name:

This query has a filter that only returns documents where the property’s object addrCity field has a value and where the document type is equal to Feature. If you wonder why we have the addrCity filter, it’s because of dirty data or documents without names or missing information in this dataset. Using a type attribute is common in mobile applications to filter out different types of documents.

We can use the Query Editor from the Couchbase Server Web Console to run this query. When I ran this on my personal Macbook Pro M1 Max, the result came back in 38.4ms. This query uses the adv_properties_addrCity_type index, which you can see by clicking the Index Advisor button.

Porting the query to Couchbase Lite

Now let’s look at this same query running on our mobile app. To view the mobile app, you can use XCode to open up the IceCreamLocator.xcodeproj file located in the src folder. Once opened in Xcode, find the IceCreamLocationRepository swift file in the Shared\Data folder. 

The init function turns on verbose logging for the database to inspect the query information. Using verbose logging, you can review what’s going on within Couchbase Lite, including query performance.

Let’s review the simple query from Couchbase Server and see how it works in Swift with the new SQL++ Query API. The function for the simple query is named getListByTypeCityOrderName, and the code is displayed below:

There are a few ways to make an SQL++ query in Swift. In this example, we use the database createQuery function to create the query. In Couchbase Lite, I’m using the underscore character (_) as the database name before the FROM statement. Changing the database name is one of the expected changes to reuse SQL++ statements. Also, to escape the quotation marks in the WHERE clause, I had to add the Swift string escape character (\) before each quotation mark. 

To test this query while running, you can open the IceCreamListViewModel file found in the ViewModels folder. Locate the init function and uncomment out the first example line located at the bottom of the function that calls the getListByTypeCityOrderName function from the _repository variable:

Now you can use Xcode to run the application on an iOS Simulator or a macOS app. Next, open the Debug Console in Xcode by clicking the Show Debugging icon in the lower right corner of the Xcode editor, as highlighted below.

Verbose logging will display a massive amount of logging information. To help find the correct information in the logs, use the filter box below the console. For example, enter the text “Query Info” and then hit the enter key on your keyboard to see information about the query. The debug console will filter the data that Couchbase Lite returns.

Inspecting the logs will show you the Query Info log entry and the number of documents found by the query, how many bytes and how long the query took. Remember, the numbers you are looking at are from a simulator, not an actual device. For performance tuning of mobile apps, you should use tools from Apple with a real device.

The speed at which this query ran is impressive. Even more impressive, there haven’t been any indexes created yet, so this query runs without an index and does a table scan which means it has to look through the database at each document to see if it matches the requirements of the query. Couchbase Lite doesn’t require indexes and will do table scans if needed, whereas Couchbase Server does require indexes because table scans can kill the performance of the entire cluster. 

In several cases adding indexes can significantly improve the performance of your queries, so let’s stop running the application and add the proper index and recheck performance.

Open the IceCreamLocationRepository and locate the createIceCreamIndexes function. Uncomment the first block of code for creating the first query index:

How do we test to see if the query used the index we created? In the getListByTypeCityOrderName function, you will see two lines that prints results from the explain function to the console:

The query explain function will run the equivalent of the SQLite EXPLAIN QUERY PLAN command and store the results in a string you send to the console using the print function. You can filter for SCAN TABLE in your debug console to find the results. When reviewing the results, make sure you remove your previous filter from the console window. Now rerun the application and look through the logs to check the performance change.

You should see the USING INDEX with the created index name, which tells you that the query uses the proper index. Note that while indexes can help queries, they can also slow down insert and updates to documents. You can find more information about indexing in the Couchbase Lite documentation.

Another query comparison

In our second comparison, we filter out results only to show ice cream shops in the State of Georgia, ordered by City and State:

This query returns 15 documents and uses a different query since we use the properties.addrState and properties.addrCity attributes in our WHERE clause. 

Note that we switched out the way we check the properties.addrCity attribute and use the IS NOT NULL statement now. This shows the flexibility of the SQL++ language and how developers can use different syntax to produce the same results.

Now, let’s go back to our mobile app to look at any differences. The function for our second query is named getListByStateGeorgia, and the code is displayed below:

To test this new query, you can open the IceCreamListViewModel. Locate the init function and comment out the first example line located at the bottom of the function that calls the getListByTypeCityOrderName function from the _repository variable and uncomment out the line that calls our new function:

You can debug the app using Xcode and check the debug area. It shows the same 15 documents that were returned by the query ran on Couchbase Server, but we are back to doing a table scan which isn’t the behavior we want—we changed the query, but we didn’t create any new indexes yet.

To fix this, we can go back to the IceCreamLocationRepository and locate the createIceCreamIndexes function. Uncomment the second block of code for creating the second query index:

We found that we could reuse the code in these two more simple queries, but we had to add the indexes to avoid table scans.

Catches with reusing queries

Couchbase Server and Couchbase Mobile don’t use the same storage or query engine. Therefore, you might run into Couchbase Server queries that need slight modifications to be optimized for Couchbase Mobile.

For more information on Couchbase Server, check out the documentation on the Cost-Based Optimizer that the query engine uses. The Couchbase Developer portal also has a good article on improving query performance on Couchbase Server that can explain why some queries are written in a specific way. 

I recommend reading the Couchbase Mobile documentation on troubleshooting queries as it has helped me write optimized queries and further explains the SQLite query plan output.

Summary

As shown in our code examples with the Couchbase Mobile 3.0 release, it is possible to reuse SQL++ code between Server and Mobile. However, you may need to modify queries and indexes for optimal performance. Given this, I highly recommend reading through the excellent documentation on SQL++ for Mobile – Differences from SQL++ for Server and SQL++ Query Strings. It has valuable information on changes you might need to make to your queries to use them in your mobile applications.

Here are some of the primary resources referenced throughout the post:

 

Author

Posted by Aaron LaBeau - Principal Software Engineer

Aaron LaBeau is a Principal Software Engineer on the Developer Experience and Ecosystem team. He has over 29 years with substantial development experience in Objective-C, Swift, Kotlin, Java, C#, Javascript, and Typescript. You can find his GitHub profile at https://www.github.com/biozal/.

Leave a reply