Unleash

Yggdrasil: The Organic Evolution of Unleash’s SDKs and the Emergence of a New Flag Evaluation Engine

As Unleash has grown, we’ve increasingly needed to manage feature flagging across multiple languages and frameworks. Initially, it was simpler – we only had a few SDKs like Node and Java, and the logic was straightforward. But as Unleash expanded, the number of SDKs and the complexity of their logic grew as well.

As an early way to solve this fragmentation, we developed our Unleash Client Specification. It helped us establish rules for edge cases and how they should be handled. We were quite proud of the client specification; it was an excellent tool with lots of detail. We felt confident that we had covered all the important test cases.

We were wrong.

The Peculiarities of Gradual Rollouts and Arbitrary Groupings

Writing logic around constraints and strategies is an excellent lesson in humility. Humans are creative. We like to come up with new ideas and ways to do things. When we wrote our client specification, we were essentially declaring that we knew all the ways people might want to release software to any type of group.

Pride Comes Before the Fall

More edge cases appeared as time passed, and our SDKs drifted apart on some specifics. We realized we hadn’t anticipated every possible situation (what should happen when a feature is 100% sticky on a value that isn’t defined?). While we were able to add more definitions to the specification, that didn’t magically update each of the SDKs, and we were increasingly confident that there were more edge cases out there. Meanwhile, coordinating updates across 30+ SDKs was awful.

Unleash Client Specification and SDKs

Effectively, our SDK logic looked like this, except worse – we ran out of screen real estate to show the whole picture. But it’s easy to look at this and see the chaos. When you put all the different client specification versions into the mix, understanding how a feature toggle would evaluate took a degree in astrophysics.

Eventually, we realized we needed the exact same code to run in all the SDKs.

The Emergence of Yggdrasil: A Single Evaluation Engine

In 2022, we started work on a new library, Yggdrasil, to address these needs. It’s named after the mythical tree that connects all in Norse cosmology, which nicely summarized our hopes.

At its core, Yggdrasil is a Rust library, accessible from other programming languages, which contains all the logic for computing feature flags. Yggdrasil means we can write code once and only once and know that it would be evaluated in the same way across all languages.

Unleash Yggdrasil Engine and SDKs

We’re not quite there yet, but this feels much better. Each SDK becomes a small piece of glue code that asks its Yggdrasil core logic how to evaluate flags. The PR for the Ruby SDK is in progress, and we LOVE the look of this diff:

Rust SDK diff: +217 -2836

One Year of Running in Production

Yggdrasil has quietly been running in production in Unleash Edge, our relay proxy, for approximately a year. It evaluates many millions of feature flags weekly, giving us opportunities to test and tune. We’re gradually integrating it into our SDKs, starting with Ruby and .Net.

While it’s difficult for us to track precise usage as an open-source project, we know that Yggdrasil has been used to evaluate trillions of flags. It’s also quite performant; our benchmarks for Yggdrasil are measured in hundreds of nanoseconds (you can verify this by cloning Yggdrasil and running cargo bench).

At its core, Yggdrasil features a Domain-Specific Language (DSL) and functions as an engine, processing, and computing results derived from this DSL.

Why a DSL?

Why make a new DSL instead of using existing options like JsonLogic? In our experience, the unique needs of feature flagging, such as gradual rollouts, make it challenging to succinctly capture them in a generalized language.

A DSL can be both human-readable and more concise than representing the same concepts in something like JsonLogic. It also allows the construction of advanced targeting rules on the fly with small payload sizes, making feature flag configurations more compact and concise.

So, using a DSL gives us several advantages:

  1. It’s easy to express the logic we want
  2. It’s potentially much faster than a generalized tool
  3. We can continue to optimize it for this domain usage
  4. Network payloads are minimized, even for potentially complicated rules
  5. It’s easy for humans to understand at a glance

Here’s an example:

const engine = new Engine();
const context = {
  "userId": 7
};
engine.evaluate("user_id > 7", context);

Why Didn’t We Use [x] Instead?

When designing Yggdrasil, we wanted it to be a seamless replacement. In terms of SDK design principles, we want to embody the qualities of a good logger: lightweight, unobtrusive, and well-behaved within their host program. Since feature toggle SDKs are not a part of the primary logic of an application, they should be almost invisible to the user.

Performance was also particularly important given that we have implementations in Go and Rust, languages known for their efficiency. We recognized that asking our users to accept a slower implementation would be a bitter pill to swallow. Building Yggdrasil in Rust meant that we could serve everyone’s performance needs.

We considered several other alternatives that we ultimately rejected. An implementation in Go, while appealing, was dismissed due to its larger runtime footprint compared to Rust. C/C++ could have been a viable option, offering a small footprint, but concerns over memory safety and our team’s limited expertise in these languages led us to rule them out. Shipping WebAssembly (WASM) was another consideration; however, the overhead of running a WASM engine in a user’s code was deemed too substantial to justify. Lastly, while JsonLogic offered extreme flexibility, it was too unconstrained for our needs, making it challenging to succinctly express the logic we wanted.

Potential Shared Library?

Meanwhile, as we have worked on Yggdrasil, we have also discovered a recent OpenFeature discussion about creating a standardized flag definition language for the OpenFeature project. OpenFeature aims to provide a vendor-neutral feature flag SDK across many languages, technologies, and frameworks. Like Unleash, OpenFeature also has a significant maintenance burden to build and support providers for every tool and vendor in every language.

While our projects have different goals, the needs and pain points may overlap sufficiently to make Yggdrasil a potentially useful shared solution. We’re excited by this possibility and encourage you to read our upcoming OpenFeature blog post to learn more.

What’s Next?

We value collaboration as an Open Source project, especially with other Open Source communities. We’ve started to engage more with OpenFeature, and the potential to collaborate on this specific code is exciting for our team. On OpenFeature’s end, there is an open discussion about whether or not this direction makes sense and, if so, what the best options might be. However, we’re hopeful that we have the basis for some technical collaboration that could be beneficial to both projects.

Get Involved

If this OpenFeature collaboration interests you, we’d welcome you to join the conversation, help collaborate on POCs, and share your expertise.

If you help maintain any Unleash SDKs and are interested in bringing Yggdrasil into yours, let us know in GitHub or Slack.

Share this article