Introducing a Rust-lang API client for the Unleash API

Robert Collins
Cognite
Published in
3 min readMay 12, 2020

Here at Cognite we write services in many different languages. Rust is one of them, which you may have read about in our recent post on 3D model loading.

We use the Unleash API to manage feature toggles within our services. Feature toggles permit us to avoid code branching as we build our products. We can have new code disabled until we are ready to launch it, kill it quickly if it misbehaves after launch, expose it to a small group of users for testing, and much more.

A woman with a leashed lobster
Just another leashed crustacean. Credit: https://www.tumblr.com/dashboard/blog/thatcatisdeceased/1061915964

We need these capabilities to write backend services in Rust. And since there was no API client for Rust, we decided to make one. It is open source under the Apache 2.0 license, available to anyone who wants to use it.

To make the API client as widely applicable as possible, we have made it usable from both asynchronous and synchronous Rust programs. The library is written using async code, which can be easily wrapped from thread-based servers.

We’ve focused on both performance and ergonomics. Feature toggles should not be so much more expensive than if() statements that people avoid using them, and reading code that uses feature toggles should be pleasant.

A single call to evaluate a feature using the “flexibleRollout” strategy takes ~256ns. That’s nearly 4 million lookups per second in a single threaded async server. In our worst benchmark with 100% contention, with 32 hardware threads doing nothing but making lookups on different features, the slowest thread’s average call latency rises to ~681ns. That is an increase, but it’s somewhat to be expected with cross-core synchronization.

Of course, 100% feature toggle lookups is not a realistic workload. If it proves to be a hotspot, there are both low hanging and more complicated options available to improve things.

Using a toggle is a one-liner (this is nonblocking lock-free code):

Here, “unleash_client” is your server-wide handle to the Unleash API, an `unleash_api_client::Client` instance.

“thunderbolts” is the name of your feature toggle, which you would configure in the Unleash API.

`false` is the default value to use if “thunderbolts” is not defined. This happens when someone rolls out code before the toggle is defined, or if the API server is down when a new instance first starts up. Repeating `false` everywhere a toggle is used adds some overhead and room for skew, but it also lowers the friction to adding toggles and is consistent with the Unleash API client SDK spec.

In the Rust world correctness is huge. Should we for example parameterize the client by some type that informs it of all the toggles the user will use and the defaults they expect? Let us know (you’ll find contact details at the end of this post).

“unleash_context” is your request-wide Unleash API `unleash_api_client::Context` instance. This summarizes all the relevant information about the request that your code is operating on. Fitting this client for a new framework will involve implementing an adapter to create a Context from your framework’s native context.

Since the Unleash Client SDK specification defines much of the Context as strings, we have for now taken the approach of instantiating these early in the request flow and keeping it with the other request baggage. This avoids over-complicating the process of adapting it for new frameworks with reasonable performance.

We’re considering whether a zero-copy approach is needed and just what that would look like. (Again, we’re very interested in feedback.)

Context is very simple:

Finally, to set up the client, you can use an optional helper module. Using it, we have:

Then finally we arrange to run `client.poll_for_updates()`s in the background to receive our features and send usage metrics to the API server.

We hope our API client will be of use to the Rust and Unleash communities. We’d love your feedback. We can be reached at our GitHub repository or in the #unleash-client-rust channel on the unleash Slack.

--

--