Getting Seq onto .NET 6 and ARM

Seq 2021.4, scheduled for December, will be our first release targeting .NET 6 and ARM. Porting to the new instruction set began right at the start of 2021, which has meant targeting both .NET 5 and .NET 6 simultaneously over the course of the year. This post is a quick rundown of how we've made that work.

Even though Seq won't be shipping on .NET 6 officially until its 2021.4 release, ARM preview images of the datalust/seq Docker image are already based on .NET 6, and I've been building Seq against .NET 6 in my own development environments (OSX on Apple Silicon and Linux on a Raspberry Pi) for the better part of a year now. That's different from past Seq releases where we've waited for RC builds of a new .NET version before targeting them. This time around, most of our dev team has still been targeting .NET 5 on x86 when they build Seq, while I've been targeting .NET 6 on ARM.

For Seq, .NET 6 has encouraged us to rethink the way we build locally, but has required very few changes to the codebase itself to support. It's absolutely viable to jump on new .NET versions early and follow their progress. In fact, I'd encourage anybody who can to do so in their own way. The more users that participate in previews, the more useful those previews are.

.NET 6 in development

The way we hold .NET SDKs is now much closer to how we use Rust with rustup. The dev team is free to use whatever versions of the .NET SDK and runtime they want locally, and we use a pinned version in CI. Instead of having a global.json at the root of the repository, we've got a file called ci.global.json. The first thing CI does is rename that file to global.json. That's just so it doesn't get in the way of our local dotnet experience, since there isn't really an equivalent of rustup override for .NET. I didn't end up using the rolling version policy support in global.json just to make sure we always build against an exact version in CI, regardless of what's already on the build agent.

It's not enough just to use a .NET 6 SDK though, I also need to be able to target the .NET 6 framework and runtime so I could actually run Seq on Apple Silicon. Rather than multi-targeting through the TargetFrameworks property, Seq technically only targets a single version at a time, which is determined based on the host environment. So in our Seq.csproj, we've got this:

<TargetFramework>$(SeqTargetFramework)</TargetFramework>

The SeqTargetFramework property is set with a little conditional logic based on the host:

<PropertyGroup Condition=" '$(IsArm64)' == 'true' ">
	<SeqTargetFramework>net6.0</SeqTargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(IsArm64)' != 'true' ">
	<SeqTargetFramework>net5.0</SeqTargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(IsWindows)' == 'true' ">
	<SeqTargetFramework>net5.0-windows</SeqTargetFramework>
</PropertyGroup>

That lets me fully target net6.0 on Apple Silicon, while other environments could still target net5.0. As an aside, we tend to use MSBuild properties and preprocessor directives a lot in Seq because we've also got native code involved.

Keeping things consistent between .NET 5 and .NET 6

This section is intentionally left blank, because we never hit an API that was either new in .NET 6 and didn't exist in .NET 5 or that existed in .NET 5 but was removed in .NET 6.

What's next?

Seq 2021.4 will be the first stable release targeting .NET 6.

Before we release 2021.4 we'll do some more detailed and reliable performance analysis, but very early results suggest just changing our target framework from net5.0 to net6.0 reduces end-to-end wall-clock time for a range of queries served from RAM on average by about 35%. Which is really impressive.

I'm also interested to explore the work with source generators and writable DOM for System.Text.Json more. Seq spends quite a lot of time serializing and deserializing JSON. I did a spike back in .NET Core 3 times using some copy-on-write APIs over the immutable JsonDocument as a replacement for Seq's existing serialization code and saw some impressive improvements all around. I'm keen to spend some more time with it now.

As a final note, I would like to extend a huge kudos to everyone who's worked on .NET 6 and Apple Silicon support. Multi-targeting between .NET 5 on x86 and .NET 6 on ARM and Apple Silicon became a business-as-usual activity for us very quickly and has remained stable and dependable throughout its development 👏