<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Nick Craver</title>
    <atom:link href="https://nickcraver.com/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Software Imagineering</description>
    <link>https://nickcraver.com/blog</link>
    <pubDate>Mon, 29 May 2023 20:30:36 +0000</pubDate>
    <lastBuildDate>Mon, 29 May 2023 20:30:36 +0000</lastBuildDate>
    <language>en-US</language>
    <generator>Jekyll v3.9.3</generator>
    
      <item>
        <title>Binding Redirects</title>
        <link>https://nickcraver.com/blog/2020/02/11/binding-redirects/</link>
        <description>&lt;blockquote&gt;
  &lt;p&gt;This isn’t part of the &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;series on Stack Overflow’s architecture&lt;/a&gt;, but is a topic that has bitten us many times. Hopefully, some of this information helps you sort out issues you hit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’re probably here because of an error like this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Could not load file or assembly ‘System.&amp;lt;…&amp;gt;, Version=4.x.x.x, Culture=neutral, PublicKeyToken=&amp;lt;…&amp;gt;’ or one of its dependencies. The system cannot find the file specified.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And you likely saw a build warning like this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;warning MSB3277: Found conflicts between different versions of “System.&amp;lt;…&amp;gt;” that could not be resolved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whelp, you’re not alone. We’re thinking about starting a survivors group. The most common troublemakers here are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Memory&lt;/code&gt; (&lt;a href=&quot;https://www.nuget.org/packages/System.Memory/&quot;&gt;NuGet link&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Net.Http&lt;/code&gt; (&lt;a href=&quot;https://www.nuget.org/packages/System.Net.Http/&quot;&gt;NuGet link&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Numerics.Vectors&lt;/code&gt; (&lt;a href=&quot;https://www.nuget.org/packages/System.Numerics.Vectors/&quot;&gt;NuGet link&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Runtime.CompilerServices.Unsafe&lt;/code&gt; (&lt;a href=&quot;https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe/&quot;&gt;NuGet link&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.ValueTuple&lt;/code&gt; (&lt;a href=&quot;https://www.nuget.org/packages/System.ValueTuple/&quot;&gt;NuGet link&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you just want out of this fresh version of DLL Hell you’ve found yourself in…&lt;/p&gt;

&lt;h3 id=&quot;the-best-fix&quot;&gt;The best fix&lt;/h3&gt;

&lt;p&gt;The best fix is “go to .NET Core”. Since .NET Framework (e.g. 4.5, 4.8, etc.) has a heavy backward compatibility burden, meaning that the assembly loader itself is basically made of unstable plutonium with a hair trigger coated in flesh eating bacteria behind a gate made of unobtanium above a moat of napalm filled with those jellyfish that kill you…that won’t ever really be fixed.&lt;/p&gt;

&lt;p&gt;However, .NET Core’s simplified assembly loading means it &lt;em&gt;just works&lt;/em&gt;. I’m not saying a migration to .NET Core is trivial, that depends on your situation, but it is generally the best long-term play. We’re almost done porting Stack Overflow to .NET Core, and this kind of pain is one of the things we’re very much looking forward to not fighting ever again.&lt;/p&gt;

&lt;h3 id=&quot;the-fix-if-youre-using-net-framework&quot;&gt;The fix if you’re using .NET Framework&lt;/h3&gt;

&lt;p&gt;The fix for .NET Framework is “&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/redirect-assembly-versions&quot;&gt;binding redirects&lt;/a&gt;”. When you are trying to load an assembly (and if it’s &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/assembly/strong-named&quot;&gt;strongly named&lt;/a&gt;, like the ones in the framework and many libs are), it’ll try to load the &lt;em&gt;specific version&lt;/em&gt; you specified. Unless it’s told to load another (likely newer) version. Think of it as “Hey little buddy. It’s okay! Don’t cry! That API you want is still available…it’s just over here”. I don’t want to replicate the official documentation here since it’ll be updated much better by the awesome people on Microsoft Docs these days. Your options to fix it are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/how-to-enable-and-disable-automatic-binding-redirection&quot;&gt;Enable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;AutoGenerateBindingRedirects&amp;gt;&lt;/code&gt;&lt;/a&gt; (&lt;em&gt;this doesn’t work for web projects&lt;/em&gt; - it doesn’t handle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.config&lt;/code&gt;)
    &lt;ul&gt;
      &lt;li&gt;Note: this isn’t always perfect. It doesn’t handle all transitive cases especially around multi-targeting and conditional references.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Build with Visual Studio and hope it has warnings and a click to fix (this &lt;em&gt;usually&lt;/em&gt; works)
    &lt;ul&gt;
      &lt;li&gt;Note: this isn’t always perfect either, hence the “hope”.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/redirect-assembly-versions#manually-editing-the-app-config-file&quot;&gt;&lt;strong&gt;Manually edit your *.config file&lt;/strong&gt;&lt;/a&gt;.
    &lt;ul&gt;
      &lt;li&gt;This is the surest way (and what Visual Studio is doing above), but also the most manual and/or fun on upgrades.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately, what that “manual editing” article doesn’t mention is the &lt;strong&gt;&lt;em&gt;assembly versions are not the NuGet versions&lt;/em&gt;&lt;/strong&gt;. For instance, &lt;a href=&quot;https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe/4.7.0&quot;&gt;System.Runtime.CompilerServices.Unsafe 4.7.0&lt;/a&gt; on NuGet is &lt;em&gt;assembly&lt;/em&gt; version 4.0.6.0. The assembly version is what matters. The easiest way I use to figure this out on Windows is the &lt;a href=&quot;https://github.com/NuGetPackageExplorer/NuGetPackageExplorer&quot;&gt;NuGet Package Explorer&lt;/a&gt; (&lt;a href=&quot;https://www.microsoft.com/en-us/p/nuget-package-explorer/9wzdncrdmdm3&quot;&gt;Windows Store link&lt;/a&gt; - easiest install option) maintained by &lt;a href=&quot;https://github.com/clairernovotny&quot;&gt;Claire Novotny&lt;/a&gt;. Either from within NPE’s feed browser or from NuGet.org (there’s a “Download package” option in the right sidebar), open the package. Select the framework you’re loading (they &lt;em&gt;should&lt;/em&gt; all match really) and double click the DLL. It’ll have a section that says “Strong Name: Yes, version: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4.x.x.x&lt;/code&gt;”&lt;/p&gt;

&lt;p&gt;For example, if you had libraries wanting various versions of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Numerics.Vectors&lt;/code&gt;, the most likely fix is to prefer the latest (as of writing this, &lt;a href=&quot;https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe/4.7.0&quot;&gt;4.7.0 on NuGet&lt;/a&gt; which is assembly version 4.0.6.0 from earlier). Part of your config would look something like this:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;runtime&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;assemblyBinding&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;urn:schemas-microsoft-com:asm.v1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependentAssembly&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;assemblyIdentity&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;System.Runtime.CompilerServices.Unsafe&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;publicKeyToken=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;b03f5f7f11d50a3a&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;culture=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;neutral&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;bindingRedirect&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;oldVersion=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0.0.0.0-4.0.6.0&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;newVersion=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4.0.6.0&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependentAssembly&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- ...and maybe some more... --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/assemblyBinding&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/runtime&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This means “anyone asking for any version before 4.0.6.0, send them to that one…it’s what in my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin\&lt;/code&gt; folder”. For a quick practical breakdown of these fields:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;: The name of the strongly named DLL&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;publicKeyToken&lt;/code&gt;: The public key of the strongly named DLL (this comes from key used to sign it)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;culture&lt;/code&gt;: Pretty much always &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neutral&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oldVersion&lt;/code&gt;: A &lt;strong&gt;range&lt;/strong&gt; of versions, starting at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0.0.0&lt;/code&gt; (almost always what you want) means “redirect everything from X to Y”&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;newVersion&lt;/code&gt;: The new version to “redirect” to, instead of any old one (usually matches end of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oldVersion&lt;/code&gt; range)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;when-do-i-need-to-do-this&quot;&gt;When do I need to do this?&lt;/h3&gt;

&lt;p&gt;Any of the above approaches to fixing it need to be revisited when a conflict arises again. This can happen when:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Updating a NuGet package (Remember: &lt;a href=&quot;https://docs.microsoft.com/en-us/nuget/concepts/dependency-resolution&quot;&gt;NuGet uses transitive dependencies&lt;/a&gt;…so anything in the chain can cause it)&lt;/li&gt;
  &lt;li&gt;Updating your target framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that binding redirects are to remedy &lt;em&gt;differences&lt;/em&gt; in the reference chain (more on that below), so when all of your things reference the same version down their transitive chains, you don’t need one. So sometimes &lt;em&gt;removing&lt;/em&gt; a binding redirect is the quick fix in an upgrade.&lt;/p&gt;

&lt;p&gt;But seriously, .NET Core. Of all the reasons we’re migrating over at Stack Overflow, binding redirects are in my top 5. We’ve lost soooo many days to this over the years.&lt;/p&gt;

&lt;h3 id=&quot;whats-going-on-why-do-i-need-this&quot;&gt;What’s going on? Why do I need this?&lt;/h3&gt;

&lt;p&gt;The overall problem is that you have two different libraries ultimately wanting two different versions of one of these (and potentially anything else — this is a general case the three above are just the &lt;em&gt;most&lt;/em&gt; common). Since dependencies are transitive, this means you just reference a library and the tooling will get what it needs. (That’s what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;PackageReference&amp;gt;&lt;/code&gt; does in your projects.) But what that means is you could have library &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A → B → System.Vectors&lt;/code&gt; (version 1.0), and another reference to something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;D → E → F → System.Vectors&lt;/code&gt; (version 1.2).&lt;/p&gt;

&lt;p&gt;Uh oh, we have two things wanting two different versions. You may be thinking (for Windows) “but…won’t they be the same file in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin\&lt;/code&gt; directory? How can you have both? How does that &lt;em&gt;even work&lt;/em&gt;?” You’re not crazy. You’re hitting the error because &lt;em&gt;one of those two versions won&lt;/em&gt;. It &lt;em&gt;should&lt;/em&gt; be the newer one.&lt;/p&gt;

&lt;p&gt;Okay, so it’s broken. The code wanting the &lt;em&gt;other&lt;/em&gt; version is what’s erroring. But maybe not on startup! That’s another fun part. It happens &lt;em&gt;the first time you touch a type that references types in that assembly&lt;/em&gt;. Bonus: if this is in a static constructor, that type you’re trying to touch will most likely disappear. Poof. Gone. What does the app do without it? WHO KNOWS?! But buckle up, because you can bet your butt it’ll be fun. Imagine the compiler said “okay” but that type wasn’t really there later…because that’s basically the situation you find yourself in.&lt;/p&gt;

&lt;p&gt;So, we need to redirect things. In theory, the framework takes care of some of these indirections, but with some pieces deployed via NuGet it’s a bit of a mess and it’s far from perfect. That’s why you’re here. I hope this helped.&lt;/p&gt;

&lt;p&gt;In some versions of the framework, things aren’t quite right in what shipped - the few libraries up top are troublemakers more than all the others for this reason. Those are the versions that were glitched in various versions of .NET Framework. “Why don’t you fix .NET 4.&lt;version&gt;??&quot; is a question Microsoft gets a lot. They did — it's called &quot;the next version&quot;. Being unable to break people, that's the only real way to fix it and not cause *other* damage. Keep in mind, we're talking about over a billion computers to update here. So, when you update to .NET 4.7.2 (most of the redirects are fixed here), .NET 4.8, etc. — more and more of the cases do go away. But with NuGet and later versions...yeah, they can come back. Generally speaking though, the later version of .NET Framework you're targeting and running on, the better. There has been progress.&lt;/version&gt;&lt;/p&gt;

&lt;p&gt;Anyway, I hope this helps some people out there. I should have written this up 5 years ago, and wish I had this info available to me 10 years ago. Good luck on fixing whatever brought you here!&lt;/p&gt;
</description>
        <pubDate>Tue, 11 Feb 2020 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2020/02/11/binding-redirects/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Stack Overflow: How We Do App Caching - 2019 Edition</title>
        <link>https://nickcraver.com/blog/2019/08/06/stack-overflow-how-we-do-app-caching/</link>
        <description>&lt;blockquote&gt;
  &lt;p&gt;This is #5 in a &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;very long series of posts&lt;/a&gt; on Stack Overflow’s architecture.&lt;br /&gt;
Previous post (#4): &lt;a href=&quot;/blog/2018/11/29/stack-overflow-how-we-do-monitoring/&quot;&gt;Stack Overflow: How We Do Monitoring - 2018 Edition&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So…caching. What is it? It’s a way to get a quick payoff by not re-calculating or fetching things over and over, resulting in performance and cost wins.
That’s even where the name comes from, it’s a short form of the “ca-ching!” cash register sound from the dark ages of 2014 when physical currency was still a thing, before Apple Pay. I’m a dad now, deal with it.&lt;/p&gt;

&lt;p&gt;Let’s say we need to call an API or query a database server or just take a bajillion numbers (Google says &lt;a href=&quot;https://www.merriam-webster.com/dictionary/bajillion&quot;&gt;that’s an actual word&lt;/a&gt;, I checked) and add them up.
Those are all relatively &lt;em&gt;crazy&lt;/em&gt; expensive. So we &lt;a href=&quot;https://en.wikipedia.org/wiki/Cache_(computing)&quot;&gt;cache&lt;/a&gt; the result – we keep it handy for re-use.&lt;/p&gt;

&lt;h3 id=&quot;why-do-we-cache&quot;&gt;Why Do We Cache?&lt;/h3&gt;

&lt;p&gt;I think it’s important here to discuss &lt;em&gt;just how expensive&lt;/em&gt; some of the above things are.
There are several layers of caching already in play in your modern computer.
&lt;!--more--&gt;
As a concrete example, we’re going to use one of our web servers which currently houses a pair of &lt;a href=&quot;https://ark.intel.com/products/81713/Intel-Xeon-Processor-E5-2690-v3-30M-Cache-2-60-GHz-&quot;&gt;Intel Xeon E5-2960 v3 CPUs&lt;/a&gt; and 2133MHz DIMMs.
Cache access is a “how many cycles” feature of a processor, so by knowing that we always run at 3.06GHz (performance power mode), we can derive the latencies (&lt;a href=&quot;https://www.intel.co.uk/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf&quot;&gt;Intel architecture reference here&lt;/a&gt; – these processors are in the Haswell generation):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;L1 (per core): 4 cycles or &lt;strong&gt;~1.3ns&lt;/strong&gt; latency - 12x 32KB+32KB&lt;/li&gt;
  &lt;li&gt;L2 (per core): 12 cycles or &lt;strong&gt;~3.92ns&lt;/strong&gt; latency - 12x 256KB&lt;/li&gt;
  &lt;li&gt;L3 (shared): 34 cycles or &lt;strong&gt;~11.11ns&lt;/strong&gt; latency - 30MB&lt;/li&gt;
  &lt;li&gt;System memory: &lt;strong&gt;~100ns&lt;/strong&gt; latency - 8x 8GB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each cache layer is able to store more, but is farther away.
It’s a trade-off in processor design with balances in play.
For example, more memory per core means (almost certainly) on average putting it farther away on the chip from the core and that has costs in latency, opportunity costs, and power consumption.
How far an electric charge has to travel has substantial impact at this scale; remember that distance is multiplied by &lt;em&gt;billions&lt;/em&gt; every second.&lt;/p&gt;

&lt;p&gt;And I didn’t get into disk latency above because we so very rarely touch disk.
Why? Well, I guess to explain that we need to…look at disks.
Ooooooooh shiny disks!
But please don’t touch them after running around in socks.
At Stack Overflow, anything production that’s not a backup or logging server is on SSDs.
Local storage generally falls into a few tiers for us:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;NVMe SSD: ~120μs (&lt;a href=&quot;https://www.anandtech.com/show/8104/intel-ssd-dc-p3700-review-the-pcie-ssd-transition-begins-with-nvme/3&quot;&gt;source&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;SATA or SAS SSD: ~400–600μs (&lt;a href=&quot;https://www.anandtech.com/show/8104/intel-ssd-dc-p3700-review-the-pcie-ssd-transition-begins-with-nvme/3&quot;&gt;source&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Rotational HDD: 2–6ms (&lt;a href=&quot;https://en.wikipedia.org/wiki/Hard_disk_drive_performance_characteristics&quot;&gt;source&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These numbers are changing all the time, so don’t focus on exact figures too much.
What we’re trying to evaluate is the magnitude of the difference of these storage tiers.
Let’s go down the list (assuming the lower bound of each, these are best case numbers):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;L1: 1.3ns&lt;/li&gt;
  &lt;li&gt;L2: 3.92ns (&lt;strong&gt;3x slower&lt;/strong&gt;)&lt;/li&gt;
  &lt;li&gt;L3: 11.11ns (&lt;strong&gt;8.5x slower&lt;/strong&gt;)&lt;/li&gt;
  &lt;li&gt;DDR4 RAM: 100ns (&lt;strong&gt;77x slower&lt;/strong&gt;)&lt;/li&gt;
  &lt;li&gt;NVMe SSD: 120,000ns (&lt;strong&gt;92,307x slower&lt;/strong&gt;)&lt;/li&gt;
  &lt;li&gt;SATA/SAS SSD: 400,000ns (&lt;strong&gt;307,692x slower&lt;/strong&gt;)&lt;/li&gt;
  &lt;li&gt;Rotational HDD: 2–6ms (&lt;strong&gt;1,538,461x slower&lt;/strong&gt;)&lt;/li&gt;
  &lt;li&gt;Microsoft Live Login: 12 redirects and 5s (&lt;strong&gt;3,846,153,846x slower&lt;/strong&gt;, approximately)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If numbers aren’t your thing, &lt;a href=&quot;https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html&quot;&gt;here’s a neat open source visualization&lt;/a&gt; (use the slider!) by &lt;a href=&quot;https://github.com/colin-scott&quot;&gt;Colin Scott&lt;/a&gt; (you can even go see how they’ve evolved over time – really neat):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Caching/SO-Cache-Latencies.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-Latencies.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-Latencies.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Caching/SO-Cache-Latencies.png&quot; loading=&quot;lazy&quot; alt=&quot;Cache Latencies&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With those performance numbers and a sense of scale in mind, let’s add some numbers that matter every day.
Let’s say our data source is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt;, where what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; is doesn’t matter.
It could be SQL, or a microservice, or a macroservice, or a leftpad service, or Redis, or a file on disk, etc.
The key here is that we’re comparing that source’s performance to that of RAM.
Let’s say our source takes…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;100ns (from RAM - fast!)&lt;/li&gt;
  &lt;li&gt;1ms (10,000x slower)&lt;/li&gt;
  &lt;li&gt;100ms (100,000x slower)&lt;/li&gt;
  &lt;li&gt;1s (1,000,000x slower)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don’t think we need to go further to illustrate the point: &lt;strong&gt;even things that take only 1 millisecond are way, &lt;em&gt;way&lt;/em&gt; slower than local RAM&lt;/strong&gt;. Remember: millisecond, microsecond, nanosecond – just in case anyone else forgets that a 1000ns != 1ms like I sometimes do…&lt;/p&gt;

&lt;p&gt;But not all cache is local.
For example, we use Redis for shared caching behind our web tier (&lt;a href=&quot;#redis&quot;&gt;which we’ll cover in a bit&lt;/a&gt;).
Let’s say we’re going across our network to get it.
For us, that’s a 0.17ms roundtrip and you need to also send some data.
For small things (our usual), that’s going to be around 0.2–0.5ms total.
Still 2,000–5,000x slower than local RAM, but also a lot faster than most sources.
Remember, these numbers are because we’re in a small local LAN. 
Cloud latency will generally be higher, so measure to see your latency.&lt;/p&gt;

&lt;p&gt;When we get the data, maybe we also want to massage it in some way.
Probably Swedish.
Maybe we need totals, maybe we need to filter, maybe we need to encode it, maybe we need to fudge with it randomly just to trick you.
That was a test to see if you’re still reading. You passed!
Whatever the reason, the commonality is generally &lt;em&gt;we want to do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;x&amp;gt;&lt;/code&gt; once&lt;/em&gt;, and not &lt;em&gt;every time we serve it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes we’re saving latency and sometimes we’re saving CPU.
One or both of those are generally why a cache is introduced.
Now let’s cover the flip side…&lt;/p&gt;

&lt;h3 id=&quot;why-wouldnt-we-cache&quot;&gt;Why Wouldn’t We Cache?&lt;/h3&gt;

&lt;p&gt;For everyone who hates caching, this is the section for you! 
Yes, I’m totally playing both sides.&lt;/p&gt;

&lt;p&gt;Given the above and how drastic the wins are, why &lt;em&gt;wouldn’t&lt;/em&gt; we cache something?
Well, because &lt;strong&gt;&lt;em&gt;every single decision has trade-offs&lt;/em&gt;&lt;/strong&gt;. Every. Single. One.
It could be as simple as time spent or opportunity cost, but there’s still a trade-off.&lt;/p&gt;

&lt;p&gt;When it comes to caching, adding a cache comes with some costs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Purging values if and when needed (cache invalidation – &lt;a href=&quot;#cache-invalidation&quot;&gt;we’ll cover that in a few&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Memory used by the cache&lt;/li&gt;
  &lt;li&gt;Latency of access to the cache (weighed against access to the source)&lt;/li&gt;
  &lt;li&gt;Additional time and mental overhead spent debugging something more complicated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever a candidate for caching comes up (usually with a new feature), we need to evaluate these things…and that’s not always an easy thing to do.
Although caching is an exact science, much like astrology, it’s still tricky.&lt;/p&gt;

&lt;p&gt;Here at Stack Overflow, our architecture has one overarching theme: keep it as simple as possible.
Simple is easy to evaluate, reason about, debug, and change if needed.
Only make it more complicated if and when it &lt;strong&gt;&lt;em&gt;needs&lt;/em&gt;&lt;/strong&gt; to be more complicated.
That includes cache. Only cache if you need to.
It adds more work and &lt;a href=&quot;https://shouldiblamecaching.com/&quot;&gt;more chances for bugs&lt;/a&gt;, so unless it’s needed: don’t.
At least, not yet.&lt;/p&gt;

&lt;p&gt;Let’s start by asking some questions.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Is it that much faster to hit cache?&lt;/li&gt;
  &lt;li&gt;What are we saving?&lt;/li&gt;
  &lt;li&gt;Is it worth the storage?&lt;/li&gt;
  &lt;li&gt;Is it worth the cleanup of said storage (e.g. garbage collection)?&lt;/li&gt;
  &lt;li&gt;Will it go on the large object heap immediately?&lt;/li&gt;
  &lt;li&gt;How often do we have to invalidate it?&lt;/li&gt;
  &lt;li&gt;How many hits per cache entry do we think we’ll get?&lt;/li&gt;
  &lt;li&gt;Will it interact with other things that complicate invalidation?&lt;/li&gt;
  &lt;li&gt;How many variants will there be?&lt;/li&gt;
  &lt;li&gt;Do we have to allocate just to calculate the key?&lt;/li&gt;
  &lt;li&gt;Is it a local or remote cache?&lt;/li&gt;
  &lt;li&gt;Is it shared between users?&lt;/li&gt;
  &lt;li&gt;Is it shared between sites?&lt;/li&gt;
  &lt;li&gt;Does it rely on quantum entanglement or does debugging it just make you think that?&lt;/li&gt;
  &lt;li&gt;What color is the cache?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these are questions that come up and affect caching decisions.
I’ll try and cover them through this post.&lt;/p&gt;

&lt;h3 id=&quot;layers-of-cache-at-stack-overflow&quot;&gt;Layers of Cache at Stack Overflow&lt;/h3&gt;

&lt;p&gt;We have our own “L1”/”L2” caches here at Stack Overflow, but I’ll refrain from referring to them that way to avoid confusion with the CPU caches mentioned above.
What we have is several types of cache.
Let’s first quickly cover local and memory caches here for terminology before a deep dive into the common bits used by them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;“Global Cache”&lt;/strong&gt;: In-memory cache (global, per web server, and backed by Redis on miss)
    &lt;ul&gt;
      &lt;li&gt;Usually things like a user’s top bar counts, shared across the network&lt;/li&gt;
      &lt;li&gt;This hits local memory (shared keyspace), and then Redis (shared keyspace, using Redis database 0)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“Site Cache”&lt;/strong&gt;: In-memory cache (per site, per web server, and backed by Redis on miss)
    &lt;ul&gt;
      &lt;li&gt;Usually things like question lists or user lists that are per-site&lt;/li&gt;
      &lt;li&gt;This hits local memory (per-site keyspace, using prefixing), and then Redis (per-site keyspace, using Redis databases)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;“Local Cache”&lt;/strong&gt;: In-memory cache (per site, per web server, backed by &lt;em&gt;nothing&lt;/em&gt;)
    &lt;ul&gt;
      &lt;li&gt;Usually things that are cheap to fetch, but huge to stream and the Redis hop isn’t worth it&lt;/li&gt;
      &lt;li&gt;This hits local memory only (per-site keyspace, using prefixing)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What do we mean by “per-site”?
Stack Overflow and the Stack Exchange network of sites is &lt;a href=&quot;/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/&quot;&gt;a multi-tenant architecture&lt;/a&gt;.
Stack Overflow is just one of &lt;a href=&quot;https://stackexchange.com/sites#traffic&quot;&gt;many hundreds of sites&lt;/a&gt;.
This means one process on the web server hosts all the sites, so we need to split up the caching where needed.
And we’ll have to purge it (&lt;a href=&quot;#cache-invalidation&quot;&gt;we’ll cover how that works too&lt;/a&gt;).&lt;/p&gt;

&lt;h3 id=&quot;redis&quot;&gt;Redis&lt;/h3&gt;

&lt;p&gt;Before we discuss how servers and shared cache work, let’s quickly cover what the shared bits are built on: Redis.
So what is &lt;a href=&quot;https://redis.io/&quot;&gt;Redis&lt;/a&gt;?
It’s an open source key/value data store with many useful data structures, additional publish/subscriber mechanisms, and rock solid stability.&lt;/p&gt;

&lt;p&gt;Why Redis and not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;something else&amp;gt;&lt;/code&gt;?
Well, because it works. And it works well.
It seemed like a good idea when we needed a shared cache.
It’s been &lt;em&gt;incredibly&lt;/em&gt; rock solid.
We don’t wait on it – it’s incredibly fast.
We know how it works.
We’re very familiar with it.
We know how to monitor it.
We know how to spell it.
We maintain one of the most used open source libraries for it.
We can tweak that library if we need.&lt;/p&gt;

&lt;p&gt;It’s a piece of infrastructure we &lt;em&gt;just don’t worry about&lt;/em&gt;.
We basically take it for granted (though we still have an HA setup of replicas – we’re not &lt;em&gt;completely&lt;/em&gt; crazy).
When making infrastructure choices, you don’t just change things for perceived possible value.
Changing takes effort, takes time, and involves risk.
If what you have works well and does what you need, why invest that time and effort and take a risk?
Well…you don’t.
There are thousands of better things you can do with your time.
Like debating which cache server is best!&lt;/p&gt;

&lt;p&gt;We have a few Redis instances to separate concerns of apps (but on the same set of servers), here’s an example of what one looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Caching/SO-Cache-Opserver.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-Opserver.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-Opserver.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Caching/SO-Cache-Opserver.png&quot; loading=&quot;lazy&quot; alt=&quot;Opserver: Redis View&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the curious, some quick stats from last Tuesday (2019-07-30) This is across all instances on the primary boxes (because we split them up for organization, not performance…one instance could handle everything we do quite easily):&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Our Redis physical servers have 256GB of memory, but less than 96GB used.&lt;/li&gt;
  &lt;li&gt;1,586,553,473 commands processed per day (3,726,580,897 commands and 86,982 per second peak across all instances – due to replicas)&lt;/li&gt;
  &lt;li&gt;Average of 2.01% CPU utilization (3.04% peak) for the entire server (&amp;lt; 1% even for the most active instance)&lt;/li&gt;
  &lt;li&gt;124,415,398 active keys (422,818,481 including replicas)&lt;/li&gt;
  &lt;li&gt;Those numbers are across 308,065,226 HTTP hits (64,717,337 of which were question pages)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;sub&gt;Note: None of these are Redis limited – we’re far from any limits. It’s just how much activity there is on our instances.&lt;/sub&gt;&lt;/p&gt;

&lt;p&gt;There are also non-cache reasons we use Redis, namely: we also use the pub/sub mechanism &lt;a href=&quot;/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/#websockets-httpsgithubcomstackexchangenetgain&quot;&gt;for our websockets&lt;/a&gt; that provide realtime updates on scores, rep, etc.
Redis 5.0 &lt;a href=&quot;https://redis.io/topics/streams-intro&quot;&gt;added Streams&lt;/a&gt; which is a perfect fit for our websockets and we’ll likely migrate to them when some other infrastructure pieces are in place (mainly limited by Stack Overflow Enterprise’s version at the moment).&lt;/p&gt;

&lt;h4 id=&quot;in-memory--redis-cache&quot;&gt;In-Memory &amp;amp; Redis Cache&lt;/h4&gt;

&lt;p&gt;Each of the above has an in-memory cache component and some have a backup in that lovely Redis server.&lt;/p&gt;

&lt;p&gt;In-memory is simple enough, we’re just caching things in…ya know, memory.
In ASP.NET MVC 5, this used to be &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.web.httpruntime.cache?view=netframework-4.7.2&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpRuntime.Cache&lt;/code&gt;&lt;/a&gt;.
These days, in preparation for our &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/&quot;&gt;ASP.NET Core&lt;/a&gt; move, we’ve moved on to &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.runtime.caching.memorycache&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MemoryCache&lt;/code&gt;&lt;/a&gt;.
The differences are tiny and don’t matter much; both generally provide a way to cache an object for some duration of time.
That’s all we need here.&lt;/p&gt;

&lt;p&gt;For the above caches, we choose a “database ID”.
These relate to the sites we have on the Stack Exchange Network and come from our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sites&lt;/code&gt; database table.
&lt;a href=&quot;https://stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt; is 1, &lt;a href=&quot;https://serverfault.com/&quot;&gt;Server Fault&lt;/a&gt; is 2, &lt;a href=&quot;https://superuser.com/&quot;&gt;Super User&lt;/a&gt; is 3, etc.&lt;/p&gt;

&lt;p&gt;For local cache, you could approach it a few ways.
Ours is simple: that ID is part of the cache key.
For Global Cache (shared), the ID is zero.
We further (for safety) prefix each cache to avoid conflicts with general key names from whatever else might be in these app-level caches. 
An example key would be:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;prod:1-related-questions:1234
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That would be the related questions in the sidebar for Question 1234 on Stack Overflow (ID: 1).
If we’re &lt;strong&gt;only&lt;/strong&gt; in-memory, serialization doesn’t matter and we can just cache any object.
However, if we’re sending that cache object somewhere (or getting one back from somewhere), we need to serialize it. And fast!
That’s where &lt;a href=&quot;https://github.com/mgravell/protobuf-net&quot;&gt;protobuf-net&lt;/a&gt; written by our own &lt;a href=&quot;https://twitter.com/marcgravell&quot;&gt;Marc Gravell&lt;/a&gt; comes in.
&lt;a href=&quot;https://developers.google.com/protocol-buffers/docs/encoding&quot;&gt;Protobuf&lt;/a&gt; is a binary encoding format that’s tremendously efficient in both speed and allocations.
A simple object we want to cache may look like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RelatedQuestionsCache&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Count&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With &lt;a href=&quot;https://github.com/mgravell/protobuf-net#basic-usage&quot;&gt;protobuf attributes&lt;/a&gt; to control serialization, it looks like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProtoContract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RelatedQuestionsCache&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ProtoMember&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Count&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ProtoMember&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So let’s say we want to cache that object in Site Cache. The flow looks like this (code simplified a bit):&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Transform to the shared cache key format, e.g. &quot;x&quot; into &quot;prod:1-x&quot;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetCacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Do we have it in memory?&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memoryCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedisWrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;local&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// We've got it local - nothing more to do.&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;local&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Is Redis connected and readable? This makes Redis a fallback and not critical&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;redisCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CanRead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Key is passed here for the case of Redis Cluster&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    	&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;remote&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;redisCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StringGetWithExpiry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Get our shared construct for caching&lt;/span&gt;
       	    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapper&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RedisWrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;From&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;remote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Set it in our local memory cache so the next hit gets it faster&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;memoryCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedisWrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;remote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Expiry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Return the value we found in Redis&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;remote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// No value found, sad panda&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Granted, this code is greatly simplified to convey the point, but we’re not leaving anything important out.&lt;/p&gt;

&lt;p&gt;Why a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RedisWrapper&amp;lt;T&amp;gt;&lt;/code&gt;?
It synonymizes platform concepts by having a value with a TTL (time-to-live) with the value, just &lt;a href=&quot;https://redis.io/commands/ttl&quot;&gt;like Redis does&lt;/a&gt;.
It also allows the caching of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; value and makes that handling not special-cased.
In other words, you can tell the difference between “It’s not cached” and “We looked it up. It was null. We cached that null. STOP ASKING!”
If you’re curious about &lt;a href=&quot;https://github.com/StackExchange/StackExchange.Redis/blob/3f7e5466c6bbff96a3ed1130b637a097d21f3fed/src/StackExchange.Redis/Interfaces/IDatabase.cs#L1871&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringGetWithExpiry&lt;/code&gt;, it’s a StackExchange.Redis method&lt;/a&gt; that returns the value and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TTL&lt;/code&gt; in one call by pipelining the commands (not 2 round-trip time costs).&lt;/p&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Set&amp;lt;T&amp;gt;&lt;/code&gt; for caching a value works exactly the same way:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Cache the value in memory.&lt;/li&gt;
  &lt;li&gt;Cache the same value in Redis.&lt;/li&gt;
  &lt;li&gt;(Optionally) Alert the other web servers that the value has been updated and instruct them to flush their copy.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;pipelining&quot;&gt;Pipelining&lt;/h4&gt;

&lt;p&gt;I want to take a moment and relay a very important thing here: our Redis connections (via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StackExchange.Redis&lt;/code&gt;) are pipelined.
Think of it like a conveyor belt you can stick something on and it goes somewhere and circles back.
You could stick thousands of things in a row on that conveyor belt before the first reaches the destination or comes back.
If you put a giant thing on there, it means you have to wait to add other things.&lt;/p&gt;

&lt;p&gt;The items may be independent, but the conveyor belt is shared.
In our case, the conveyor belt is the connection and the items are commands.
If a large payload goes on or comes back, it occupies the belt for a bit.
This means if you’re waiting on a specific thing but some nasty big item clogs up he works for a second or two, it may cause collateral damage.
That’s a timeout.&lt;/p&gt;

&lt;p&gt;We often see issues filed from people putting tremendous many-megabyte payloads into Redis with low timeouts, but that doesn’t work unless the pipe is very, very fast.
They don’t see the many-megabyte command timing out…they usually see things waiting &lt;em&gt;behind&lt;/em&gt; it timing out.&lt;/p&gt;

&lt;p&gt;It’s important to realize that a pipeline is just like any pipe outside a computer.
Whatever its narrowest constraint is, that’s where it’ll bottleneck.
Except this is a dynamic pipe, more like a hose that can expand or bend or kink.
The bottlenecks are not 100% constant.
In practical terms, this can be &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool&quot;&gt;thread pool&lt;/a&gt; exhaustion (either feeding commands in or handling them coming out).
Or it may be network bandwidth.
And maybe something &lt;em&gt;else&lt;/em&gt; is using that network bandwidth impacting us.&lt;/p&gt;

&lt;p&gt;Remember that at these levels of latency, viewing things at 1Gb/s or 10Gb/s isn’t really using the correct unit of time.
For me, it helps to not think in terms of 1Gb/s, but instead in terms of 1Mb/ms.
If we’re traversing the network in about a millisecond or less, that payload really does matter and can increase the time taken by very measurable and impactful amounts.
That’s all to say: think small here.
The limits on any system when you’re dealing with short durations must be considered with relative constraints proportional to the same durations.
When we’re talking about milliseconds, the fact that we think of most computing concepts only down to the second is often a factor that confuses thinking and discussion.&lt;/p&gt;

&lt;h4 id=&quot;pipelining-retries&quot;&gt;Pipelining: Retries&lt;/h4&gt;

&lt;p&gt;The pipelined nature is also why we can’t retry commands with any confidence.
In this sad world our conveyor belt has turned into the airport baggage pickup loopy thingamajig (&lt;a href=&quot;https://www.merriam-webster.com/dictionary/thingamajig&quot;&gt;also in the dictionary&lt;/a&gt;, for the record) we all know and love.&lt;/p&gt;

&lt;p&gt;You put an bag on the thingamajig.
The bag contains something important, probably some really fancy pants.
They’re going to someone who you wanted to impress.
(We’re using airport luggage as a very reasonably priced alternative to UPS in this scenario.)
But Mr. Fancy Pants is super nice and promised to return your bag.
So nice.
You did your part.
The bag went on the thingamajig and went out of sight…and never make it back.
Okay…&lt;em&gt;where&lt;/em&gt; did it go?!?
We don’t know!
DAMN YOU BAG!&lt;/p&gt;

&lt;p&gt;Maybe the bag made it to the lovely person and got lost on the return trip.
Or maybe it didn’t.
We still don’t know!
Should we send it again?
What if we’re sending them a second pair of fancy pants?
Will they think we think they spill ketchup a lot?
That’d be weird.
We don’t want to come on too strong.
And now we’re just confused and bagless.
So let’s talk about something that makes even less sense: cache invalidation.&lt;/p&gt;

&lt;h4 id=&quot;cache-invalidation&quot;&gt;Cache Invalidation&lt;/h4&gt;

&lt;p&gt;I keep referring to purging above, so how’s that work?
Redis has &lt;a href=&quot;https://redis.io/topics/pubsub&quot;&gt;a pub/sub feature&lt;/a&gt; where you can push a message out and subscribers all receive it (this message goes to all replicas as well).
Using this simple concept, we can simply have a cache clearing channel we &lt;a href=&quot;https://redis.io/commands/subscribe&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SUBSCRIBE&lt;/code&gt;&lt;/a&gt; to.
When we want to remove a value early (rather than waiting for TTLs to fall out naturally), we just &lt;a href=&quot;https://redis.io/commands/publish&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUBLISH&lt;/code&gt;&lt;/a&gt; that key name to our channel and the listener (think event handler here) just purges the key from local cache.&lt;/p&gt;

&lt;p&gt;The steps are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Purge the value from Redis via &lt;a href=&quot;https://redis.io/commands/del&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DEL&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://redis.io/commands/unlink&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UNLINK&lt;/code&gt;&lt;/a&gt;. Or, just replace the value with a new one…whatever state we’re after.&lt;/li&gt;
  &lt;li&gt;Broadcast the key to the purge channel.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Order is important, because reversing these would create a race and end up in a re-fetch of the old value sometimes.
Note that &lt;strong&gt;we’re not pushing the new value out&lt;/strong&gt;.
That’s not the goal.
Maybe web servers 1–5 that had the value cache won’t even ask for it again this duration…so let’s not be over-eager and wasteful.
All we’re making them do is get it from Redis &lt;em&gt;if and when&lt;/em&gt; it’s asked for.&lt;/p&gt;

&lt;h4 id=&quot;combining-everything-getset&quot;&gt;Combining Everything: GetSet&lt;/h4&gt;

&lt;p&gt;If you look at the above, you’d think we’re doing this a lot:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SiteCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FetchFromSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SiteCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timespan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But here’s where we can greatly improve on things.
First, it’s repetitive. Ugh.
But more importantly, that code will result in hundreds of simultaneous calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FetchFromSource()&lt;/code&gt; at scale when the cache expires.
What if that fetch is heavy?
Presumably it’s &lt;em&gt;somewhat&lt;/em&gt; expensive, since we’ve decided to cache it in the first place.
We need a better plan.&lt;/p&gt;

&lt;p&gt;This is where our most common approach comes in: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetSet&amp;lt;T&amp;gt;()&lt;/code&gt;.
Okay, so naming is hard.
Let’s just agree everyone has regrets and move on.
What do we &lt;em&gt;really&lt;/em&gt; want to do here?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Get a value if it’s there&lt;/li&gt;
  &lt;li&gt;Calculate or fetch a value if it’s not there (and shove it in cache)&lt;/li&gt;
  &lt;li&gt;Prevent calculating or fetching the same value many times&lt;/li&gt;
  &lt;li&gt;Ensure users wait as little as possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can use some attributes about who we are and what we do to optimize here.
Let’s say you load the web page now, or a second ago, or 3 seconds from now.
Does it matter?
Is the Stack Overflow question going to change that much?
The answer is: only if there’s anything to notice.
Maybe you made an upvote, or an edit, or a comment, etc.
These are things you’d notice.
We must refresh any caches that pertain to those kinds of activities &lt;em&gt;for you&lt;/em&gt;.
But for any of hundreds of other users simultaneously loading that page, skews in data are imperceptible.&lt;/p&gt;

&lt;p&gt;That means we have wiggle room. Let’s exploit that wiggle room for performance.&lt;/p&gt;

&lt;p&gt;Here’s what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetSet&amp;lt;T&amp;gt;&lt;/code&gt; looks like today (yes, there’s an equivalent-ish &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; version):&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GetSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ImmediateSiteCache&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MicroContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lookup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serveStaleDataSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;GetSetFlags&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GetSetFlags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PreferMaster&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The key arguments to this are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;durationSecs&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serveStaleDataSecs&lt;/code&gt;.
A call often looks something like this (it’s a contrived example for simplicity of discussion):&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lookup&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SiteCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;User:DisplayNames&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;old&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DisplayName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Select Id, DisplayName From Users&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                       &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToDictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; 
    &lt;span class=&quot;m&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This call goes to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Users&lt;/code&gt; table and caches an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Id&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DisplayName&lt;/code&gt; lookup (we don’t actually do this, I just needed a simple example).
The key part is the values at the end.
We’re saying “cache for 60 seconds, but serve stale for 5 minutes”.&lt;/p&gt;

&lt;p&gt;The behavior is that for 60 seconds, any hits against this cache only return it.
But we keep the value in memory (and Redis) for 6 minutes total.
In the time between 60 seconds and 6 minutes (from the time cached), we’ll happily &lt;em&gt;still serve the value to users&lt;/em&gt;.
But, we’ll kick off a background refresh on another thread at the same time so future users get a fresh value. Rinse and repeat.&lt;/p&gt;

&lt;p&gt;Another important detail here is we keep a per-server local lock table (a &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConcurrentDictionary&lt;/code&gt;&lt;/a&gt;) that prevents two calls from trying to run that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lookup&lt;/code&gt; function and getting the value at the same time.
For example, there’s no win in querying the database 400 times for 400 users.
Users 2 though 400 are better off waiting on the first cache to complete and our database server isn’t kinda sorta murdered in the process.
Why a &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConcurrentDictionary&amp;lt;string, object&amp;gt;&lt;/code&gt;&lt;/a&gt; instead of say a &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashSet&amp;lt;string&amp;gt;&lt;/code&gt;&lt;/a&gt;?
Because we want to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lock&lt;/code&gt; on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; in that dictionary for subsequent callers.
They’re all waiting on the same fetch and that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; represents our fetch.&lt;/p&gt;

&lt;p&gt;If you’re curious about that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MicroContext&lt;/code&gt;, that goes back to being multi-tenant.
Since the fetch may happen on a background thread, we need to know what it was for.
Which site? Which database? What was the previous cache value?
Those are things we put on the context before passing it to the background thread for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lookup&lt;/code&gt; to grab a new value.
Passing the old value also lets us handle an error case as desired, e.g. logging the error and still returning the old value, because giving a user slightly out-of-date data is always always better than an error page.
We choose per call here though.
If returning the old value is bad for some reason – just don’t do that.&lt;/p&gt;

&lt;h4 id=&quot;types-and-things&quot;&gt;Types and Things&lt;/h4&gt;

&lt;p&gt;A question I often get is how do we use DTOs (&lt;a href=&quot;https://en.wikipedia.org/wiki/Data_transfer_object&quot;&gt;data transfer objects&lt;/a&gt;)?
In short, we don’t.
We only use additional types and allocations &lt;em&gt;when we need to&lt;/em&gt;.
For example, if we can run a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Query&amp;lt;MyType&amp;gt;(&quot;Select...&quot;);&lt;/code&gt; from Dapper and stick it into cache, we will.
There’s little reason to create &lt;em&gt;another&lt;/em&gt; type just to cache.&lt;/p&gt;

&lt;p&gt;If it makes sense to cache the type that’s 1:1 with a database table (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Post&lt;/code&gt; for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Posts&lt;/code&gt; table, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt; for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Users&lt;/code&gt; table), we’ll cache that.
If there’s some subtype or combination of things that are the columns from a combined query, we’ll just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Query&amp;lt;T&amp;gt;&lt;/code&gt; as that type, populating from those columns, and cache that.
If that still sounds abstract, here’s a more concrete example:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProtoContract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserCounts&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ProtoMember&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ProtoMember&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PostCount&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ProtoMember&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CommentCount&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserCounts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetUserCounts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SiteCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserCounts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;All-User-Counts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;old&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserCounts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@&quot;
  Select u.Id UserId, PostCount, CommentCount
    From Users u
         Cross Apply (Select Count(*) PostCount From Posts p Where u.Id = p.OwnerUserId) p
         Cross Apply (Select Count(*) CommentCount From PostComments pc Where u.Id = pc.UserId) pc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToDictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;old&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Return the old value&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this example we are taking advantage of &lt;a href=&quot;https://github.com/StackExchange/Dapper&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dapper&lt;/code&gt;&lt;/a&gt;’s built-in column mapping. 
(Note that it sets &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get&lt;/code&gt;-only properties.)
The type used is just for this.
For example, it could even be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;private&lt;/code&gt;, and make this method take a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int userId&lt;/code&gt; with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dictionary&amp;lt;int, UserCount&amp;gt;&lt;/code&gt; being a method-internal detail.
We’re also showing how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T old&lt;/code&gt; and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MicroContext&lt;/code&gt; are used here.
If an error occurs, we log it and return the previous value.&lt;/p&gt;

&lt;p&gt;So…types. Yeah. We do whatever works.
Our philosophy is to not create a lot of types unless they’re useful.
DTOs generally don’t come with just the type, but also include a lot of mapping code – or more magical code (e.g. reflection) that maps across and is subject to unintentional breaks down the line.
Keep. It. Simple. That’s all we’re doing here.
Simple &lt;em&gt;also&lt;/em&gt; means fewer allocations and instantiations.
Performance is often the byproduct of simplicity.&lt;/p&gt;

&lt;h4 id=&quot;redis-the-good-types&quot;&gt;Redis: The Good Types&lt;/h4&gt;

&lt;p&gt;Redis has &lt;a href=&quot;https://redis.io/topics/data-types&quot;&gt;a variety of data types&lt;/a&gt;.
All of the key/value examples thus far use “String” type in Redis.
But don’t think of this as a string data type like you’re used to in programming (for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt; &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.string?view=netcore-2.2&quot;&gt;in .NET&lt;/a&gt;, or &lt;a href=&quot;https://docs.oracle.com/javase/7/docs/api/java/lang/String.html&quot;&gt;in Java&lt;/a&gt;).
It basically means “some bytes” in the Redis usage.
It could be a string, it could be a binary image, or it could be…well, anything you can store in some bytes!
But, we use most of the other data types in various ways as well:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Redis Lists are useful for queues like our aggregator or account actions to execute in-order.&lt;/li&gt;
  &lt;li&gt;Redis Sets are useful for unique lists of items like “which account IDs are in this alpha test?” (things that are unique, but not ordered).&lt;/li&gt;
  &lt;li&gt;Redis Hashes are useful for things that are dictionary-like, such as “What’s the latest activity date for a site?” (where the hash key ID is site and the value is a date).
We use this to determine “Do we need to run badges on site X this time?” and other questions.&lt;/li&gt;
  &lt;li&gt;Redis Sorted Sets are useful for ordered things like storing the slowest 100 MiniProfiler traces per route.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Speaking of sorted sets, we need to replace the &lt;a href=&quot;https://stackoverflow.com/users&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/users&lt;/code&gt; page&lt;/a&gt; to be backed by sorted sets (one per reputation time range) with range queries instead.
Marc and I planned how to do this at a company meetup in Denver many years ago but keep forgetting to do it…&lt;/p&gt;

&lt;h3 id=&quot;monitoring-cache-performance&quot;&gt;Monitoring Cache Performance&lt;/h3&gt;

&lt;p&gt;There are a few things to keep an eye on here.
Remember those latency factors above?
It’s super slow to go off box.
When we’re rendering question pages in an average of 18–20ms, taking ~0.5ms for a Redis call is a lot.
A few calls quickly add up to a significant part of our rendering time.&lt;/p&gt;

&lt;p&gt;First, we’ll want to keep an eye on this at the page level.
For this, we use &lt;a href=&quot;https://miniprofiler.com/dotnet/&quot;&gt;MiniProfiler&lt;/a&gt; to see every Redis call involved in a page load.
It’s hooked up with &lt;a href=&quot;https://stackexchange.github.io/StackExchange.Redis/Profiling_v2.html&quot;&gt;StackExchange.Redis’s profiling API&lt;/a&gt;.
Here’s an example of what that looks like on a question page, getting my live across-the-network counts for the top bar:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Caching/SO-Cache-MiniProfiler.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-MiniProfiler.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-MiniProfiler.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Caching/SO-Cache-MiniProfiler.png&quot; loading=&quot;lazy&quot; alt=&quot;MiniProfiler: Redis Calls&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second, we want to keep an eye on the Redis instances.
For that, we use &lt;a href=&quot;https://github.com/Opserver/Opserver&quot;&gt;Opserver&lt;/a&gt;.
Here’s what a single instance looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Caching/SO-Cache-Opserver-Instance.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-Opserver-Instance.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-Opserver-Instance.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Caching/SO-Cache-Opserver-Instance.png&quot; loading=&quot;lazy&quot; alt=&quot;Opserver: Redis Instance&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have some built-in tools there to analyze key usage and the ability to group them by regex pattern.
This lets us combine what we know (we’re the ones caching!) with the data to see what’s eating the most space.&lt;/p&gt;

&lt;p&gt;&lt;sub&gt;
Note: Running such an analysis should only be done on a secondary.
It’s very abusive on a master at scale.
Opserver will by default run such analysis on a replica and block running it on a master without an override.
&lt;/sub&gt;&lt;/p&gt;

&lt;h3 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h3&gt;

&lt;p&gt;.NET Core is well underway here at Stack.
We’ve ported most support services and are working on the main applications now.
There’s honestly not a lot of cache to the caching layer, but one interesting possibility is &lt;a href=&quot;https://github.com/dotnet/corefx/issues/30503&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Utf8String&lt;/code&gt;&lt;/a&gt; (which hasn’t landed yet).
We cache a lot of stuff in total, lots of tiny strings in various places – things like “Related Questions” in the sidebar.
If those cache entries were UTF8 instead of &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/base-types/character-encoding&quot;&gt;the .NET default of UTF16&lt;/a&gt;, they’d be half the size.
When you’re dealing with hundreds of thousands of strings at any given time, it adds up.&lt;/p&gt;

&lt;h3 id=&quot;story-time&quot;&gt;Story Time&lt;/h3&gt;

&lt;p&gt;I asked what people wanted to know about caching on Twitter and how failures happen came up a lot.
For fun, let’s recall a few that stand out in my mind:&lt;/p&gt;

&lt;h4 id=&quot;taking-redis-down-while-trying-to-save-it&quot;&gt;Taking Redis Down While Trying to Save It&lt;/h4&gt;

&lt;p&gt;At one point, our Redis primary cache was getting to be about 70GB total.
This was on 96GB servers.
When we saw the growth over time, we planned a server upgrade and transition.
By the time we got hardware in place and were ready to failover to a new master server, we had reached about 90GB of cache usage.
Phew, close. But we made it!&lt;/p&gt;

&lt;p&gt;…or not. I was travelling for this one, but helped planned it all out.
What we didn’t account for was the memory fork that happens for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BGSAVE&lt;/code&gt; in Redis (at least in that version – this was back in 2.x).
We were all very relieved to have made preparations in time, so on a weekend we hit the button and fired up data replication to the new server to prepare a failover to it.&lt;/p&gt;

&lt;p&gt;And all of our websites promptly went offline.&lt;/p&gt;

&lt;p&gt;What happens in the memory fork is that data that’s changed during the migration gets shadow copied, something that isn’t released until the clone finishes…because we need the state the server was at to initialize along with all changes since then to replicate to the new node (else we lose those new changes).
So the rate at which new changes rack up is your memory growth during a copy.
That 6GB went fast.
Really fast.
Then Redis crashed, the web servers went without Redis (something they hadn’t done in years), and they &lt;em&gt;really&lt;/em&gt; didn’t handle it well.&lt;/p&gt;

&lt;p&gt;So I pulled over on the side of the road, hopped on our team call, and we got the sites back up…against the new server and an empty cache.
It’s important to note that Redis didn’t do anything wrong, we did.
And Redis has been rock solid for a decade here.
It’s one of the most stable pieces of infrastructure we have…you just don’t think about it.&lt;/p&gt;

&lt;p&gt;But anyway, another lesson learned.&lt;/p&gt;

&lt;h4 id=&quot;accidentally-not-using-local-cache&quot;&gt;Accidentally Not Using Local Cache&lt;/h4&gt;

&lt;p&gt;A certain developer we have here will be reading this and cursing my name, but I love you guys and gals so let’s share anyway!&lt;/p&gt;

&lt;p&gt;When we get a cache value back from Redis in our local/remote 2-layer cache story, we actually send two commands: a fetch of the key and a &lt;a href=&quot;https://redis.io/commands/ttl&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TTL&lt;/code&gt;&lt;/a&gt;.
The result of the TTL tells us how many seconds Redis is caching it for…that’s how long we also cache it in local server memory.
We used to use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-1&lt;/code&gt; sentinel value for TTL through some library code to indicate something didn’t have a TTL.
The semantics changed in a refactor to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; for “no TTL”…and we got some boolean logic wrong. Oops.
A rather simple statement like this from our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Get&amp;lt;T&amp;gt;&lt;/code&gt; mentioned earlier:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ttl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ... push into L1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Became:&lt;/p&gt;
&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ttl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ... push into L1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But &lt;em&gt;most&lt;/em&gt; of our caches &lt;strong&gt;DO&lt;/strong&gt; have a TTL.
This meant the vast majority of keys (probably something like 95% or more) were no longer caching in L1 (local server memory).
Every single call to any of these keys was going to Redis and back.
Redis was so resilient and fast, we didn’t notice for a few hours.
The actual logic was then corrected to:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ttl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ... push into L1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…and everyone lived happily ever after.&lt;/p&gt;

&lt;h4 id=&quot;accidentally-caching-pages-for-000006-seconds&quot;&gt;Accidentally Caching Pages For .000006 Seconds&lt;/h4&gt;

&lt;p&gt;You read that right.&lt;/p&gt;

&lt;p&gt;Back in 2011, we found a bit of code in our page-level output caching when looking into something unrelated:&lt;/p&gt;
&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TimeSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This was intended to cache a thing for a minute.
Which would have worked great if the default constructor for &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.timespan.-ctor?view=netframework-4.8#System_TimeSpan__ctor_System_Int64_&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TimeSpan&lt;/code&gt; was seconds and not ticks&lt;/a&gt;.
But! We were excited to find this.
How could cache be broken?
But hey, good news – wow we’re going to get such a performance boost by fixing this!&lt;/p&gt;

&lt;p&gt;Nope. Not even a little.
All we saw was memory usage increase a tiny bit.
CPU usage also went up.&lt;/p&gt;

&lt;p&gt;For fun: this was included at the end of in an interview &lt;a href=&quot;https://channel9.msdn.com/Events/Ch9Live/MIX11/C9L105&quot;&gt;back at MIX 2011 with Scott Hanselman&lt;/a&gt;.
Wow. We looked so much younger then.
Anyway, that leads us to…&lt;/p&gt;

&lt;h4 id=&quot;doing-more-harm-than-good&quot;&gt;Doing More Harm Than Good&lt;/h4&gt;

&lt;p&gt;For many years, we used to &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/improving-performance-with-output-caching-cs&quot;&gt;output cache&lt;/a&gt; major pages.
This included the homepage, question list pages, question pages themselves, and RSS feeds.&lt;/p&gt;

&lt;p&gt;Remember earlier: when you cache, you need to vary the cache keys based on the variants of cache.
Concretely, this means: anonymous, or not? mobile, or not? deflate, gzip, or no compression?
Realistically, we can’t (or shouldn’t) ever output cache for logged-in users.
Your stats are in the top bar and it’s per-user.
You’d notice your rep was inconsistent between page views and such.&lt;/p&gt;

&lt;p&gt;Anyway, when you step back and combine these variants with the fact that about 80% of all questions are visited every two weeks, you realize that the cache hit rate is low.
Really low.
But the cost of memory to store those strings (most large enough to go directly on the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap&quot;&gt;large object heap&lt;/a&gt;) is very non-trivial.
And the cost of the garbage collector cleaning them up is also non-trivial.&lt;/p&gt;

&lt;p&gt;It turns out those two pieces of the equation are &lt;em&gt;so&lt;/em&gt; non-trivial that caching did far more harm than good.
The savings we got from the relatively occasional cache hit were drastically outweighed by the cost of having and cleaning up the cache.
This puzzled us a bit at first, but when you zoom out and look at the numbers, it makes perfect sense.&lt;/p&gt;

&lt;p&gt;For the past several years, Stack Overflow (and all Q&amp;amp;A sites) output cache &lt;em&gt;nothing&lt;/em&gt;.
Output caching is also not present in ASP.NET Core, so phew on not using it.&lt;/p&gt;

&lt;p&gt;Full disclosure: we still cache full XML response strings (similar to, but not using output cache) specifically on some RSS feed routes.
We do so because the hit rate is quite high on these routes.
This specific cache has all of the downsides mentioned above, except it’s well worth it on the hit ratios.&lt;/p&gt;

&lt;h4 id=&quot;figuring-out-that-the-world-is-crazier-than-you-are&quot;&gt;Figuring Out That The World Is Crazier Than You Are&lt;/h4&gt;

&lt;p&gt;When .NET 4.6.0 came out, we found a bug.
I was digging into why MiniProfiler didn’t show up on the first page load locally, slowly went insane, and then grabbed &lt;a href=&quot;https://twitter.com/marcgravell&quot;&gt;Marc Gravell&lt;/a&gt; to go descend into madness with me.&lt;/p&gt;

&lt;p&gt;The bug happened in our cache layer due to the nature of the issue and how it specifically affected only tail calls.
You can read about &lt;a href=&quot;https://nickcraver.com/blog/2015/07/27/why-you-should-wait-on-dotnet-46/&quot;&gt;how it manifested here&lt;/a&gt;, but the general problem is: &lt;strong&gt;methods weren’t called with the parameters you passed in&lt;/strong&gt;.
Ouch.
This resulted in random cache durations for us and was pretty scary when you think about it.
Luckily the problem with the RyuJIT was hotfixed the following month.&lt;/p&gt;

&lt;h4 id=&quot;net-462-caching-responses-for-2017-years&quot;&gt;.NET 4.6.2 Caching Responses for 2,017 years&lt;/h4&gt;

&lt;p&gt;Okay this one isn’t server-side caching at all, but I’m throwing it in because it was super “fun”. 
Shortly after deploying .NET 4.6.2, we noticed some oddities with client cache behavior, CDN caches growing, and other crazy.
It turns out, &lt;a href=&quot;https://twitter.com/nick_craver/status/850403727060107265&quot;&gt;there was a bug in .NET 4.6.2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The cause was simple enough: when comparing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt; values of “now” vs. when a response cache should expire and calculating the difference between those to figure out the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max-age&lt;/code&gt; portion of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cache-Control&lt;/code&gt; header&lt;/a&gt;, the value was subtly reset to 0 on the “now” side.
So let’s say:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2017-04-05 01:17:01 (cache until) - 2017-04-05 01:16:01 (now) = 60 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, let’s say that “now” value was instead &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0001-01-01 00:00:00&lt;/code&gt;…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2017-04-05 01:17:01 (cache until) - 0001-01-01 00:00:00 (now) = 63,626,951,821 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Luckily the math is super easy.
We’re telling a browser to cache that value for 2017 years, 4 months, 5 days, 1 hour, 17 minutes and 1 second.
Which &lt;em&gt;might&lt;/em&gt; be a tad bit of overkill.
Or on CDNs, we’re telling CDNs to cache things for that long…also problematic.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Caching/SO-Cache-Max-Age.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-Max-Age.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Caching/SO-Cache-Max-Age.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Caching/SO-Cache-Max-Age.jpg&quot; alt=&quot;Oops: Max Age&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well crap. We didn’t realize this early enough. (Would you have checked for this? Are we idiots?)
So that was in production and a rollback was not a quick solution.
So what do we do?&lt;/p&gt;

&lt;p&gt;Luckily, we had moved to Fastly at this point, &lt;a href=&quot;https://docs.fastly.com/guides/vcl-tutorials/guide-to-vcl&quot;&gt;which uses Varnish &amp;amp; VCL&lt;/a&gt;.
So we can just hop in there and detect these crazy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max-age&lt;/code&gt; values and override them to something sane.
Unless, of course, you screw that up.
Yep.
Turns out on first push we missed a critical piece of the normal hash algorithm for cache keys on Fastly and did things like render people’s flair back when you tried to load a question.
This was corrected in a few minutes, but still: oops.
Sorry about that.
I reviewed and okayed that code to go out personally.&lt;/p&gt;

&lt;h4 id=&quot;when-its-sorta-redis-but-not&quot;&gt;When It’s Sorta Redis But Not&lt;/h4&gt;

&lt;p&gt;One issue we had was the host itself interrupting Redis and having perceptible pipeline timeouts.
We looked in Redis: the slowness was random.
The commands doing it didn’t form any pattern or make any sense (e.g. tiny keys).
We looked at the network and packet traces from both the client and server looked clean – the pause was inside the Redis host based on the correlated timings.&lt;/p&gt;

&lt;p&gt;Okay so…what is it?
Turns out after a lot of manual profiling and troubleshooting, we found &lt;em&gt;another&lt;/em&gt; process on the host that spiked at the same time.
It was a small spike we thought nothing of at first, but &lt;em&gt;how&lt;/em&gt; it spiked was important.&lt;/p&gt;

&lt;p&gt;It turns out that a monitoring process (dammit!) was kicking off &lt;a href=&quot;https://en.wikipedia.org/wiki/Vmstat&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vmstat&lt;/code&gt;&lt;/a&gt; to get memory statistics.
Not that often, not that abusive – it was actually pretty reasonable.
But what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vmstat&lt;/code&gt; did was punt Redis off of the CPU core it was running on.
Like a jerk.
Sometimes. Randomly.
Which Redis instance? Well, that depends on the order they started in.
So yeah…this bug &lt;em&gt;seemed&lt;/em&gt; to hop around.
The changing of context to another core was enough to see timeouts with how &lt;em&gt;often&lt;/em&gt; we were hitting Redis with a constant pipe.&lt;/p&gt;

&lt;p&gt;Once we found this and factored in that we have plenty of cores in these boxes, we began pinning Redis to specific cores on the physical hosts.
This ensures the primary function of the servers always has priority and monitoring is secondary.&lt;/p&gt;

&lt;p&gt;Since writing this and asking for reviews, I learned that &lt;a href=&quot;https://redis.io/topics/latency-monitor&quot;&gt;Redis now has built-in latency monitoring&lt;/a&gt; which was added shortly after the earlier 2.8 version we were using at the time. Check out &lt;a href=&quot;https://redis.io/topics/latency-monitor#latency-doctor&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LATENCY DOCTOR&lt;/code&gt;&lt;/a&gt; especially.
AWESOME.
Thank you Salvatore Sanfilippo! He’s the lovely &lt;a href=&quot;https://twitter.com/antirez&quot;&gt;@antirez&lt;/a&gt;, author of Redis.&lt;/p&gt;

&lt;p&gt;Now I need to go put the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LATENCY&lt;/code&gt; bits into StackExchange.Redis and Opserver…&lt;/p&gt;

&lt;h3 id=&quot;caching-faq&quot;&gt;Caching FAQ&lt;/h3&gt;

&lt;p&gt;I also often get a lot of questions that don’t really fit so well above, but I wanted to cover them for the curious.
And since you &lt;em&gt;know&lt;/em&gt; we love Q&amp;amp;A up in here, let’s try a new section in these posts that I can easily add to as new questions come up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: Why don’t you use &lt;a href=&quot;https://redis.io/topics/cluster-tutorial&quot;&gt;Redis Cluster&lt;/a&gt;?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: There are a few reasons here:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;We use &lt;a href=&quot;https://redis.io/commands/select&quot;&gt;databases&lt;/a&gt;, which aren’t a feature in Cluster (to minimize the message replication header size). We can get around this by moving the database ID into the cache key instead (as we do with local cache above). But, one giant database has maintainability trade-offs, like when you go to figure out which keys are using so much room.&lt;/li&gt;
  &lt;li&gt;The replication topology thus far has been node to node, meaning maintenance on the master cluster would require shifting the same topology on a secondary cluster in our DR data center. This would make maintenance harder instead of easier. We’re waiting for cluster &amp;lt;-&amp;gt; cluster replication, rather than node &amp;lt;-&amp;gt; node replication there.&lt;/li&gt;
  &lt;li&gt;It would require 3+ nodes to run correctly (due to elections and such). We currently only run 2 physical Redis servers per data center. Just 1 server is way more performance than we need, and the second is a replica/backup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: Why don’t you use &lt;a href=&quot;https://redis.io/topics/sentinel&quot;&gt;Redis Sentinel&lt;/a&gt;?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: We looked into this, but the overall management of it wasn’t any simpler than we had today.
The idea of connecting to an endpoint and being directed over is great, but the management is complicated enough that it’s not worth changing our current strategy given how incredibly stable Redis is.
One of the biggest issues with Sentinel is the writing of the current topology state &lt;a href=&quot;https://groups.google.com/forum/#!searchin/redis-db/puppet$20cluster%7Csort:date/redis-db/1JB7OkaaxZo/w1bAZ23dAgAJ&quot;&gt;into the same config file&lt;/a&gt;.
This makes it very unfriendly to anyone with managed configs. For example, we use &lt;a href=&quot;https://puppet.com/&quot;&gt;Puppet&lt;/a&gt; here and the file changes would fight with it every run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: How do you secure &lt;a href=&quot;https://stackoverflow.com/teams&quot;&gt;Stack Overflow for Teams&lt;/a&gt; cache?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: We maintain an isolated network and separate Redis servers for private data.
&lt;a href=&quot;https://stackoverflow.com/enterprise&quot;&gt;Stack Overflow Enterprise&lt;/a&gt; customers each have their own isolated network and Redis instances as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: What if Redis goes down?!?1!eleven&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: First, there’s a backup in the data center.
But let’s assume that fails too!
Who doesn’t love a good apocalypse? 
Without Redis at all, we’d limp a bit when restarting the apps.
The cold cache would hurt a bit, smack SQL Server around a little, but we’d get back up.
You’d want to build slowly if Redis was down (or just hold off on building in general in this scenario).
As for the data, we would lose very little.
We have treated Redis as optional for local development since before the days it was an infrastructure component at all, and it remains optional today.
This means it’s not the source of truth for &lt;em&gt;anything&lt;/em&gt;.
All cache data it contains could be re-populated from whatever the source is.
That leaves us only with active queues.
The queues in Redis are account merge type actions (executed sub-second – so a short queue), the aggregator (tallying network events into our central database), and some analytics (it’s okay if we lose some A/B test data for a minute).
All of these are okay to have a gap on – it’d be minimal loses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: Are there downsides to databases?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: Yes, one that I’m aware of.
At a high limit, it can eventually impact performance by measurable amounts.
When Redis expires keys, it loops over databases to find and clear those keys – think of it as checking each “namespace”.
At a high count, it’s a bigger loop.
Since this runs every 100ms, that number being big can impact performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: Are you going to open source the “L1”/”L2” cache implementation?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: We’ve always wanted to, but a few things have stood in the way:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;It’s very “us”. By that I mean it’s very multi-tenant focused and that’s probably not the best API surface for everyone. This means we really need to sit down and design that API. It’s a set of APIs we’d love to put into our &lt;a href=&quot;https://github.com/StackExchange/StackExchange.Redis&quot;&gt;StackExchange.Redis client&lt;/a&gt; directly or as another library that uses it.&lt;/li&gt;
  &lt;li&gt;There has been an idea to have more core support (e.g. what we use the pub/sub mechanism for) in Redis server itself. That’s &lt;a href=&quot;http://antirez.com/news/130&quot;&gt;coming in Redis version 6&lt;/a&gt;, so we can do a lot less custom pub/sub and use more standard things other clients will understand there. The less we write for “just us” or “just our client”, the better it is for everyone.&lt;/li&gt;
  &lt;li&gt;Time. I wish we all had more of it. It’s the most precious thing you have – never take it for granted.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: With pipelining, how do you handle large Redis payloads?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: We have a separate connection called “bulky” for this.
It has higher timeouts and is much more rarely used.
That’s &lt;em&gt;if&lt;/em&gt; it should go in Redis.
If a worth-of-caching item is large but not particularly expensive to fetch, we may not use Redis and simply use “Local Cache”, fetching it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; times for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; web servers.
Per-user features (since user sessions are sticky to web servers on Q&amp;amp;A) may fit this bill as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: What happens when someone runs &lt;a href=&quot;https://redis.io/commands/keys&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KEYS&lt;/code&gt;&lt;/a&gt; on production?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: Tasers, if they’ll let me.
Seriously though, since Redis 2.8.0 you should at least use &lt;a href=&quot;https://redis.io/commands/scan&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCAN&lt;/code&gt;&lt;/a&gt; which doesn’t block Redis for a full key dump – it does so in chunks and lets other commands go through.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KEYS&lt;/code&gt; can cause a production blockage in a hurry.
And by “can”, I mean 100% of the time at our scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: What happens when someone runs &lt;a href=&quot;https://redis.io/commands/flushall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLUSHALL&lt;/code&gt;&lt;/a&gt; on production?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: It’s against policy to comment on future criminal investigations.
&lt;a href=&quot;https://redis.io/topics/acl&quot;&gt;Redis 6 is adding ACLs though&lt;/a&gt;, which will limit the suspect pool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: How do the police investigators figure out what happened in either of the above cases? Or any latency spike?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: Redis has a nifty feature called &lt;a href=&quot;https://redis.io/commands/slowlog&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SLOWLOG&lt;/code&gt;&lt;/a&gt; which (by default) logs every command over 10ms in duration.
You can adjust this, but everything should be &lt;em&gt;very&lt;/em&gt; fast, so that default 10ms is a relative eternity and what we keep it at.
When you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SLOWLOG&lt;/code&gt; you can see the last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; entries (configurable), the command, and the arguments.
Opserver will show these on the instance page, making it easy to find the offender.
But, it could be network latency or an unrelated CPU spike/theft on the host. (We pin the Redis instances using processor affinity to avoid this.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: Do you use &lt;a href=&quot;https://azure.microsoft.com/en-us/services/cache/&quot;&gt;Azure Cache for Redis&lt;/a&gt; for &lt;a href=&quot;https://stackoverflow.com/enterprise&quot;&gt;Stack Overflow Enterprise&lt;/a&gt;?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: Yes, but we may not long-term.
It takes &lt;a href=&quot;https://feedback.azure.com/forums/169382-cache/suggestions/7049852-work-on-creation-time-of-redis-cache&quot;&gt;a surprisingly long time&lt;/a&gt; to provision when creating one for test environments and such.
We’re talking dozens of minutes up to an hour here.
We’ll likely use containers later, which will help us control the version used across all deployment modes as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: Do you expect every dev to know all of this to make caching decisions?&lt;br /&gt;
&lt;strong&gt;A&lt;/strong&gt;: Absolutely not.
I had to look up exact values in many places here. 
My goal is that developers understand a little bit about the layer beneath and relative costs of things – or at least a rough idea of them.
That’s why I stress the orders of magnitude here.
Those are the units you should be considering for cost/benefit evaluations on where and how you choose to cache.
Most people do not run at hundreds of millions of hits a day where the cost multiplier is so high it’ll ruin your day and optimizations decisions are far less important/impactful.
Do what works for you. This is what works for us, with some context on “why?” in hopes that it helps you make your decisions.&lt;/p&gt;

&lt;h3 id=&quot;tools&quot;&gt;Tools&lt;/h3&gt;

&lt;p&gt;I just wanted to provide a handy list of the tools mentioned in the article as well as a few other bits we use to help with caching:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/StackExchange.Redis&quot;&gt;StackExchange.Redis&lt;/a&gt; - Our open source .NET Redis client library.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/Opserver/Opserver&quot;&gt;Opserver&lt;/a&gt; - Our open source dashboard for monitoring, including Redis.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/MiniProfiler/dotnet/&quot;&gt;MiniProfiler&lt;/a&gt; - Our open source .NET profiling tool, with which we view Redis commands issued on any page load.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/Dapper&quot;&gt;Dapper&lt;/a&gt; - Our open source object relational mapper for any ADO.NET data source.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mgravell/protobuf-net&quot;&gt;protobuf-net&lt;/a&gt; - &lt;a href=&quot;https://twitter.com/marcgravell&quot;&gt;Marc Gravell&lt;/a&gt;’s Protocol Buffers library for idiomatic .NET.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What’s next?
The way &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;this series&lt;/a&gt; works is I blog in order of what the community wants to know about most.
I normally go by &lt;a href=&quot;https://trello.com/b/0zgQjktX/blog-post-queue-for-stack-overflow-topics&quot;&gt;the Trello board&lt;/a&gt; to see what’s next, but we probably have a queue jumper coming up.
We’re almost done porting Stack Overflow to .NET Core and we have a lot of stories and tips to share as well as tools we’ve built to make that migration easier.
The next time you see a lot of words from me may be the next Trello board item, or it may be .NET Core.
If you have questions that you want to see answered in such a post, please put them &lt;a href=&quot;https://trello.com/c/fpwHYK97/90-the-move-to-net-core&quot;&gt;on the .NET Core card&lt;/a&gt; (open to the public) and I’ll be reviewing all of that when I start writing it.
Stay tuned, and thanks for following along.&lt;/p&gt;
</description>
        <pubDate>Tue, 06 Aug 2019 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2019/08/06/stack-overflow-how-we-do-app-caching/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Stack Overflow: How We Do Monitoring - 2018 Edition</title>
        <link>https://nickcraver.com/blog/2018/11/29/stack-overflow-how-we-do-monitoring/</link>
        <description>&lt;blockquote&gt;
  &lt;p&gt;This is #4 in a &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;very long series of posts&lt;/a&gt; on Stack Overflow’s architecture.&lt;br /&gt;
Previous post (#3): &lt;a href=&quot;/blog/2016/05/03/stack-overflow-how-we-do-deployment-2016-edition/&quot;&gt;Stack Overflow: How We Do Deployment - 2016 Edition&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What is monitoring? As far as I can tell, it means different things to different people.
But we more or less agree on the concept.
I think. Maybe. Let’s find out!
When someone says monitoring, I think of:&lt;/p&gt;

&lt;div style=&quot;max-width:400px;text-align: center;margin: 0 auto;&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Monitored.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Monitored.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Monitored.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Monitored.png&quot; loading=&quot;lazy&quot; alt=&quot;You are being monitored!&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;…but evidently some people think of other things.
Those people are obviously wrong, but let’s continue.
When I’m not a walking zombie after reading a 10,000 word blog post some idiot wrote, I see monitoring as the process of keeping an eye on your stuff, like a security guard sitting at a desk full of cameras somewhere.
Sometimes they fall asleep–that’s monitoring going down.
Sometimes they’re distracted with a doughnut delivery–that’s an upgrade outage.
Sometimes the camera is on a loop–I don’t know where I was going with that one, but someone’s probably robbing you.
And then you have the fire alarm. You don’t need a human to trigger that.
The same applies when a door gets opened, maybe that’s wired to a siren.
Or maybe it’s not.
Or maybe the siren broke in 1984.&lt;/p&gt;

&lt;p&gt;I know what you’re thinking: Nick, what the hell?
&lt;!--more--&gt;
My point is only that monitoring any application isn’t that much different from monitoring anything else.
Some things you can automate.
Some things you can’t.
Some things have thresholds for which alarms are valid.
Sometimes you’ll get those thresholds wrong (especially on holidays).
And sometimes, when setting up further automation isn’t quite worth it, you just make using human eyes easier.&lt;/p&gt;

&lt;p&gt;What I’ll discuss here is what &lt;em&gt;we&lt;/em&gt; do. It’s not the same for everyone.
What’s important and “worth it” will be different for almost everyone.
As with &lt;em&gt;everything&lt;/em&gt; else in life, it’s full of trade-off decisions.
Below are the ones we’ve made so far.
They’re not perfect.
They are evolving.
And when new data or priorities arise, we will change earlier decisions when it warrants doing so.
That’s how brains are supposed to work.&lt;/p&gt;

&lt;p&gt;And once again this post got longer and longer as I wrote it (but a lot of pictures make up that scroll bar!). So, links for your convenience:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#types-of-data&quot;&gt;Types of Data&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#logs&quot;&gt;Logs&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#logs-haproxy&quot;&gt;Logs: HAProxy&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#health-checks&quot;&gt;Health Checks&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#health-checks-httpunit&quot;&gt;Health Checks: httpUnit&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#health-checks-fastly&quot;&gt;Health Checks: Fastly&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#health-checks-external&quot;&gt;Health Checks: External&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#metrics&quot;&gt;Metrics&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#alerting&quot;&gt;Alerting&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bosun&quot;&gt;Bosun&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#bosun-metrics&quot;&gt;Bosun: Metrics&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#bosun-alerting&quot;&gt;Bosun: Alerting&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#grafana&quot;&gt;Grafana&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#client-timings&quot;&gt;Client Timings&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#miniprofiler&quot;&gt;MiniProfiler&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#opserver&quot;&gt;Opserver&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#opserver-primary-dashboard&quot;&gt;Opserver: Primary Dashboard&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#opserver-sql-server&quot;&gt;Opserver: SQL Server&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#opserver-redis&quot;&gt;Opserver: Redis&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#opserver-elasticsearch&quot;&gt;Opserver: Elasticsearch&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#opserver-exceptions&quot;&gt;Opserver: Exceptions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#opserver-haproxy&quot;&gt;Opserver: HAProxy&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#opserver-faqs&quot;&gt;Opserver: FAQs&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#where-do-we-go-next&quot;&gt;Where Do We Go Next?&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#health-check-next-steps&quot;&gt;Health Check Next Steps&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#bosun-next-steps&quot;&gt;Bosun Next Steps&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#metrics-next-steps&quot;&gt;Metrics Next Steps&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tools-summary&quot;&gt;Tools Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;types-of-data&quot;&gt;Types of Data&lt;/h3&gt;

&lt;p&gt;Monitoring generally consists of a few types of data (I’m absolutely arbitrarily making some groups here):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Logs: Rich, detailed text and data but not awesome for alerting&lt;/li&gt;
  &lt;li&gt;Metrics: Tagged numbers of data for telemetry–good for alerting, but lacking in detail&lt;/li&gt;
  &lt;li&gt;Health Checks: Is it up? Is it down? Is it sideways? Very specific, often for alerting&lt;/li&gt;
  &lt;li&gt;Profiling: Performance data from the application to see how long things are taking&lt;/li&gt;
  &lt;li&gt;…and other complex combinations for specific use cases that don’t really fall into any one of these.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;logs&quot;&gt;Logs&lt;/h4&gt;

&lt;p&gt;Let’s talk about &lt;a href=&quot;https://www.youtube.com/watch?v=2C7mNr5WMjA&quot;&gt;logs&lt;/a&gt;. You can log almost anything!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Info messages? Yep.&lt;/li&gt;
  &lt;li&gt;Errors? Heck yeah!&lt;/li&gt;
  &lt;li&gt;Traffic? Sure!&lt;/li&gt;
  &lt;li&gt;Email? Careful, GDPR.&lt;/li&gt;
  &lt;li&gt;Anything else? Well, I guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds good. I can log whatever I want! What’s the catch?
Well, it’s a trade-off.
Have you ever run a program with tons of console output?
Run the same program without it? Goes faster, doesn’t it?
Logging has a few costs.
First, you often need to allocate strings for the logging itself.
That’s memory and garbage collection (for .NET and some other platforms).
When you’re logging &lt;em&gt;somewhere&lt;/em&gt;, that usually that means disk space.
If we’re traversing a network (and to some degree locally), it also means bandwidth and latency.&lt;/p&gt;

&lt;p&gt;…and I was just kidding about &lt;a href=&quot;https://en.wikipedia.org/wiki/General_Data_Protection_Regulation&quot;&gt;GDPR&lt;/a&gt; only being a concern for email…&lt;strong&gt;GDPR is a concern for all of the above&lt;/strong&gt;. Keep retention and compliance in mind when logging anything. It’s another cost to consider.&lt;/p&gt;

&lt;p&gt;Let’s say none of those are significant problems and we want to log all the things.
Tempting, isn’t it? Well, then we can have too much of a good thing.
What happens when you need to look at those logs?
It’s more to dig through.
It can make finding the problem much harder and slower.
With all logging, it’s a balance of logging what you think you’ll need vs. what you end up needing.
You’ll get it wrong. All the time.
And you’ll find a newly added feature didn’t have the right logging when things went wrong.
And you’ll finally figure that out (probably after it goes south)…and add that logging.
That’s life.
Improve and move on.
Don’t dwell on it, just take the lesson and learn from it.
You’ll think about it more in code reviews and such afterwards.&lt;/p&gt;

&lt;p&gt;So what do we log? It depends on the system.
For any systems we build, we &lt;strong&gt;&lt;em&gt;always&lt;/em&gt;&lt;/strong&gt; log errors. (Otherwise, why even throw them?).
We do this with &lt;a href=&quot;https://github.com/NickCraver/StackExchange.Exceptional&quot;&gt;StackExchange.Exceptional&lt;/a&gt;, an open source .NET error logger I maintain.
It logs to SQL Server.
These are viewable in-app or via Opserver (&lt;a href=&quot;#opserver&quot;&gt;which we’ll talk more about in a minute&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For systems like &lt;a href=&quot;https://redis.io/&quot;&gt;Redis&lt;/a&gt;, &lt;a href=&quot;https://www.elastic.co/products/elasticsearch&quot;&gt;Elasticsearch&lt;/a&gt;, and &lt;a href=&quot;https://www.microsoft.com/en-us/sql-server/sql-server-2017&quot;&gt;SQL Server&lt;/a&gt;, we’re simply logging to local disk using their built-in mechanisms for logging and log rotation.
For other SNMP-based systems like network gear, we forward all of that to our &lt;a href=&quot;https://www.elastic.co/products/logstash&quot;&gt;Logstash&lt;/a&gt; cluster which we have &lt;a href=&quot;https://www.elastic.co/products/kibana&quot;&gt;Kibana&lt;/a&gt; in front of for querying.
A lot of the above is also queried at alert time by &lt;a href=&quot;https://bosun.org&quot;&gt;Bosun&lt;/a&gt; for details and trends, which we’ll dive into next.&lt;/p&gt;

&lt;h4 id=&quot;logs-haproxy&quot;&gt;Logs: HAProxy&lt;/h4&gt;

&lt;p&gt;We also log a minimal summary of public HTTP requests (only top level…no cookies, no form data, etc.) that go through &lt;a href=&quot;https://www.haproxy.org/&quot;&gt;HAProxy&lt;/a&gt; (our load balancer) because when someone can’t log in, an account gets merged, or any of a hundred other bug reports come in it’s immensely valuable to go see what flow led them to disaster.
We do this in SQL Server via &lt;a href=&quot;http://www.nikoport.com/columnstore/&quot;&gt;clustered columnstore indexes&lt;/a&gt;.
For the record, &lt;a href=&quot;https://twitter.com/jarrod_dixon&quot;&gt;Jarrod Dixon&lt;/a&gt; first suggested and started the HTTP logging about 8 years ago and we all told him he was an insane lunatic and it was a tremendous waste of resources.
Please no one tell him he was totally right.
A new per-month storage format will be coming soon, but that’s another story.&lt;/p&gt;

&lt;p&gt;In those requests, we use profiling &lt;a href=&quot;#miniprofiler&quot;&gt;that we’ll talk about shortly&lt;/a&gt; and send headers to HAProxy with certain performance numbers.
HAProxy captures and strips those headers into the syslog row we forward for processing into SQL.
Those headers include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ASP.NET Overall Milliseconds (encompasses those below)&lt;/li&gt;
  &lt;li&gt;SQL Count (queries) &amp;amp; Milliseconds&lt;/li&gt;
  &lt;li&gt;Redis Count (hits) &amp;amp; Milliseconds&lt;/li&gt;
  &lt;li&gt;HTTP Count (requests sent) &amp;amp; Milliseconds&lt;/li&gt;
  &lt;li&gt;Tag Engine Count (queries) &amp;amp; Milliseconds&lt;/li&gt;
  &lt;li&gt;Elasticsearch Count (hits) &amp;amp; Milliseconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If something gets better or worse we can easily query and compare historical data.
It’s also useful in ways we never really thought about.
For example, we’ll see a request and the count of SQL queries run and it’ll tell us how far down a code path a user went.
Or when SQL connection pools pile up, we can look at all requests from a particular server at a particular time to see what caused that contention.
All we’re doing here is tracking a count of calls and time for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; services.
It’s super simple, but also extremely effective.&lt;/p&gt;

&lt;p&gt;The thing that listens for syslog and saves to SQL is called the Traffic Processing Service, because we planned for it to send reports one day.&lt;/p&gt;

&lt;p&gt;Alongside those headers, the &lt;a href=&quot;https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#8.2.3&quot;&gt;default HAProxy log row format&lt;/a&gt; has a few other timings per request:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;TR: Time a client took to send us the request (fairly useless when &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_persistent_connection&quot;&gt;keepalive&lt;/a&gt; is in play)&lt;/li&gt;
  &lt;li&gt;Tw: Time spent waiting in queues&lt;/li&gt;
  &lt;li&gt;Tc: Time spent waiting to connect to the web server&lt;/li&gt;
  &lt;li&gt;Tr: Time the web server took to fully render a response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As another example of simple but important, the delta between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tr&lt;/code&gt; and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AspNetDurationMs&lt;/code&gt; header (a timer started and ended on the very start and tail of a request) tells us how much time was spent in the OS, waiting for a thread in IIS, etc.&lt;/p&gt;

&lt;h4 id=&quot;health-checks&quot;&gt;Health Checks&lt;/h4&gt;

&lt;p&gt;Health checks are things that check…well, health. “Is this healthy?” has four general answers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Yes&lt;/strong&gt;: “ALL GOOD CAPTAIN!”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No&lt;/strong&gt;: “@#$%! We’re down!”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Kinda&lt;/strong&gt;: “Well, I guess we’re &lt;em&gt;technically&lt;/em&gt; online…”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Unknown&lt;/strong&gt;: “No clue…they won’t answer the phone”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The conventions on these are generally green, red, yellow, and grey (or gray, whatever) respectively.
Health checks have a few general usages.
In any load distribution setup such as a cluster of servers working together or a load balancer in front of a group of servers, health checks are a way to see if a member is up to a role or task.
For example in Elasticsearch if a node is down, it’ll rebalance shards and load across the other members…and do so again when the node returns to healthy.
In a web tier, a load balancer will stop sending traffic to a down node and continue to balance it across the healthy ones.&lt;/p&gt;

&lt;p&gt;For HAProxy, we use the built-in health checks with a caveat.
As of late 2018 when I’m writing this post, we’re in &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/mvc/mvc5&quot;&gt;ASP.NET MVC5&lt;/a&gt; and still working on our transition to &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/&quot;&gt;.NET Core&lt;/a&gt;.
An important detail is that our error page is a redirect, for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/questions&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/error?aspxerrorpath=/questions&lt;/code&gt;.
It’s an implementation detail of how the old .NET infrastructure works, but when combined with HAProxy, it’s an issue. For example if you have:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server ny-web01 10.x.x.1:80 check
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…then it will accept a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200-399&lt;/code&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_HTTP_status_codes&quot;&gt;HTTP status code response&lt;/a&gt;. (Also remember: it’s making a HEAD request only.)
A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;400&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;500&lt;/code&gt; will trigger unhealthy, but our &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_302&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;302&lt;/code&gt; redirect&lt;/a&gt; will not.
A browser would get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5xx&lt;/code&gt; status code &lt;strong&gt;&lt;em&gt;after following the redirect&lt;/em&gt;&lt;/strong&gt;, but HAProxy isn’t doing that. It’s only doing the initial hit and a “healthy” &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;302&lt;/code&gt; is all it sees.
Luckily, you can change this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http-check expect 200&lt;/code&gt; (or any status code or range or regex–&lt;a href=&quot;https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#4.2-http-check%20expect&quot;&gt;here are the docs&lt;/a&gt;) on the same backend.
This means &lt;strong&gt;only&lt;/strong&gt; a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;200&lt;/code&gt; is allowed from our health check endpoint.
Yes, it’s bitten us more than once.&lt;/p&gt;

&lt;p&gt;Different apps vary on what the health check endpoint is, but for &lt;a href=&quot;https://stackoverflow.com/&quot;&gt;stackoverflow.com&lt;/a&gt;, it’s the home page.
We’ve debated changing this a few times, but the reality is the home page checks things we may not otherwise check, and a holistic check is important.
By this I mean, “If users hit the same page, would it work?”
If we made a health check that hit the database and some caches and sanity checked the big things that we know need to be online, that’s great and it’s way better than nothing.
But let’s say we put a bug in the code and a cache that doesn’t even seem that important doesn’t reload right and it turns out it was needed to render the top bar for all users.
It’s now breaking every page.
A health check route running some code wouldn’t trigger, but just the act of loading the master view ensures a huge number of dependencies are evaluated and working for the check.&lt;/p&gt;

&lt;p&gt;If you’re curious, that’s not a hypothetical.
You know that little dot on the review queue that indicates a lot of items currently in queue?
Yeah…fun Tuesday.&lt;/p&gt;

&lt;p&gt;We also have health checks inside libraries.
The simplest manifestation of this is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Heartbeat_(computing)&quot;&gt;heartbeat&lt;/a&gt;.
This is something that for example &lt;a href=&quot;https://github.com/StackExchange/StackExchange.Redis&quot;&gt;StackExchange.Redis&lt;/a&gt; uses to routinely check if the socket connection to Redis is active.
We use the same approach to see if the socket is still open and working to websocket consumers on Stack Overflow.
This is a monitoring of sorts not heavily used here, but it is used.&lt;/p&gt;

&lt;p&gt;Other health checks we have in place include our tag engine servers.
We could load balance this through HAProxy (which would add a hop) but making every web tier server aware of every tag server directly has been a better option for us.
We can 1) choose how to spread load, 2) much more easily test new builds, and 3) get per-server op count counts metrics and performance data.
All of that is another post, but for this topic: we have a simple “ping” health check that pokes the tag server once a second and gets just a little data from it, such as when it last updated from the database.&lt;/p&gt;

&lt;p&gt;So, that’s a thing.
Your health checks can absolutely be used to communicate as much state as you want.
If having it provides some advantage and the overhead is worth it (e.g. are you running another query?), have at it.
The Microsoft .NET team has been working on &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/monitor-app-health&quot;&gt;a unified way to do health checks in ASP.NET Core&lt;/a&gt;, but I’m not sure if we’ll go that way or not.
I hope we can provide some ideas and unify things there when we get to it…more thoughts on that towards the end.&lt;/p&gt;

&lt;p&gt;However, keep in mind that health checks also generally run often. Very often. Their expense and expansiveness should be related to the frequency they’re running at. If you’re hitting it once every 100ms, once a second, once every 5 seconds, or once a minute, what you’re checking and how many dependencies are evaluated (and take a while to check…) very much matters. For example a 100ms check can’t take 200ms. That’s trying to do too much.&lt;/p&gt;

&lt;p&gt;Another note here is a health check can generally reflect a few levels of “up”. One is “I’m here”, which is as basic as it gets. The other is “I’m ready to serve”. The latter is much more important for almost every use case. But don’t phrase it quite like that to the machines, you’ll want to be in their favor when the uprising begins.&lt;/p&gt;

&lt;p&gt;A practical example of this happens at Stack Overflow: when you flip an HAProxy backend server from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAINT&lt;/code&gt; (maintenance mode) to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENABLE&lt;/code&gt;, the assumption is that the backend is up until a health check says otherwise. However, when you go from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DRAIN&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENABLE&lt;/code&gt;, the assumption is the service is down, and must pass 3 health checks before getting traffic. When we’re dealing with thread pool growth limitations and caches trying to spin up (like our Redis connections), we can get very nasty thread pool starvation issues because of how the health check behaves. The impact is drastic. When we spin up slowly from a drain, it takes about 8-20 seconds to be fully ready to serve traffic on a freshly built web server. If we go from maintenance which slams the server with traffic during startup, it takes 2-3 minutes. The health check and traffic influx may seem like salient details, but it’s critical to our &lt;a href=&quot;/blog/2016/05/03/stack-overflow-how-we-do-deployment-2016-edition/&quot;&gt;deployment pipeline&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;health-checks-httpunit&quot;&gt;Health Checks: httpUnit&lt;/h4&gt;

&lt;p&gt;An internal tool (again, open sourced!) is &lt;a href=&quot;https://github.com/StackExchange/httpunit&quot;&gt;httpUnit&lt;/a&gt;.
It’s a fairly simple-to-use tool we use to check endpoints for compliance.
Does this URL return the status code we expect?
How about some text to check for?
Is the certificate valid? (We couldn’t connect if it isn’t.)
Does the firewall allow the rule?&lt;/p&gt;

&lt;p&gt;By having something continually checking this and feeding into alerts when it fails, we can quickly identify issues, especially those from invalid config changes to the infrastructure.
We can also readily test new configurations or infrastructure, firewall rules, etc. before user load is applied.
For more details, see &lt;a href=&quot;https://github.com/StackExchange/httpunit&quot;&gt;the GitHub README&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;health-checks-fastly&quot;&gt;Health Checks: Fastly&lt;/h4&gt;

&lt;p&gt;If we zoom out from the data center, we need to see what’s hitting us.
That’s usually our CDN &amp;amp; proxy: &lt;a href=&quot;https://www.fastly.com/&quot;&gt;Fastly&lt;/a&gt;.
Fastly has a concept of &lt;a href=&quot;https://docs.fastly.com/guides/basic-setup/working-with-services&quot;&gt;services&lt;/a&gt;, which are akin to HAProxy backends when you think about it like a load balancer.
Fastly also has &lt;a href=&quot;https://docs.fastly.com/guides/basic-configuration/working-with-health-checks&quot;&gt;health checks&lt;/a&gt; built in.
In each of our data centers, we have two sets of ISPs coming in for redundancy.
We can configure things in Fastly to optimize uptime here.&lt;/p&gt;

&lt;p&gt;Let’s say our NY data center is primary at the moment, and CO is our backup.
In that case, we want to try:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;NY primary ISPs&lt;/li&gt;
  &lt;li&gt;NY secondary ISPs&lt;/li&gt;
  &lt;li&gt;CO primary ISPs&lt;/li&gt;
  &lt;li&gt;CO secondary ISPs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason for primary and secondary ISPs has to do with best transit options, commits, overages, etc.
With that in mind, we want to prefer one set over another.
With health checks, we can very quickly failover from #1 through #4.
Let’s say someone cuts the fiber on both ISPs in #1 or &lt;a href=&quot;https://en.wikipedia.org/wiki/Border_Gateway_Protocol&quot;&gt;BGP&lt;/a&gt; &lt;a href=&quot;https://youtu.be/yhVDhcuRY1I?t=29&quot;&gt;goes wonky&lt;/a&gt;, then #2 kicks in immediately.
We may drop thousands of requests before it happens, but we’re talking about an order of seconds and users just refreshing the page are probably back in business.
Is it perfect? No.
Is it better than being down indefinitely? Hell yeah.&lt;/p&gt;

&lt;h4 id=&quot;health-checks-external&quot;&gt;Health Checks: External&lt;/h4&gt;

&lt;p&gt;We also use some external health checks.
Monitoring a global service, well…globally, is important.
Are we up? Is Fastly up? Are we up here? Are we up there? Are we up in Siberia? Who knows!?
We could get a bunch of nodes on a bunch of providers and monitor things with lots of set up and configuration…or we could just pay someone many orders of magnitude less money to outsource it.
We use &lt;a href=&quot;https://www.pingdom.com/&quot;&gt;Pingdom&lt;/a&gt; for this.
When things go down, it alerts us.&lt;/p&gt;

&lt;h4 id=&quot;metrics&quot;&gt;Metrics&lt;/h4&gt;

&lt;p&gt;What are metrics? They can take a few forms, but for us they’re tagged &lt;a href=&quot;https://en.wikipedia.org/wiki/Time_series&quot;&gt;time series data&lt;/a&gt;.
In short, this means you have a name, a timestamp, a value, and in our case, some tags.
For example, a single entry looks like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Name: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet.memory.gc_collections&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Time: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2018-01-01 18:30:00&lt;/code&gt; (Of course it’s UTC, we’re not barbarians.)&lt;/li&gt;
  &lt;li&gt;Value: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;129,389,139&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Tags: Server: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NY-WEB01&lt;/code&gt;, Application: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StackExchange-Network&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value in an entry can also take a few forms, but the general case is counters.
Counters report an ever-increasing value (often reset to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; on restarts and such though).
By taking the difference in value over time, you can find out the value delta in that window.
For example, if we had 129,389,039 ten minutes before, we know that process on that server in those ten minutes ran 100 Gen 0 garbage collection passes.
Another case is just reporting an absolute point-in-time value, for example “This GPU is currently 87°”.
So what do we use to handle Metrics?
In just a minute &lt;a href=&quot;#bosun&quot;&gt;we’ll talk about Bosun&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;alerting&quot;&gt;Alerting&lt;/h3&gt;

&lt;p&gt;Alrighty, what do we do with all that data? ALERTS!
As we all know, “alert” is an anagram that comes from “le rat”, meaning “one who squealed to authorities”.&lt;/p&gt;

&lt;p&gt;This happens at several levels and we customize it to the team in question and how they operate best.
For the SRE (&lt;a href=&quot;https://en.wikipedia.org/wiki/Site_Reliability_Engineering&quot;&gt;Site Reliability Engineering&lt;/a&gt;) team, Bosun is our primary alerting source internally.
For a detailed view of how alerts in Bosun work, I recommend watching &lt;a href=&quot;https://www.usenix.org/conference/lisa14/conference-program/presentation/brandt&quot;&gt;Kyle’s presentation at LISA&lt;/a&gt; (starting about 15 minutes in).
In general, we’re alerting when:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Something is down or warning directly (e.g. iDRAC logs)&lt;/li&gt;
  &lt;li&gt;Trends don’t match previous trends (e.g. fewer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; events than normal–fun fact: this tends to false alarm over the holidays)&lt;/li&gt;
  &lt;li&gt;Something is heading towards a wall (e.g. disk space or network maxing out)&lt;/li&gt;
  &lt;li&gt;Something is past a threshold (e.g. a queue somewhere is building)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and lots of other little things, but those are the big categories that come to mind.&lt;/p&gt;

&lt;p&gt;If any problems are bad enough, we go to the next level: waking someone up.
That’s when things get real.
Some things that we monitor do not pass go and get their $200. They just go straight to &lt;a href=&quot;https://www.pagerduty.com/&quot;&gt;PagerDuty&lt;/a&gt; and wake up the on-call SRE.
If that SRE doesn’t acknowledge, it escalates to another soon after.
Significant others &lt;em&gt;love&lt;/em&gt; when all this happens!
Things of this magnitude are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;stackoverflow.com (or any other important property) going offline (as seen by Pingdom)&lt;/li&gt;
  &lt;li&gt;Significantly high error rates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have all the boring stuff out of the way, let’s dig into the tools. Yummy tools!&lt;/p&gt;

&lt;h3 id=&quot;bosun&quot;&gt;Bosun&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://bosun.org&quot;&gt;Bosun&lt;/a&gt; is our internal data collection tool for metrics and metadata. &lt;a href=&quot;https://github.com/bosun-monitor/&quot;&gt;It’s open source&lt;/a&gt;.
Nothing out there really did what we wanted with metrics and alerting, so Bosun was created about four years ago and has helped us tremendously.
We can add the metrics we want whenever we want, new functionality as we need, etc. It has all the benefits of an in-house system.
And it has all of the costs too. I’ll get to that later. It’s written in &lt;a href=&quot;https://golang.org/&quot;&gt;Go&lt;/a&gt;, primarily because the vast majority of the metrics collection is agent-based.
The agent, &lt;a href=&quot;https://github.com/bosun-monitor/bosun/tree/master/cmd/scollector&quot;&gt;scollector&lt;/a&gt; (heavily based on principles from &lt;a href=&quot;http://opentsdb.net/docs/build/html/user_guide/utilities/tcollector.html&quot;&gt;tcollector&lt;/a&gt;) needed to run on all platforms and Go was our top choice for this.
“Hey Nick, what about .NET Core??” Yeah, maybe, but it’s not quite there yet.
The story is getting more compelling, though.
Right now we can deploy a single executable very easily and Go is still ahead there.&lt;/p&gt;

&lt;p&gt;Bosun is backed by &lt;a href=&quot;https://github.com/OpenTSDB/opentsdb&quot;&gt;OpenTSDB&lt;/a&gt; for storage.
It’s a &lt;a href=&quot;https://en.wikipedia.org/wiki/Time_series_database&quot;&gt;time-series database&lt;/a&gt; built on top of &lt;a href=&quot;https://hbase.apache.org/&quot;&gt;HBase&lt;/a&gt; that’s made to be very scalable.
At least that’s what people tell us.
The problems we hit at Stack Exchange/Stack Overflow usually come from efficiency and throughput perspectives. We do a lot with a little hardware.
In some ways, this is impressive and we’re proud of it.
In other ways, it bends and breaks things that aren’t designed to run that way.
In the OpenTSDB case, we don’t &lt;em&gt;need&lt;/em&gt; lots of hardware to run it from a space standpoint, but the way HBase is designed we have to give it more hardware (especially on the network front).
It’s an HBase replication issue when dealing with tiny amounts of data that I don’t want to get too far into here, as that’s a post all by itself.
A long one.
For some definition of long.&lt;/p&gt;

&lt;p&gt;Let’s just say it’s a pain in the ass and it costs money to work around, so much so that we’ve tried to get Bosun backed by SQL Server clustered column store indexes instead.
We have this working, but the queries for certain cardinalities aren’t spectacular and cause high CPU usage.
Things like getting aggregate bandwidth for the Nexus switch cores summing up 400x more data points than most other metrics is not awesome.
Most stuff runs great. Logging 50–100k metrics per second only uses ~5% CPU on a decent server–that’s not an issue.
Certain queries are the pain point and we haven’t returned to that problem…it’s a “maybe” on if we can solve it and how much time that would take.
Anyway, that’s another post too.&lt;/p&gt;

&lt;p&gt;If you want to know more about our Bosun setup and configuration, &lt;a href=&quot;http://kbrandt.com/post/bosun_arch/&quot;&gt;Kyle Brandt has an awesome architecture post here.&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;bosun-metrics&quot;&gt;Bosun: Metrics&lt;/h4&gt;

&lt;p&gt;In the .NET case, we’re sending metrics with &lt;a href=&quot;https://github.com/StackExchange/BosunReporter&quot;&gt;BosunReporter&lt;/a&gt;, another open source &lt;a href=&quot;https://www.nuget.org/packages/BosunReporter/&quot;&gt;NuGet library&lt;/a&gt; we maintain. It looks like this:&lt;/p&gt;

&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Set it up once globally&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collector&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MetricsCollector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;BosunOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HandleException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;MetricsNamePrefix&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MyApp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;BosunUrl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://bosun.mydomain.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;PropertyToTagName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NameTransformers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CamelToLowerSnakeCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;DefaultTags&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;host&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NameTransformers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Sanitize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MachineName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToLower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Whenever you want a metric, create one! This should be likely be static somewhere&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Arguments: metric name, unit name, description&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;searchCounter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CreateMetric&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;web.search.count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;searches&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Searches against /search&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ...and whenever the event happens, increment the counter&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;searchCounter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That’s pretty much it.
We now have a counter of data flowing into Bosun.
We can add more tags–for example, we are including which server it’s happening on (via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host&lt;/code&gt; tag), but we could add the application pool in IIS, or the Q&amp;amp;A site the user’s hitting, etc.
For more details, check out &lt;a href=&quot;https://github.com/StackExchange/BosunReporter&quot;&gt;the BosunReporter README&lt;/a&gt;. It’s awesome.&lt;/p&gt;

&lt;p&gt;Many other systems can send metrics, and scollector &lt;a href=&quot;https://bosun.org/scollector/&quot;&gt;has a ton built-in&lt;/a&gt; for Redis, Windows, Linux, etc.
Another external example  that we use for critical monitoring is a small Go service that listens to &lt;a href=&quot;https://docs.fastly.com/guides/detailed-product-descriptions/about-fastlys-realtime-log-streaming-features&quot;&gt;the real-time stream of Fastly logs&lt;/a&gt;.
Sometimes Fastly may return a 503 because it couldn’t reach us, or because…who knows?
Anything between us and them could go wrong.
Maybe it’s severed sockets, or a routing issue, or a bad certificate.
Whatever the cause, we want to alert when these requests are failing and users are feeling it.
This small service just listens to the log stream, parses a bit of info from each entry, and sends aggregate metrics to Bosun.
This isn’t open source at the moment because…well I’m not sure we’ve ever mentioned it exists.
If there’s demand for such a thing, shout and we’ll take a look.&lt;/p&gt;

&lt;h4 id=&quot;bosun-alerting&quot;&gt;Bosun: Alerting&lt;/h4&gt;

&lt;p&gt;A key feature of Bosun I really love is the ability to test an alert against history while designing it.
This helps seeing &lt;em&gt;when it would have triggered&lt;/em&gt;.
It’s an awesome sanity check.
Let’s be honest, monitoring isn’t perfect, it was never perfect, and it won’t ever be perfect.
A lot of monitoring comes from lessons learned, because the things that go wrong often include things you never even considered going wrong…and that means you didn’t have monitoring and/or alerts on them from day 1.
Alerts are often added &lt;em&gt;after&lt;/em&gt; something goes wrong.
Despite your best intentions and careful planning, you will miss things and alerts will be added after the first incident.
That’s okay. It’s in the past.
All you can do &lt;em&gt;now&lt;/em&gt; is make things better in the hopes that it doesn’t happen again.
Whether you’re designing ahead of time or in retrospect, this feature is awesome:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Alert.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Alert.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Alert.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Alert.png&quot; loading=&quot;lazy&quot; alt=&quot;Bosun Alert Editor&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Alert-Test.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Alert-Test.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Alert-Test.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Alert-Test.png&quot; loading=&quot;lazy&quot; alt=&quot;Bosun Alert Test&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see on November 18th there was one system that got low enough to trigger a warning here, but otherwise all green.
Sanity checking if an alert is noisy before anyone ever gets notified? I love it.&lt;/p&gt;

&lt;p&gt;And then we have critical errors that are so urgent they need to be addressed ASAP.
For those cases, we post them to our internal chat rooms.
These are things like errors creating a &lt;a href=&quot;https://stackoverflow.com/teams&quot;&gt;Stack Overflow Team&lt;/a&gt; (You’re trying to give us money and we’re erroring? Not. Cool.) or a scheduled task is failing.
We also have metrics monitoring (via Bosun) errors in a few ways:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;From our Exceptional error logs (summed up per application)&lt;/li&gt;
  &lt;li&gt;From Fastly and HAProxy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we’re seeing a high error rate for any reason on either of these, messages with details land in chat a minute or two after. (Since they’re aggregate count based, they can’t be immediate.)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Chat-Alerts.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Chat-Alerts.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Chat-Alerts.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Chat-Alerts.png&quot; loading=&quot;lazy&quot; alt=&quot;Bosun Chat Alerts&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These messages with links let us quickly dig into the issue.
Did the network blip?
Is there a routing issue between us and &lt;a href=&quot;https://www.fastly.com/&quot;&gt;Fastly&lt;/a&gt; (our proxy and CDN)?
Did some bad code go out and it’s erroring like crazy?
Did someone trip on a power cable?
Did some idiot plug both power feeds into the same failing UPS?
All of these are extremely important and we want to dig into them ASAP.&lt;/p&gt;

&lt;p&gt;Another way alerts are relayed is email. Bosun has some nice functionality here that assists us.
An email may be a simple alert. Let’s say disk space is running low or CPU is high and a simple graph of that in the email tells a lot.
…and then we have more complex alerts.
Let’s say we’re throwing over our allowed threshold of errors in the shared error store.
Okay great, we’ve been alerted! But…which app was it?
Was it a one-off spike? Ongoing?
Here’s where the ability to define queries for more data from SQL or Elasticsearch come in handy (remember all that logging?).
We can add breakdowns and details to the email itself.
You can be better informed to handle (or even decide to ignore) an email alert without digging further.
Here’s an example email from NY-TSDB03’s CPU spiking a few days ago:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email.png&quot; loading=&quot;lazy&quot; alt=&quot;Bosun: Email&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email2.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email2.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email2.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email2.png&quot; loading=&quot;lazy&quot; alt=&quot;Bosun: Email&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email3.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email3.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email3.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Bosun-Email3.png&quot; loading=&quot;lazy&quot; alt=&quot;Bosun: Email&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also include the last 10 incidents for this alert on the systems in question so you can easily identify a pattern, see why they were dismissed, etc.
They’re just not in this particular email I was using as an example.&lt;/p&gt;

&lt;h3 id=&quot;grafana&quot;&gt;Grafana&lt;/h3&gt;

&lt;p&gt;Okay cool. Alerts are nice, but I just want to see some data…
I got you fam!&lt;/p&gt;

&lt;p&gt;After all, what good is all that data if you can’t see it?
Presentation and accessibility matter.
Being able to quickly consume the data is important.
Graphical vizualizations for time series data are an excellent way of exploring.
When it comes to monitoring, you have to either 1) be looking at the data, or 2) have rock solid 100% coverage with alerts so no one has to ever look at the data.
And #2 isn’t possible.
When a problem is found, often you’ll need to go back and see when it started.
“HOW HAVE WE NOT NOTICED THIS IN 2 WEEKS?!?” isn’t as uncommon as you’d think.
So, historical views help.&lt;/p&gt;

&lt;p&gt;This is where we use &lt;a href=&quot;https://grafana.com/&quot;&gt;Grafana&lt;/a&gt;.
It’s an excellent open source tool, for which we provide &lt;a href=&quot;https://grafana.com/plugins/bosun-app/installation&quot;&gt;a Bosun plugin&lt;/a&gt; so it can be a data source. (Technically you can use OpenTSDB directly, but this adds functionality.)
Our use of Grafana is probably best explained in pictures, so a few examples are in order.&lt;/p&gt;

&lt;p&gt;Here’s a status dashboard showing how Fastly is doing.
Since we’re behind them for DDoS protection and faster content delivery, their current status is also very much our current status.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-Fastly.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-Fastly.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-Fastly.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-Fastly.png&quot; loading=&quot;lazy&quot; alt=&quot;Grafana: Fastly&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is just a random dashboard that I think is pretty cool.
It’s traffic broken down by country of origin.
It’s split into major continents and you can see how traffic rolls around the world as people are awake.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-Continents.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-Continents.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-Continents.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-Continents.png&quot; loading=&quot;lazy&quot; alt=&quot;Grafana: Continents&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you follow me on Twitter, you’re likely aware &lt;a href=&quot;https://github.com/aspnet/AspNetCore/issues/3409#issuecomment-436677987&quot;&gt;we’re having some garbage collection issues with .NET Core&lt;/a&gt;.
Needing to keep an eye on this isn’t new though. We’ve had this dashboard for years:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-GC.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-GC.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-GC.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Grafana-GC.png&quot; loading=&quot;lazy&quot; alt=&quot;Grafana: Garbage Collection&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: Don’t go by any numbers above for scale of any sort, these screenshots were taken on a holiday weekend.&lt;/p&gt;

&lt;h3 id=&quot;client-timings&quot;&gt;Client Timings&lt;/h3&gt;

&lt;p&gt;An important note about &lt;strong&gt;&lt;em&gt;everything&lt;/em&gt;&lt;/strong&gt; above is that &lt;strong&gt;it’s server side&lt;/strong&gt;.
Did you think about it until now?
If you did, awesome.
A lot of people don’t think that way until one day when it matters.
But it’s always important.&lt;/p&gt;

&lt;p&gt;It’s critical to remember that how fast you render a webpage doesn’t matter.
Yes, I said that.
It doesn’t matter, not directly anyway.
The only thing that matters is how fast users &lt;em&gt;think&lt;/em&gt; your site is.
How fast does it &lt;em&gt;feel&lt;/em&gt;?
This manifests in many ways on the client experience, from the initial painting of a page to when content blinks in (please don’t blink or shift!), ads render, etc.&lt;/p&gt;

&lt;p&gt;Things that factor in here are, for example, how long did it take to…&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Connect over TCP?  (&lt;a href=&quot;https://daniel.haxx.se/blog/2018/11/11/http-3/&quot;&gt;HTTP/3&lt;/a&gt; isn’t here yet)&lt;/li&gt;
  &lt;li&gt;Negotiate the TLS connection?&lt;/li&gt;
  &lt;li&gt;Finish sending the request?&lt;/li&gt;
  &lt;li&gt;Get the first byte?&lt;/li&gt;
  &lt;li&gt;Get the last byte?&lt;/li&gt;
  &lt;li&gt;Initially paint the page?&lt;/li&gt;
  &lt;li&gt;Issue requests for resources in the page?&lt;/li&gt;
  &lt;li&gt;Render all the things?&lt;/li&gt;
  &lt;li&gt;Finish attaching JavaScript handlers?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…hmmm, good questions!
These are the things that matter to the user experience.
Our question pages render in 18ms.
I think that’s awesome.
And I might even be biased.
…but it also doesn’t mean crap to a user if it takes forever to get to them.&lt;/p&gt;

&lt;p&gt;So, what can we do?
Years back, I threw together a client timings pipeline when the pieces we needed first became available in browsers.
The concept is simple: use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API&quot;&gt;navigation timings API&lt;/a&gt; available in web browsers and record it.
That’s it.
There’s some sanity checks in there (you wouldn’t believe the number of NTP clock corrections that yield invalid timings from syncs &lt;em&gt;during a render&lt;/em&gt; making clocks go backwards…), but otherwise that’s pretty much it.
For 5% of requests to Stack Overflow (or any Q&amp;amp;A site on our network), we ask the browser to send these timings. We can adjust this percentage at will.&lt;/p&gt;

&lt;p&gt;For a description of how this works, you can visit &lt;a href=&quot;https://teststackoverflow.com/&quot;&gt;teststackoverflow.com&lt;/a&gt;. Here’s what it looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-Breakdown.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-Breakdown.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-Breakdown.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-Breakdown.png&quot; loading=&quot;lazy&quot; alt=&quot;Client Timings: Breakdown&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-JSON.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-JSON.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-JSON.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-JSON.png&quot; loading=&quot;lazy&quot; alt=&quot;Client Timings: JSON&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This domain isn’t &lt;em&gt;exactly&lt;/em&gt; monitoring, but it kind of is.
We use it to test things like &lt;a href=&quot;/blog/2017/05/22/https-on-stack-overflow/&quot;&gt;when we switched to HTTPS&lt;/a&gt; what the impact for everyone would be with connection times around the world (that’s why I originally created the timings pipeline).
It was also used &lt;a href=&quot;https://blog.serverfault.com/2017/01/09/surviving-the-next-dns-attack/&quot;&gt;when we added DNS providers&lt;/a&gt;, something we now have several of after &lt;a href=&quot;https://en.wikipedia.org/wiki/2016_Dyn_cyberattack&quot;&gt;the Dyn DNS attack&lt;/a&gt; in 2016.
How? Sometimes I sneakily embed it as an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt; to throw a lot of traffic at it so we can test something.
But don’t tell anyone, it’ll just be between you and me.&lt;/p&gt;

&lt;p&gt;Okay, so now we have some data.
If we take that for 5% of traffic, send it to a server, plop it in a giant clustered columnstore in SQL and send some metrics to Bosun along the way, we have something useful.
We can test before and after configs, looking at the data.
We can also keep an eye on current traffic and look for problems.
We use Grafana for the last part, and it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-Grafana.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-Grafana.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-Grafana.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-ClientTimings-Grafana.png&quot; loading=&quot;lazy&quot; alt=&quot;Client Timings: Grafana&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: that’s a 95% percentile view, the median total render time is the white dots towards the bottom (under 500ms most days).&lt;/p&gt;

&lt;h3 id=&quot;miniprofiler&quot;&gt;MiniProfiler&lt;/h3&gt;

&lt;p&gt;Sometimes the data you want to capture is more specific and detailed than the scenarios above.
In our case, we decided almost a decade ago that we wanted to see how long a webpage takes to render in the corner of every single page view.
Equally important to monitoring anything is &lt;em&gt;looking at it&lt;/em&gt;.
Making it visible on every single page you look at is a good way of making that happen.
And thus, &lt;a href=&quot;https://miniprofiler.com/&quot;&gt;MiniProfiler&lt;/a&gt; was born.
It comes in a few flavors (the projects vary a bit): &lt;a href=&quot;https://github.com/MiniProfiler/dotnet&quot;&gt;.NET&lt;/a&gt;, &lt;a href=&quot;https://github.com/MiniProfiler/rack-mini-profiler&quot;&gt;Ruby&lt;/a&gt;, &lt;a href=&quot;https://github.com/MiniProfiler/go&quot;&gt;Go&lt;/a&gt;, and &lt;a href=&quot;https://github.com/MiniProfiler/node&quot;&gt;Node.js&lt;/a&gt;.
We’re looking at the .NET version I maintain here:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler.png&quot; loading=&quot;lazy&quot; alt=&quot;MiniProfiler&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The number is all you see by default, but you can expand it to see a breakdown of which things took how long, in tree form.
The commands that are linked there are also viewable, so you can quickly see the SQL or Elastic query that ran, or the HTTP call made, or the Redis key fetched, etc. Here’s what that looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Queries.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Queries.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Queries.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Queries.png&quot; loading=&quot;lazy&quot; alt=&quot;MiniProfiler: Queries&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: If you’re thinking, that’s &lt;strong&gt;&lt;em&gt;way&lt;/em&gt;&lt;/strong&gt; longer than we say our question renders take on average (or even 99th percentile), yes, it is.
That’s because I’m a moderator here and we load a lot more stuff for moderators.&lt;/p&gt;

&lt;p&gt;Since MiniProfiler has minimal overhead, we can run it on every request.
To that end, we keep a sample of profiles per-MVC-route in Redis.
For example, we keep the 100 slowest profiles of any route at a given time.
This allows us to see what users may be hitting that we aren’t.
Or maybe anonymous users use a different query and it’s slow…we need to see that.
We can see the routes being slow in Bosun, the hits in HAProxy logs, and the profile snapshots to dig in.
All of this without seeing any code at all, that’s a powerful overview combination.
MiniProfiler is awesome (like I’m not biased…) but it is also part of a bigger set of tools here.&lt;/p&gt;

&lt;p&gt;Here’s a view of what those snapshots and aggregate summaries looks like:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Snapshots&lt;/th&gt;
      &lt;th&gt;Summary&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Snapshots.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Snapshots.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Snapshots.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Snapshots.png&quot; loading=&quot;lazy&quot; alt=&quot;MiniProfiler Snapshots&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Snapshots2.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Snapshots2.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Snapshots2.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-MiniProfiler-Snapshots2.png&quot; loading=&quot;lazy&quot; alt=&quot;MiniProfiler Summary&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;…we should probably put an example of that in the repo.
I’ll try and get around to it soon.&lt;/p&gt;

&lt;p&gt;MiniProfiler was started by &lt;a href=&quot;https://twitter.com/marcgravell&quot;&gt;Marc Gravell&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/samsaffron&quot;&gt;Sam Saffron&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/jarrod_dixon&quot;&gt;Jarrod Dixon&lt;/a&gt;.
I am the primary maintainer since 4.x, but these gentleman are responsible for it existing.
We put MiniProfiler in all of our applications.&lt;/p&gt;

&lt;p&gt;Note: see those GUIDs in the screenshots?
That’s MiniProfiler just generating an ID.
We now use that as a “Request ID” and it gets logged in those HAProxy logs and to any exceptions as well.
Little things like this help tie the world together and let you correlate things easily.&lt;/p&gt;

&lt;h3 id=&quot;opserver&quot;&gt;Opserver&lt;/h3&gt;

&lt;p&gt;So, what is &lt;a href=&quot;https://github.com/opserver/Opserver&quot;&gt;Opserver&lt;/a&gt;?
It’s a web-based dashboard and monitoring tool I started when SQL Server’s built-in monitoring lied to us one day.
About 5 years ago, we had an issue where SQL Server AlwaysOn Availability Groups showed green on the SSMS dashboard (powered by the primary), but the replicas hadn’t seen new data for days.
This was an example of extremely broken monitoring.
What happened was the HADR thread pool exhausted and stopped updating a view that had a state of “all good”.
I’d link you to the Connect item but they just deleted them all.
I’m not bitter.
The design of such isn’t necessarily flawed, but when caching/storing the state of a thing, &lt;strong&gt;it needs to have a timestamp&lt;/strong&gt;.
If it hasn’t been updated in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;pick a threshold&amp;gt;&lt;/code&gt;,  that’s a red alert.
Nothing about the state can be trusted.
Anyway, enter Opserver.
The first thing it did was monitor each SQL node rather than trusting the master.&lt;/p&gt;

&lt;p&gt;Since then, I’ve added monitoring for our other systems we wanted in a quick web-based view.
We can see all servers (based on Bosun, or Orion, or WMI directly).
Here is an overview of where Opserver is today:&lt;/p&gt;

&lt;h4 id=&quot;opserver-primary-dashboard&quot;&gt;Opserver: Primary Dashboard&lt;/h4&gt;

&lt;p&gt;The landing dashboard is a server list showing an overview of what’s up.
Users can search by name, service tag, IP address, VM host, etc.
You can also drill down to all-time history graphs for CPU, memory, and network on each node.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers.png&quot; loading=&quot;lazy&quot; alt=&quot;Dashboard&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Within each node looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers-Node.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers-Node.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers-Node.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers-Node.png&quot; loading=&quot;lazy&quot; alt=&quot;Dashboard: Node&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If using Bosun and running Dell servers, we’ve added hardware metadata like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers-Node-Hardware.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers-Node-Hardware.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers-Node-Hardware.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Servers-Node-Hardware.png&quot; loading=&quot;lazy&quot; alt=&quot;Dashboard: Node Hardware&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;opserver-sql-server&quot;&gt;Opserver: SQL Server&lt;/h4&gt;

&lt;p&gt;In the SQL dashboard, we can see server status and how the availability groups are doing.
We can see how much activity each node has and which one is primary (in blue) at any given time.
The bottom section is &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/database-engine/availability-groups/windows/overview-of-always-on-availability-groups-sql-server&quot;&gt;AlwaysOn Availability Groups&lt;/a&gt;, we can see who’s primary for each, how far behind replication is, and how much queues are backed up.
If things go south and a replica is unhealthy, some more indicators pop in like which databases are having issues and the free disk space on the primary for all drives involved in T-logs (since they will start growing if replication remains down):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Dashboard&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s also a top-level all-jobs view for quick monitoring and enabling/disabling:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Jobs.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Jobs.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Jobs.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Jobs.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Jobs&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in the per-instance view we can see the stats about the server, caches, etc., that we’ve found relevant over time.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Instance.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Instance.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Instance.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Instance.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Instance&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each instance, we also report top queries (based on plan cache, not query store yet), active-right now queries (based on &lt;a href=&quot;http://whoisactive.com/&quot;&gt;sp_whoisactive&lt;/a&gt;), connections, and database info.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Top.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Top.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Top.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Top.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Top Queries&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Active.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Active.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Active.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Active.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Active Queries&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Connections.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Connections.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Connections.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Connections.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Connections&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…and if you want to drill down into a top query, it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Top-Query.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Top-Query.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Top-Query.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Top-Query.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Top Queries&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the databases view, there are drill downs to see tables, indexes, views, stored procedures, storage usage, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Databases.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Databases.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Databases.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Databases.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Databases&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Database&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Table.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Table.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Table.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Table.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Database Table&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Storage.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Storage.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Storage.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Storage.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Database Storage&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Unused-Indexes.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Unused-Indexes.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Unused-Indexes.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-SQL-Database-Unused-Indexes.png&quot; loading=&quot;lazy&quot; alt=&quot;SQL Unused Indexes&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;opserver-redis&quot;&gt;Opserver: Redis&lt;/h4&gt;

&lt;p&gt;For Redis, we want to see the topology chain of primary and replicas as well as the overall status of each instance:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis-Instance.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis-Instance.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis-Instance.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis-Instance.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that you can kill client connections, get the active config, change server topologies, and analyze the data in each database (configurable via &lt;a href=&quot;https://twitter.com/Nick_Craver/status/1051221337413431298&quot;&gt;Regexes&lt;/a&gt;).
The last one is a heavy &lt;a href=&quot;https://redis.io/commands/keys&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KEYS&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://redis.io/commands/debug-object&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DEBUG OBJECT&lt;/code&gt;&lt;/a&gt; scan, so we run it on a replica node or are allowed to force running it on a master (for safety).
Analysis looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis-Analyze.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis-Analyze.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis-Analyze.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Redis-Analyze.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;opserver-elasticsearch&quot;&gt;Opserver: Elasticsearch&lt;/h4&gt;

&lt;p&gt;For Elasticsearch, we usually want to see things in a cluster view since that’s how it behaves.
What isn’t seen below is that when an index goes yellow or red. When that happens, new sections of the dashboard appear showing shards that are in trouble, what they’re doing (initializing, relocating, etc.), and counts appear in each cluster summarizing how many are in which status.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Elastic.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Elastic.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Elastic.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Elastic.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Elastic-Node.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Elastic-Node.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Elastic-Node.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Elastic-Node.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: the PagerDuty tab up there pulls from the PagerDuty API and displays on-call information, who’s primary, secondary, allows you to see and claim incidents, etc.
Since it’s almost 100% not data you’d want to share, there’s no screenshot here. :)
It also has a configurable raw HTML section to give visitors instructions on what to do or who to reach out to.&lt;/p&gt;

&lt;h4 id=&quot;opserver-exceptions&quot;&gt;Opserver: Exceptions&lt;/h4&gt;

&lt;p&gt;Exceptions in Opserver are based on &lt;a href=&quot;https://github.com/NickCraver/StackExchange.Exceptional&quot;&gt;StackExchange.Exceptional&lt;/a&gt;.
In this case specifically, we’re looking at the SQL Server storage provider for Exceptional.
Opserver is a way for many applications to share a single database and table layout and have developers view their exceptions in one place.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The top level view here can just be applications (the default), or it can be configured in groups.
In the above case, we’re configuring application groups by team so a team can bookmark or quickly click on the exceptions they’re responsible for.
In the per-exception page, the detail looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions-Stack.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions-Stack.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions-Stack.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions-Stack.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions-Command.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions-Command.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions-Command.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-Exceptions-Command.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are also details recorded like request headers (with security filters so we don’t log authentication cookies for example), query parameters, and any other custom data added to an exception.&lt;/p&gt;

&lt;p&gt;Note: you can configure multiple stores, for instance we have New York and Colorado above.
These are separate databases allowing all applications to log to a very-local store and still get to them from a single dashboard.&lt;/p&gt;

&lt;h4 id=&quot;opserver-haproxy&quot;&gt;Opserver: HAProxy&lt;/h4&gt;

&lt;p&gt;The HAProxy section is pretty straightforward–we’re simply presenting the current HAProxy status and allowing control of it. Here’s what the main dashboard looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-HAProxy.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-HAProxy.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-HAProxy.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-HAProxy.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each background group, specific backend server, entire server, or entire tier, it also allows some control.
We can take a backend server out of rotation, or an entire backend down, or a web server out of all backends if we need to shut it down for emergency maintenance, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-HAProxy-Control.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-HAProxy-Control.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-HAProxy-Control.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Monitoring/SO-Monitoring-Opserver-HAProxy-Control.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;opserver-faqs&quot;&gt;Opserver: FAQs&lt;/h4&gt;

&lt;p&gt;I get the same questions about Opserver routinely, so let’s knock a few of them out:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Opserver does &lt;strong&gt;not&lt;/strong&gt; require a data store of any kind for itself (it’s all config and in-memory state).
    &lt;ul&gt;
      &lt;li&gt;This may happen in the future to enhance functionality, but there are no plans to require anything.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Only the dashboard tab and per-node view is powered by Bosun, Orion, or WMI - all other screens like SQL, Elastic, Redis, etc. have no dependency…Opserver monitors these directly.&lt;/li&gt;
  &lt;li&gt;Authentication is both global and per-tab pluggable (who can view and who’s an admin are separate), but built-in configuration is via groups and Active Directory is included.
    &lt;ul&gt;
      &lt;li&gt;On admin vs. viewer: A viewer gets a read-only view. For example, HAProxy controls wouldn’t be shown.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;All tabs are not required–each is independent and only appears if configured.
    &lt;ul&gt;
      &lt;li&gt;For example, if you wanted to only use Opserver as an Elastic or Exceptions dashboard, go nuts.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Opserver is currently being ported to ASP.NET Core as I have time at night.
This should allow it to run without IIS and hopefully run on other platforms as well soon.
Some things like AD auth to SQL Servers from Linux and such is still on the figure-it-out list.
If you’re looking to deploy Opserver, just be aware deployment and configuration will change drastically soon (it’ll be simpler) and it may be better to wait.&lt;/p&gt;

&lt;h3 id=&quot;where-do-we-go-next&quot;&gt;Where Do We Go Next?&lt;/h3&gt;

&lt;p&gt;Monitoring is an ever-evolving thing.
For just about everyone, I think.
But I can only speak of plans I’m involved in…so what do &lt;em&gt;we&lt;/em&gt; do next?&lt;/p&gt;

&lt;h4 id=&quot;health-check-next-steps&quot;&gt;Health Check Next Steps&lt;/h4&gt;

&lt;p&gt;Health check improvements are something I’ve had in mind for a while, but haven’t found the time for. When you’re monitoring things, the source of truth is a matter of concern.
There’s what a thing &lt;strong&gt;&lt;em&gt;is&lt;/em&gt;&lt;/strong&gt; and what a thing &lt;strong&gt;&lt;em&gt;should be&lt;/em&gt;&lt;/strong&gt;.
Who defines the latter?
I think we can improve things here on the dependency front and have it generally usable across the board (…and I really hope someone’s already doing similar, tell me if so!).
What if we had a simple structure from the health checks like this:&lt;/p&gt;

&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HealthResult&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AppName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ServerName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HealthStatus&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Status&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tags&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HealthResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dependencies&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dictionary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Attributes&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HealthStatus&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Healthy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Critical&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Unknown&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is just me thinking out loud here, but the key part is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dependencies&lt;/code&gt;.
What if you asked a web server “Hey buddy, how ya doing?” and it returned not a simple JSON object, but a tree of them?
But each level is all the same thing, so overall we’d have a recursive list of dependencies.
For example, a dependency list that included Redis–if we couldn’t reach 1 of 2 Redis nodes, we’d have 2 dependencies in the list, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Healthy&lt;/code&gt; for one and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Critical&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Unknown&lt;/code&gt; for the other in the dependency list and the web server would be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Warning&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Healthy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The main point here is: &lt;strong&gt;the monitoring system doesn’t need to know about dependencies&lt;/strong&gt;.
The systems themselves define them and return them.
This way we don’t get into config skew where what’s being monitored doesn’t match what should be there.
This can happen often in deployments with topology or dependency changes.&lt;/p&gt;

&lt;p&gt;This may be a terrible idea, but it’s a general one I have for Opserver (or any script really) to get a health reading and the &lt;em&gt;why&lt;/em&gt; of a health reading.
If we lose another node, these &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; things break.
Or, we see the common cause of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; health warnings.
By pointing at a few endpoints, we could get a tree view of everything.
Need to add more data for your use case? Sure!
It’s JSON, so just inherit from the object and add more stuff as needed.
It’s an easily extensible model. I think.
I need to take the time to build this…maybe it’s full of problems.
Or maybe someone reading this will tell me it’s already done (hey you!).&lt;/p&gt;

&lt;h4 id=&quot;bosun-next-steps&quot;&gt;Bosun Next Steps&lt;/h4&gt;

&lt;p&gt;Bosun has largely been in maintenance mode due to a lack of resources and other priorities.
We haven’t done as much as we’d like because we need to have the discussion on the best path forward.
Have other tools caught up and filled the gaps that caused us to build it in the first place?
Has SQL 2017 or 2019 already improved the queries we had issues with lowering the bar greatly?
We need to take some time and look at the landscape and evaluate what we want to do.
This is something we want to get into during Q1 2019.&lt;/p&gt;

&lt;p&gt;We know of some things we’d like to do, such as improving the alert editing experience and some other UI areas.
We just need to weigh some things and figure out where our time is best spent as with all things.&lt;/p&gt;

&lt;h4 id=&quot;metrics-next-steps&quot;&gt;Metrics Next Steps&lt;/h4&gt;

&lt;p&gt;We are drastically under-utilizing metrics across our applications.
We know this.
The system was built for SREs and developers primarily, but showing developers all the benefits and how powerful metrics are (including how easy they are to add) is something we haven’t done well.
This is a topic we discussed at a company meetup last month.
They’re so, so cheap to add and we could do a lot better.
Views differ here, but I think it’s mostly a training and awareness issue we’ll strive to improve.&lt;/p&gt;

&lt;p&gt;The health checks above…maybe we easily allow integrating metrics from BosunReporter there as well (probably only when asked for) to make one decently powerful API to check the health and status of a service.
This would allow a pull model for the same metrics we normally push.
It needs to be as cheap as possible and allocate little, though.&lt;/p&gt;

&lt;h3 id=&quot;tools-summary&quot;&gt;Tools Summary&lt;/h3&gt;

&lt;p&gt;I mentioned several tools we’ve built and open sourced above.
Here’s a handy list for reference:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://bosun.org&quot;&gt;Bosun&lt;/a&gt;: Go-based monitoring and alerting system – primary developed by &lt;a href=&quot;https://twitter.com/kylembrandt&quot;&gt;Kyle Brandt&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/captncraig&quot;&gt;Craig Peterson&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/mjibson&quot;&gt;Matt Jibson&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://grafana.com/plugins/bosun-app&quot;&gt;Bosun: Grafana Plugin&lt;/a&gt;: A data source plugin for Grafana – developed by &lt;a href=&quot;https://twitter.com/kylembrandt&quot;&gt;Kyle Brandt&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/BosunReporter&quot;&gt;BosunReporter&lt;/a&gt;: .NET metrics collector/sender for Bosun – developed by &lt;a href=&quot;https://twitter.com/bretcope&quot;&gt;Bret Copeland&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/httpunit&quot;&gt;httpUnit&lt;/a&gt;: Go-based HTTP monitor for testing compliance of web endpoints – developed by &lt;a href=&quot;https://twitter.com/mjibson&quot;&gt;Matt Jibson&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/yesthattom&quot;&gt;Tom Limoncelli&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/MiniProfiler/dotnet&quot;&gt;MiniProfiler&lt;/a&gt;: .NET-based (with other languages available like Node and Ruby) lightweight profiler for seeing page render times in real-time – created by &lt;a href=&quot;https://twitter.com/marcgravell&quot;&gt;Marc Gravell&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/samsaffron&quot;&gt;Sam Saffron&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/jarrod_dixon&quot;&gt;Jarrod Dixon&lt;/a&gt; and maintained by &lt;a href=&quot;https://twitter.com/Nick_Craver&quot;&gt;Nick Craver&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/opserver/Opserver&quot;&gt;Opserver&lt;/a&gt;: ASP.NET-based monitoring dashboard for Bosun, SQL, Elasticsearch, Redis, Exceptions, and HAProxy – developed by &lt;a href=&quot;https://twitter.com/Nick_Craver&quot;&gt;Nick Craver&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/NickCraver/StackExchange.Exceptional&quot;&gt;StackExchange.Exceptional&lt;/a&gt;: .NET exception logger to SQL, MySQL, Postgres, etc. – developed by &lt;a href=&quot;https://twitter.com/Nick_Craver&quot;&gt;Nick Craver&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and all of these tools have additional contributions from our developer and SRE teams as well as the community at large.&lt;/p&gt;

&lt;p&gt;What’s next?
The way &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;this series&lt;/a&gt; works is I blog in order of what the community wants to know about most.
Going by &lt;a href=&quot;https://trello.com/b/0zgQjktX/blog-post-queue-for-stack-overflow-topics&quot;&gt;the Trello board&lt;/a&gt;, it looks like &lt;a href=&quot;https://trello.com/c/OztwfkG7/16-caching-redis&quot;&gt;Caching&lt;/a&gt; is the next most interesting topic.
So next time expect to learn how we cache data both on the web tier and Redis, how we handle cache invalidation, and take advantage of pub/sub for various tasks along the way.&lt;/p&gt;

</description>
        <pubDate>Thu, 29 Nov 2018 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2018/11/29/stack-overflow-how-we-do-monitoring/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>HTTPS on Stack Overflow: The End of a Long Road</title>
        <link>https://nickcraver.com/blog/2017/05/22/https-on-stack-overflow/</link>
        <description>&lt;p&gt;Today, we deployed HTTPS by default on &lt;a href=&quot;https://stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt;. 
All traffic is now redirected to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; and Google links will change over the next few weeks. 
The activation of this is quite literally flipping a switch (feature flag), but getting to that point has taken years of work.
As of now, HTTPS is the default on all Q&amp;amp;A websites.&lt;/p&gt;

&lt;p&gt;We’ve been rolling it out across the Stack Exchange network &lt;a href=&quot;https://meta.stackoverflow.com/q/345012/13249&quot;&gt;for the past 2 months&lt;/a&gt;. 
Stack Overflow is the last site, and by far the largest. 
This is a huge milestone for us, but by no means the end. 
There’s still more work to do, which &lt;a href=&quot;#next-steps&quot;&gt;we’ll get to&lt;/a&gt;.
But the end is finally in sight, hooray!&lt;/p&gt;

&lt;p&gt;Fair warning: This is the story of a long journey. Very long. 
As indicated by your scroll bar being very tiny right now. 
While Stack Exchange/Overflow is not unique in the problems we faced along the way, the combination of problems is fairly rare.
I hope you find some details of our trials, tribulations, mistakes, victories, and even some open source projects that resulted along the way to be helpful.
It’s hard to structure such an intricate dependency chain into a chronological post, so I’ll break this up by topic: infrastructure, application code, mistakes, etc.
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;I think it’s first helpful to preface with a list of problems that makes our situation somewhat unique:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;We have hundreds of domains (&lt;a href=&quot;https://stackexchange.com/sites&quot;&gt;many sites&lt;/a&gt; and other services)
    &lt;ul&gt;
      &lt;li&gt;Many second-level domains (&lt;a href=&quot;https://stackoverflow.com/&quot;&gt;stackoverflow.com&lt;/a&gt;, &lt;a href=&quot;https://stackexchange.com/&quot;&gt;stackexchange.com&lt;/a&gt;, &lt;a href=&quot;https://askubuntu.com/&quot;&gt;askubuntu.com&lt;/a&gt;, etc.)&lt;/li&gt;
      &lt;li&gt;Many 4th level domains (e.g. &lt;a href=&quot;http://meta.gaming.stackexchange.com&quot;&gt;meta.gaming.stackexchange.com&lt;/a&gt;)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;We allow user submitted &amp;amp; embedded content (e.g. images and YouTube videos in posts)&lt;/li&gt;
  &lt;li&gt;We serve from a single data center (latency to a single origin)&lt;/li&gt;
  &lt;li&gt;We have ads (and ad networks)&lt;/li&gt;
  &lt;li&gt;We use websockets, north of 500,000 active at any given (connection counts)&lt;/li&gt;
  &lt;li&gt;We get DDoSed (proxy)&lt;/li&gt;
  &lt;li&gt;We have many sites &amp;amp; apps communicating via HTTP APIs (proxy issues)&lt;/li&gt;
  &lt;li&gt;We’re obsessed with performance (&lt;em&gt;maybe&lt;/em&gt; a little too much)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since this post is a bit crazy, links for your convenience:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#the-beginning&quot;&gt;The Beginning&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#quick-specs&quot;&gt;Quick Specs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Infrastructure
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#certificates&quot;&gt;Certificates&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#certificates-child-metas-metastackexchangecom&quot;&gt;Child Metas (meta.*.stackexchange.com)&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#performance-http2&quot;&gt;Performance: HTTP/2&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#haproxy-serving-up-https&quot;&gt;HAProxy: Serving up HTTPS&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#cdnproxy-countering-latency-with-cloudflare--fastly&quot;&gt;CDN/Proxy: Countering Latency with Cloudflare &amp;amp; Fastly&lt;/a&gt;
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#preparing-for-a-proxy-client-timings&quot;&gt;Preparing for a Proxy: Client Timings&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#cloudflare&quot;&gt;CloudFlare&lt;/a&gt;
            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#cloudflare-railgun&quot;&gt;Railgun&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#fastly&quot;&gt;Fastly&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#global-dns&quot;&gt;Global DNS&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#testing&quot;&gt;Testing&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Applications/Code
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#preparing-the-applications&quot;&gt;Preparing the Applications&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#global-login&quot;&gt;Global Login&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#local-https-development&quot;&gt;Local HTTPS Development&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;Mixed Content
        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#mixed-content-from-you&quot;&gt;From You&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#mixed-content-from-us&quot;&gt;From Us&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#redirects-301s&quot;&gt;Redirects (301s)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#websockets&quot;&gt;Websockets&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#unknowns&quot;&gt;Unknowns&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#mistakes&quot;&gt;Mistakes&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#mistakes-protocol-relative-urls&quot;&gt;Protocol-Relative URLs&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#mistakes-apis-and-internal&quot;&gt;APIs and .internal&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#mistakes-301-caching&quot;&gt;301 Caching&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#mistakes-help-center-snafu&quot;&gt;Help Center SNAFU&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#open-source&quot;&gt;Open Source&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#next-steps&quot;&gt;Next Steps&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#hsts-preloading&quot;&gt;HSTS Preloading&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#chat&quot;&gt;Chat&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#today&quot;&gt;Today&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;the-beginning&quot;&gt;The Beginning&lt;/h3&gt;

&lt;p&gt;We began thinking about deploying HTTPS on Stack Overflow &lt;a href=&quot;/blog/2013/04/23/stackoverflow-com-the-road-to-ssl/&quot;&gt;back in 2013&lt;/a&gt;. 
So the obvious question: It’s 2017. &lt;strong&gt;What the hell took 4 years?&lt;/strong&gt;
The same 2 reasons that delay almost any IT project: dependencies and priorities. 
Let’s be honest, the information on Stack Overflow isn’t as valuable (to secure) as most other data. 
We’re not a bank, we’re not a hospital, we don’t handle credit card payments, and &lt;a href=&quot;https://archive.org/details/stackexchange&quot;&gt;we even publish most of our database both by HTTP and via torrent once a quarter&lt;/a&gt;. 
That means from a security standpoint, it’s just not as high of a priority as it is in other situations. 
We also had far more dependencies than most, a rather unique combination of some huge problem areas when deploying HTTPS.
As you’ll see later, some of the domain problems are also permanent.&lt;/p&gt;

&lt;p&gt;The biggest areas that caused us problems were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;User content (users can upload images or specify URLs)&lt;/li&gt;
  &lt;li&gt;Ad networks (contracts and support)&lt;/li&gt;
  &lt;li&gt;Hosting from a single data center (latency)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackexchange.com/sites&quot;&gt;&lt;strong&gt;Hundreds&lt;/strong&gt; of domains&lt;/a&gt;, at multiple levels (certificates)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Okay, so why &lt;strong&gt;do&lt;/strong&gt; we want HTTPS on our websites? 
Well, the data isn’t the only thing that needs security. 
We have moderators, developers, and employees with various levels of access via the web. 
We want to secure their communications with the site.
We want to secure every user’s browsing history.
Some people live in fear every day knowing that someone may find out they secretly like monads.
Google also &lt;a href=&quot;https://webmasters.googleblog.com/2014/08/https-as-ranking-signal.html&quot;&gt;gives a boost to HTTPS websites&lt;/a&gt; in ranking (though we have no clue how much).&lt;/p&gt;

&lt;p&gt;Oh, and &lt;strong&gt;performance&lt;/strong&gt;. We love performance. I love performance. You love performance. My dog loves performance. Let’s have a performance hug. That was nice. Thank you. You smell nice.&lt;/p&gt;

&lt;h3 id=&quot;quick-specs&quot;&gt;Quick Specs&lt;/h3&gt;

&lt;p&gt;Some people just want the specs, so quick Q&amp;amp;A here (we love Q&amp;amp;A!):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Q: Which protocols do you support?
    &lt;ul&gt;
      &lt;li&gt;A: TLS 1.0, 1.1, 1.2 (Note: &lt;a href=&quot;https://www.fastly.com/blog/phase-two-our-tls-10-and-11-deprecation-plan&quot;&gt;Fastly has a TLS 1.0 and 1.1 deprecation plan&lt;/a&gt;). TLS 1.3 support is coming&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Do you support SSL v2, v3?
    &lt;ul&gt;
      &lt;li&gt;A: No, these are &lt;a href=&quot;http://disablessl3.com/&quot;&gt;broken, insecure protocols&lt;/a&gt;. Everyone should disable them ASAP.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Which ciphers do you support?
    &lt;ul&gt;
      &lt;li&gt;A: At the CDN, we use &lt;a href=&quot;https://www.ssllabs.com/ssltest/analyze.html?d=meta.stackexchange.com&amp;amp;s=151.101.129.69#suitesHeading&quot;&gt;Fastly’s default suite&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;A: At our load balancer, we use &lt;a href=&quot;https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility&quot;&gt;Mozilla’s Modern compatibility suite&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Does Fastly connect to the origin over HTTPS?
    &lt;ul&gt;
      &lt;li&gt;A: Yes, if the CDN request is HTTPS, the origin request is HTTPS.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Do you support forward secrecy?
    &lt;ul&gt;
      &lt;li&gt;A: Yes&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Do you support &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security&quot;&gt;HSTS&lt;/a&gt;?
    &lt;ul&gt;
      &lt;li&gt;A: Yes, we’re ramping it up across Q&amp;amp;A sites now. Once done we’ll move it to the edge.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Do you support HPKP?
    &lt;ul&gt;
      &lt;li&gt;A: No, and we likely won’t.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Do you support SNI?
    &lt;ul&gt;
      &lt;li&gt;A: No, we have a combined wildcard certificate for HTTP/2 performance reasons (details below).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Where do you get certificates?
    &lt;ul&gt;
      &lt;li&gt;A: We use &lt;a href=&quot;https://www.digicert.com/&quot;&gt;DigiCert&lt;/a&gt;, they’ve been awesome.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: Do you support IE 6?
    &lt;ul&gt;
      &lt;li&gt;A: This move finally kills it, completely. IE 6 does not support TLS (default - though 1.0 can be enabled), we do not support SSL. With 301 redirects in place, most IE6 users can no longer access Stack Overflow. When TLS 1.0 is removed, none can.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: What load balancer do you use?
    &lt;ul&gt;
      &lt;li&gt;A: &lt;a href=&quot;https://www.haproxy.org/&quot;&gt;HAProxy&lt;/a&gt; (it uses &lt;a href=&quot;https://www.openssl.org/&quot;&gt;OpenSSL&lt;/a&gt; internally).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Q: What’s the motivation for HTTPS?
    &lt;ul&gt;
      &lt;li&gt;A: People kept attacking our admin routes like &lt;a href=&quot;https://stackoverflow.com/admin.php&quot;&gt;stackoverflow.com/admin.php&lt;/a&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;certificates&quot;&gt;Certificates&lt;/h3&gt;

&lt;p&gt;Let’s talk about certificates, because there’s a lot of misinformation out there. 
I’ve lost count of the number of people who say you just install a certificate and you’re ready to go on HTTPS.
Take another look at the tiny size of your scroll bar and take a wild guess if I agree.
We prefer &lt;a href=&quot;https://en.wikipedia.org/wiki/Scientific_wild-ass_guess&quot;&gt;the SWAG method&lt;/a&gt; for our guessing.&lt;/p&gt;

&lt;p&gt;The most common question we get: “Why not use &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt;?”&lt;/p&gt;

&lt;p&gt;Answer: because they don’t work for us. 
Let’s Encrypt is doing a great thing. 
I hope they keep at it. 
If you’re on a single domain or only a few domains, they’re a pretty good option for a wide variety of scenarios.
We are simply not in that position.
Stack Exchange has &lt;a href=&quot;https://stackexchange.com/sites&quot;&gt;hundreds of domains&lt;/a&gt;.
Let’s Encrypt &lt;a href=&quot;https://letsencrypt.org/docs/faq/&quot;&gt;doesn’t offer wildcards&lt;/a&gt;.
These two things are at odds with each other.
We’d have to get a certificate (or two) every time we deployed a new Q&amp;amp;A site (or any other service).
That greatly complicates deployment, and either a) drops non-SNI clients (around 2% of traffic these days) or b) requires far more IP space than we have.&lt;/p&gt;

&lt;p&gt;Another reason we want to control the certificate is we need to install the exact same certificates on both our local load balancers and our CDN/proxy provider.
Unless we can do that, we can’t failover (away from a proxy) cleanly in all cases.
Anyone that has the certificate pinned &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning&quot;&gt;via HPKP&lt;/a&gt; (HTTP Public Key Pinning) would fail validation.
We’re evaluating whether we’ll deploy HPKP, but we’ve prepped as if we will later.&lt;/p&gt;

&lt;p&gt;I’ve gotten a lot of raised eyebrows at our main certificate having all of our primary domains + wildcards.
Here’s what that looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/HTTPS-MainCertificate.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/HTTPS-MainCertificate.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/HTTPS-MainCertificate.png&quot; /&gt;&lt;img src=&quot;/blog/content/HTTPS-MainCertificate.png&quot; loading=&quot;lazy&quot; alt=&quot;Main Certificate&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why do this? 
Well, to be fair, &lt;a href=&quot;https://www.digicert.com/&quot;&gt;DigiCert&lt;/a&gt; is the one who does this for us upon request.
Why go through the pain of a manual certificate merge for every change?
First, because we wanted to support as many people as possible.
That includes clients that don’t support SNI (for example, Android 2.3 was a big thing when we started).
But also because of HTTP/2 and reality.
We’ll cover that in a minute.&lt;/p&gt;

&lt;h4 id=&quot;certificates-child-metas-metastackexchangecom&quot;&gt;Certificates: Child Metas (meta.*.stackexchange.com)&lt;/h4&gt;

&lt;p&gt;One of the tenets of the Stack Exchange network is having a place to talk about each Q&amp;amp;A site.
We call it the &lt;a href=&quot;https://stackoverflow.blog/2010/04/29/do-trilogy-sites-need-a-third-place/&quot;&gt;“second place”&lt;/a&gt;.
As an example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.gaming.stackexchange.com&lt;/code&gt; exists to talk about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gaming.stackexchange.com&lt;/code&gt;.
So why does that matter? Well, it doesn’t really. We only care about the domain here. It’s 4 levels deep.&lt;/p&gt;

&lt;p&gt;I’ve &lt;a href=&quot;/blog/2013/04/23/stackoverflow-com-the-road-to-ssl/&quot;&gt;covered this before&lt;/a&gt;, but where did we end up?
First the problem: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.stackexchange.com&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; cover &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gaming.stackexchange.com&lt;/code&gt; (and hundreds of other sites), but it &lt;strong&gt;does not&lt;/strong&gt; cover &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.gaming.stackexchange.com&lt;/code&gt;.
&lt;a href=&quot;https://tools.ietf.org/html/rfc6125#section-6.4.3&quot;&gt;RFC 6125 (Section 6.4.3)&lt;/a&gt; states that:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The client SHOULD NOT attempt to match a presented identifier in which the wildcard character comprises a label other than the left-most label (e.g., do not match &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bar.*.example.net&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means we cannot have a wildcard of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.*.stackexchange.com&lt;/code&gt;. Well, shit.
So what do we do?&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Option 1: Deploying &lt;a href=&quot;https://www.digicert.com/subject-alternative-name.htm&quot;&gt;SAN certificates&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;We’d need 3 (the limit is ~100 domains per), we’d need to dedicate 3 IPs, and we’d complicate new site launches (until the scheme changed, which it already has)&lt;/li&gt;
      &lt;li&gt;We’d have to pay for 3 custom certs for all time at the CDN/proxy&lt;/li&gt;
      &lt;li&gt;We’d have to have a DNS entry for every child meta under the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.*&lt;/code&gt; scheme
        &lt;ul&gt;
          &lt;li&gt;Due to the rules of DNS, we’d actually have to add a DNS entry for every single site, complicating site launches and maintenance.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Option 2: Move all domains to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.meta.stackexchange.com&lt;/code&gt;?
    &lt;ul&gt;
      &lt;li&gt;We’d have a painful move, but it’s 1-time and simplifies all maintenance and certificates&lt;/li&gt;
      &lt;li&gt;We’d have to build a global login system  (&lt;a href=&quot;#global-login&quot;&gt;details here&lt;/a&gt;)&lt;/li&gt;
      &lt;li&gt;This solution also creates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includeSubDomains&lt;/code&gt; HSTS preloading problem (&lt;a href=&quot;#hsts-preloading&quot;&gt;details here&lt;/a&gt;)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Option 3: We’ve had a good run, shut ‘er down
    &lt;ul&gt;
      &lt;li&gt;This one is the easiest, but was not approved&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We &lt;a href=&quot;#global-login&quot;&gt;built a global login system&lt;/a&gt; and later moved the child meta domains (with 301s), and they’re now at their new homes. For example, &lt;a href=&quot;https://gaming.meta.stackexchange.com&quot;&gt;https://gaming.meta.stackexchange.com&lt;/a&gt;. 
After doing this, we realized how much of a problem the HSTS preload list was going to be simply because those domains &lt;em&gt;ever&lt;/em&gt; existed.
I’ll cover that &lt;a href=&quot;#hsts-preloading&quot;&gt;near the end&lt;/a&gt;, as it’s still in progress.
Note that the problems here are mirrored on our journey for things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.pt.stackoverflow.com&lt;/code&gt;, but were more limited in scale since only 4 non-English versions of Stack Overflow exist.&lt;/p&gt;

&lt;p&gt;Oh, and this created &lt;em&gt;another&lt;/em&gt; problem in itself. 
By moving cookies to the top-level domain and relying on the subdomain inheritance of them, we now had to move domains.
As an example, we use SendGrid to send email in our new system (rolling out now).
The reason that it sends from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.email&lt;/code&gt; with links pointed at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sg-links.stackoverflow.email&lt;/code&gt; (a &lt;a href=&quot;https://en.wikipedia.org/wiki/CNAME_record&quot;&gt;CNAME&lt;/a&gt; pointed to them), is so that your browser doesn’t send any sensitive cookies.
If it was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sg-links.stackoverflow.com&lt;/code&gt; (or anything beneath &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt;), your browser would send our cookies to them.
This is a concrete example of new things, but there were also miscellaneous not-hosted-by-us services under our DNS.
Each one of these subdomains had to be moved or retired to get out from under our authenticated domains…or else we’d be sending your cookies to not-our-servers.
It’d be a shame to do all this work just to be leaking cookies to other servers at the end of it.&lt;/p&gt;

&lt;p&gt;We tried to work around this in one instance by proxying one of our Hubspot properties for a while, stripping the cookies on the way through.
But unfortunately, Hubspot uses &lt;a href=&quot;https://www.akamai.com/&quot;&gt;Akamai&lt;/a&gt; which started treating our HAProxy instance as a bot and blocking it in oh so fun various ways on a weekly basis.
It was fun, the first 3 times.
So anyway, that &lt;em&gt;really&lt;/em&gt; didn’t work out.
It went so badly we’ll never do it again.&lt;/p&gt;

&lt;p&gt;Were you curious why we have the Stack Overflow Blog at &lt;a href=&quot;https://stackoverflow.blog/&quot;&gt;https://stackoverflow.blog/&lt;/a&gt;?
Yep, security.
It’s hosted on an external service so that the marketing team and others can iterate faster.
To facilitate this, we needed it off the cookied domains.&lt;/p&gt;

&lt;p&gt;The above issues with meta subdomains also introduced related problems with &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security&quot;&gt;HSTS&lt;/a&gt;, &lt;a href=&quot;https://hstspreload.org/&quot;&gt;preloading&lt;/a&gt;, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includeSubDomains&lt;/code&gt; directive. 
But we’ll see why that’s become a moot point later.&lt;/p&gt;

&lt;h3 id=&quot;performance-http2&quot;&gt;Performance: HTTP/2&lt;/h3&gt;

&lt;p&gt;The conventional wisdom long ago was that HTTPS was slower. And it was.
But times change. We’re not talking about HTTPS anymore. We’re talking about HTTPS with HTTP/2.
While &lt;a href=&quot;https://http2.github.io/faq/#does-http2-require-encryption&quot;&gt;HTTP/2 doesn’t require encryption&lt;/a&gt;, &lt;em&gt;effectively&lt;/em&gt; it does. 
This is because the major browsers require a secure connection to enable most of its features.
You can argue specs and rules all day long, but browsers are the reality we all live in.
I wish they would have just called it HTTPS/2 and saved everyone a lot of time.
Dear browser makers, it’s not too late. 
Please, listen to reason, you’re our only hope!&lt;/p&gt;

&lt;p&gt;HTTP/2 has a lot of performance benefits, especially with pushing resources opportunistically to the user ahead of asking for them.
I won’t write in detail about those benefits, &lt;a href=&quot;https://hpbn.co/http2/&quot;&gt;Ilya Grigorik has done a fantastic job of that already&lt;/a&gt;. 
As a quick overview, the largest optimizations (for us) include:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://hpbn.co/http2/#request-and-response-multiplexing&quot;&gt;Request/Response Multiplexing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hpbn.co/http2/#server-push&quot;&gt;Server Push&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hpbn.co/http2/#header-compression&quot;&gt;Header Compression&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hpbn.co/http2/#stream-prioritization&quot;&gt;Stream Prioritization&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hpbn.co/http2/#one-connection-per-origin&quot;&gt;Fewer Origin Connections&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hey wait a minute, what about that silly certificate?&lt;/p&gt;

&lt;p&gt;A lesser-known feature of HTTP/2 is that &lt;a href=&quot;https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding&quot;&gt;you can push content not on the same domain&lt;/a&gt;, as long as certain criteria are met:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The origins resolve to the same server IP address.&lt;/li&gt;
  &lt;li&gt;The origins are covered by the same TLS certificate (bingo!)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, let’s take a peek at our current DNS:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;λ dig stackoverflow.com +noall +answer
; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.10.2-P3 &amp;lt;&amp;lt;&amp;gt;&amp;gt; stackoverflow.com +noall +answer
;; global options: +cmd
stackoverflow.com.      201     IN      A       151.101.1.69
stackoverflow.com.      201     IN      A       151.101.65.69
stackoverflow.com.      201     IN      A       151.101.129.69
stackoverflow.com.      201     IN      A       151.101.193.69

λ dig cdn.sstatic.net +noall +answer
; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.10.2-P3 &amp;lt;&amp;lt;&amp;gt;&amp;gt; cdn.sstatic.net +noall +answer
;; global options: +cmd
cdn.sstatic.net.        724     IN      A       151.101.193.69
cdn.sstatic.net.        724     IN      A       151.101.1.69
cdn.sstatic.net.        724     IN      A       151.101.65.69
cdn.sstatic.net.        724     IN      A       151.101.129.69
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Heyyyyyy, those IPs match, and they have the same certificate!
This means that we can get all the wins of HTTP/2 server pushes &lt;strong&gt;without harming HTTP/1.1 users&lt;/strong&gt;. 
HTTP/2 gets push and HTTP/1.1 gets &lt;a href=&quot;https://blog.stackpath.com/glossary/domain-sharding/&quot;&gt;domain sharding&lt;/a&gt; (via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sstatic.net&lt;/code&gt;).
We haven’t deployed server push quite yet, but all of this is in preparation.&lt;/p&gt;

&lt;p&gt;So in regards to performance, HTTPS is only a means to an end. 
And I’m okay with that. 
I’m okay saying that our primary drive is performance, and security for the site is not.
We want security, but security alone in our situation is not enough justification for the time investment needed to deploy HTTPS across our network.
When you combine all the factors above though, we can justify the immense amount of time and effort required to get this done.
In 2013, HTTP/2 wasn’t a big thing, but that changed as support increased and ultimately helped as a driver for us to invest time in HTTPS.&lt;/p&gt;

&lt;p&gt;It’s also worth noting that the HTTP/2 landscape changed quite a bit during our deployment.
The web moved from &lt;a href=&quot;https://en.wikipedia.org/wiki/SPDY&quot;&gt;SPDY&lt;/a&gt; to &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/2&quot;&gt;HTTP/2&lt;/a&gt; and &lt;a href=&quot;https://tools.ietf.org/id/draft-agl-tls-nextprotoneg-03.html&quot;&gt;NPN&lt;/a&gt; to &lt;a href=&quot;https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation&quot;&gt;ALPN&lt;/a&gt;. 
I won’t cover all that because we didn’t do anything there.
We watched, and benefited, but the giants of the web were driving all of that.
If you’re curious though, &lt;a href=&quot;https://blog.cloudflare.com/introducing-http2/&quot;&gt;Cloudflare has a good write up of these moves&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;haproxy-serving-up-https&quot;&gt;HAProxy: Serving up HTTPS&lt;/h3&gt;

&lt;p&gt;We deployed initial HTTPS support in HAProxy back in 2013.
Why &lt;a href=&quot;https://www.haproxy.org/&quot;&gt;HAProxy&lt;/a&gt;?
Because we’re already using it and they added support back in 2013 (released as GA in 2014) &lt;a href=&quot;https://www.haproxy.org/news.html&quot;&gt;with version 1.5&lt;/a&gt;.
We had, for a time, nginx in front of HAProxy (&lt;a href=&quot;/blog/2013/04/23/stackoverflow-com-the-road-to-ssl/&quot;&gt;as you can see in the last blog post&lt;/a&gt;).
But simpler is often better, and eliminating a lot of conntrack, deployment, and general complexity issues is usually a good idea.&lt;/p&gt;

&lt;p&gt;I won’t cover a lot of detail here because there’s simply not much to cover.
HAProxy supports HTTPS natively via OpenSSL since 1.5 and the configuration is straightforward.
Our configuration highlights are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Run on 4 processes
    &lt;ul&gt;
      &lt;li&gt;1 is dedicated to HTTP/front-end handling&lt;/li&gt;
      &lt;li&gt;2-4 are dedicated to HTTPS negotiation&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;HTTPS front-ends are connected to HTTP backends via &lt;a href=&quot;https://unix.stackexchange.com/a/206395/400&quot;&gt;an abstract named socket&lt;/a&gt;. This reduces overhead tremendously.&lt;/li&gt;
  &lt;li&gt;Each front-end or “tier” (we have 4: Primary, Secondary, Websockets, and dev) has corresponding :443 listeners.&lt;/li&gt;
  &lt;li&gt;We append request headers (and strip ones you’d send - nice try) when forwarding to the web tier to indicate how a connection came in.&lt;/li&gt;
  &lt;li&gt;We use the &lt;a href=&quot;https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility&quot;&gt;Modern compatibility cipher suite recommended by Mozilla&lt;/a&gt;. Note: this is not the same suite our CDN runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HAProxy was the relatively simple and first step of supporting a :443 endpoint with valid SSL certificates.
In retrospect, it was only a tiny spec of the effort needed.&lt;/p&gt;

&lt;p&gt;Here’s a logical layout of what I described above…and we’ll cover that little cloud in front next:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/HTTPS-Layout.svg&quot;&gt;&lt;img src=&quot;/blog/content/HTTPS-Layout.svg&quot; alt=&quot;Logical Architecture&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;cdnproxy-countering-latency-with-cloudflare--fastly&quot;&gt;CDN/Proxy: Countering Latency with Cloudflare &amp;amp; Fastly&lt;/h3&gt;

&lt;p&gt;One of the things I’m most proud of at Stack Overflow is &lt;a href=&quot;https://stackexchange.com/performance&quot;&gt;the efficiency&lt;/a&gt; of &lt;a href=&quot;/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/&quot;&gt;our stack&lt;/a&gt;.
That’s awesome, right? Running a major website on a small set of servers from one data center?
Nope. Not so much. Not this time.
While it’s awesome to be efficient for some things, when it comes to latency it suddenly becomes a problem.
We’ve never needed a lot of servers. 
We’ve never needed to expand to multiple locations (but yes, we have another for DR).
This time, that’s a problem. We can’t (yet!) solve fundamental problems with latency, due to the speed of light.
We’re told someone else is working on this, but there was a minor setback with tears in the fabric of space-time and losing the gerbils.&lt;/p&gt;

&lt;p&gt;When it comes to latency, let’s look at the numbers.
It’s almost exactly 40,000km around the equator (worst case for speed of light round-trip).
The &lt;a href=&quot;https://en.wikipedia.org/wiki/Speed_of_light&quot;&gt;speed of light&lt;/a&gt; is 299,792,458 meters/second &lt;strong&gt;in a vacuum&lt;/strong&gt;.
Unfortunately, a lot of people use this number, but most fiber isn’t in a vacuum.
Realistically, most optical fiber is &lt;a href=&quot;https://physics.stackexchange.com/q/80043/653&quot;&gt;30-31% slower&lt;/a&gt;.
So we’re looking at (40,075,000 m) / (299,792,458 m/s * .70) = 0.191 seconds, or 191ms for a round-trip in the worst case, right?
Well…no, not really. 
That’s also assuming an optimal path, but going between two destinations on the internet is very rarely a straight line.
There are routers, switches, buffers, processor queues, and all sorts of additional little delays in the way. 
They add up to measurable latency.
Let’s not even talk about Mars, yet.&lt;/p&gt;

&lt;p&gt;So why does that matter to Stack Overflow?
This is an area where the cloud wins.
It’s very likely that the server you’re hitting with a cloud provider is relatively local.
With us, it’s not.
With a direct connection, the further you get away from our New York or Denver data centers (whichever one is active), the slower your experience gets.
When it comes to HTTPS, there’s an &lt;em&gt;additional&lt;/em&gt; round trip to negotiate the connection before any data is sent.
That’s under the best of circumstances (though &lt;a href=&quot;https://blog.cloudflare.com/introducing-0-rtt/&quot;&gt;that’s improving with TLS 1.3 and 0-RTT&lt;/a&gt;).
And &lt;a href=&quot;https://twitter.com/igrigorik&quot;&gt;Ilya Grigorik&lt;/a&gt; has &lt;a href=&quot;https://istlsfastyet.com/&quot;&gt;a great summary here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; and &lt;a href=&quot;https://www.fastly.com/&quot;&gt;Fastly&lt;/a&gt;.
HTTPS wasn’t a project deployed in a silo; as you read on, you’ll see that several other projects multiplex in along the way.
In the case of a local-to-the-user HTTPS termination endpoint (to minimize that round trip duration), we were looking for a few main criteria:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Local HTTPS termination&lt;/li&gt;
  &lt;li&gt;DDoS protection&lt;/li&gt;
  &lt;li&gt;CDN functionality&lt;/li&gt;
  &lt;li&gt;Performance equivalent or better than direct-to-us&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;preparing-for-a-proxy-client-timings&quot;&gt;Preparing for a Proxy: Client Timings&lt;/h3&gt;

&lt;p&gt;Before moving to any proxy, testing for performance had to be in place.
To do this, we set up a full pipeline of timings to get performance metrics from browsers.
For years now, browsers have included performance timings accessible via JavaScript, &lt;a href=&quot;https://www.w3.org/TR/navigation-timing/&quot;&gt;via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.performance&lt;/code&gt;&lt;/a&gt;.
Go ahead, open up the inspector and try it!
We want to be very transparent about this, that’s why details have &lt;a href=&quot;https://teststackoverflow.com/&quot;&gt;been available on teststackoverflow.com&lt;/a&gt; since day 1.
There’s no sensitive data transferred, only the URIs of resources &lt;em&gt;directly&lt;/em&gt; loaded by the page and their timings.
For each page load recorded, we get timings that look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/HTTPS-Teststackoverflow.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/HTTPS-Teststackoverflow.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/HTTPS-Teststackoverflow.png&quot; /&gt;&lt;img src=&quot;/blog/content/HTTPS-Teststackoverflow.png&quot; loading=&quot;lazy&quot; alt=&quot;teststackoverflow.com&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Currently we attempt to record performance timings from 5% of traffic.
The process isn’t that complicated, but all the pieces had to be built:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Transform the timings into JSON&lt;/li&gt;
  &lt;li&gt;Upload the timings after a page load completes&lt;/li&gt;
  &lt;li&gt;Relay those timings to our backend Traffic Processing Service (it has reports)&lt;/li&gt;
  &lt;li&gt;Store those timings in a &lt;a href=&quot;http://www.nikoport.com/columnstore/&quot;&gt;clustered columnstore&lt;/a&gt; in SQL Server&lt;/li&gt;
  &lt;li&gt;Relay aggregates of the timings to &lt;a href=&quot;https://bosun.org/&quot;&gt;Bosun&lt;/a&gt; (via &lt;a href=&quot;https://github.com/bretcope/BosunReporter.NET&quot;&gt;BosunReporter.NET&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The end result is we now have a great real-time overview of &lt;em&gt;actual&lt;/em&gt; user performance all over the world that we can readily view, alert on, and use for evaluating any changes.
Here’s a view of timings coming in live:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/HTTPS-ClientTimings.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/HTTPS-ClientTimings.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/HTTPS-ClientTimings.png&quot; /&gt;&lt;img src=&quot;/blog/content/HTTPS-ClientTimings.png&quot; loading=&quot;lazy&quot; alt=&quot;Client Timings Dashboard&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily, we have enough sustained traffic to get useful data here.
At this point, we have over 5 billion points (and growing) of data to help drive decisions.
Here’s a quick overview of that data:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/HTTPS-ClientTimingsDatabase.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/HTTPS-ClientTimingsDatabase.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/HTTPS-ClientTimingsDatabase.png&quot; /&gt;&lt;img src=&quot;/blog/content/HTTPS-ClientTimingsDatabase.png&quot; loading=&quot;lazy&quot; alt=&quot;Client Timings Database&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, so now we have our baseline data. 
Time to test candidates for our CDN/Proxy setup.&lt;/p&gt;

&lt;h3 id=&quot;cloudflare&quot;&gt;Cloudflare&lt;/h3&gt;

&lt;p&gt;We evaluated many CDN/DDoS proxy providers.
We picked &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; based on their infrastructure, responsiveness, and the promise of &lt;a href=&quot;https://www.cloudflare.com/website-optimization/railgun/&quot;&gt;Railgun&lt;/a&gt;.
So how can we do test what life &lt;em&gt;would&lt;/em&gt; be like behind Cloudflare all over the world?
How many servers would we need to set up to get enough data points?
None!&lt;/p&gt;

&lt;p&gt;Stack Overflow has an excellent resource here: billions of hits a month. 
Remember those client timings we just talked about?
We already have tens of millions of users hitting us every day, so why don’t we ask them?
We can do just that, by embedding an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; in Stack Overflow pages.
Cloudflare was already our &lt;a href=&quot;https://cdn.sstatic.net/&quot;&gt;cdn.sstatic.net&lt;/a&gt; host (our shared, cookieless static content domain) from earlier.
But, this was done with &lt;a href=&quot;https://en.wikipedia.org/wiki/CNAME_record&quot;&gt;a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt; DNS record&lt;/a&gt;, we served the DNS which pointed at their DNS.
To use Cloudflare as a proxy though, we needed them to serve our DNS.
So first, we needed to test performance of their DNS.&lt;/p&gt;

&lt;p&gt;Practically speaking, to test performance we needed to delegate a second-level domain to them, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;something.stackoverflow.com&lt;/code&gt;, which would have different &lt;a href=&quot;https://wiki.gandi.net/en/glossary/glue-record&quot;&gt;glue records&lt;/a&gt; and sometimes isn’t handled the same way (causing 2 lookups).
To clarify, &lt;a href=&quot;https://en.wikipedia.org/wiki/Top-level_domain&quot;&gt;Top-Level Domains (TLDs)&lt;/a&gt; are things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.com&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.net&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.org&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.dance&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.duck&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.fail&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gripe&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.here&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.horse&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ing&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.kim&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.lol&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ninja&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.pink&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.red&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vodka&lt;/code&gt;. and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.wtf&lt;/code&gt;.
Nope, &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains&quot;&gt;I’m not kidding&lt;/a&gt; (and &lt;a href=&quot;https://www.iana.org/domains/root/db&quot;&gt;here’s the full list&lt;/a&gt;).
&lt;a href=&quot;https://en.wikipedia.org/wiki/Second-level_domain&quot;&gt;Second-Level Domains (SLDs)&lt;/a&gt; are one level below, what most sites would be: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;superuser.com&lt;/code&gt;, etc.
That’s what we need to test the behavior and performance of.
Thus, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;teststackoverflow.com&lt;/code&gt; was born.
With this new domain, we could test DNS performance all over the world.
By embedding the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; for a certain percentage of visitors (we turned it on and off for each test), we could easily get data from each DNS and hosting configuration.&lt;/p&gt;

&lt;p&gt;Note that it’s important to test for ~24hours at a minimum here. 
The behavior of the internet changes throughout the day as people are awake or asleep or streaming Netflix all over the world as it rolls through time zones.
So to measure a single country, you really want a full day. 
Within weekdays, preferably (e.g. not half into a Saturday).
Also be aware that shit happens. 
It happens all the time. 
The performance of the internet is not a stable thing, we’ve got the data to prove it.&lt;/p&gt;

&lt;p&gt;Our initial assumptions going into this was we’d lose some page load performance going through Cloudflare (an extra hop almost always adds latency), but we’d make it up with the increases in DNS performance.
The DNS side of this paid off. 
Cloudflare had DNS servers far more local to the users than we do in a single data center.
The performance there was far better.
I hope that we can find the time to release this data soon. 
It’s just a lot to process (and host), and time isn’t something I have in ample supply right now.&lt;/p&gt;

&lt;p&gt;Then we began testing page load performance by proxying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;teststackoverflow.com&lt;/code&gt; through Cloudflare, again in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;.
We saw the US and Canada slightly slower (due to the extra hop), but the rest of the world on par or better.
This lined up with expectations overall, and we proceeded with a move behind Cloudflare’s network.
A few DDoS attacks along the way sped up this migration a bit, but that’s another story.
Why did we accept slightly slower performance in the US and Canada? 
Well at ~200-300ms page loads for most pages, that’s still pretty damn fast.
But we don’t like to lose.
We thought &lt;a href=&quot;https://www.cloudflare.com/website-optimization/railgun/&quot;&gt;Railgun&lt;/a&gt; would help us win that performance back.&lt;/p&gt;

&lt;p&gt;Once all the testing panned out, we needed to put the pieces in for DDoS protection.
This involved installing additional, dedicated ISPs in our data center for the CDN/Proxy to connect to.
After all, DDoS protection via a proxy isn’t very effective if you can just go around it.
This meant we were serving off of 4 ISPs per data center now, with 2 sets of routers, all running &lt;a href=&quot;https://en.wikipedia.org/wiki/Border_Gateway_Protocol&quot;&gt;BGP&lt;/a&gt; with full tables.
It also meant 2 new load balancers, dedicated to CDN/Proxy traffic.&lt;/p&gt;

&lt;h4 id=&quot;cloudflare-railgun&quot;&gt;Cloudflare: Railgun&lt;/h4&gt;

&lt;p&gt;At the time, this setup also meant 2 more boxes just for &lt;a href=&quot;https://www.cloudflare.com/website-optimization/railgun/&quot;&gt;Railgun&lt;/a&gt;. 
The way Railgun works is by caching the last result of that URL in &lt;a href=&quot;https://memcached.org/&quot;&gt;memcached&lt;/a&gt; locally and on Cloudflare’s end.
When Railgun is enabled, every page (under a size threshold) is cached on the way out.
On the next request, if the entry was in Cloudflare’s edge cache and our cache (keyed by URL), we still ask the web server for it.
But instead of sending the whole page back to Cloudflare, it only sends a diff.
That diff is applied to their cache and served back to the client.
By nature of the pipe, it also meant the &lt;a href=&quot;https://en.wikipedia.org/wiki/Gzip&quot;&gt;gzip compression&lt;/a&gt; for transmission moved from 9 web servers for Stack Overflow to the 1 active Railgun box…so this had to be a pretty CPU-beefy machine.
I point this out because all of this had to be evaluated, purchased and deployed on our way.&lt;/p&gt;

&lt;p&gt;As an example, think about 2 users viewing a question. 
Take a picture of each browser.
They’re &lt;em&gt;almost&lt;/em&gt; the same page, so that’s a very small diff.
It’s a huge optimization if we can send only that diff down most of the journey to the user.&lt;/p&gt;

&lt;p&gt;Overall, the goal here is to reduce the amount of data sent back in hopes of a performance win.
And when it worked, that was indeed the case.
Railgun also had another huge advantage: requests weren’t fresh connections.
Another consequence of latency is the duration and speed of the ramp up of &lt;a href=&quot;https://en.wikipedia.org/wiki/TCP_congestion_control#Slow_start&quot;&gt;TCP slow start&lt;/a&gt;, part of the congestion control that keeps the Internet flowing.
Railgun maintains a constant connection to Cloudflare edges and multiplexes user requests, all of them over a pre-primed connection not heavily delayed by slow start.
The smaller diffs also lessened the need for ramp up overall.&lt;/p&gt;

&lt;p&gt;Unfortunately, we never got Railgun to work without issues in the long run. 
To my knowledge, we were (at the time) the largest deployment of the technology and we stressed it further than it has been pushed before.
Though we tried to troubleshoot it for over a year, we ultimately gave up and moved on.
It simply wasn’t saving us more than it was costing us in the end.
It’s been several years now, though.
If you’re evaluating Railgun, you should evaluate the current version, with &lt;a href=&quot;https://www.cloudflare.com/docs/railgun/changelog.html&quot;&gt;the improvements they’ve made&lt;/a&gt;, and decide for yourself.&lt;/p&gt;

&lt;h3 id=&quot;fastly&quot;&gt;Fastly&lt;/h3&gt;

&lt;p&gt;Moving to &lt;a href=&quot;https://www.fastly.com/&quot;&gt;Fastly&lt;/a&gt; was relatively recent, but since we’re on the CDN/Proxy topic I’ll cover it now.
The move itself wasn’t terribly interesting because most of the pieces needed for any proxy were done in the Cloudflare era above.
But of course everyone will ask: why did we move?
While Cloudflare was very appealing in many regards - mainly, many data centers, stable bandwidth pricing, and included DNS - it wasn’t the best fit for us anymore.
We needed a few things that Fastly simply did to fit us better: more flexibility at the edge, faster change propagation, and the ability to fully automate configuration pushes.
That’s not to say Cloudflare is bad, it was just no longer the best fit for Stack Overflow.&lt;/p&gt;

&lt;p&gt;Since actions speak louder: If I didn’t think highly of Cloudflare, my personal blog wouldn’t be behind them right now.
Hi there! You’re reading it.&lt;/p&gt;

&lt;p&gt;The main feature of Fastly that was so compelling to us was &lt;a href=&quot;https://en.wikipedia.org/wiki/Varnish_(software)&quot;&gt;Varnish&lt;/a&gt; and the &lt;a href=&quot;https://docs.fastly.com/guides/vcl/&quot;&gt;VCL&lt;/a&gt;.
This makes the edge highly configurable.
So features that Cloudflare couldn’t readily implement (as they might affect &lt;em&gt;all&lt;/em&gt; customers), we could do ourselves.
This is simply a different architectural approach to how these two companies work, and the highly-configurable-in-code approach suits us very well.
We also liked how open they were with details of infrastructure at conferences, in chats, etc.&lt;/p&gt;

&lt;p&gt;Here’s an example of where VCL comes in &lt;em&gt;very&lt;/em&gt; handy. 
Recently we deployed .NET 4.6.2 which had a &lt;a href=&quot;https://github.com/Microsoft/dotnet/issues/330&quot;&gt;very nasty bug&lt;/a&gt; that set max-age on cache responses to over 2000 years.
The quickest way to mitigate this for all of our services affected was to override that cache header as-needed at the edge.
As I write this, the following VCL is active:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;vcl_fetch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;beresp.http.Cache-Control&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req.url.path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;^/users/flair/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;beresp.http.Cache-Control&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;public, max-age=180&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;beresp.http.Cache-Control&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;private&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This allows us to cache user flair for 3 minutes (since it’s a decent volume of bytes), and bypass everything else. 
This is an easy-to-deploy global solution to workaround an urgent cache poisoning problem across all applications.
We’re very, very happy with all the things we’re able to do at the edge now.
Luckily we have &lt;a href=&quot;https://twitter.com/alioth&quot;&gt;Jason Harvey&lt;/a&gt; who picked up the VCL bits and wrote automated-pushed of our configs.
We had to improve on existing libraries in Go here, so check out &lt;a href=&quot;https://github.com/alienth/fastlyctl&quot;&gt;fastlyctl&lt;/a&gt;, another open source bit to come out of this.&lt;/p&gt;

&lt;p&gt;Another important facet of Fastly (that Cloudflare also had, but we never utilized due to cost) is using your own certificate.
As we covered earlier, we’re already using this in preparation for HTTP/2 pushes.
But, Fastly doesn’t do something Cloudflare does: DNS.
So we need to solve that now.
Isn’t this dependency chain fun?&lt;/p&gt;

&lt;h3 id=&quot;global-dns&quot;&gt;Global DNS&lt;/h3&gt;

&lt;p&gt;When moving from Cloudflare to Fastly, we had to evaluate and deploy new (to us) global DNS providers.
That in itself in an entirely different post, &lt;a href=&quot;http://blog.serverfault.com/2017/01/09/surviving-the-next-dns-attack/&quot;&gt;one that’s been written&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/thefarseeker&quot;&gt;Mark Henderson&lt;/a&gt;.
Along the way, we were also controlling:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Our own DNS servers (still up as a fall back)&lt;/li&gt;
  &lt;li&gt;Name.com servers (for redirects not needing HTTPS)&lt;/li&gt;
  &lt;li&gt;Cloudflare DNS&lt;/li&gt;
  &lt;li&gt;Route 53 DNS&lt;/li&gt;
  &lt;li&gt;Google DNS&lt;/li&gt;
  &lt;li&gt;Azure DNS&lt;/li&gt;
  &lt;li&gt;…and several others (for testing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a whole project in itself. 
We had to come up with  means to do this efficiently, and so &lt;a href=&quot;http://blog.serverfault.com/2017/04/11/introducing-dnscontrol-dns-as-code-has-arrived/&quot;&gt;DNSControl was born&lt;/a&gt;.
This is now an &lt;a href=&quot;https://stackexchange.github.io/dnscontrol/&quot;&gt;open source project&lt;/a&gt;, &lt;a href=&quot;https://github.com/StackExchange/dnscontrol&quot;&gt;available on GitHub&lt;/a&gt;, written in &lt;a href=&quot;https://golang.org/&quot;&gt;Go&lt;/a&gt;.
In short: we push a change in the JavaScript config to git, and it’s deployed worldwide in under a minute.
Here’s a sample config from one of our simpler-in-DNS sites, &lt;a href=&quot;https://askubuntu.com/&quot;&gt;askubuntu.com&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;askubuntu.com&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;REG_NAMECOM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;DnsProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;R53&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;DnsProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;GOOGLECLOUD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;SPF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;TXT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;google-site-verification=PgJFv7ljJQmUa7wupnJgoim3Lx22fbQzyhES7-Q9cv8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// webmasters&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ADDRESS24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FASTLY_ON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;CNAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;www&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;CNAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chat.stackexchange.com.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ADDRESS24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FASTLY_ON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Okay great, how do you test that all of this is working?
Client Timings! 
The &lt;a href=&quot;#preparing-for-a-proxy-client-timings&quot;&gt;ones we covered above&lt;/a&gt; let us test all of this DNS deployment with real-world data, not simulations. 
But we also need to test that everything &lt;em&gt;just works&lt;/em&gt;.&lt;/p&gt;

&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;

&lt;p&gt;Client Timings in deploying the above was very helpful for testing performance.
But it wasn’t good for testing configuration.
After all, Client Timings is awesome for seeing the result, but most configuration missteps result in no page load, and therefore no timings at all.
So we had to build &lt;a href=&quot;https://godoc.org/github.com/StackExchange/httpunit&quot;&gt;httpUnit&lt;/a&gt; (yes, the team figured out &lt;a href=&quot;http://httpunit.sourceforge.net/&quot;&gt;the naming conflict&lt;/a&gt; later…).
This is now another &lt;a href=&quot;https://github.com/StackExchange/httpunit&quot;&gt;open source project&lt;/a&gt; written in Go.
An example config for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;teststackoverflow.com&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[[plan]]&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;teststackoverflow_com&quot;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;http://teststackoverflow.com&quot;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;ips&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;[&quot;28i&quot;]&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;title&amp;gt;Test Stack Overflow Domain&amp;lt;/title&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;tags&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;[&quot;so&quot;]&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;[[plan]]&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;tls_teststackoverflow_com&quot;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://teststackoverflow.com&quot;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;ips&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;[&quot;28&quot;]&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;title&amp;gt;Test Stack Overflow Domain&amp;lt;/title&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;py&quot;&gt;tags&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;[&quot;so&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It was important to test as we changed firewalls, certificates, bindings, redirects, etc. along the way.
We needed to make sure every change was good before we activated it for users (by deploying it on our secondary load balancers first).
httpUnit is what allowed us to do that and run an integration test suite to ensure we had no regressions.&lt;/p&gt;

&lt;p&gt;There’s another tool we developed internally (by our lovely &lt;a href=&quot;https://twitter.com/yesthattom&quot;&gt;Tom Limoncelli&lt;/a&gt;) for more easily managing &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_IP_address&quot;&gt;Virtual IP Address&lt;/a&gt; groups on our load balancers.
We test on the inactive load balancer via a secondary range, then move all traffic over, leaving the previous master in a known-good state.
If anything goes wrong, we flip back.
If everything goes right (yay!), we apply changes to that load balancer as well.
This tool is called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;keepctl&lt;/code&gt; (short for keepalived control) - look for this to be open sourced as soon as time allows.&lt;/p&gt;

&lt;h3 id=&quot;preparing-the-applications&quot;&gt;Preparing the Applications&lt;/h3&gt;

&lt;p&gt;Almost all of the above has been just the infrastructure work.
This is generally done by a team of &lt;a href=&quot;http://stackoverflow.com/company/team#Engineering&quot;&gt;several other Site Reliability Engineers at Stack Overflow&lt;/a&gt; and I getting things situated.
There’s also so much more that needed doing inside the applications themselves.
It’s a long list. I’d grab some coffee and a Snickers.&lt;/p&gt;

&lt;p&gt;One important thing to note here is that &lt;a href=&quot;/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/&quot;&gt;the architecture of Stack Overflow &amp;amp; Stack Exchange&lt;/a&gt; Q&amp;amp;A sites is &lt;a href=&quot;https://en.wikipedia.org/wiki/Multitenancy&quot;&gt;multi-tenant&lt;/a&gt;.
This means that if you hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;superuser.com&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bicycles.stackexchange.com&lt;/code&gt;, you’re hitting the &lt;em&gt;exact&lt;/em&gt; same thing.
You’re hitting the &lt;em&gt;exact same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w3wp.exe&lt;/code&gt; process on the exact same server&lt;/em&gt;.
Based on &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host&quot;&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host&lt;/code&gt; header&lt;/a&gt; the browser sends, we change the context of the request.
Several pieces of what follows will be clearer if you understand &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Current.Site&lt;/code&gt; in our code is the site &lt;em&gt;of the request&lt;/em&gt;.
Things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Current.Site.Url()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Current.Site.Paths.FaviconUrl&lt;/code&gt; are all driven off this core concept.&lt;/p&gt;

&lt;p&gt;Another way to make this concept/setup clearer: we can run the entire Q&amp;amp;A network off of a single process on a single server and you wouldn’t know it.
We run a single process today on each of 9 servers purely for rolling builds and redundancy.&lt;/p&gt;

&lt;h4 id=&quot;global-login&quot;&gt;Global Login&lt;/h4&gt;

&lt;p&gt;Quite a few of these projects seemed like good ideas on their own (and they were), but were part of a bigger HTTPS picture.
Login was one of those projects.
I’m covering it first, because it was rolled out much earlier than the other changes below.&lt;/p&gt;

&lt;p&gt;For the first 5-6 years Stack Overflow (and Stack Exchange) existed, you logged into a particular site.
As an example, each of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackexchange.com&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gaming.stackexchange.com&lt;/code&gt; had their own per-site cookies.
Of note here: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.gaming.stackexchange.com&lt;/code&gt;’s login depended on the cookie from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gaming.stackexchange.com&lt;/code&gt; flowing to the subdomain. 
These are the “meta” sites we talked about with certificates earlier.
Their logins were tied together, you always logged in through the parent.
This didn’t really matter much technically, but from a user experience standpoint it sucked. You had to login to each site.
&lt;a href=&quot;https://stackoverflow.blog/2010/09/11/global-network-auto-login/&quot;&gt;We “fixed” that&lt;/a&gt; with “global auth”, which was an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; in the page that logged everyone in through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackauth.com&lt;/code&gt; if they were logged in elsewhere.
Or it tried to.
The experience was decent, but a popup bar telling you to click to reload and be logged in wasn’t really awesome. 
We could do better.
Oh and ask &lt;a href=&quot;https://twitter.com/kevinmontrose&quot;&gt;Kevin Montrose&lt;/a&gt; about mobile Safari private mode. I dare you.&lt;/p&gt;

&lt;p&gt;Enter “Universal Login”. Why the name “Universal”? Because global was taken. We’re simple people.
Luckily, cookies are also pretty simple.
A cookie present on a parent domain (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackexchange.com&lt;/code&gt;) will be sent by your browser to all subdomains (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gaming.stackexchange.com&lt;/code&gt;).
When you zoom out from our network, we have only a handful of second-level domains:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://askubuntu.com/&quot;&gt;askubuntu.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://mathoverflow.net/&quot;&gt;mathoverflow.net&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://serverfault.com/&quot;&gt;serverfault.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackapps.com/&quot;&gt;stackapps.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackexchange.com/&quot;&gt;stackexchange.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/&quot;&gt;stackoverflow.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://superuser.com/&quot;&gt;superuser.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, we have other domains that redirect to these, like &lt;a href=&quot;http://askdifferent.com&quot;&gt;askdifferent.com&lt;/a&gt;. 
But they’re only redirects and don’t have cookies or logged-in users.&lt;/p&gt;

&lt;p&gt;There’s a lot of backend work that I’m glossing over here (props to &lt;a href=&quot;https://twitter.com/superdalgas&quot;&gt;Geoff Dalgas&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/aalear&quot;&gt;Adam Lear&lt;/a&gt; especially), but the general gist is that when you login, we set a cookie on these domains.
We do this via third-party cookies and &lt;a href=&quot;https://en.wikipedia.org/wiki/Cryptographic_nonce&quot;&gt;nonces&lt;/a&gt;.
When you login to any of the above domains, 6 cookies are issued via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags on the destination page for the other domains, effectively logging you in.
This doesn’t work &lt;em&gt;everywhere&lt;/em&gt; (in particular, mobile safari is quirky), but it’s a vast improvement over previous.&lt;/p&gt;

&lt;p&gt;The client code isn’t complicated, here’s what it looks like:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/users/login/universal/request&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arrayId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/users/login/universal.gif?authToken=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; 
          &lt;span class=&quot;nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;amp;nonce=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Nonce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#footer&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;lt;img style=&quot;display:none&quot; src=&quot;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&quot;&amp;gt;&amp;lt;/img&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…but to do this, we have to move to Account-level authentication (it was previously user level), change how cookies are viewed, change how child-meta login works, and also provide integration for these new bits to other applications.
For example, Careers (now Talent and Jobs) is a different codebase.
We needed to make those applications view the cookies and call into the Q&amp;amp;A application via an API to get the account.
We deploy this via a NuGet library to minimize repeated code.
Bottom line: you login once and you are logged into all domains. No messages, no page reloads.&lt;/p&gt;

&lt;p&gt;For the technical side, we now don’t have to worry about where the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.*.stackexchange.com&lt;/code&gt; domains are. 
As long as they’re under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackexchange.com&lt;/code&gt;, we’re good. 
While on the surface this had nothing to do with HTTPS, it allowed us to move things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.gaming.stackexchange.com&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gaming.meta.stackexchange.com&lt;/code&gt; without any interruptions to users.
It’s one giant, really ugly puzzle.&lt;/p&gt;

&lt;h4 id=&quot;local-https-development&quot;&gt;Local HTTPS Development&lt;/h4&gt;

&lt;p&gt;To make any kind of progress here, local environments need to match dev and production as much as possible.
Luckily, we’re on IIS which makes this fairly straightforward to do.
There’s a tool we use to setup developer environments called “dev local setup” because, again, we’re simple people.
It installs tooling (Visual Studio, git, SSMS, etc.), services (SQL Server, Redis, Elasticsearch), repositories, databases, websites, and a few other bits.
We had the basic tooling setup, we just needed to add SSL/TLS certs.
An abbreviated setup for Core looks like this:&lt;/p&gt;
&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Websites&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;StackOverflow&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Site&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;local.mse.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Aliases&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;discuss.local.area51.lse.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;local.sstatic.net&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Databases&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Sites.Database&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Local.StackExchange.Meta&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Local.Area51&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Local.Area51.Meta&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Certificate&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;StackExchange.Website&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Site&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;local.lse.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Databases&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Sites.Database&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Local.StackExchange&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Local.StackExchange.Meta&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Local.Area51.Meta&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Certificate&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And the code that uses this &lt;a href=&quot;https://gist.github.com/NickCraver/6b5e75c153d60d0df5b0970d52412d4e&quot;&gt;I’ve put in a gist here: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Register-Websites.psm1&lt;/code&gt;&lt;/a&gt;.
We setup our websites via host headers (adding those in aliases), give them certificates if directed (hmmm, we should default this to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$true&lt;/code&gt; now…), and grant those AppPool accounts access to the databases.
Okay, so now we’re set to develop against &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; locally.
Yes, I know - we really should open source this setup, but we have to strip out some specific-to-us bits in a fork somehow.
One day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is this important?&lt;/strong&gt;
Before this, we loaded static content from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/content&lt;/code&gt;, not from another domain.
This was convenient, but also hid issues like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS&quot;&gt;Cross-Origin Requests (or CORS)&lt;/a&gt;.
What may load just fine on the same domain on the same protocol may readily fail in dev and production.
&lt;a href=&quot;https://blog.codinghorror.com/the-works-on-my-machine-certification-program/&quot;&gt;“It works on my machine.”&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By having a CDN and app domains setup with the same protocols and layout we have in production, we find and fix many more issues before they leave a developer’s machine.
For example, did you know that when going from an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; page to an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; one, &lt;a href=&quot;https://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3&quot;&gt;the browser does not send the referer&lt;/a&gt;?
It’s a security issue, there could be sensitive bits in the URL that would be sent over &lt;span title=&quot;We saw this was a typo and opted to leave it.&quot;&gt;paintext&lt;/span&gt; in the referer header.&lt;/p&gt;

&lt;p&gt;“That’s bullshit Nick, we get Google referers!”
Well, yes. You do. But because they &lt;em&gt;explicitly opt into it&lt;/em&gt;. If you look at the Google search page, you’ll find this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; directive:&lt;/p&gt;
&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;origin&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mref&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;referrer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;…and that’s why you get it from them.&lt;/p&gt;

&lt;p&gt;Okay, we’re setup to build some stuff, where do we go from here?&lt;/p&gt;

&lt;h3 id=&quot;mixed-content-from-you&quot;&gt;Mixed Content: From You&lt;/h3&gt;

&lt;p&gt;This one has a simple label with a lot of implications for a site with user-submitted content.
What kind of mixed content problems had we accumulated over the years?
Unfortunately, quite a few.
Here’s the list of user-submitted content we had to tackle:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; images in &lt;a href=&quot;https://stackoverflow.com/questions&quot;&gt;questions&lt;/a&gt;, answers, &lt;a href=&quot;https://stackoverflow.com/tags&quot;&gt;tags&lt;/a&gt;, wikis, etc. (all post types)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; avatars&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; avatars in chat (which appear on the site in the sidebar)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; images in “about me” sections of profiles&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; images in &lt;a href=&quot;https://stackoverflow.com/help&quot;&gt;help center articles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; YouTube videos (some sites have this enabled, like &lt;a href=&quot;https://gaming.stackexchange.com/&quot;&gt;gaming.stackexchange.com&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; images in &lt;a href=&quot;https://stackoverflow.com/help/privileges&quot;&gt;privilege descriptions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; images in &lt;a href=&quot;http://stackoverflow.com/users/story/13249&quot;&gt;developer stories&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; images in &lt;a href=&quot;https://stackoverflow.com/jobs&quot;&gt;job descriptions&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; images in &lt;a href=&quot;https://stackoverflow.com/jobs/companies&quot;&gt;company pages&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; sources in &lt;a href=&quot;https://meta.stackoverflow.com/q/269753/13249&quot;&gt;JavaScript snippets&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these had specific problems attached, I’ll stick to the interesting bits here.
Note: each of the solutions I’m talking about has to be scaled to run across hundreds of sites and databases given our architecture.&lt;/p&gt;

&lt;p&gt;In each of the above cases (except snippets), there was a common first step to eliminating mixed content.
You need to eliminate &lt;em&gt;new&lt;/em&gt; mixed content. Otherwise, all cleanups continue indefinitely.
Plug the hole, then drain the ship.
To that end, &lt;a href=&quot;https://meta.stackexchange.com/q/291947/135201&quot;&gt;we started enforcing only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;-only image embeds across the network&lt;/a&gt;.
Once that was done and holes were plugged, we could get to work cleaning up.&lt;/p&gt;

&lt;p&gt;For images in questions, answers, and other post types we had to do a lot of analysis and see what path to take.
First, we tackled the known 90%+ case: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stack.imgur.com&lt;/code&gt;.
Stack Overflow has its own hosted instance of Imgur since before my time.
When you upload an image with our editor, it goes there.
The vast majority of posts take this approach, and they added proper HTTPS support for us years ago.
This was a straight-forward find and replace re-bake (what we call re-processing post markdown) across the board.&lt;/p&gt;

&lt;p&gt;Then, we analyzed all the remaining image paths via our &lt;a href=&quot;https://www.elastic.co/&quot;&gt;Elasticsearch&lt;/a&gt; index of all content.
And by we, I mean &lt;a href=&quot;https://twitter.com/m0sa&quot;&gt;Samo&lt;/a&gt;. He put in a ton of work on mixed-content throughout this.
After seeing that many of the most repetitive domains actually supported HTTPS, we decided to:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Try each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;img&amp;gt;&lt;/code&gt; source on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; instead. If that worked, replace the link in the post.&lt;/li&gt;
  &lt;li&gt;If the source didn’t support &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;, convert it to a link.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But of course, that didn’t &lt;em&gt;actually&lt;/em&gt; just work. 
It turns out the regex to match URLs in posts was broken for years and no one noticed…so we fixed that and re-indexed first.
Oops.&lt;/p&gt;

&lt;p&gt;We’ve been asked: “why not just proxy it?”
Well, that’s a legally and ethically gray area for much of our content.
For example, we have photographers on &lt;a href=&quot;https://photo.stackexchange.com/&quot;&gt;photo.stackexchange.com&lt;/a&gt; that explicitly do not use Imgur to retain all rights.
Totally understandable.
If we start proxying and caching &lt;em&gt;the full image&lt;/em&gt;, that gets legally tricky at best really quick.
It turns out that out of millions of image embeds on the network, only a few thousand both didn’t support &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; and weren’t already 404s anyway.
So, we elected to not build a complicated proxy setup. The percentages (far less than 1%) just didn’t come anywhere close to justifying it.&lt;/p&gt;

&lt;p&gt;We did &lt;em&gt;research&lt;/em&gt; building a proxy though.
What would it cost?
How much storage would we need?
Do we have enough bandwidth?
We found estimates to all these questions, with some having various answers.
For example, do we use Fastly site shield, or take the bandwidth brunt over the ISP pipes?
Which option is faster?
Which option is cheaper?
Which option scales?
Really, that’s another blog post all by itself, but if you have specific questions ask them in comments and I’ll try to answer.&lt;/p&gt;

&lt;p&gt;Luckily, along the way &lt;a href=&quot;https://twitter.com/balpha&quot;&gt;balpha&lt;/a&gt; had revamped YouTube embeds to fix a few things with HTML5.
The rebake forced &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; for all as a side effect, yay! All done.&lt;/p&gt;

&lt;p&gt;The rest of the content areas were the same story: kill new mixed-content coming in, and replace what’s there.
This required changes in the following code areas:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Posts&lt;/li&gt;
  &lt;li&gt;Profiles&lt;/li&gt;
  &lt;li&gt;Dev Stories&lt;/li&gt;
  &lt;li&gt;Help Center&lt;/li&gt;
  &lt;li&gt;Jobs/Talent&lt;/li&gt;
  &lt;li&gt;Company Pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disclaimer: JavaScript snippets remains unsolved. It’s not so easy because:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The resource you want may not be available over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; (e.g. a library)&lt;/li&gt;
  &lt;li&gt;Due it being JavaScript, you could just construct any URL you want. This is basically impossible to check for.
    &lt;ul&gt;
      &lt;li&gt;If you have a clever way to do this, &lt;strong&gt;please tell us&lt;/strong&gt;. We’re stuck on usability vs. security on that one.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;mixed-content-from-us&quot;&gt;Mixed Content: From Us&lt;/h3&gt;

&lt;p&gt;Problems don’t stop at user-submitted content.
We have a fair bit of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; baggage as well.
While the moves of these things aren’t particularly interesting, in the interest of “what took so long?” they’re at least worth enumerating:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Ad Server (Calculon)&lt;/li&gt;
  &lt;li&gt;Ad Server (Adzerk)&lt;/li&gt;
  &lt;li&gt;Tag Sponsorships&lt;/li&gt;
  &lt;li&gt;JavaScript assumptions&lt;/li&gt;
  &lt;li&gt;Area 51 (the whole damn thing really - it’s an ancient codebase)&lt;/li&gt;
  &lt;li&gt;Analytics trackers (Quantcast, GA)&lt;/li&gt;
  &lt;li&gt;Per-site JavaScript includes (community plugins)&lt;/li&gt;
  &lt;li&gt;Everything under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/jobs&lt;/code&gt; on Stack Overflow (which is actually a proxy, surprise!)&lt;/li&gt;
  &lt;li&gt;User flair&lt;/li&gt;
  &lt;li&gt;…and almost anywhere else &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; appears in code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JavaScript and links were a bit painful, so I’ll cover those in a little detail.&lt;/p&gt;

&lt;p&gt;JavaScript is an area some people forget, but of course it’s a thing.
We had several assumptions about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; in our JavaScript where we only passed a host down.
There were also many baked-in assumptions about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.&lt;/code&gt; being the prefix for meta sites.
So many. Oh so many.
Send help.
But they’re gone now, and the server now renders the fully qualified site roots in our options object at the top of the page.
It looks something like this (abbreviated):&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;StackExchange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;en&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;stackAuthUrl&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://stackauth.com&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Stack Overflow&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;childUrl&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://meta.stackoverflow.com&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;gravatar&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;div class=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;gravatar-wrapper-32&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;gt;&amp;lt;img src=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://i.stack.imgur.com/nGCYr.jpg&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;profileUrl&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;https://stackoverflow.com/users/13249/nick-craver&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We had so many static links over the years in our code. 
For example, in the header, in the footer, in the help section…just all over the place.
For each of these, the solution wasn’t that complicated: change them to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;site&amp;gt;.Url(&quot;/path&quot;)&lt;/code&gt;.
Finding and killing these was a little fun because you can’t just search for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;http://&quot;&lt;/code&gt;.
Thank you &lt;em&gt;so much&lt;/em&gt; W3C for gems like this:&lt;/p&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;svg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Yep, those are identifiers.
You can’t change them.
This is why I want Visual Studio to add an “exclude file types” option to the find dialog.
Are you listening Visual Studio???
VS Code added it a while ago.
I’m not above bribery.&lt;/p&gt;

&lt;p&gt;Okay so this isn’t really fun, it’s hunt and kill for over a thousand links in our code (including code comments, license links, etc.)
But, that’s life. It had to be done.
By converting them to be method calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Url()&lt;/code&gt;, we made the links dynamically switch to HTTPS when the site was ready.
For example, we couldn’t switch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.*.stackexchange.com&lt;/code&gt; sites over until they moved.
The password to our data center is pickles.
I didn’t think anyone would read this far and it seemed like a good place to store it.
After they moved, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Url()&lt;/code&gt; would keep working, and enabling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Url()&lt;/code&gt; rendering https-by-default would also keep working.
It changed a static thing to a dynamic thing and appropriately hooked up all of our feature flags.&lt;/p&gt;

&lt;p&gt;Oh and another important thing: it made dev and local environments work correctly, rather than always linking to production.
This was pretty painful and boring, but a worthwhile set of changes.
And yes, this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Url()&lt;/code&gt; code includes canonicals, so Google sees that pages should be HTTPS as soon as users do.&lt;/p&gt;

&lt;p&gt;Once a site is moved to HTTPS (by enabling a feature flag), we then crawled the network to update the links to it.
This is to both correct “Google juice” as we call it, and to prevent users eating a 301.&lt;/p&gt;

&lt;h3 id=&quot;redirects-301s&quot;&gt;Redirects (301s)&lt;/h3&gt;

&lt;p&gt;When you move a site from HTTP, there are 2 critical things you need to do for Google:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Update the canonical links, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;link rel=&quot;canonical&quot; href=&quot;https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454&quot; /&amp;gt;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;301 the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; link to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t complicated, it isn’t grand, but it’s very, &lt;em&gt;very&lt;/em&gt; important.
Stack Overflow gets most of its traffic from Google search results, so it’s imperative we don’t adversely affect that.
I’d literally be out of a job if we lost traffic, it’s our livelihood.
Remember those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.internal&lt;/code&gt; API calls? Yeah, we can’t just redirect &lt;em&gt;everything&lt;/em&gt; either.
So there’s a bit of logic into what gets redirected (e.g. we don’t redirect &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; requests during the transition…browsers don’t handle that well), but it’s fairly straightforward.
Here’s the actual code:&lt;/p&gt;
&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PerformHttpsRedirects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;https&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HTTPS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// If we're on HTTPS, never redirect back&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsSecureConnection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Not HTTPS-by-default? Abort.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Not supposed to redirect anyone yet? Abort.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedirectFor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SiteSettings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedirectAudience&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NoOne&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Don't redirect .internal or any other direct connection&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...as this would break direct HOSTS to webserver as well&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RequestIPIsInternal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Only redirect GET/HEAD during the transition - we'll 301 and HSTS everything in Fastly later&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HttpMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StringComparison&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InvariantCultureIgnoreCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Equals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HttpMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;HEAD&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StringComparison&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InvariantCultureIgnoreCase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Only redirect if we're redirecting everyone, or a crawler (if we're a crawler)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedirectFor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SiteSettings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedirectAudience&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Everyone&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedirectFor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SiteSettings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedirectAudience&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Crawlers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsSearchEngine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerHttpContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 301 when we're really sure (302 is the default)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RedirectVia301&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RedirectPermanent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PathAndQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Site&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PathAndQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerHttpContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ApplicationInstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CompleteRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that we don’t start with a 301 (there’s a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.RedirectVia301&lt;/code&gt; setting for this), because you &lt;em&gt;really&lt;/em&gt; want to test these things carefully before doing anything permanent.
We’ll talk about &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security&quot;&gt;HSTS&lt;/a&gt; and permanent consequences &lt;a href=&quot;#hsts-preloading&quot;&gt;a bit later&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;websockets&quot;&gt;Websockets&lt;/h3&gt;

&lt;p&gt;This one’s a quick mention.
Websockets was not hard, it was the easiest thing we did…in some ways.
We use websockets for real-time updates to users like reputation changes, inbox notifications, new questions being asked, new answers added, etc.
This means that basically for every page open to Stack Overflow, we have a corresponding websocket connection to our load balancer.&lt;/p&gt;

&lt;p&gt;So what’s the change? Pretty simple: install a certificate, listen on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:443&lt;/code&gt;, and use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wss://qa.sockets.stackexchange.com&lt;/code&gt; instead of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ws://&lt;/code&gt; (insecure) version.
The latter of that was done above in prep for everything (we decided on a specific certificate here, but nothing special).
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ws://&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wss://&lt;/code&gt; change was simply a configuration one.
During the transition we had &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ws://&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wss://&lt;/code&gt; as a fallback, but this has since become &lt;em&gt;only&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wss://&lt;/code&gt;.
The reasons to go with secure websockets in general are 2-fold:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;It’s a mixed content warning on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; if you don’t.&lt;/li&gt;
  &lt;li&gt;It supports more users, due to many old proxies not handling websockets well. With encrypted traffic, most pass it along without screwing it up. This is especially true for mobile users.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The big question here was: “can we handle the load?”
Our network handles quite a few concurrent websockets; as I write this we have over 600,000 &lt;strong&gt;concurrent&lt;/strong&gt; connections open. 
Here’s a view of our HAProxy dashboard in &lt;a href=&quot;https://github.com/opserver/Opserver&quot;&gt;Opserver&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/HTTPS-Websockets.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/HTTPS-Websockets.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/HTTPS-Websockets.png&quot; /&gt;&lt;img src=&quot;/blog/content/HTTPS-Websockets.png&quot; loading=&quot;lazy&quot; alt=&quot;HAProxy Websockets&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s a lot of connections on a) the terminators, b) the abstract named socket, and c) the frontend.
It’s also much more load in HAProxy itself, due to enabling &lt;a href=&quot;https://tools.ietf.org/html/rfc5077&quot;&gt;TLS session resumption&lt;/a&gt;.
To enable a user to reconnect faster the next time, the first negotiation results in a token a user can send back the next time.
If we have enough memory and the timeout hasn’t passed, we’ll resume that session instead of negotiating a new session every time.
This saves CPU and improves performance for users, but it has a cost in memory.
This cost varies by key size (2048, 4096 bits? more?). We’re currently at 4,096 bit keys.
With about 600,000 websockets open at any given time (the majority of our memory usage), we’re still sitting at only 19GB of RAM utilized on our 64GB load balancers.
Of this, about 12GB is being utilized by HAProxy, and most of that is the TLS session cache.
So…it’s not too bad, and &lt;em&gt;if we had to buy RAM&lt;/em&gt;, it’d still be one of the cheapest things about this move.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/HTTPS-WebsocketMemory.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/HTTPS-WebsocketMemory.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/HTTPS-WebsocketMemory.png&quot; /&gt;&lt;img src=&quot;/blog/content/HTTPS-WebsocketMemory.png&quot; loading=&quot;lazy&quot; alt=&quot;HAProxy Websocket Memory&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;unknowns&quot;&gt;Unknowns&lt;/h3&gt;

&lt;p&gt;I guess now’s a good time to cover the unknowns (gambles really) we took on with this move.
There are a few things we couldn’t &lt;em&gt;really&lt;/em&gt; know until we tested out an actual move:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;How Google Analytics traffic appeared (Do we lose referers?)&lt;/li&gt;
  &lt;li&gt;How Google Webmasters transitions worked (Do the 301s work? The canonicals? The sitemaps? How fast?)&lt;/li&gt;
  &lt;li&gt;How Google search analytics worked (do we see search analytics in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;?)&lt;/li&gt;
  &lt;li&gt;Will we fall in search result rankings? (scariest of all)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s a lot of advice out there of people who have converted to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;, but we’re not the usual use case.
We’re not a site.
We’re a network of sites across many domains.
We have very little insight into how Google treats our network.
Does it know &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;superuser.com&lt;/code&gt; are related?
Who knows. And we’re not holding our breath for Google to give us any insight.&lt;/p&gt;

&lt;p&gt;So, we test.
In our &lt;a href=&quot;https://meta.stackexchange.com/q/292058/135201&quot;&gt;network-wide rollout&lt;/a&gt;, we tested a few domains first:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://meta.stackexchange.com/&quot;&gt;meta.stackexchange.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://security.stackexchange.com/&quot;&gt;security.stackexchange.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://superuser.com/&quot;&gt;superuser.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These were chosen super carefully after a detailed review in a 3-minute meeting between Samo and I.
Meta because it’s our main feedback site (that &lt;a href=&quot;https://meta.stackexchange.com/q/292058/135201&quot;&gt;the announcement&lt;/a&gt; is also on).
Security because they have experts who may notice problems other sites don’t, especially in the HTTPS space.
And last, Super User.
We needed to test the search impact of our content. 
While meta and security are smaller and have relatively smaller traffic levels, Super User gets significantly more traffic.
More importantly, it gets this traffic &lt;em&gt;from Google&lt;/em&gt;, organically.&lt;/p&gt;

&lt;p&gt;The reason for a long delay between Super User and the rest of the network is we were watching and assessing the search impact.
As far as we can tell: there was barely any.
The amount of week-to-week change in searches, results, clicks, and positions is well within the normal up/down noise.
Our company &lt;em&gt;depends&lt;/em&gt; on this traffic.
This was incredibly important to be damn sure about.
Luckily, we were concerned for little reason and could continue rolling out.&lt;/p&gt;

&lt;h3 id=&quot;mistakes&quot;&gt;Mistakes&lt;/h3&gt;

&lt;p&gt;Writing this post isn’t a very decent exercise if I didn’t also cover our screw-ups along the way.
Failure is always an option.
We have the experience to prove it.
Let’s cover a few things we did and ended up regretting along the way.&lt;/p&gt;

&lt;h4 id=&quot;mistakes-protocol-relative-urls&quot;&gt;Mistakes: Protocol-Relative URLs&lt;/h4&gt;

&lt;p&gt;When you have a URL to a resource, typically you see something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://example.com&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://example.com&lt;/code&gt;, this includes paths for images, etc.
Another option you can use is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;//example.com&lt;/code&gt;. These are called &lt;a href=&quot;https://en.wikipedia.org/wiki/Wikipedia:Protocol-relative_URL&quot;&gt;protocol-relative URLs&lt;/a&gt;.
We used these early on for images, JavaScript, CSS, etc. (that we served, not user-submitted content).
Years later, we found out this was a bad idea, at least for us.
The way protocol-relative links work is they are relative &lt;em&gt;to the page&lt;/em&gt;.
When you’re on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://stackoverflow.com&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;//example.com&lt;/code&gt; is the same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://example.com&lt;/code&gt;, and on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://stackoverflow.com&lt;/code&gt;, it’s the same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://example.com&lt;/code&gt;.
So what’s the problem?&lt;/p&gt;

&lt;p&gt;Well, URLs to images aren’t only used in pages, they’re also used in places like email, our API, and mobile applications.
This bit us once when I normalized the pathing structure and used the same image paths everywhere.
While the change drastically reduced code duplication and simplified many things, the result was protocol-relative URLs in email.
Most email clients (appropriately) don’t render such images. 
Because they don’t know which protocol. 
Email is neither &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; nor &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;. 
You may just be viewing it in a web browser and it &lt;em&gt;might&lt;/em&gt; have worked.&lt;/p&gt;

&lt;p&gt;So what do we do?
Well, we switched everything everywhere to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;.
I unified all of our pathing code down to 2 variables: the root of the CDN, and the folder for the particular site.
For example Stack Overflow’s stylesheet resides at: &lt;a href=&quot;https://cdn.sstatic.net/Sites/stackoverflow/all.css&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://cdn.sstatic.net/Sites/stackoverflow/all.css&lt;/code&gt;&lt;/a&gt; (but with a cache breaker!). 
Locally, it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://local.sstatic.net/Sites/stackoverflow/all.css&lt;/code&gt;. 
You can see the similarity.
By calculating all routes, life is simpler.
By enforcing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;, people are getting the benefits of HTTP/2 even before the site itself switches over, since static content was already prepared.
All &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; also meant we could use &lt;strong&gt;one&lt;/strong&gt; property for a URL in web, email, mobile, and API.
The unification also meant we have a consistent place to handle all pathing - this means cache breakers are built in &lt;em&gt;everywhere&lt;/em&gt;, while still being simpler.&lt;/p&gt;

&lt;p&gt;Note: when you’re cache breaking resources like we do, for example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://cdn.sstatic.net/Sites/stackoverflow/all.css?v=070eac3e8cf4&lt;/code&gt;, please don’t do it with a build number.
Our cache breakers are a &lt;a href=&quot;https://en.wikipedia.org/wiki/Checksum&quot;&gt;checksum&lt;/a&gt; of the file, which means you only download a new copy &lt;em&gt;when it actually changes&lt;/em&gt;.
Doing a build number may be slightly simpler, but it’s likely quite literally costing you money and performance at the same time.&lt;/p&gt;

&lt;p&gt;Okay, all of that’s cool - so why the hell didn’t we just do this from the start?
Because HTTPS, at the time, was a performance penalty.
Users would have suffered slower load times on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; pages.
For an idea of scale: we served up 4 billion requests on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sstatic.net&lt;/code&gt; last month, totaling 94TB.
That would be a lot of collective latency back when HTTPS was slower.
Now that the tables have turned on performance with HTTP/2 and our CDN/proxy setup, it’s a net win for most users as well as being simpler.
Yay!&lt;/p&gt;

&lt;h4 id=&quot;mistakes-apis-and-internal&quot;&gt;Mistakes: APIs and .internal&lt;/h4&gt;

&lt;p&gt;So what did we find when we got the proxies up and testing?
We forgot something critical.
I forgot something critical.
We use HTTP for a truckload of internal APIs.
Oh, right. &lt;em&gt;Dammit&lt;/em&gt;.
While these continued to work, they got slower, more complicated, and more brittle at the same time.&lt;/p&gt;

&lt;p&gt;Let’s say an internal API hits &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com/some-internal-route&lt;/code&gt;.
Previously, the hops there were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Origin app&lt;/li&gt;
  &lt;li&gt;Gateway/Firewall (exiting to public IP space)&lt;/li&gt;
  &lt;li&gt;Local load balancer&lt;/li&gt;
  &lt;li&gt;Destination web server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt; used to resolve &lt;em&gt;to us&lt;/em&gt;. 
The IP it went to was our load balancer.
In a proxy scenario, in order for users to hit the nearest hop to them, they’re hitting a different IP and destination.
The IP their DNS resolves to is the CDN/Proxy (Fastly) now.
Well, crap.
That means our path to the same place is now:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Origin app&lt;/li&gt;
  &lt;li&gt;Gateway/Firewall (exiting to public IP space)&lt;/li&gt;
  &lt;li&gt;Our external router&lt;/li&gt;
  &lt;li&gt;ISP (multiple hops)&lt;/li&gt;
  &lt;li&gt;Proxy (Cloudflare/Fastly)&lt;/li&gt;
  &lt;li&gt;ISPs (proxy path to us)&lt;/li&gt;
  &lt;li&gt;Our external router&lt;/li&gt;
  &lt;li&gt;Local load balancer&lt;/li&gt;
  &lt;li&gt;Destination web server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Okay…that seems worse. 
To make an application call from A to B, we have a drastic increase in dependencies that aren’t necessary and kill performance at the same time.
I’m not saying our proxy is slow, but compared to a sub 1ms connection inside the data center…well yeah, it’s slow.&lt;/p&gt;

&lt;p&gt;A lot of internal discussion ensued about the simplest way to solve this problem. 
We could have made requests like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal.stackoverflow.com&lt;/code&gt;, but this would require substantial app changes to how the sites work (and potentially create conflicts later). 
It would also have created an external leak of DNS for internal-only addresses (and created wildcard inheritance issues). 
We could have made &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt; resolve different internally (this is known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Split-horizon_DNS&quot;&gt;split-horizon DNS&lt;/a&gt;), but that’s both harder to debug and creates other issues like multi-datacenter “who-wins?” scenarios.&lt;/p&gt;

&lt;p&gt;Ultimately, we ended up with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.internal&lt;/code&gt; suffix to all domains we had external DNS for.  For example, inside our network &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com.internal&lt;/code&gt; resolves to an internal subnet on the back (DMZ) side of our load balancer.
We did this for several reasons:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;We can override and contain a top-level domain on our internal DNS servers (Active Directory)&lt;/li&gt;
  &lt;li&gt;We can strip the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.internal&lt;/code&gt; from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host&lt;/code&gt; header as it passes through HAProxy back to a web application (the application side isn’t even aware)&lt;/li&gt;
  &lt;li&gt;If we need internal-to-DMZ SSL, we can do so with a very similar wildcard combination.&lt;/li&gt;
  &lt;li&gt;Client API code is simple (if in this domain list, add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.internal&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client API code is done via a NuGet package/library called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StackExchange.Network&lt;/code&gt; mostly written by &lt;a href=&quot;https://twitter.com/marcgravell&quot;&gt;Marc Gravell&lt;/a&gt;.
We simply call it in a static way with every URL we’re about to hit (so only in a few places, our utility fetch methods).
It returns the “internalized” URL, if there is one, or returns it untouched.
This means any changes to logic here can be quickly deployed to all applications with a simple NuGet update.
The call is simple enough:&lt;/p&gt;
&lt;div class=&quot;language-c# highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SubstituteInternalUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s a concrete illustration for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt; DNS behavior:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Fastly: 151.101.193.69, 151.101.129.69, 151.101.65.69, 151.101.1.69&lt;/li&gt;
  &lt;li&gt;Direct (public routers): 198.252.206.16&lt;/li&gt;
  &lt;li&gt;Internal: 10.7.3.16&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember &lt;a href=&quot;https://github.com/StackExchange/dnscontrol&quot;&gt;dnscontrol&lt;/a&gt; we mentioned earlier?
That keeps all of this in sync. 
Thanks to the JavaScript config/definitions, we can easily share all and simplify code.
We match the last octet of all IPs (in all subnets, in all data centers), so with a few variables all the DNS entries both in AD and externally are aligned.
This also means our HAProxy config is simpler as well, it boils down to this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;stacklb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;frontend_normal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'t1_http-in'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;section_name&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'http-in'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;maxconn&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$t1_http_in_maxconn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;inputs&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;${external_ip_base}.16:80&quot;&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'name stackexchange'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;${external_ip_base}.17:80&quot;&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'name careers'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;${external_ip_base}.18:80&quot;&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'name openid'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;${external_ip_base}.24:80&quot;&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'name misc'&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Overall, the API path is now faster and more reliable than before:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Origin app&lt;/li&gt;
  &lt;li&gt;Local load balancer (DMZ side)&lt;/li&gt;
  &lt;li&gt;Destination web server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A dozen problems solved, several hundred more to go.&lt;/p&gt;

&lt;h4 id=&quot;mistakes-301-caching&quot;&gt;Mistakes: 301 Caching&lt;/h4&gt;

&lt;p&gt;Something we didn’t realize and should have tested is that when we started 301ing traffic from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; for enabled sites, Fastly was caching the response.
In Fastly, the &lt;a href=&quot;https://docs.fastly.com/guides/vcl/manipulating-the-cache-key&quot;&gt;default cache key&lt;/a&gt; doesn’t take the protocol into account.
I personally disagree with this behavior, since by default enabling 301 redirects at the origin will result in infinite redirects.
The problem happens with this series of events:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;A user visits a page on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;They get redirected via a 301 to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Fastly caches that redirect&lt;/li&gt;
  &lt;li&gt;Any user (including the one in #1 above) visits the same page on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Fastly serves the 301 to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;, even though you’re already on it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that’s how we get an infinite redirect.
To fix this, we turned off 301s, purged Fastly cache, and investigated.
After fixing it via a hash change, we worked with Fastly support &lt;a href=&quot;https://docs.fastly.com/guides/vcl/manipulating-the-cache-key#purging-adjustments-when-making-additions-to-cache-keys&quot;&gt;which recommended adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fastly-SSL&lt;/code&gt; to the vary instead&lt;/a&gt;, like this:&lt;/p&gt;
&lt;div class=&quot;language-lua highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vcl_fetch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;beresp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Vary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;beresp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Vary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;beresp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Vary&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fastly-SSL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In my opinion, this should be the default behavior.&lt;/p&gt;

&lt;h4 id=&quot;mistakes-help-center-snafu&quot;&gt;Mistakes: Help Center SNAFU&lt;/h4&gt;

&lt;p&gt;Remember those help posts we had to fix?
Help posts are mostly per-language with few being per-site, so it makes sense for them to be shared.
To not duplicate a ton of code and storage structure for just this, we do them a little differently.
We store the actual Post object (same as a question or answer) in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.stackexchange.com&lt;/code&gt;, or whatever specific site the post is for.
We store the resulting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelpPost&lt;/code&gt; in our central &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sites&lt;/code&gt; database, which is just the baked HTML.
In terms of mixed-content, we fixed the posts in the individual sites &lt;em&gt;already&lt;/em&gt;, because they were the same posts are other things.
Sweet! That was easy!&lt;/p&gt;

&lt;p&gt;After the original posts were fixed, we simply had to backfill the rebaked HTML into the Sites table.
And that’s where I left off a critical bit of code.
The backfill looked at &lt;em&gt;the current site&lt;/em&gt; (the ones the backfill was invoked on) rather than the site the original post came from.
As an example, this resulted in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelpPost&lt;/code&gt; from post 12345 on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.stackechange.com&lt;/code&gt; being replaced with whatever was in post 12345 on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stackoverflow.com&lt;/code&gt;.
Sometimes it was an answer, sometimes a question, sometimes a tag wiki.
This resulted in some &lt;a href=&quot;https://meta.stackoverflow.com/q/345280/13249&quot;&gt;very interesting help articles across the network&lt;/a&gt;.
Here are &lt;a href=&quot;https://meta.stackoverflow.com/a/345282/13249&quot;&gt;some of the gems created&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At least the commit to fix my mistake was simple enough:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/HTTPS-HelpCommit.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/HTTPS-HelpCommit.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/HTTPS-HelpCommit.png&quot; /&gt;&lt;img src=&quot;/blog/content/HTTPS-HelpCommit.png&quot; loading=&quot;lazy&quot; alt=&quot;Me being a dumbass&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…and re-running the backfill fixed it all. Still, that was some very public “fun”. Sorry about that.&lt;/p&gt;

&lt;h3 id=&quot;open-source&quot;&gt;Open Source&lt;/h3&gt;

&lt;p&gt;Here are quick links to all the projects that resulted or improved from our HTTPS deployment.
Hopefully these save the world some time:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/blackbox&quot;&gt;BlackBox&lt;/a&gt; (Safely store secrets in source control) by &lt;a href=&quot;https://twitter.com/yesthattom&quot;&gt;Tom Limoncelli&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/capnproto-net&quot;&gt;capnproto-net&lt;/a&gt; (UNSUPPORTED - &lt;a href=&quot;https://capnproto.org/&quot;&gt;Cap’n Proto&lt;/a&gt; for .NET) by &lt;a href=&quot;https://twitter.com/marcgravell&quot;&gt;Marc Gravell&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/dnscontrol&quot;&gt;DNSControl&lt;/a&gt; (Controlling multiple DNS providers) by &lt;a href=&quot;https://twitter.com/captncraig&quot;&gt;Craig Peterson&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/yesthattom&quot;&gt;Tom Limoncelli&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/httpunit&quot;&gt;httpUnit&lt;/a&gt; (Integration tests for websites) by &lt;a href=&quot;https://twitter.com/mjibson&quot;&gt;Matt Jibson&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/yesthattom&quot;&gt;Tom Limoncelli&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/opserver/Opserver&quot;&gt;Opserver&lt;/a&gt; (with support for Cloudflare DNS) by &lt;a href=&quot;https://twitter.com/Nick_Craver&quot;&gt;Nick Craver&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/alienth/fastlyctl&quot;&gt;fastlyctl&lt;/a&gt; (Fastly API calls from Go) by &lt;a href=&quot;https://twitter.com/alioth&quot;&gt;Jason Harvey&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/alienth/fastly-ratelimit&quot;&gt;fastly-ratelimit&lt;/a&gt; (Rate limiting based on Fastly syslog traffic) by &lt;a href=&quot;https://twitter.com/alioth/&quot;&gt;Jason Harvey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h3&gt;

&lt;p&gt;We’re not done.
There’s quite a bit left to do.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;We need to fix mixed content on our chat domains like &lt;a href=&quot;https://chat.stackoverflow.com/&quot;&gt;chat.stackoverflow.com&lt;/a&gt; (from user embedded images, etc.)&lt;/li&gt;
  &lt;li&gt;We need to join (if we can) &lt;a href=&quot;https://hstspreload.org/&quot;&gt;the Chrome HSTS preload list&lt;/a&gt; on all domains where possible.&lt;/li&gt;
  &lt;li&gt;We need to evaluate &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning&quot;&gt;HPKP&lt;/a&gt; and if we want to deploy it (it’s pretty dangerous - currently leaning heavily towards “no”)&lt;/li&gt;
  &lt;li&gt;We need to move chat to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;We need to migrate all cookies over to secure-only&lt;/li&gt;
  &lt;li&gt;We’re awaiting HAProxy 1.8 (ETA is around September) which is slated to support HTTP/2&lt;/li&gt;
  &lt;li&gt;We need to utilize HTTP/2 pushes (I’m discussing this with Fastly in June - they don’t support cross-domain pushes yet)&lt;/li&gt;
  &lt;li&gt;We need to move the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; 301 out to the CDN/Proxy for performance (it was necessary to do it per-site as we rolled out)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;hsts-preloading&quot;&gt;HSTS Preloading&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security&quot;&gt;HSTS&lt;/a&gt; stands for “HTTP Strict Transport Security”.
OWASP has a great little write-up &lt;a href=&quot;https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet&quot;&gt;here&lt;/a&gt;.
It’s a fairly simple concept:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;When you visit an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; page, we send you a header like this: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Strict-Transport-Security: max-age=31536000&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;For that duration (in seconds), your browser only visits that domain over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you click a link that’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt;, your browser goes &lt;em&gt;directly&lt;/em&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;.
It never goes through the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; redirect that’s likely also set up, it goes right for SSL/TLS.
This prevents people intercepting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; (insecure) request and hijacking it.
As an example, it could redirect you to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://stack&amp;lt;LooksLikeAnOButIsReallyCrazyUnicode&amp;gt;verflow.com&lt;/code&gt;, for which they may even have a proper SSL/TLS certificate.
By never visiting there, you’re safer.&lt;/p&gt;

&lt;p&gt;But that requires hitting the site once to get the header in the first place, right?
Yep, that’s right.
So there’s &lt;a href=&quot;https://hstspreload.org/&quot;&gt;HSTS preloading&lt;/a&gt;, which is a list of domains that ships with all major browsers and how to preload them.
Effectively, they get the directive to only visit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; before the first visit.
There’s &lt;strong&gt;never&lt;/strong&gt; any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://&lt;/code&gt; communication once this is in place.&lt;/p&gt;

&lt;p&gt;Okay cool!
So what’s it take to get on that list?
Here are the requirements:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Serve a valid certificate.&lt;/li&gt;
  &lt;li&gt;Redirect from HTTP to HTTPS on the same host, if you are listening on port 80.&lt;/li&gt;
  &lt;li&gt;Serve all subdomains over HTTPS.
    &lt;ul&gt;
      &lt;li&gt;In particular, you must support HTTPS for the www subdomain if a DNS record for that subdomain exists.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Serve an HSTS header on the base domain for HTTPS requests:
    &lt;ul&gt;
      &lt;li&gt;The max-age must be at least eighteen weeks (10886400 seconds).&lt;/li&gt;
      &lt;li&gt;The includeSubDomains directive must be specified.&lt;/li&gt;
      &lt;li&gt;The preload directive must be specified.&lt;/li&gt;
      &lt;li&gt;If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS header (rather than the page it redirects to).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That sounds good, right?
We’ve got all our active domains on HTTPS now, with valid certificates.
Nope, we’ve got a problem.
Remember how we had &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.gaming.stackexchange.com&lt;/code&gt; for years?
While it redirects to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gaming.meta.stackexchange.com&lt;/code&gt; that redirect does not have a valid certificate.&lt;/p&gt;

&lt;p&gt;Using metas as an example, if we pushed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includeSubDomains&lt;/code&gt; on our HSTS header, we would change every link on the internet pointing at the old domains from a working redirect into a landmine.
Instead of landing on an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; site (as they do today), they’d get an invalid certificate error.
Based on our traffic logs yesterday, we’re still getting 80,000 hits a day just to the child meta domains for the 301s.
A lot of this is web crawlers catching up (it takes quite a while), but a lot is also human traffic from blogs, bookmarks, etc.
…and some crawlers are just really stupid and never update their information based on a 301.
You know who you are.
And why are you still reading this? I fell asleep 3 times writing this damn thing.&lt;/p&gt;

&lt;p&gt;So what do we do?
Do we set up several SAN certs with hundreds of domains on them and host that strictly for 301s piped through our infrastructure?
It couldn’t reasonably be done through Fastly without a higher cost (more IPs, more certs, etc.)
&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; &lt;em&gt;is&lt;/em&gt; actually helpful here.
Getting the cert would be low cost, if you ignore the engineering effort required to set it up and maintain it (since we don’t use it today for reasons &lt;a href=&quot;#certificates&quot;&gt;listed above&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;There’s one more critical piece of archaeology here: our internal domain is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ds.stackexchange.com&lt;/code&gt;.
Why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ds.&lt;/code&gt;? I’m not sure. My assumption is we didn’t know how to spell data center.
This means &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includeSubDomains&lt;/code&gt; automatically includes &lt;em&gt;every internal endpoint&lt;/em&gt;.
Now, most of our things are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; already, but making everything need HTTPS for even development from the first moment internally will cause some issues and delays.
It’s not that we wouldn’t want &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; everywhere inside, but that’s an entire project (mainly around certificate distribution and maintenance, as well as multi-level certificates) that you really don’t want coupled.
Why not just change the internal domain?
Because we don’t have a few spare months for a lateral move. 
It requires a lot of time and coordination to do a move like that.&lt;/p&gt;

&lt;p&gt;For the moment, I will be ramping up the HSTS &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max-age&lt;/code&gt; duration slowly to 2 years across all Q&amp;amp;A sites &lt;strong&gt;without&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includeSubDomains&lt;/code&gt;.
I’m actually going to remove this setting from the code until needed, since it’s so dangerous.
Once we get all Q&amp;amp;A site header durations ramped up, I think we can work with Google to add them to the HSTS list without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includeSubDomains&lt;/code&gt;, at least as a start.
You can see &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.json&quot;&gt;on the current list&lt;/a&gt; that this does happen in rare circumstances.
I hope they’ll agree for securing Stack Overflow.&lt;/p&gt;

&lt;h4 id=&quot;chat&quot;&gt;Chat&lt;/h4&gt;

&lt;p&gt;In order to enable &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Secure&lt;/code&gt; cookies&lt;/a&gt; (ones only sent over HTTPS) as fast as possible, we’ll be redirecting chat (&lt;a href=&quot;https://chat.stackoverflow.com/&quot;&gt;chat.stackoverflow.com&lt;/a&gt;, &lt;a href=&quot;https://chat.stackexchange.com/&quot;&gt;chat.stackexchange.com&lt;/a&gt;, and &lt;a href=&quot;https://chat.meta.stackexchange.com/&quot;&gt;chat.meta.stackexchange.com&lt;/a&gt;) to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;.
Chat relies on the cookie on the second-level domain like all the other Universal Login apps do, so if the cookies are only sent over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;, you can only be logged in over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There’s more to think through on this, but making chat itself &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&lt;/code&gt; with mixed-content while we solve those issues is still a net win.
It allows us to secure the network fast and work on mixed-content in real-time chat afterwards.
Look for this to happen in the next week or two. It’s next on my list.&lt;/p&gt;

&lt;h4 id=&quot;today&quot;&gt;Today&lt;/h4&gt;

&lt;p&gt;So anyway, that’s where we stand today and what we’ve been up to the last 4 years.
A lot of things came up with higher priority that pushed HTTPS back - this is &lt;em&gt;very far&lt;/em&gt; from the only thing we’ve been working on.
That’s life. The people working on this are the same ones that fight the fires we hope you never see.
There are also far more people involved than mentioned here.
I was narrowing the post to the complicated topics (otherwise it would have been long) that each took significant amounts of work, but many others at Stack Overflow and outside helped along the way.&lt;/p&gt;

&lt;p&gt;I know a lot of you will have many questions, concerns, complaints, and suggestions on how we can do things better.
We more than welcome all of these things.
We’ll watch the comments below, our metas, Reddit, Hacker News, and Twitter this week and answer/help as much as we can.
Thanks for your time, and you’re crazy for reading all of this. &amp;lt;3&lt;/p&gt;
</description>
        <pubDate>Mon, 22 May 2017 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2017/05/22/https-on-stack-overflow/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Stack Overflow: How We Do Deployment - 2016 Edition</title>
        <link>https://nickcraver.com/blog/2016/05/03/stack-overflow-how-we-do-deployment-2016-edition/</link>
        <description>&lt;blockquote&gt;
  &lt;p&gt;This is #3 in a &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;very long series of posts&lt;/a&gt; on Stack Overflow’s architecture.&lt;br /&gt;
Previous post (#2): &lt;a href=&quot;/blog/2016/03/29/stack-overflow-the-hardware-2016-edition/&quot;&gt;Stack Overflow: The Hardware - 2016 Edition&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’ve talked about &lt;a href=&quot;/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/&quot;&gt;Stack Overflow’s architecture&lt;/a&gt; and &lt;a href=&quot;/blog/2016/03/29/stack-overflow-the-hardware-2016-edition/&quot;&gt;the hardware behind it&lt;/a&gt;. The next &lt;a href=&quot;https://trello.com/b/0zgQjktX/blog-post-queue-for-stack-overflow-topics&quot;&gt;most requested topic&lt;/a&gt; was Deployment. How do we get code a developer (or some random stranger) writes into production? Let’s break it down. Keep in mind that we’re talking about deploying Stack Overflow for the example, but most of our projects follow almost an identical pattern to deploy a website or a service.&lt;/p&gt;

&lt;!-- I typed this late at night; I probably should have slept instead.
### The Codez
First, we have some code. It's usually made up of letters, numbers, dots, squiggly braces, not-so-squiggly braces, line returns, apostrophes, quotes, hashes, equals, bangs, slashes, have we weeded out the weak yet, brackets, parens, dollar signs, and probably some others people are going to let me know I missed in the comments. And colons and semicolons. Oh and all the symbols you need a shift key to make. And commas and greater than, less than, backticks, pipes and holy crap there's a lot of lexicon here.
--&gt;

&lt;p&gt;I’m going ahead and inserting a set of section links here because this post got a bit long with all of the bits that need an explanation:
&lt;!--more--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#source&quot;&gt;Source &amp;amp; Context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-human-steps&quot;&gt;The Human Steps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#branches&quot;&gt;Branches&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#git-on-premises&quot;&gt;Git On-Premises&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-build-system&quot;&gt;The Build System&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#whats-in-the-build&quot;&gt;What’s In The Build?&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#steps-1--2-migrations&quot;&gt;Steps 1 &amp;amp; 2: Migrations&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-3-finding-moonspeak-translation&quot;&gt;Step 3: Finding Moonspeak (Translation)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-4-translation-dump-javascript-edition&quot;&gt;Step 4: Translation Dump (JavaScript Edition)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-5-msbuild&quot;&gt;Step 5: MSBuild&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-6-translation-dump-c-edition&quot;&gt;Step 6: Translation Dump (C# Edition)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-7-importing-english-strings&quot;&gt;Step 7: Importing English Strings&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-8-deploy-website&quot;&gt;Step 8: Deploy Website&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#step-9-new-strings-hook&quot;&gt;Step 9: New Strings Hook&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tiers&quot;&gt;Tiers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#database-migrations&quot;&gt;Database Migrations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#localizationtranslations-moonspeak&quot;&gt;Localization/Translations (Moonspeak)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#building-without-breaking&quot;&gt;Building Without Breaking&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Extra resources because I love you all
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;https://gist.github.com/NickCraver/b59ff38567b32936e2a3440e439d5d5c&quot;&gt;GitHub Gist (scripts)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;https://gist.github.com/NickCraver/d22d285e35ea6816bc4efe8e81ff152c&quot;&gt;GitHub Gist (logs)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;source&quot;&gt;Source&lt;/h3&gt;
&lt;p&gt;This is our starting point for this article. We have the Stack Overflow repository on a developer’s machine. For the sake of discussing the process, let’s say they added a column to a database table and the corresponding property to the C# object — that way we can dig into how database migrations work along the way.&lt;/p&gt;

&lt;h3 id=&quot;a-little-context&quot;&gt;A Little Context&lt;/h3&gt;
&lt;p&gt;We deploy roughly 25 times per day to development (our CI build) just for Stack Overflow Q&amp;amp;A. Other projects also push many times. We deploy to production about 5-10 times on a typical day. A deploy from first push to full deploy is under 9 minutes (2:15 for dev, 2:40 for meta, and 3:20 for all sites). We have roughly 15 people pushing to the repository used in this post. The repo contains the code for these applications: &lt;a href=&quot;https://stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt; (every single Q&amp;amp;A site), &lt;a href=&quot;https://stackexchange.com/&quot;&gt;stackexchange.com&lt;/a&gt; (root domain only), &lt;a href=&quot;https://stacksnippets.net/&quot;&gt;Stack Snippets&lt;/a&gt; (for Stack Overflow JavaScript snippets), &lt;a href=&quot;https://stackauth.com/&quot;&gt;Stack Auth&lt;/a&gt; (for OAuth), &lt;a href=&quot;https://sstatic.net/&quot;&gt;sstatic.net&lt;/a&gt; (cookieless CDN domain), &lt;a href=&quot;https://api.stackexchange.com/&quot;&gt;Stack Exchange API v2&lt;/a&gt;, &lt;a href=&quot;https://mobile.stackexchange.com/&quot;&gt;Stack Exchange Mobile&lt;/a&gt; (iOS and Android API), Stack Server (Tag Engine and Elasticsearch indexing Windows service), and Socket Server (our WebSocket Windows service).&lt;/p&gt;

&lt;h3 id=&quot;the-human-steps&quot;&gt;The Human Steps&lt;/h3&gt;
&lt;p&gt;When we’re coding, if a database migration is involved then we have some extra steps. First, we check the chatroom (and confirm in the local repo) which SQL migration number is available next (we’ll get to how this works). Each project with a database has their own migration folder and number. For this deploy, we’re talking about the Q&amp;amp;A migrations folder, which applies to all Q&amp;amp;A databases. Here’s what chat and the local repo look like before we get started:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Stars.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Stars.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Stars.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Stars.png&quot; loading=&quot;lazy&quot; alt=&quot;Chat Stars&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here’s the local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%Repo%\StackOverflow.Migrations\&lt;/code&gt; folder:
&lt;a href=&quot;/blog/content/SO-Deployment-Migrations.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Migrations.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Migrations.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Migrations.png&quot; loading=&quot;lazy&quot; alt=&quot;StackOverflow.Migrations&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see both in chat and locally that 726 was the last migration number taken. So we’ll issue a “taking 727 - Putting JSON in SQL to see who it offends” message in chat. This will claim the next migration so that we don’t collide with someone else also doing a migration. We just type a chat message, a bot pins it. Fun fact: it also pins when I say “taking web 2 offline”, but we think it’s funny and refuse to fix it. Here’s our little Pinbot trolling:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Pinbot.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Pinbot.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Pinbot.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Pinbot.png&quot; loading=&quot;lazy&quot; alt=&quot;Oh Pinbot&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s add some code — we’ll keep it simple here:&lt;/p&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\StackOverflow\Models\User.cs&lt;/code&gt; diff:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;gi&quot;&gt;+ public string PreferencesJson { get; set; }&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;And our new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\StackOverflow.Migrations\727 - Putting JSON in SQL to see who it offends.sql&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;n&quot;&gt;If&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dbo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fnColumnExists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Users'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'PreferencesJson'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;Begin&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;Alter&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Users&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Add&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PreferencesJson&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nvarchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;End&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;We’ve tested the migration works by running it against our local Q&amp;amp;A database of choice in SSMS and that the code on top of it works. Before deploying though, we need to make sure it runs &lt;em&gt;as a migration&lt;/em&gt;. For example, sometimes you may forget to put a &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/ms188037.aspx&quot;&gt;GO&lt;/a&gt; separating something that must be the first or only operation in a batch such as creating a view. So, we test it in the runner. To do this, we run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;migrate.local.bat&lt;/code&gt; you see in the screenshot above. The contents are simple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-bat&quot; data-lang=&quot;bat&quot;&gt;..\Build\Migrator&lt;span class=&quot;na&quot;&gt;-Fast --tier&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;local&lt;/span&gt; 
  &lt;span class=&quot;na&quot;&gt;--sites&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Data Source=.;Initial Catalog=Sites.Database;Integrated Security=True&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;PAUSE&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;Note: the migrator is a project, but we simply drop the .exe in the solutions using it, since that’s the simplest and most portable thing that works.&lt;/p&gt;

&lt;p&gt;What does this migrator do? It hits our local copy of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sites&lt;/code&gt; database. It contains a list of all the Q&amp;amp;A sites that developer runs locally and the migrator uses that list to connect and run all migrations against all databases, in Parallel. Here’s what a run looks like on a simple install with a single Q&amp;amp;A database:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Migration-Log.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Migration-Log.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Migration-Log.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Migration-Log.png&quot; loading=&quot;lazy&quot; alt=&quot;Migration Log&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far, so good. We have code and a migration that works and code that does…some stuff (which isn’t relevant to this process). Now it’s time to take our little code baby and send it out into the world. It’s time to fly little code, be &lt;a href=&quot;https://youtu.be/LnlFDduJV8E?t=48s&quot;&gt;freeeeee&lt;/a&gt;! Okay now that we’re excited, the typical process is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;git add &amp;lt;files&amp;gt; (usually --all for small commits)
git commit -m &quot;Migration 727: Putting JSON in SQL to see who it offends&quot;
git pull --rebase
git push&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;Note: we first check our team chatroom to see if anyone is in the middle of a deploy. Since our deployments are pretty quick, the chances of this aren’t &lt;em&gt;that&lt;/em&gt; big. But, given how often we deploy, collisions can and do happen. Then we yell at the designer responsible.
&lt;!-- I kid, everyone does this sooner or later...and our designers are AWESOME. But they're still our scape goats. And we're their scapegoats. &lt;3 --&gt;&lt;/p&gt;

&lt;p&gt;With respect to the Git commands above: if a command line works for you, use it. If a GUI works for you, use it. Use the best tooling for you and don’t give a damn what anyone else thinks. The entire point of tooling from an ancient hammer to a modern Git install is to save time and effort of the user. Use whatever saves &lt;em&gt;you&lt;/em&gt; the most time and effort. Unless it’s Emacs, then consult a doctor immediately.&lt;/p&gt;

&lt;h3 id=&quot;branches&quot;&gt;Branches&lt;/h3&gt;
&lt;p&gt;I didn’t cover branches above because compared to many teams, we very rarely use them. Most commits are on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. Generally, we branch for only one of a few reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A developer is new, and early on we want code reviews&lt;/li&gt;
  &lt;li&gt;A developer is working on a big (or risky) feature and wants a one-off code review&lt;/li&gt;
  &lt;li&gt;Several developers are working on a big feature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other than the (generally rare) cases above, almost all commits are directly to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; and deployed soon after. We don’t like a big build queue. This encourages us to make small to medium size commits often and deploy often. It’s just how we choose to operate. I’m not recommending it for most teams or any teams for that matter. &lt;em&gt;Do what works for you&lt;/em&gt;. This is simply what works for us.&lt;/p&gt;

&lt;p&gt;When we do branch, merging back in is always a topic people are interested in. In the vast majority of cases, we’ll squash when merging into master so that rolling back the changes is straightforward. We also keep the original branch around a few days (for anything major) to ensure we don’t need to reference what that &lt;em&gt;specific&lt;/em&gt; change was about. That being said, we’re practical. If a squash presents a ton of developer time investment, then we just eat the merge history and go on with our lives.&lt;/p&gt;

&lt;h3 id=&quot;git-on-premises&quot;&gt;Git On-Premises&lt;/h3&gt;
&lt;p&gt;Alright, so our code is sent to the server-side repo. Which repo? We’re currently using &lt;a href=&quot;https://about.gitlab.com/&quot;&gt;Gitlab&lt;/a&gt; for repositories. It’s pretty much &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;, hosted on-prem. If Gitlab pricing keeps getting crazier (note: I said “crazier”, not “more expensive”), we’ll certainly re-evaluate &lt;a href=&quot;https://enterprise.github.com/home&quot;&gt;GitHub Enterprise&lt;/a&gt; again.&lt;/p&gt;

&lt;p&gt;Why on-prem for Git hosting? For the sake of argument, let’s say we used GitHub instead (we did evaluate this option). What’s the difference? First, builds are slower. While GitHub’s protocol implementation of Git is much faster, latency and bandwidth making the builds slower than pulling over 2x10Gb locally. But to be fair, GitHub is far faster than Gitlab at &lt;em&gt;most&lt;/em&gt; operations (especially search and viewing large diffs).&lt;/p&gt;

&lt;p&gt;However, depending on GitHub (or any offsite third party) has a few critical downsides for us. The main downside is the dependency chain. We aren’t just relying on GitHub servers to be online (their uptime is pretty good). We’re relying on them to be online &lt;em&gt;and being able to get to them&lt;/em&gt;. For that matter, we’re also relying on all of our remote developers to be able to push code in the first place. That’s a lot of switching, routing, fiber, and DDoS surface area in-between us and the bare essentials needed to build: &lt;strong&gt;code&lt;/strong&gt;. We can drastically shorten that dependency chain by being on a local server. It also alleviates &lt;em&gt;most&lt;/em&gt; security concerns we have with any sensitive code being on a third-party server. We have no inside knowledge of any GitHub security issues or anything like that, we’re just extra careful with such things. Quite simply: if something doesn’t need to leave your network, the best security involves it not leaving your network.&lt;/p&gt;

&lt;p&gt;All of that being said, our open source projects &lt;a href=&quot;https://github.com/StackExchange/&quot;&gt;are hosted on GitHub&lt;/a&gt; and it works great. The critical ones are also mirrored internally on Gitlab for the same reasons as above. We have no issues with GitHub (they’re awesome), only the dependency chain. For those unaware, even this website &lt;a href=&quot;https://github.com/NickCraver/nickcraver.github.com&quot;&gt;is running on GitHub pages&lt;/a&gt;…so if you see a typo in this post, &lt;a href=&quot;https://github.com/NickCraver/nickcraver.github.com/pulls&quot;&gt;submit a PR&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-build-system&quot;&gt;The Build System&lt;/h3&gt;
&lt;p&gt;Once the code is in the repo, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Continuous_integration&quot;&gt;continuous integration&lt;/a&gt; build takes over. This is just a fancy term for a build kicked off by a commit. For builds, we use &lt;a href=&quot;https://www.jetbrains.com/teamcity/&quot;&gt;TeamCity&lt;/a&gt;. The TeamCity server is actually on the same VM as Gitlab since neither is useful without the other and it makes TeamCity’s polling for changes a fast and cheap operation. Fun fact: since Linux has no built-in DNS caching, most of the DNS queries are looking for…itself. Oh wait, that’s not a fun fact — it’s actually a pain in the ass.&lt;/p&gt;

&lt;p&gt;As you may have heard, we like to keep things really simple. We have extra compute capacity on our web tier, so…we use it. Builds for all of the websites run on agents right on the web tier itself, this means we have 11 build agents local to each data center. There are a few additional Windows and Linux build agents (for puppet, rpms, and internal applications) on other VMs, but they’re not relevant to this deploy process.&lt;/p&gt;

&lt;p&gt;Like most CI builds, we simply poll the Git repo on an interval to see if there are changes. This repo is heavy hit, so we poll for changes every 15 seconds. We don’t like waiting. Waiting sucks. Once a change is detected, the build server instructs an agent to run a build.&lt;/p&gt;

&lt;p&gt;Since our repos are large (we include dependencies like NuGet packages, though &lt;a href=&quot;https://github.com/NuGet/NuGetGallery/issues/3004&quot;&gt;this is changing&lt;/a&gt;), we use what TeamCity calls &lt;a href=&quot;https://confluence.jetbrains.com/display/TCD9/VCS+Checkout+Mode&quot;&gt;agent-side checkout&lt;/a&gt;. This means the agent does the actual fetching of content directly from the repository, rather than the default of the web server doing the checkout and sending all of the source to the agent. On top of this, we’re using &lt;a href=&quot;https://confluence.jetbrains.com/display/TCD9/Git#Git-AgentSettings&quot;&gt;Git mirrors&lt;/a&gt;. Mirrors maintain a full repository (one per repo) on the agent. This means the very first time the agent builds a given repository, it’s a full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone&lt;/code&gt;. However, every time &lt;em&gt;after&lt;/em&gt; that it’s just a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull&lt;/code&gt;. Without this optimization, we’re talking about a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git clone --depth 1&lt;/code&gt;, which grabs the current file state and no history — just what we need for a build. With the very small delta we’ve pushed above (like most commits) a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git pull&lt;/code&gt; of &lt;em&gt;only&lt;/em&gt; that delta will always beat the pants off grabbing all of files across the network. That first-build cost is a no-brainer tradeoff.&lt;/p&gt;

&lt;p&gt;As I said earlier, there are many projects in this repo (all connected), so we’re really talking about several builds running each commit (5 total):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Dev-Builds.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Dev-Builds.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Dev-Builds.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Dev-Builds.png&quot; loading=&quot;lazy&quot; alt=&quot;Dev Builds&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;whats-in-the-build&quot;&gt;What’s In The Build?&lt;/h3&gt;
&lt;p&gt;Okay…what’s that build actually doing? Let’s take a top level look and break it down. Here are the 9 build steps in our development/CI build:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Dev-Build-Steps.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Dev-Build-Steps.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Dev-Build-Steps.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Dev-Build-Steps.png&quot; loading=&quot;lazy&quot; alt=&quot;Dev Build Steps&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here’s what the log of the build we triggered above looks like (&lt;a href=&quot;https://gist.github.com/NickCraver/d22d285e35ea6816bc4efe8e81ff152c#file-teamcity-dev-build-log-txt&quot;&gt;you can see the full version in a gist here&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Dev-Build-Log.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Dev-Build-Log.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Dev-Build-Log.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Dev-Build-Log.png&quot; loading=&quot;lazy&quot; alt=&quot;Dev Build Log&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&quot;steps-1--2-migrations&quot;&gt;Steps 1 &amp;amp; 2: Migrations&lt;/h4&gt;
&lt;p&gt;The first 2 steps are migrations. In development, we automatically migrate the “Sites” database. This database is our central store that contains the master list of sites and other network-level items like the inbox. This same migration isn’t automatic in production since “should this run be before or after code is deployed?” is a 50/50 question. The second step is what we ran locally, just against dev. In dev, it’s acceptable to be down for a second, but that still shouldn’t happen. In the Meta build, we migrate &lt;strong&gt;all&lt;/strong&gt; production databases. This means Stack Overflow’s database gets new SQL bits &lt;em&gt;minutes&lt;/em&gt; before code. We order deploys appropriately.&lt;/p&gt;

&lt;p&gt;The important part here is &lt;strong&gt;databases are always migrated before code is deployed&lt;/strong&gt;. Database migrations are a topic all in themselves and something people have expressed interest in, so I detail them a bit more &lt;a href=&quot;#database-migrations&quot;&gt;a little later in this post&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;step-3-finding-moonspeak-translation&quot;&gt;Step 3: Finding Moonspeak (Translation)&lt;/h4&gt;
&lt;p&gt;Due to the structure and limitations of the build process, we have to locate our Moonspeak tooling since we don’t know the location for sure (it changes with each version due to the version being in the path). Okay, what’s Moonspeak? Moonspeak is the &lt;a href=&quot;https://meta.stackexchange.com/a/25529/135201&quot;&gt;codename&lt;/a&gt; for our localization tooling. Don’t worry, &lt;a href=&quot;https://trello.com/c/GdywwBgb/24-localization-moonspeak-translations&quot;&gt;we’ll cover it in-depth later&lt;/a&gt;. The step itself is simple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;##teamcity[setParameter name='system.moonspeaktools' 
  value='&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get-childitem&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-directory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;packages/StackExchange.MoonSpeak.2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FullName&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\tools']&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;It’s just grabbing a directory path and setting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system.moonspeaktools&lt;/code&gt; TeamCity variable to the result. If you’re curious about all of the various ways to interact with TeamCity’s build, &lt;a href=&quot;https://confluence.jetbrains.com/display/TCD9/Build+Script+Interaction+with+TeamCity&quot;&gt;there’s an article here&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;step-4-translation-dump-javascript-edition&quot;&gt;Step 4: Translation Dump (JavaScript Edition)&lt;/h4&gt;
&lt;p&gt;In dev specifically, we run the dump of all of our need-to-be-translated strings in JavaScript for localization. Again the command is pretty simple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;%system.moonspeaktools%\Jerome.exe extract 
  %system.translationsDumpPath%\artifact-%build.number%-js.{0}.txt en;pt-br;mn-mn;ja;es;ru
  &quot;.\StackOverflow\Content\Js\*.js;.\StackOverflow\Content\Js\PartialJS\**\*.js&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;Phew, that was easy. I don’t know why everyone hates localization. Just kidding, localization sucks here too. Now I don’t want to dive too far into localization because that’s a whole (very long) post on its own, but here are the translation basics:&lt;/p&gt;

&lt;p&gt;Strings are surrounded by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_s()&lt;/code&gt; (regular string) or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_m()&lt;/code&gt; (markdown) in code. We love &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_s()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_m()&lt;/code&gt;. It’s almost identical for both JavaScript and C#. During the build, we extract these strings by analyzing the JavaScript (with &lt;a href=&quot;https://www.nuget.org/packages/AjaxMin/&quot;&gt;AjaxMin&lt;/a&gt;) and C#/Razor (with a custom &lt;a href=&quot;https://github.com/dotnet/roslyn&quot;&gt;Roslyn&lt;/a&gt;-based build). We take these strings and stick them in files to use for the translators, our community team, and ultimately back into the build later. There’s obviously &lt;em&gt;way&lt;/em&gt; more going on - but those are the relevant bits. It’s worth noting here that we’re excited about the proposed &lt;a href=&quot;https://github.com/dotnet/roslyn/blob/features/source-generators/docs/features/generators.md&quot;&gt;Source Generators&lt;/a&gt; feature specced for a future Roslyn release. We hope in its final form we’ll be able to re-write this portion of Moonspeak as a much simpler generator while still avoiding as many runtime allocations as possible.&lt;/p&gt;

&lt;h4 id=&quot;step-5-msbuild&quot;&gt;Step 5: MSBuild&lt;/h4&gt;
&lt;p&gt;This is where most of the magic happens. It’s a single step, but behind the scenes, we’re doing unspeakable things to MSBuild that I’m going to…speak about, I guess. The full &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.msbuild&lt;/code&gt; file &lt;a href=&quot;https://gist.github.com/NickCraver/b59ff38567b32936e2a3440e439d5d5c#file-build-msbuild&quot;&gt;is in the earlier Gist&lt;/a&gt;. The most relevant section is the description of crazy:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;THIS IS HOW WE ROLL:  
CompileWeb - ReplaceConfigs - - - - - - BuildViews - - - - - - - - - - - - - PrepareStaticContent  
                   &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;                                                           /|  
                    '- BundleJavaScript - TranslateJsContent - CompileNode   - '  
NOTE:  
since msbuild requires separate projects for parallel execution of targets, this build file is copied
2 times, the DefaultTargets of each copy is set to one of BuildViews, CompileNode or CompressJSContent. 
thus the absence of the DependesOnTarget=&quot;ReplaceConfigs&quot; on those _call_ targets&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;While we maintain 1 copy of the file in the repo, during the build it actually forks into 2 parallel MSBuild processes. We simply copy the file, change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DefaultTargets&lt;/code&gt;, and kick it off in parallel &lt;a href=&quot;https://gist.github.com/NickCraver/b59ff38567b32936e2a3440e439d5d5c#file-build-xml-L146&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first process is building the ASP.NET MVC views with our custom Roslyn-based build in &lt;a href=&quot;https://github.com/StackExchange/StackExchange.Precompilation&quot;&gt;StackExchange.Precompilation&lt;/a&gt;, &lt;a href=&quot;https://stackoverflow.blog/2015/07/23/announcing-stackexchange-precompilation/&quot;&gt;explained by Samo Prelog here&lt;/a&gt;. It’s not only building the views but also plugging in localized strings for each language via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch&lt;/code&gt; statements. There’s a hint at how that works &lt;a href=&quot;#localizationtranslations-moonspeak&quot;&gt;a bit further down&lt;/a&gt;. We wrote this process for localization, but it turns out controlling the speed and batching of the view builds allows us to be &lt;em&gt;much&lt;/em&gt; faster than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aspnet_compiler&lt;/code&gt; used to be. Rumor is performance has gotten better there lately, though.&lt;/p&gt;

&lt;p&gt;The second process is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.less&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.css&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.js&lt;/code&gt; compilation and minification which involves a few components. First up are the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.jsbundle&lt;/code&gt; files. They are simple files that look like this example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;items&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;full-anon.jsbundle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;PartialJS&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;full&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;*.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bounty.js&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;These files are true to their name, they are simply concatenated bundles of files for use further on. This allows us to maintain JavaScript divided up nicely across many files but handle it as one file for the rest of the build. The same bundler code runs as an HTTP handler locally to combine on the fly for local development. This sharing allows us to mimic production as best we can.&lt;/p&gt;

&lt;p&gt;After bundling, we have regular old &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.js&lt;/code&gt; files with JavaScript in them. They have letters, numbers, and even some semicolons. They’re delightful. After that, they go through the translator &lt;em&gt;of doom&lt;/em&gt;. We think. No one really knows. It’s black magic. Really what happens here isn’t relevant, but we get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;full.en.js&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;full.ru.js&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;full.pt.js&lt;/code&gt;, etc. with the appropriate translations plugged in. It’s the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;filename&amp;gt;.&amp;lt;locale&amp;gt;.js&lt;/code&gt; pattern for every file. I’ll do a deep-dive with Samo on &lt;a href=&quot;https://trello.com/c/GdywwBgb/24-localization&quot;&gt;the localization post&lt;/a&gt; (go vote it up if you’re curious).&lt;/p&gt;

&lt;p&gt;After JavaScript translation completes (10-12 seconds), we move on to the Node.js piece of the build. Note: node is not installed on the build servers; we have everything needed inside the repo. Why do we use Node.js? Because it’s the native platform for &lt;a href=&quot;http://lesscss.org/&quot;&gt;Less.js&lt;/a&gt; and &lt;a href=&quot;https://github.com/mishoo/UglifyJS2&quot;&gt;UglifyJS&lt;/a&gt;. Once upon a time we used &lt;a href=&quot;http://www.dotlesscss.org/&quot;&gt;dotLess&lt;/a&gt;, but we got tired of maintaining the fork and went with a node build process for faster absorption of new versions.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-compile.js&lt;/code&gt; is &lt;a href=&quot;https://gist.github.com/NickCraver/b59ff38567b32936e2a3440e439d5d5c#file-node-compile-js&quot;&gt;also in the Gist&lt;/a&gt;. It’s a simple forking script that sets up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; node worker processes to handle the hundreds of files we have (due to having hundreds of sites) with the main thread dishing out work. Files that are identical (e.g. the beta sites) are calculated once then cached, so we don’t do the same work a hundred times. It also does things like add cache breakers on our SVG URLs based on a hash of their contents. Since we also serve the CSS with a cache breaker at the application level, we have a cache-breaker that changes from bottom to top, properly cache-breaking at the client when anything changes. The script can probably be vastly improved (and I’d welcome it), it was just the simplest thing that worked and met our requirements when it was written and hasn’t needed to change much since.&lt;/p&gt;

&lt;p&gt;Note: a (totally unintentional) benefit of the cache-breaker calculation has been that we never deploy an incorrect image path in CSS. That situation blows up because we can’t find the file to calculate the hash…and the build fails.&lt;/p&gt;

&lt;p&gt;The totality of node-compile’s job is minifying the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.js&lt;/code&gt; files (in place, not something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.min.js&lt;/code&gt;) and turning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.less&lt;/code&gt; into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.css&lt;/code&gt;. After that’s done, MSBuild has produced all the output we need to run a fancy schmancy website. Or at least something like Stack Overflow. Note that we’re slightly odd in that we share styles across many site themes, so we’re transforming hundreds of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.less&lt;/code&gt; files at once. That’s the reason for spawning workers — the number spawned scales based on core count.&lt;/p&gt;

&lt;h4 id=&quot;step-6-translation-dump-c-edition&quot;&gt;Step 6: Translation Dump (C# Edition)&lt;/h4&gt;
&lt;p&gt;This step we call the transmogulator. It copies all of the to-be-localized strings we use in C# and Razor inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_s()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_m()&lt;/code&gt; out so we have the total set to send to the translators. This isn’t a direct extraction, it’s a collection of some custom attributes added when we translated things during compilation in the previous step. This step is just a slightly more complicated version of what’s happening in step #4 for JavaScript. We dump the files in raw &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.txt&lt;/code&gt; files for use later (and as a history of sorts). We also dump the overrides here, where we supply overrides directly &lt;em&gt;on top of&lt;/em&gt; what our translators have translated. These are typically community fixes we want to upstream.&lt;/p&gt;

&lt;p&gt;I realize a lot of that doesn’t make a ton of sense without going heavily into how the translation system works - which will be a topic for &lt;a href=&quot;https://trello.com/c/GdywwBgb/24-localization&quot;&gt;a future post&lt;/a&gt;. The basics are: we’re dumping all the strings from our codebase so that people can translate them. When they are translated, they’ll be available for step #5 above in the next build after.&lt;/p&gt;

&lt;p&gt;Here’s the entire step:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;%system.moonspeaktools%\Transmogulator.exe .\StackOverflow\bin en;pt-br;mn-mn;ja;es;ru
  &quot;%system.translationsDumpPath%\artifact-%build.number%.{0}.txt&quot; MoonSpeak
%system.moonspeaktools%\OverrideExporter.exe export &quot;%system.translationConnectionString%&quot;
  &quot;%system.translationsDumpPath%&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-7-importing-english-strings&quot;&gt;Step 7: Importing English Strings&lt;/h4&gt;
&lt;p&gt;One of the weird things to think about in localization is the &lt;em&gt;simplest&lt;/em&gt; way to translate is to &lt;em&gt;not&lt;/em&gt; special case English. To that end, here we are special casing it. Dammit, we already screwed up. But, by special casing it at build time, we prevent having to special case it later. Almost every string we put in would be correct in English, only needing the translation overrides for multiples and such (e.g. “1 item” vs “2 items”), so we want to immediately import anything added to the English result set so that it’s ready for Stack Overflow as soon as it’s built the first time (e.g. no delay on the translators for deploying a new feature). Ultimately, this step takes the text files created for English in Steps 4 and 6 and turns around and inserts them (into our translations database) for the English entries.&lt;/p&gt;

&lt;p&gt;This step also posts all new strings added to a special internal chatroom alerting our translators in all languages so that they can be translated ASAP. Though we don’t want to delay builds and deploys on new strings (they may appear in English for a build and we’re okay with that), we want to minimize it - so we have an alert pipe so to speak. Localization delays are binary: either you wait on all languages or you don’t. We choose faster deploys.&lt;/p&gt;

&lt;p&gt;Here’s the call for step 7:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;%system.moonspeaktools%\MoonSpeak.Importer.exe &quot;%system.translationConnectionString%&quot;
  &quot;%system.translationsDumpPath%\artifact-%build.number%.en.txt&quot; 9 false 
  &quot;https://teamcity/viewLog.html?buildId=%teamcity.build.id%&amp;amp;tab=buildChangesDiv&quot;
%system.moonspeaktools%\MoonSpeak.Importer.exe &quot;%system.translationConnectionString%&quot;
  &quot;%system.translationsDumpPath%\artifact-%build.number%-js.en.txt&quot; 9 false
  &quot;https://teamcity/viewLog.html?buildId=%teamcity.build.id%&amp;amp;tab=buildChangesDiv&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;h4 id=&quot;step-8-deploy-website&quot;&gt;Step 8: Deploy Website&lt;/h4&gt;
&lt;p&gt;Here’s where all of our hard work pays off. Well, the build server’s hard work really…but we’re taking credit. We have one goal here: take our built code and turn it into the active code on all target web servers. This is where you can get really complicated when you really just need to do something simple. What do you &lt;em&gt;really&lt;/em&gt; need to perform to deploy updated code to a web server? Three things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Stop the website&lt;/li&gt;
  &lt;li&gt;Overwrite the files&lt;/li&gt;
  &lt;li&gt;Start the website&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. That’s all the major pieces. So let’s get as close to the stupidest, simplest process as we can. Here’s the call for that step, it’s a PowerShell script we pre-deploy on all build agents (with a build) that very rarely changes. We use the same set of scripts for all IIS website deployments, even the Jekyll-based blog. Here are the arguments we pass to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebsiteDeploy.ps1&lt;/code&gt; script:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;nt&quot;&gt;-HAProxyServers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%deploy.HAProxy.Servers%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-HAProxyPort&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deploy.HAProxy.Port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Servers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%deploy.ServerNames%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Backends&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%deploy.HAProxy.Backends%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Site&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%deploy.WebsiteName%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Delay&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deploy.HAProxy.Delay.IIS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-DelayBetween&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deploy.HAProxy.Delay.BetweenServers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-WorkingDir&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%teamcity.build.workingDir%\%deploy.WebsiteDirectory%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ExcludeFolders&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%deploy.RoboCopy.ExcludedFolders%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ExcludeFiles&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%deploy.RoboCopy.ExcludedFiles%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ContentSource&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%teamcity.build.workingDir%\%deploy.contentSource%&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ContentSStaticFolder&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;%deploy.contentSStaticFolder%&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;I’ve included script &lt;a href=&quot;https://gist.github.com/NickCraver/b59ff38567b32936e2a3440e439d5d5c#file-deployscripts-ps1&quot;&gt;in the Gist here&lt;/a&gt;, with all the relevant functions from the profile included for completeness. The meat of the main script is here (lines shortened for fit below, but the complete version is in the Gist):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ServerSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-ServerSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ServerSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-ne&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Execute&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Server: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HAProxyPost&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;drain&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# delay between taking a server out and killing the site, so current requests can finish&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Delay&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Delay&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# kill website in IIS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToggleSite&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ServerSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ServerSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;stop&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Site&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Site&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# inform HAProxy this server is down, so we don't come back up immediately&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HAProxyPost&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;hdown&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# robocopy!&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CopyDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Source&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$WorkingDir&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Destination&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;\\&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;\...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# restart website in IIS&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToggleSite&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ServerSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ServerSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Site&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$Site&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# stick the site back in HAProxy rotation&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HAProxyPost&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ready&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# session cleanup&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$ServerSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Remove-PSSession&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;The steps here are the minimal needed to &lt;em&gt;gracefully&lt;/em&gt; update a website, informing the load balancer of what’s happening and impacting users as little as possible. Here’s what happens:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Tell &lt;a href=&quot;https://www.haproxy.org/&quot;&gt;HAProxy&lt;/a&gt; to stop sending new traffic&lt;/li&gt;
  &lt;li&gt;Wait a few seconds for all current requests to finish&lt;/li&gt;
  &lt;li&gt;Tell IIS to stop the site (&lt;a href=&quot;https://technet.microsoft.com/en-us/library/ee790607.aspx&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stop-Website&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Tell HAProxy that this webserver is down (rather than waiting for it to detect)&lt;/li&gt;
  &lt;li&gt;Copy the new code (&lt;a href=&quot;https://technet.microsoft.com/en-us/library/cc733145.aspx&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;robocopy&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Tell IIS to start the new site (&lt;a href=&quot;https://technet.microsoft.com/en-us/library/hh867884(v=wps.630).aspx&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Start-Website&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Tell HAProxy this website is ready to come back up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that HAProxy doesn’t &lt;em&gt;immediately&lt;/em&gt; bring the site back online. It will do so after 3 successful polls, this is a key difference between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAINT&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DRAIN&lt;/code&gt; in HAProxy. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAINT&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;READY&lt;/code&gt; assumes the server is instantly up. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DRAIN&lt;/code&gt; -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;READY&lt;/code&gt; assumes down. The former has a very nasty effect on &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/system.threading.threadpool.aspx&quot;&gt;ThreadPool&lt;/a&gt; growth waiting with the initial slam while things are spinning up.&lt;/p&gt;

&lt;p&gt;We repeat the above for all webservers in the build. There’s also a slight pause between each server, all of which is tunable with TeamCity settings.&lt;/p&gt;

&lt;p&gt;Now the above is what happens &lt;em&gt;for a single website&lt;/em&gt;. In reality, this step deploys twice. The reason why is race conditions. For the best client-side performance, our static assets have headers set to cache for 7 days. We break this cache &lt;em&gt;only when it changes&lt;/em&gt;, not on every build. After all, you only need to fetch new CSS, SVGs, or JavaScript if they actually changed. Since &lt;a href=&quot;https://cdn.sstatic.net/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdn.sstatic.net&lt;/code&gt;&lt;/a&gt; comes from our web tier underneath, here’s what &lt;em&gt;could&lt;/em&gt; happen due to the nature of a rolling build:&lt;/p&gt;

&lt;p&gt;You hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ny-web01&lt;/code&gt; and get a brand spanking new querystring for the new version. Your browser then hits our CDN at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdn.sstatic.net&lt;/code&gt;, which let’s say hits &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ny-web07&lt;/code&gt;…which has the old content. Oh crap, now we have old content cached with the new hash for a hell of a long time. That’s no good, that’s a hard reload to fix, &lt;em&gt;after&lt;/em&gt; you purge the CDN. We avoid that by pre-deploying the static assets to another website in IIS specifically serving the CDN. This way &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sstatic.net&lt;/code&gt; gets the content in one rolling deploy, just before the new code issuing new hashes goes out. This means that there is a slight chance that someone will get &lt;em&gt;new&lt;/em&gt; static content with an &lt;em&gt;old&lt;/em&gt; hash (if they hit a CDN miss for a piece of content that actually changed this build). The big difference is that (rarely hit) problem fixes itself on a page reload, since the hash will change as soon as the new code is running a minute later. It’s a much better direction to fail in.&lt;/p&gt;

&lt;p&gt;At the end of this step (in production), 7 of 9 web servers are typically online and serving users. The last 2 will finish their spin-up shortly after. The step takes about 2 minutes for 9 servers. But yay, our code is live! Now we’re free to deploy again for that bug we probably just sent out.&lt;/p&gt;

&lt;h4 id=&quot;step-9-new-strings-hook&quot;&gt;Step 9: New Strings Hook&lt;/h4&gt;
&lt;p&gt;This dev-only step isn’t particularly interesting, but useful. All it does is call a webhook telling it that some new strings were present in this build if there were any. The hook target triggers an upload to our translation service to tighten the iteration time on translations (similar to our chat mechanism above). It’s last because strictly speaking it’s optional and we don’t want it to interfere.&lt;/p&gt;

&lt;p&gt;That’s it. Dev build complete. Put away the &lt;a href=&quot;https://xkcd.com/303/&quot;&gt;rolly chairs and swords&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;tiers&quot;&gt;Tiers&lt;/h3&gt;
&lt;p&gt;What we covered above was the entire development CI build with all the things™. All of the translation bits are development only because we just need to get the strings once. The meta and production builds are a simpler subset of the steps. Here’s a simple visualization that compares the build steps across tiers:&lt;/p&gt;

&lt;table class=&quot;comparison&quot;&gt;
  &lt;tr&gt;&lt;th&gt;Build Step&lt;/th&gt;&lt;th&gt;Dev&lt;/th&gt;&lt;th&gt;Meta&lt;/th&gt;&lt;th&gt;Prod&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;1 - Migrate Sites DB&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;2 - Migrate Q&amp;amp;A DBs&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;3 - Find MoonSpeak Tools&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;4 - Translation Dump (JavaScript)&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;5 - MSBuild (Compile Compress and Minify)&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;6 - Translation Dump (C#)&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;7 - Translations Import English Strings&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;8 - Deploy Website&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;/tr&gt;
  &lt;tr&gt;&lt;td&gt;9 - New Strings Hook&lt;/td&gt;&lt;td class=&quot;lit&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;!-- Since you were bored enough to look at the source, here's something to do: https://www.youtube.com/watch?v=W3TtS1wkb7M --&gt;

&lt;p&gt;What do the tiers really translate to? All of our development sites are on WEB10 and WEB11 servers (under different application pools and websites). Meta runs on WEB10 and WEB11 servers, this is specifically &lt;a href=&quot;https://meta.stackexchange.com/&quot;&gt;meta.stackexchange.com&lt;/a&gt; and &lt;a href=&quot;https://meta.stackoverflow.com/&quot;&gt;meta.stackoverflow.com&lt;/a&gt;. Production (all other Q&amp;amp;A sites and metas) like Stack Overflow are on WEB01-WEB09.&lt;/p&gt;

&lt;p&gt;Note: we do a chat notification for build as someone goes through the tiers. Here’s me (against all sane judgement) building out some changes at 5:17pm on a Friday. Don’t try this at home, I’m a professional. Sometimes. Not often.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Chat.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Chat.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Chat.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Chat.png&quot; loading=&quot;lazy&quot; alt=&quot;Chat Messages&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;database-migrations&quot;&gt;Database Migrations&lt;/h3&gt;
&lt;p&gt;See? I promised we’d come back to these. To reiterate: if new code is needed to handle the database migrations, &lt;em&gt;it must be deployed first&lt;/em&gt;.  In practice though, you’re likely dropping a table, or adding a table/column. For the removal case, we remove it from code, deploy, then deploy again (or later) with the drop migration. For the addition case, we would typically add it as nullable or unused in code. If it needs to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;not null&lt;/code&gt;, a foreign key, etc. we’d do that in a later deploy as well.&lt;/p&gt;

&lt;p&gt;The database migrator we use is a very simple repo we could open source, but honestly, there are dozens out there and the “same migration against n databases” is fairly specific. The others are probably much better and ours is very specific to &lt;em&gt;only&lt;/em&gt; our needs. The migrator connects to the Sites database, gets the list of databases to run against, and executes all migrations against every one (running multiple databases in parallel). This is done by looking at the passed-in migrations folder and loading it (once) as well as hashing the contents of every file. Each database has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Migrations&lt;/code&gt; table that keeps track of what has already been run. It looks like this (descending order):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Migrations-Table.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Migrations-Table.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Migrations-Table.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Migrations-Table.png&quot; loading=&quot;lazy&quot; alt=&quot;Migrations Table&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the above aren’t all in file number order. That’s because 724 and 725 were in a branch for a few days. That’s not an issue, order is not guaranteed. &lt;strong&gt;Each migration itself is written to be idempotent&lt;/strong&gt;, e.g. “don’t try to add the column if it’s already there”, but the specific order isn’t usually relevant. Either they’re all per-feature, or they’re actually going in-order anyway. The migrator respects the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GO&lt;/code&gt; operator to separate batches and by default runs all migrations in a transaction. The transaction behavior can be changed with a first-line comment in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.sql&lt;/code&gt; file: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-- no transaction --&lt;/code&gt;. Perhaps the most useful explanation to the migrator is the README.md I wrote for it. &lt;a href=&quot;https://gist.github.com/NickCraver/b59ff38567b32936e2a3440e439d5d5c#file-sql-migrator-readme-md&quot;&gt;Here it is in the Gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In memory, we compare the list of migrations that already ran to those needing to run then execute what needs running, in file order. If we find the hash of a filename doesn’t match the migration with the same file name in the table, we abort as a safety measure. We can &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--force&lt;/code&gt; to resolve this in the rare cases a migration &lt;em&gt;should&lt;/em&gt; have changed (almost always due to developer error). After all migrations have run, we’re done.&lt;/p&gt;

&lt;p&gt;Rollbacks. We rarely do them. In fact, I can’t remember ever having done one. We avoid them through the approach in general: we deploy small and often. It’s often quicker to fix code and deploy than reverse a migration, especially across hundreds of databases. We also make development mimic production as often as possible, restoring production data periodically. If we needed to reverse something, we could just push another migration negating whatever we did that went boom. The tooling has no concept of rollback though. Why roll back when you can roll forward?&lt;/p&gt;

&lt;h3 id=&quot;localizationtranslations-moonspeak&quot;&gt;Localization/Translations (Moonspeak)&lt;/h3&gt;
&lt;p&gt;This will get its own post, but I wanted to hint at why we do all of this work at compile time. After all, I always advocate strongly for simplicity (yep, even in this 6,000-word blog post - the irony is not lost on me). You should only do something more complicated when you &lt;em&gt;need&lt;/em&gt; to do something more complicated. This is one of those cases, for performance. &lt;a href=&quot;https://twitter.com/m0sa&quot;&gt;Samo&lt;/a&gt; does a lot of work to make our localizations have as little &lt;strong&gt;runtime&lt;/strong&gt; impact as possible. We’ll gladly trade a bit of build complexity to make that happen. While there are options such as &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/ekyft91f.aspx&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.resx&lt;/code&gt; files&lt;/a&gt; or &lt;a href=&quot;https://github.com/aspnet/localization&quot;&gt;the new localization in ASP.NET Core 1.0&lt;/a&gt;, most of these allocate more than necessary especially with tokenized strings. Here’s what strings look like in our code:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Deployment-Translations-1.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Translations-1.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Translations-1.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Translations-1.png&quot; loading=&quot;lazy&quot; alt=&quot;Translations: IDE&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here’s what that line looks like compiled (via Reflector):
&lt;a href=&quot;/blog/content/SO-Deployment-Translations-2.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Translations-2.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Translations-2.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Translations-2.png&quot; loading=&quot;lazy&quot; alt=&quot;Translations: Reflected&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
…and most importantly, the compiled implementation:
&lt;a href=&quot;/blog/content/SO-Deployment-Translations-3.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Deployment-Translations-3.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Deployment-Translations-3.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Deployment-Translations-3.png&quot; loading=&quot;lazy&quot; alt=&quot;Translations: Reflected 2&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that we aren’t allocating the entire string together, only the pieces (with most interned). This may seem like a small thing, but at scale that’s a &lt;em&gt;huge&lt;/em&gt; number of allocations and a lot of time in a garbage collector. I’m sure that just raises a ton of questions about how Moonspeak works. If so, &lt;a href=&quot;https://trello.com/c/GdywwBgb/24-localization-moonspeak-translations&quot;&gt;go vote it up&lt;/a&gt;. It’s a big topic in itself, I only wanted to justify the compile-time complication it adds here. To us, it’s worth it.&lt;/p&gt;

&lt;h3 id=&quot;building-without-breaking&quot;&gt;Building Without Breaking&lt;/h3&gt;
&lt;p&gt;A question I’m often asked is how we prevent breaks while rolling out new code constantly. Here are some common things we run into and how we avoid them.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Cache object changes:
    &lt;ul&gt;
      &lt;li&gt;If we have a cache object that totally changes. That’s a new cache key and we let the old one fall out naturally with time.&lt;/li&gt;
      &lt;li&gt;If we have a cache object that only changes &lt;em&gt;locally&lt;/em&gt; (in-memory): nothing to do. The new app domain doesn’t collide.&lt;/li&gt;
      &lt;li&gt;If we have a cache object that changes &lt;em&gt;in redis&lt;/em&gt;, then we need to make sure the old and new &lt;a href=&quot;https://github.com/mgravell/protobuf-net&quot;&gt;protobuf&lt;/a&gt; signatures are compatible…or change the key.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Tag Engine:
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/#service-tier-iis-aspnet-mvc-523-net-461-and-httpsys&quot;&gt;The tag engine&lt;/a&gt; reloads on every build (currently). This is triggered by checking every minute for a new build hash on the web tier. If one is found, the application &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\bin&lt;/code&gt; and a few configs are downloaded to the Stack Server host process and spun up as a new app domain. This sidesteps the need for a deploy to those boxes and keeps local development setup simple (we run no separate process locally).&lt;/li&gt;
      &lt;li&gt;This one is changing drastically soon, since reloading every build is way more often that necessary. We’ll be moving to a more traditional deploy-it-when-it-changes model there soon. Possibly using GPUs. Stay tuned.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Renaming SQL objects:
    &lt;ul&gt;
      &lt;li&gt;“Doctor it hurts when I do that!”&lt;/li&gt;
      &lt;li&gt;“Don’t do that.”&lt;/li&gt;
      &lt;li&gt;We may add and migrate, but a live rename is almost certain to cause an outage of some sort. We don’t do that outside of dev.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;APIs:
    &lt;ul&gt;
      &lt;li&gt;Deploy the new endpoint before the new consumer.&lt;/li&gt;
      &lt;li&gt;If changing an existing endpoint, it’s usually across 3 deploys: add (endpoint), migrate (consumer), cleanup (endpoint).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Bugs:
    &lt;ul&gt;
      &lt;li&gt;Try not to deploy bugs.&lt;/li&gt;
      &lt;li&gt;If you screw up, try not to do it the same way twice.&lt;/li&gt;
      &lt;li&gt;Accept that crap happens, live, learn, and move on.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s all of the major bits of our deployment process. But as always, ask any questions you have in comments below and you’ll get an answer.&lt;/p&gt;

&lt;p&gt;I want to take a minute and thank the teams at Stack Overflow here. We build all of this, together. Many people help me review these blog posts before they go out to make sure everything is accurate. The posts are not short, and several people are reviewing them in off-hours because they simply saw a post in chat and wanted to help out. These same people hop into comment threads here, on Reddit, on Hacker News, and other places discussions pop up. They answer questions as they arise or relay them to someone who can answer. They do this on their own, out of a love for the community. I’m tremendously appreciative of their effort and it’s a privilege to work with some of the best programmers and sysadmins in the world. &lt;a href=&quot;https://twitter.com/E_Craver&quot;&gt;My lovely wife Elise&lt;/a&gt; also gives her time to help edit these before they go live. To all of you: thanks.&lt;/p&gt;

&lt;p&gt;What’s next? The way &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;this series&lt;/a&gt; works is I blog in order of what the community wants to know about most. Going by &lt;a href=&quot;https://trello.com/b/0zgQjktX/blog-post-queue-for-stack-overflow-topics&quot;&gt;the Trello board&lt;/a&gt;, it looks like &lt;a href=&quot;https://trello.com/c/1Oc9cC6u/11-monitoring&quot;&gt;Monitoring&lt;/a&gt; is the next most interesting topic. So next time expect to learn how we monitor all of the systems here at Stack. I’ll cover how we monitor servers and services as well as the performance of Stack Overflow 24/7 as users see it all over the world. I’ll also cover many of the monitoring tools we’re using and have built; we’ve open sourced several big ones. Thanks for reading this post which ended up way longer than I envisioned and see you next time.&lt;/p&gt;
</description>
        <pubDate>Tue, 03 May 2016 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2016/05/03/stack-overflow-how-we-do-deployment-2016-edition/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Stack Overflow: The Hardware - 2016 Edition</title>
        <link>https://nickcraver.com/blog/2016/03/29/stack-overflow-the-hardware-2016-edition/</link>
        <description>&lt;blockquote&gt;
  &lt;p&gt;This is #2 in a &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;very long series of posts&lt;/a&gt; on Stack Overflow’s architecture.&lt;br /&gt;
Previous post (#1): &lt;a href=&quot;/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/&quot;&gt;Stack Overflow: The Architecture - 2016 Edition&lt;/a&gt;&lt;br /&gt;
Next post (#3): &lt;a href=&quot;/blog/2016/05/03/stack-overflow-how-we-do-deployment-2016-edition/&quot;&gt;Stack Overflow: How We Do Deployment - 2016 Edition&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Who loves hardware? Well, I do and this is my blog so I win. If you &lt;em&gt;don’t&lt;/em&gt; love hardware then I’d go ahead and close the browser.&lt;/p&gt;

&lt;p&gt;Still here? Awesome. Or your browser is crazy slow, in which case you should think about some new hardware.&lt;/p&gt;

&lt;p&gt;I’ve repeated many, &lt;em&gt;many&lt;/em&gt; times: &lt;strong&gt;&lt;a href=&quot;https://blog.codinghorror.com/performance-is-a-feature/&quot;&gt;performance is a feature&lt;/a&gt;&lt;/strong&gt;. Since your code is only as fast as the hardware it runs on, the hardware definitely matters. Just like any other platform, Stack Overflow’s architecture comes in layers. Hardware is the foundation layer for us, and having it in-house affords us many luxuries not available in other scenarios…like running on someone else’s servers. It also comes with direct and indirect costs. But that’s not the point of this post, &lt;a href=&quot;https://trello.com/c/4e6TOnA7/87-on-prem-vs-aws-azure-etc-why-the-cloud-isn-t-for-us&quot;&gt;that comparison will come later&lt;/a&gt;. For now, I want to provide a detailed inventory of our infrastructure for reference and comparison purposes. And pictures of servers. Sometimes naked servers. This web page could have loaded much faster, but I couldn’t help myself.&lt;/p&gt;

&lt;p&gt;In many posts through this series I will give a lot of numbers and specs. When I say “our SQL server utilization is almost always at 5–10% CPU,” well, that’s great. But, 5–10% &lt;em&gt;of what?&lt;/em&gt; That’s when we need a point of reference. This hardware list is meant to both answer those questions and serve as a source for comparison when looking at other platforms and what utilization may look like there, how much capacity to compare to, etc.
&lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-we-do-hardware&quot;&gt;How We Do Hardware&lt;/h2&gt;

&lt;p&gt;Disclaimer: I don’t do this alone. George Beech (&lt;a href=&quot;https://twitter.com/GABeech&quot;&gt;@GABeech&lt;/a&gt;) is my main partner in crime when speccing hardware here at Stack. We carefully spec out each server for its intended purpose. What we don’t do is order in bulk and assign tasks later. We’re not alone in this process though; you have to know what’s going to run on the hardware to spec it optimally. We’ll work with the developer(s) and/or other site reliability engineers to best accommodate what is intended live on the box.&lt;/p&gt;

&lt;p&gt;We’re also looking at what’s best &lt;em&gt;in the system&lt;/em&gt;. Each server is not an island. How it fits into the overall architecture is definitely a consideration. What services can share this platform? This data store? This log system? There is inherent value in managing fewer things, or at least fewer variations of anything.&lt;/p&gt;

&lt;p&gt;When we spec out our hardware, we look at a myriad of requirements that help determine what to order. I’ve never really written this mental checklist down, so let’s give it a shot:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Is this a scale up or scale out problem? (Are we buying one bigger machine or a few smaller ones?)
    &lt;ul&gt;
      &lt;li&gt;How much redundancy do we need/want? (How much headroom and failover capability?)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Storage:
    &lt;ul&gt;
      &lt;li&gt;Will this server/application touch disk? (Do we need anything besides the spinny OS drives?)
        &lt;ul&gt;
          &lt;li&gt;If so, how much? (How much bandwidth? How many small files? Does it need SSDs?)&lt;/li&gt;
          &lt;li&gt;If SSDs, what’s the write load? (Are we talking Intel S3500/3700s? P360x? P3700s?)
            &lt;ul&gt;
              &lt;li&gt;How much SSD capacity do we need? (And should it be a 2-tier solution with HDDs as well?)&lt;/li&gt;
              &lt;li&gt;Is this data totally transient? (Are SSDs without capacitors, which are far cheaper, a better fit?)&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Will the storage needs likely expand? (Do we get a 1U/10-bay server or a 2U/26-bay server?)&lt;/li&gt;
      &lt;li&gt;Is this a data warehouse type scenario? (Are we looking at 3.5” drives? If so, in a 12 or 16 drives per 2U chassis?)
        &lt;ul&gt;
          &lt;li&gt;Is the storage trade-off for the 3.5” backplane worth the 120W TDP limit on processing?&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Do we need to expose the disks directly? (Does the controller need to support pass-through?)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Memory:
    &lt;ul&gt;
      &lt;li&gt;How much memory does it need? (What &lt;em&gt;must&lt;/em&gt; we buy?)&lt;/li&gt;
      &lt;li&gt;How much memory &lt;em&gt;could&lt;/em&gt; it use? (What’s &lt;em&gt;reasonable&lt;/em&gt; to buy?)&lt;/li&gt;
      &lt;li&gt;Do we think it will need more memory later? (What memory channel configuration should we go with?)&lt;/li&gt;
      &lt;li&gt;Is this a memory-access-heavy application? (Do we want to max out the clock speed?)
        &lt;ul&gt;
          &lt;li&gt;Is it highly parallel access? (Do we want to spread the same space across more DIMMs?)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;CPU:
    &lt;ul&gt;
      &lt;li&gt;What kind of processing are we looking at? (Do we need base CPUs or power?)&lt;/li&gt;
      &lt;li&gt;Is it heavily parallel? (Do we want fewer, faster cores? Or, does it call for more, slower cores?)
        &lt;ul&gt;
          &lt;li&gt;In what ways? Will there be heavy L2/L3 cache contention? (Do we need a huge L3 cache for performance?)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Is it mostly single core performance? (Do we want maximum clock?)
        &lt;ul&gt;
          &lt;li&gt;If so, how many processes at once? (Which turbo spread do we want here?)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Network:
    &lt;ul&gt;
      &lt;li&gt;Do we need additional 10Gb network connectivity? (Is this a “through” machine, such as a load balancer?)&lt;/li&gt;
      &lt;li&gt;How much balance do we need on Tx/Rx buffers? (What CPU core count balances best?)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Redundancy:
    &lt;ul&gt;
      &lt;li&gt;Do we need servers in the DR data center as well?
        &lt;ul&gt;
          &lt;li&gt;Do we need the same number, or is less redundancy acceptable?&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Do we need a power cord? No. No we don’t.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let’s see what hardware in our New York QTS data center serves the sites. Secretly, it’s really New Jersey, but let’s just keep that between us. Why do we say it’s the NY data center? Because we don’t want to rename all those NY- servers. I’ll note in the list below when and how Denver differs slightly in specs or redundancy levels.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#&quot; class=&quot;button toggle-stack-overflow-the-hardware-2016-edition&quot; style=&quot;min-width: 110px;&quot;&gt;Hide Pictures&lt;/a&gt; (in case you’re using this as a hardware reference list later)&lt;/p&gt;

&lt;h2 id=&quot;servers-running-stack-overflow--stack-exchange-sites&quot;&gt;Servers Running Stack Overflow &amp;amp; Stack Exchange Sites&lt;/h2&gt;

&lt;p&gt;A few global truths so I need not repeat them in each server spec below:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;OS drives are not included unless they’re special. Most servers use a pair of 250 or 500GB SATA HDDs for the OS partition, &lt;strong&gt;always&lt;/strong&gt; in a RAID 1. Boot time is not a concern we have and &lt;em&gt;even if it were&lt;/em&gt;, the vast majority of our boot time on any physical server isn’t dependent on drive speed (for example, checking 768GB of memory).&lt;/li&gt;
  &lt;li&gt;All servers are connected by 2 or more 10Gb network links in active/active &lt;a href=&quot;https://en.wikipedia.org/wiki/Link_aggregation#Link_Aggregation_Control_Protocol&quot;&gt;LACP&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;All servers run on 208V single phase power (via 2 PSUs feeding from 2 PDUs backed by 2 sources).&lt;/li&gt;
  &lt;li&gt;All servers in New York have cable arms, all servers in Denver do not (local engineer’s preference).&lt;/li&gt;
  &lt;li&gt;All servers have both an &lt;a href=&quot;http://en.community.dell.com/techcenter/systems-management/w/wiki/3204.dell-remote-access-controller-drac-idrac&quot;&gt;iDRAC&lt;/a&gt; connection (via the management network) and a KVM connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;network&quot;&gt;Network&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;2x Cisco Nexus &lt;a href=&quot;https://www.cisco.com/c/en/us/products/switches/nexus-5596up-switch/index.html&quot;&gt;5596UP&lt;/a&gt; core switches (96 SFP+ ports each at 10 Gbps)&lt;/li&gt;
  &lt;li&gt;10x Cisco Nexus &lt;a href=&quot;https://www.cisco.com/c/en/us/products/switches/nexus-2232tm-10ge-fabric-extender/index.html&quot;&gt;2232TM&lt;/a&gt; Fabric Extenders (&lt;strong&gt;2 per rack&lt;/strong&gt; - each has 32 BASE-T ports each at 10Gbps + 8 SFP+ 10Gbps uplinks)&lt;/li&gt;
  &lt;li&gt;2x Fortinet &lt;a href=&quot;http://www.fortinet.com/products/fortigate/enterprise-firewalls.html&quot;&gt;800C&lt;/a&gt; Firewalls&lt;/li&gt;
  &lt;li&gt;2x Cisco &lt;a href=&quot;https://www.cisco.com/c/en/us/products/routers/asr-1001-router/index.html&quot;&gt;ASR-1001&lt;/a&gt; Routers&lt;/li&gt;
  &lt;li&gt;2x Cisco &lt;a href=&quot;https://www.cisco.com/c/en/us/products/routers/asr-1001-x-router/index.html&quot;&gt;ASR-1001-x&lt;/a&gt; Routers&lt;/li&gt;
  &lt;li&gt;6x Cisco &lt;a href=&quot;https://www.cisco.com/c/en/us/support/switches/catalyst-2960s-48ts-l-switch/model.html&quot;&gt;2960S-48TS-L&lt;/a&gt; Management network switches (&lt;strong&gt;1 Per Rack&lt;/strong&gt; - 48 1Gbps ports + 4 SFP 1Gbps)&lt;/li&gt;
  &lt;li&gt;1x Dell &lt;a href=&quot;http://accessories.us.dell.com/sna/productdetail.aspx?c=us&amp;amp;l=en&amp;amp;s=bsd&amp;amp;cs=04&amp;amp;sku=A7546775&quot;&gt;DMPU4032&lt;/a&gt; KVM&lt;/li&gt;
  &lt;li&gt;7x Dell &lt;a href=&quot;http://accessories.us.dell.com/sna/productdetail.aspx?c=us&amp;amp;l=en&amp;amp;s=bsd&amp;amp;cs=04&amp;amp;sku=A7546777&quot;&gt;DAV2216&lt;/a&gt; KVM Aggregators (&lt;strong&gt;1–2 per rack&lt;/strong&gt; - each uplinks to the DPMU4032)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: Each FEX has 80 Gbps of uplink bandwidth to its core, and the cores have a 160 Gbps port channel between them. Due to being a more recent install, the hardware in our Denver data center is slightly newer. All 4 routers are &lt;a href=&quot;https://www.cisco.com/c/en/us/products/routers/asr-1001-x-router/index.html&quot;&gt;ASR-1001-x&lt;/a&gt; models and the 2 cores are &lt;a href=&quot;https://www.cisco.com/c/en/us/products/switches/nexus-56128p-switch/index.html&quot;&gt;Cisco Nexus 56128P&lt;/a&gt;, which have 96 SFP+ 10Gbps ports and 8 QSFP+ 40Gbps ports each. This saves 10Gbps ports for future expansion since we can bond the cores with 4x 40Gbps links, instead of eating 16x 10Gbps ports as we do in New York.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;pics-stack-overflow-the-hardware-2016-edition&quot;&gt;
  &lt;p&gt;Here’s what the network gear looks like in New York:&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Network-NewYork-Rack.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Network-NewYork-Rack-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Network-NewYork-Rack-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Network-NewYork-Rack-Small.jpg&quot; alt=&quot;New York Rack&quot; style=&quot;float: right; width: 477px; height: 708px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;a href=&quot;/blog/content/SO-Hardware-Network-NewYork-Fiber.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Network-NewYork-Fiber-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Network-NewYork-Fiber-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Network-NewYork-Fiber-Small.jpg&quot; alt=&quot;New York Fiber&quot; style=&quot;width: 477px; height: 346px; padding-right: 16px; padding-bottom: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;a href=&quot;/blog/content/SO-Hardware-Network-NewYork-Fortinet.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Network-NewYork-Fortinet-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Network-NewYork-Fortinet-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Network-NewYork-Fortinet-Small.jpg&quot; alt=&quot;New York Fortinet&quot; style=&quot;width: 477px; height: 346px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;…and in Denver:&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Network-Denver-Raw.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Network-Denver-Raw-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Network-Denver-Raw-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Network-Denver-Raw-Small.jpg&quot; alt=&quot;Denver network before install&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Network-Denver-Racked.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Network-Denver-Racked-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Network-Denver-Racked-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Network-Denver-Racked-Small.jpg&quot; alt=&quot;Denver Network - Racked&quot; style=&quot;width: 477px; height: 620px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Network-Denver-Installed.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Network-Denver-Installed-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Network-Denver-Installed-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Network-Denver-Installed-Small.jpg&quot; alt=&quot;Denver Network - Installed&quot; style=&quot;width: 477px; height: 620px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;Give a shout to &lt;a href=&quot;https://twitter.com/thefarseeker&quot;&gt;Mark Henderson&lt;/a&gt;, one of our Site Reliability Engineers who made a special trip to the New York DC to get me some high-res, current photos for this post.&lt;/p&gt;
&lt;/div&gt;

&lt;h4 id=&quot;sql-servers-stack-overflow-cluster&quot;&gt;SQL Servers (Stack Overflow Cluster)&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;2 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r720xd/pd&quot;&gt;R720xd&lt;/a&gt; Servers, each with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/75283/Intel-Xeon-Processor-E5-2697-v2-30M-Cache-2_70-GHz&quot;&gt;E5-2697v2&lt;/a&gt; Processors (12 cores @2.7–3.5GHz each)&lt;/li&gt;
  &lt;li&gt;384 GB of RAM (24x 16 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;1x Intel &lt;a href=&quot;https://www.intel.com/content/www/us/en/solid-state-drives/solid-state-drives-dc-p3608-series.html&quot;&gt;P3608&lt;/a&gt; 4 TB NVMe PCIe SSD (RAID 0, 2 controllers per card)&lt;/li&gt;
  &lt;li&gt;24x Intel &lt;a href=&quot;https://ark.intel.com/products/56584/Intel-SSD-710-Series-200GB-2_5in-SATA-3Gbs-25nm-MLC&quot;&gt;710&lt;/a&gt; 200 GB SATA SSDs (RAID 10)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;sql-servers-stack-exchange-and-everything-else-cluster&quot;&gt;SQL Servers (Stack Exchange “…and everything else” Cluster)&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;2 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r730xd/pd&quot;&gt;R730xd&lt;/a&gt; Servers, each with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/83361/Intel-Xeon-Processor-E5-2667-v3-20M-Cache-3_20-GHz&quot;&gt;E5-2667v3&lt;/a&gt; Processors (8 cores @3.2–3.6GHz each)&lt;/li&gt;
  &lt;li&gt;768 GB of RAM (24x 32 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;3x Intel &lt;a href=&quot;https://ark.intel.com/products/79620/Intel-SSD-DC-P3700-Series-2_0TB-12-Height-PCIe-3_0-20nm-MLC&quot;&gt;P3700&lt;/a&gt; 2 TB NVMe PCIe SSD (RAID 0)&lt;/li&gt;
  &lt;li&gt;24x 10K Spinny 1.2 TB SATA HDDs (RAID 10)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: Denver SQL hardware is identical in spec, but there is only 1 SQL server for each corresponding pair in New York.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;pics-stack-overflow-the-hardware-2016-edition&quot;&gt;
  &lt;p&gt;Here’s what the SQL Servers in New York looked like while getting their PCIe SSD upgrades in February:&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Hardware-SQL-Inside.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-SQL-Inside-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-SQL-Inside-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-SQL-Inside-Small.jpg&quot; alt=&quot;SQL Server - Inside&quot; style=&quot;width: 477px; height: 354px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-SQL-SSDs.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-SQL-SSDs-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-SQL-SSDs-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-SQL-SSDs-Small.jpg&quot; alt=&quot;SQL Server - SSDs&quot; style=&quot;width: 477px; height: 354px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;a href=&quot;/blog/content/SO-Hardware-SQL-Front.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-SQL-Front-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-SQL-Front-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-SQL-Front-Small.jpg&quot; alt=&quot;SQL Server - Front&quot; style=&quot;width: 477px; height: 354px; padding-top: 16px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-SQL-Top.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-SQL-Top-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-SQL-Top-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-SQL-Top-Small.jpg&quot; alt=&quot;SQL Server - Top&quot; style=&quot;width: 477px; height: 354px; padding-top: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;h4 id=&quot;web-servers&quot;&gt;Web Servers&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;11 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r630/pd&quot;&gt;R630&lt;/a&gt; Servers, each with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/81713/Intel-Xeon-Processor-E5-2690-v3-30M-Cache-2_60-GHz&quot;&gt;E5-2690v3&lt;/a&gt; Processors (12 cores @2.6–3.5GHz each)&lt;/li&gt;
  &lt;li&gt;64 GB of RAM (8x 8 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;2x Intel &lt;a href=&quot;https://ark.intel.com/products/56567/Intel-SSD-320-Series-300GB-2_5in-SATA-3Gbs-25nm-MLC&quot;&gt;320&lt;/a&gt; 300GB SATA SSDs (RAID 1)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;pics-stack-overflow-the-hardware-2016-edition&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Web-Tier-Front.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Web-Tier-Front-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Web-Tier-Front-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Web-Tier-Front-Small.jpg&quot; alt=&quot;Web Tier - Front&quot; style=&quot;width: 477px; height: 354px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Web-Tier-Back.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Web-Tier-Back-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Web-Tier-Back-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Web-Tier-Back-Small.jpg&quot; alt=&quot;Web Tier - Back&quot; style=&quot;width: 477px; height: 354px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;a href=&quot;/blog/content/SO-Hardware-Web-Tier-Unboxed.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Web-Tier-Unboxed-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Web-Tier-Unboxed-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Web-Tier-Unboxed-Small.jpg&quot; alt=&quot;Web Tier - Unboxed&quot; style=&quot;width: 477px; height: 354px; padding-top: 16px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Web-Tier-Front2.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Web-Tier-Front2-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Web-Tier-Front2-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Web-Tier-Front2-Small.jpg&quot; alt=&quot;Web Tier - Front 2&quot; style=&quot;width: 477px; height: 354px; padding-top: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;h4 id=&quot;service-servers-workers&quot;&gt;Service Servers (Workers)&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;2 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r630/pd&quot;&gt;R630&lt;/a&gt; Servers, each with:
    &lt;ul&gt;
      &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/81900/Intel-Xeon-Processor-E5-2643-v3-20M-Cache-3_40-GHz&quot;&gt;E5-2643 v3&lt;/a&gt; Processors (6 cores @3.4–3.7GHz each)&lt;/li&gt;
      &lt;li&gt;64 GB of RAM (8x 8 GB DIMMs)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;1 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r620/pd&quot;&gt;R620&lt;/a&gt; Server, with:
    &lt;ul&gt;
      &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/64589/Intel-Xeon-Processor-E5-2667-15M-Cache-2_90-GHz-8_00-GTs-Intel-QPI&quot;&gt;E5-2667&lt;/a&gt; Processors (6 cores @2.9–3.5GHz each)&lt;/li&gt;
      &lt;li&gt;32 GB of RAM (8x 4 GB DIMMs)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;2x Intel &lt;a href=&quot;https://ark.intel.com/products/56567/Intel-SSD-320-Series-300GB-2_5in-SATA-3Gbs-25nm-MLC&quot;&gt;320&lt;/a&gt; 300GB SATA SSDs (RAID 1)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: NY-SERVICE03 is still an R620, due to not being old enough for replacement at the same time. It will be upgraded later this year.&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;redis-servers-cache&quot;&gt;Redis Servers (Cache)&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;2 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r630/pd&quot;&gt;R630&lt;/a&gt; Servers, each with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/81909/Intel-Xeon-Processor-E5-2687W-v3-25M-Cache-3_10-GHz&quot;&gt;E5-2687W v3&lt;/a&gt; Processors (10 cores @3.1–3.5GHz each)&lt;/li&gt;
  &lt;li&gt;256 GB of RAM (16x 16 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;2x Intel &lt;a href=&quot;https://ark.intel.com/products/66250/Intel-SSD-520-Series-240GB-2_5in-SATA-6Gbs-25nm-MLC&quot;&gt;520&lt;/a&gt; 240GB SATA SSDs (RAID 1)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;elasticsearch-servers-search&quot;&gt;Elasticsearch Servers (Search)&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;3 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r620/pd&quot;&gt;R620&lt;/a&gt; Servers, each with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/64583/Intel-Xeon-Processor-E5-2680-20M-Cache-2_70-GHz-8_00-GTs-Intel-QPI&quot;&gt;E5-2680&lt;/a&gt; Processors (8 cores @2.7–3.5GHz each)&lt;/li&gt;
  &lt;li&gt;192 GB of RAM (12x 16 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;2x Intel &lt;a href=&quot;https://ark.intel.com/products/75685/Intel-SSD-DC-S3500-Series-800GB-2_5in-SATA-6Gbs-20nm-MLC&quot;&gt;S3500&lt;/a&gt; 800GB SATA SSDs (RAID 1)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;haproxy-servers-load-balancers&quot;&gt;HAProxy Servers (Load Balancers)&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;2 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r620/pd&quot;&gt;R620&lt;/a&gt; Servers (CloudFlare Traffic), each with:
    &lt;ul&gt;
      &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/81900/Intel-Xeon-Processor-E5-2643-v3-20M-Cache-3_40-GHz&quot;&gt;E5-2637 v2&lt;/a&gt; Processors (4 cores @3.5–3.8GHz each)&lt;/li&gt;
      &lt;li&gt;192 GB of RAM (12x 16 GB DIMMs)&lt;/li&gt;
      &lt;li&gt;6x Seagate &lt;a href=&quot;https://www.amazon.com/SEAGATE-ST91000640NS-Constellation-6-0Gb-internal/dp/B004HZEF2I&quot;&gt;Constellation 7200RPM&lt;/a&gt; 1TB SATA HDDs (RAID 10) (Logs)&lt;/li&gt;
      &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC) - Internal (DMZ) Traffic&lt;/li&gt;
      &lt;li&gt;Dual 10 Gbps network (Intel X540) - External Traffic&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;2 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r620/pd&quot;&gt;R620&lt;/a&gt; Servers (Direct Traffic), each with:
    &lt;ul&gt;
      &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/64590/Intel-Xeon-Processor-E5-2650-20M-Cache-2_00-GHz-8_00-GTs-Intel-QPI&quot;&gt;E5-2650&lt;/a&gt; Processors (8 cores @2.0–2.8GHz each)&lt;/li&gt;
      &lt;li&gt;64 GB of RAM (4x 16 GB DIMMs)&lt;/li&gt;
      &lt;li&gt;2x Seagate &lt;a href=&quot;https://www.amazon.com/SEAGATE-ST91000640NS-Constellation-6-0Gb-internal/dp/B004HZEF2I&quot;&gt;Constellation 7200RPM&lt;/a&gt; 1TB SATA HDDs (RAID 10) (Logs)&lt;/li&gt;
      &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC) - Internal (DMZ) Traffic&lt;/li&gt;
      &lt;li&gt;Dual 10 Gbps network (Intel X540) - External Traffic&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: These servers were ordered at different times and as a result, differ in spec. Also, the two CloudFlare load balancers have more memory for a memcached install (which we no longer run today) for CloudFlare’s &lt;a href=&quot;https://www.cloudflare.com/railgun/&quot;&gt;Railgun&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;pics-stack-overflow-the-hardware-2016-edition&quot;&gt;
  &lt;p&gt;The service, redis, search, and load balancer boxes above are all 1U servers in a stack. Here’s what that stack looks like in New York:&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Service-Redis-Search-Front.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Service-Redis-Search-Front-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Service-Redis-Search-Front-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Service-Redis-Search-Front-Small.jpg&quot; alt=&quot;Redis &amp;amp; Search - Front&quot; style=&quot;width: 477px; height: 354px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Service-Rear.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Service-Rear-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Service-Rear-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Service-Rear-Small.jpg&quot; alt=&quot;Service - Rear&quot; style=&quot;width: 477px; height: 354px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;a href=&quot;/blog/content/SO-Hardware-Redis-Inside.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Redis-Inside-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Redis-Inside-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Redis-Inside-Small.jpg&quot; alt=&quot;Redis - Inside&quot; style=&quot;width: 477px; height: 354px; padding-top: 16px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Service-Inside.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Service-Inside-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Service-Inside-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Service-Inside-Small.jpg&quot; alt=&quot;Service - Inside&quot; style=&quot;width: 477px; height: 354px; padding-top: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;servers-for-other-bits&quot;&gt;Servers for Other Bits&lt;/h2&gt;

&lt;p&gt;We have other servers not directly or indirectly involved in serving site traffic. These are either only tangentially related (e.g., domain controllers which are seldom used for application pool authentication and run as VMs) or are for nonessential purposes like monitoring, log storage, backups, etc.&lt;/p&gt;

&lt;p&gt;Since this post is meant to be an appendix for many future posts &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;in the series&lt;/a&gt;, I’m including all of the interesting “background” servers as well. This also lets me share more server porn with you, and who doesn’t love that?&lt;/p&gt;

&lt;h4 id=&quot;vm-servers-vmware-currently&quot;&gt;VM Servers (VMWare, Currently)&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;2 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-fx/pd&quot;&gt;FX2s&lt;/a&gt; Blade Chassis, each with 2 of 4 blades populated
    &lt;ul&gt;
      &lt;li&gt;4 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-fx/pd#Misc&quot;&gt;FC630&lt;/a&gt; Blade Servers (2 per chassis), each with:
        &lt;ul&gt;
          &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/81900/Intel-Xeon-Processor-E5-2643-v3-20M-Cache-3_40-GHz&quot;&gt;E5-2698 v3&lt;/a&gt; Processors (16 cores @2.3–3.6GHz each)&lt;/li&gt;
          &lt;li&gt;768 GB of RAM (24x 32 GB DIMMs)&lt;/li&gt;
          &lt;li&gt;2x 16GB SD Cards (Hypervisor - no local storage)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Dual 4x 10 Gbps network (FX IOAs - BASET)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;1 EqualLogic &lt;a href=&quot;http://www.dell.com/us/business/p/equallogic-ps6210-series/pd&quot;&gt;PS6210X&lt;/a&gt; iSCSI SAN
    &lt;ul&gt;
      &lt;li&gt;24x Dell 10K RPM 1.2TB SAS HDDs (RAID10)&lt;/li&gt;
      &lt;li&gt;Dual 10Gb network (10-BASET)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;1 EqualLogic &lt;a href=&quot;http://www.dell.com/us/business/p/equallogic-ps6110x/pd&quot;&gt;PS6110X&lt;/a&gt; iSCSI SAN
    &lt;ul&gt;
      &lt;li&gt;24x Dell 10K RPM 900GB SAS HDDs (RAID10)&lt;/li&gt;
      &lt;li&gt;Dual 10Gb network (SFP+)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;pics-stack-overflow-the-hardware-2016-edition&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Hardware-VMs-Blades.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-VMs-Blades-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-VMs-Blades-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-VMs-Blades-Small.jpg&quot; alt=&quot;VM Blades - 1&quot; style=&quot;width: 477px; height: 708px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-VMs-Blades2.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-VMs-Blades2-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-VMs-Blades2-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-VMs-Blades2-Small.jpg&quot; alt=&quot;VM Blades - 2&quot; style=&quot;width: 477px; height: 708px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;a href=&quot;/blog/content/SO-Hardware-VMs-Front.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-VMs-Front-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-VMs-Front-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-VMs-Front-Small.jpg&quot; alt=&quot;VMs - Front&quot; style=&quot;width: 477px; height: 708px; padding-top: 16px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-VMs-Rear.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-VMs-Rear-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-VMs-Rear-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-VMs-Rear-Small.jpg&quot; alt=&quot;VMs - Rear&quot; style=&quot;width: 477px; height: 708px; padding-top: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;There a few more noteworthy servers behind the scenes that aren’t VMs. These perform background tasks, help us troubleshoot with logging, store tons of data, etc.&lt;/p&gt;

&lt;h4 id=&quot;machine-learning-servers-providence&quot;&gt;Machine Learning Servers (Providence)&lt;/h4&gt;
&lt;p&gt;These servers are idle about 99% of the time, but do heavy lifting for a nightly processing job: refreshing Providence. They also serve as an inside-the-datacenter place to test new algorithms on large datasets.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;2 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r620/pd&quot;&gt;R620&lt;/a&gt; Servers, each with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/75283/Intel-Xeon-Processor-E5-2697-v2-30M-Cache-2_70-GHz&quot;&gt;E5-2697 v2&lt;/a&gt; Processors (12 cores @2.7–3.5GHz each)&lt;/li&gt;
  &lt;li&gt;384 GB of RAM (24x 16 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;4x Intel &lt;a href=&quot;https://ark.intel.com/products/75336/Intel-SSD-530-Series-480GB-2_5in-SATA-6Gbs-20nm-MLC&quot;&gt;530&lt;/a&gt; 480GB SATA SSDs (RAID 10)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;machine-learning-redis-servers-still-providence&quot;&gt;Machine Learning Redis Servers (Still Providence)&lt;/h4&gt;
&lt;p&gt;This is the redis data store for Providence. The usual setup is one master, one slave, and one instance used for testing the latest version of our ML algorithms. While not used to serve the Q&amp;amp;A sites, this data is used when serving job matches on Careers as well as the sidebar job listings.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;3 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r720xd/pd&quot;&gt;R720xd&lt;/a&gt; Servers, each with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/75269/Intel-Xeon-Processor-E5-2650-v2-20M-Cache-2_60-GHz&quot;&gt;E5-2650 v2&lt;/a&gt; Processors (8 cores @2.6–3.4GHz each)&lt;/li&gt;
  &lt;li&gt;384 GB of RAM (24x 16 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;4x Samsung &lt;a href=&quot;https://www.samsung.com/semiconductor/products/flash-storage/client-ssd&quot;&gt;840 Pro&lt;/a&gt; 480 GB SATA SSDs (RAID 10)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;logstash-servers-for-ya-knowlogs&quot;&gt;Logstash Servers (For ya know…logs)&lt;/h4&gt;
&lt;p&gt;Our Logstash cluster (using Elasticsearch for storage) stores logs from, well, everything. We plan to replicate HTTP logs in here but are hitting performance issues. However, we do aggregate all network device logs, syslogs, and Windows and Linux system logs here so we can get a network overview or search for issues very quickly. This is also used as a data source in Bosun to get additional information when alerts fire. The total cluster’s raw storage is 6x12x4 = 288 TB.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;6 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r720xd/pd&quot;&gt;R720xd&lt;/a&gt; Servers, each with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/75272/Intel-Xeon-Processor-E5-2660-v2-25M-Cache-2_20-GHz&quot;&gt;E5-2660 v2&lt;/a&gt; Processors (10 cores @2.2–3.0GHz each)&lt;/li&gt;
  &lt;li&gt;192 GB of RAM (12x 16 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;12x 7200 RPM Spinny 4 TB SATA HDDs (RAID 0 x3 - 4 drives per)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;http-logging-sql-server&quot;&gt;HTTP Logging SQL Server&lt;/h4&gt;
&lt;p&gt;This is where we log every single HTTP hit to our load balancers (sent from HAProxy via syslog) to a SQL database. We only record a few top level bits like URL, Query, UserAgent, timings for SQL, Redis, etc. in here – so it all goes into a Clustered Columnstore Index per day. We use this for troubleshooting user issues, detecting botnets, etc.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;1 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r730xd/pd&quot;&gt;R730xd&lt;/a&gt; Server with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/81706/Intel-Xeon-Processor-E5-2660-v3-25M-Cache-2_60-GHz&quot;&gt;E5-2660 v3&lt;/a&gt; Processors (10 cores @2.6–3.3GHz each)&lt;/li&gt;
  &lt;li&gt;256 GB of RAM (16x 16 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;2x Intel &lt;a href=&quot;https://ark.intel.com/products/80995/Intel-SSD-DC-P3600-Series-2_0TB-2_5in-PCIe-3_0-20nm-MLC&quot;&gt;P3600&lt;/a&gt; 2 TB NVMe PCIe SSD (RAID 0)&lt;/li&gt;
  &lt;li&gt;16x Seagate &lt;a href=&quot;http://www.seagate.com/internal-hard-drives/enterprise-hard-drives/hdd/enterprise-capacity-3-5-hdd/?sku=ST6000NM0024&quot;&gt;ST6000NM0024&lt;/a&gt; 7200RPM Spinny 6 TB SATA HDDs (RAID 10)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;development-sql-server&quot;&gt;Development SQL Server&lt;/h4&gt;
&lt;p&gt;We like for dev to simulate production as much as possible, so SQL matches as well…or at least it used to. We’ve upgraded production processors since this purchase. We’ll be refreshing this box with a 2U solution at the same time as we upgrade the Stack Overflow cluster later this year.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;1 Dell &lt;a href=&quot;http://www.dell.com/us/business/p/poweredge-r620/pd&quot;&gt;R620&lt;/a&gt; Server with:&lt;/li&gt;
  &lt;li&gt;Dual &lt;a href=&quot;https://ark.intel.com/products/64594/Intel-Xeon-Processor-E5-2620-15M-Cache-2_00-GHz-7_20-GTs-Intel-QPI&quot;&gt;E5-2620&lt;/a&gt; Processors (6 cores @2.0–2.5GHz each)&lt;/li&gt;
  &lt;li&gt;384 GB of RAM (24x 16 GB DIMMs)&lt;/li&gt;
  &lt;li&gt;8x Intel &lt;a href=&quot;https://ark.intel.com/products/71916/Intel-SSD-DC-S3700-Series-800GB-2_5in-SATA-6Gbs-25nm-MLC&quot;&gt;S3700&lt;/a&gt; 800 GB SATA SSDs (RAID 10)&lt;/li&gt;
  &lt;li&gt;Dual 10 Gbps network (Intel X540/I350 NDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;pics-stack-overflow-the-hardware-2016-edition&quot;&gt;

  &lt;p&gt;That’s it for the hardware actually serving the sites or that’s generally interesting. We, of course, have other servers for the background tasks such as logging, monitoring, backups, etc. If you’re especially curious about specs of any other systems, just ask in comments and I’m happy to detail them out. Here’s what the full setup looks like in New York as of a few weeks ago:&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Racks.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Racks-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Racks-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Racks-Small.jpg&quot; alt=&quot;Racks - Left Aisle&quot; style=&quot;width: 477px; height: 708px; padding-right: 16px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;a href=&quot;/blog/content/SO-Hardware-Racks2.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Hardware-Racks2-Small.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Hardware-Racks2-Small.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Hardware-Racks2-Small.jpg&quot; alt=&quot;Racks - Right Aisle&quot; style=&quot;width: 477px; height: 708px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;What’s next? The way &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;this series&lt;/a&gt; works is I blog in order of what the community wants to know about most. Going by &lt;a href=&quot;https://trello.com/b/0zgQjktX/blog-post-queue-for-stack-overflow-topics&quot;&gt;the Trello board&lt;/a&gt;, it looks like &lt;a href=&quot;https://trello.com/c/bh4GZ30c/25-deployment&quot;&gt;Deployment&lt;/a&gt; is the next most interesting topic. So next time expect to learn how code goes from a developers machine to production and everything involved along the way. I’ll cover database migrations, rolling builds, CI infrastructure, how our dev environment is set up, and share stats on all things deployment.&lt;/p&gt;

&lt;script&gt;
(function () {
    var pics = document.querySelectorAll('.pics-stack-overflow-the-hardware-2016-edition'), a = document.querySelector('.toggle-stack-overflow-the-hardware-2016-edition');
    a.addEventListener('click', function(e) { 
        e.preventDefault();
        var hide = this.innerHTML === 'Hide Pictures';
        for (var i = 0; i &lt; pics.length; i++) {
            pics[i].style.display = hide ? 'none' : 'block';
        }
        this.innerHTML = hide ? 'Show Pictures' : 'Hide Pictures';
        localStorage.setItem('hide-stack-overflow-the-hardware-2016-edition', hide);
        return false;
    }, false);
    if (localStorage.getItem('hide-stack-overflow-the-hardware-2016-edition') === 'true') {
        a.dispatchEvent(new Event('click'));
    }
})();
&lt;/script&gt;

</description>
        <pubDate>Tue, 29 Mar 2016 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2016/03/29/stack-overflow-the-hardware-2016-edition/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Stack Overflow: The Architecture - 2016 Edition</title>
        <link>https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/</link>
        <description>&lt;blockquote&gt;
  &lt;p&gt;This is #1 in a &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;very long series of posts&lt;/a&gt; on Stack Overflow’s architecture. Welcome.
Previous post (#0): &lt;a href=&quot;/blog/2016/02/03/stack-overflow-a-technical-deconstruction/&quot;&gt;Stack Overflow: A Technical Deconstruction&lt;/a&gt;
Next post (#2): &lt;a href=&quot;/blog/2016/03/29/stack-overflow-the-hardware-2016-edition/&quot;&gt;Stack Overflow: The Hardware - 2016 Edition&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To get an idea of what all of this stuff “does,” let me start off with an update on the average day at Stack Overflow. So you can compare to the &lt;a href=&quot;/blog/2013/11/22/what-it-takes-to-run-stack-overflow/&quot;&gt;previous numbers from November 2013&lt;/a&gt;, here’s a day of statistics from February 9th, 2016 with differences since November 12th, 2013:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;209,420,973&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+61,336,090)&lt;/span&gt; HTTP requests to our load balancer&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;66,294,789&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+30,199,477)&lt;/span&gt; of those were page loads&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;1,240,266,346,053&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+406,273,363,426)&lt;/span&gt; bytes (1.24 TB) of HTTP traffic sent&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;569,449,470,023&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+282,874,825,991)&lt;/span&gt; bytes (569 GB) total received&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;3,084,303,599,266&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+1,958,311,041,954)&lt;/span&gt; bytes (3.08 TB) total sent&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;504,816,843&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+170,244,740)&lt;/span&gt; SQL Queries (from HTTP requests alone)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;5,831,683,114&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+5,418,818,063)&lt;/span&gt; Redis hits&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;17,158,874&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(not tracked in 2013)&lt;/span&gt; Elastic searches&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;3,661,134&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+57,716)&lt;/span&gt; Tag Engine requests&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;607,073,066&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+48,848,481)&lt;/span&gt; ms (168 hours) spent running SQL queries&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;10,396,073&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(-88,950,843)&lt;/span&gt; ms (2.8 hours) spent on Redis hits&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;147,018,571&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(+14,634,512)&lt;/span&gt; ms (40.8 hours) spent on Tag Engine requests&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;1,609,944,301&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(-1,118,232,744)&lt;/span&gt; ms (447 hours) spent processing in ASP.Net&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;22.71&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(-5.29)&lt;/span&gt; ms average (19.12 ms in ASP.Net) for 49,180,275 question page renders&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;11.80&lt;/strong&gt; &lt;span class=&quot;note&quot;&gt;(-53.2)&lt;/span&gt; ms average (8.81 ms in ASP.Net) for 6,370,076 home page renders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may be wondering about the drastic ASP.Net reduction in processing time compared to 2013 (which was 757 hours) despite 61 million more requests a day. That’s due to both &lt;a href=&quot;http://blog.serverfault.com/2015/03/05/how-we-upgrade-a-live-data-center/&quot;&gt;a hardware upgrade in early 2015&lt;/a&gt; as well as a lot of performance tuning inside the applications themselves. Please don’t forget: &lt;a href=&quot;https://blog.codinghorror.com/performance-is-a-feature/&quot;&gt;performance is still a feature&lt;/a&gt;. If you’re curious about more hardware specifics than I’m about to provide—fear not. The next post will be an appendix with detailed hardware specs for all of the servers that run the sites (I’ll update this with a link when it’s live).
&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;So what’s changed in the last 2 years? Besides replacing some servers and network gear, not much. Here’s a top-level list of hardware that runs the sites today (noting what’s different since 2013):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;4 Microsoft SQL Servers (new hardware for 2 of them)&lt;/li&gt;
  &lt;li&gt;11 IIS Web Servers (new hardware)&lt;/li&gt;
  &lt;li&gt;2 &lt;a href=&quot;https://redis.io/&quot;&gt;Redis&lt;/a&gt; Servers (new hardware)&lt;/li&gt;
  &lt;li&gt;3 Tag Engine servers (new hardware for 2 of the 3)&lt;/li&gt;
  &lt;li&gt;3 &lt;a href=&quot;https://www.elastic.co/&quot;&gt;Elasticsearch&lt;/a&gt; servers (same)&lt;/li&gt;
  &lt;li&gt;4 &lt;a href=&quot;https://www.haproxy.org/&quot;&gt;HAProxy&lt;/a&gt; Load Balancers (added 2 to support CloudFlare)&lt;/li&gt;
  &lt;li&gt;2 Networks (each a &lt;a href=&quot;https://www.cisco.com/c/en/us/products/collateral/switches/nexus-5000-series-switches/data_sheet_c78-618603.html&quot;&gt;Nexus 5596 Core&lt;/a&gt; + &lt;a href=&quot;https://www.cisco.com/c/en/us/products/switches/nexus-2232tm-10ge-fabric-extender/index.html&quot;&gt;2232TM Fabric Extenders&lt;/a&gt;, upgraded to 10Gbps everywhere)&lt;/li&gt;
  &lt;li&gt;2 Fortinet &lt;a href=&quot;https://www.fortinet.com/products/firewalls/firewall/fortigate-mid-range.html&quot;&gt;800C&lt;/a&gt; Firewalls (replaced Cisco 5525-X ASAs)&lt;/li&gt;
  &lt;li&gt;2 Cisco &lt;a href=&quot;https://www.cisco.com/c/en/us/products/routers/asr-1001-router/index.html&quot;&gt;ASR-1001&lt;/a&gt; Routers (replaced Cisco 3945 Routers)&lt;/li&gt;
  &lt;li&gt;2 Cisco &lt;a href=&quot;https://www.cisco.com/c/en/us/products/routers/asr-1001-x-router/index.html&quot;&gt;ASR-1001-x&lt;/a&gt; Routers (new!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What do we &lt;strong&gt;&lt;em&gt;need&lt;/em&gt;&lt;/strong&gt; to run Stack Overflow? &lt;a href=&quot;/blog/2013/11/22/what-it-takes-to-run-stack-overflow/#core-hardware&quot;&gt;That hasn’t changed much since 2013&lt;/a&gt;, but due to the optimizations and new hardware mentioned above, we’re down to &lt;strong&gt;&lt;em&gt;needing&lt;/em&gt;&lt;/strong&gt; only 1 web server. We have unintentionally tested this, successfully, a few times. To be clear: I’m saying it works. I’m not saying it’s a good idea. It’s fun though, every time.&lt;/p&gt;

&lt;p&gt;Now that we have some baseline numbers for an idea of scale, let’s see how we make those fancy web pages. Since few systems exist in complete isolation (and ours is no exception), architecture decisions often make far less sense without a bigger picture of how those pieces fit into the whole. That’s the goal here, to cover the big picture. Many &lt;a href=&quot;https://trello.com/b/0zgQjktX/blog-post-queue-for-stack-overflow-topics&quot;&gt;subsequent posts&lt;/a&gt; will do deep dives into specific areas. This will be a logistical overview with hardware highlights only; the next post will have the hardware details.&lt;/p&gt;

&lt;p&gt;For those of you here to see what the hardware looks like these days, here are a few pictures I took of rack A (it has a matching sister rack B) during &lt;a href=&quot;http://blog.serverfault.com/2015/03/05/how-we-upgrade-a-live-data-center/&quot;&gt;our February 2015 upgrade&lt;/a&gt;:&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Architecture/SO-Architecture-RackB-Top.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture/SO-Architecture-RackB-Top.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Architecture/SO-Architecture-RackB-Top.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture/SO-Architecture-RackB-Top.jpg&quot; alt=&quot;Rack B - Top&quot; style=&quot;width:384px;height:512px;padding:5px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;a href=&quot;/blog/content/SO-Architecture/SO-Architecture-RackB-Bottom.jpg&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture/SO-Architecture-RackB-Bottom.webp&quot; /&gt;&lt;source type=&quot;image/jpg&quot; srcset=&quot;/blog/content/SO-Architecture/SO-Architecture-RackB-Bottom.jpg&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture/SO-Architecture-RackB-Bottom.jpg&quot; alt=&quot;Rack B - Bottom&quot; style=&quot;width:384px;height:512px;padding:5px;&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;…and if you’re into that kind of thing, &lt;a href=&quot;https://imgur.com/a/X1HoY&quot;&gt;here’s the entire 256 image album from that week&lt;/a&gt; (you’re damn right that number’s intentional). Now, let’s dig into layout. Here’s a logical overview of the major systems in play:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/content/SO-Architecture-Overview-Logical.svg&quot; alt=&quot;Logical Overview&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;ground-rules&quot;&gt;Ground Rules&lt;/h3&gt;

&lt;p&gt;Here are some rules that apply globally so I don’t have to repeat them with every setup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Everything is redundant.&lt;/li&gt;
  &lt;li&gt;All servers and network gear have at least 2x 10Gbps connectivity.&lt;/li&gt;
  &lt;li&gt;All servers have 2 power feeds via 2 power supplies from 2 UPS units backed by 2 generators and 2 utility feeds.&lt;/li&gt;
  &lt;li&gt;All servers have a redundant partner between rack A and B.&lt;/li&gt;
  &lt;li&gt;All servers and services are doubly redundant via another data center (Colorado), though I’m mostly talking about New York here.&lt;/li&gt;
  &lt;li&gt;Everything is redundant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;the-internets&quot;&gt;The Internets&lt;/h3&gt;

&lt;p&gt;First, you have to find us—that’s &lt;a href=&quot;https://en.wikipedia.org/wiki/Domain_Name_System&quot;&gt;DNS&lt;/a&gt;. Finding us needs to be fast, so we farm this out to &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;CloudFlare&lt;/a&gt; (currently) because they have DNS servers nearer to almost everyone around the world. We update our DNS records via an API and they do the “hosting” of DNS. But since we’re jerks with deeply-rooted trust issues, we still have our own DNS servers as well. Should the apocalypse happen (probably caused by the GPL, &lt;a href=&quot;https://twitter.com/JasonPunyon&quot;&gt;Punyon&lt;/a&gt;, or caching) and people still want to program to take their mind off of it, we’ll flip them on.&lt;/p&gt;

&lt;p&gt;After you find our secret hideout, HTTP traffic comes from one of our four ISPs (Level 3, Zayo, Cogent, and Lightower in New York) and flows through one of our four edge routers. We peer with our ISPs using &lt;a href=&quot;https://en.wikipedia.org/wiki/Border_Gateway_Protocol&quot;&gt;BGP&lt;/a&gt; (fairly standard) in order to control the flow of traffic and provide several avenues for traffic to reach us most efficiently. These &lt;a href=&quot;https://www.cisco.com/c/en/us/products/routers/asr-1001-router/index.html&quot;&gt;ASR-1001&lt;/a&gt; and &lt;a href=&quot;https://www.cisco.com/c/en/us/products/routers/asr-1001-x-router/index.html&quot;&gt;ASR-1001-X&lt;/a&gt; routers are in 2 pairs, each servicing 2 ISPs in active/active fashion—so we’re redundant here. Though they’re all on the same physical 10Gbps network, external traffic is in separate isolated external &lt;a href=&quot;https://en.wikipedia.org/wiki/Virtual_LAN&quot;&gt;VLANs&lt;/a&gt; which the load balancers are connected to as well. After flowing through the routers, you’re headed for a load balancer.&lt;/p&gt;

&lt;p&gt;I suppose this may be a good time to mention we have a 10Gbps &lt;a href=&quot;https://en.wikipedia.org/wiki/Multiprotocol_Label_Switching&quot;&gt;MPLS&lt;/a&gt; between our 2 data centers, but it is not directly involved in serving the sites. We use this for data replication and quick recovery in the cases where we need a burst. “But Nick, that’s not redundant!” Well, you’re technically correct (&lt;a href=&quot;https://www.youtube.com/watch?v=hou0lU8WMgo&quot;&gt;the best kind of correct&lt;/a&gt;), that’s a single point of failure on its face. But wait! We maintain 2 more failover &lt;a href=&quot;https://en.wikipedia.org/wiki/Open_Shortest_Path_First&quot;&gt;OSPF&lt;/a&gt; routes (the MPLS is #1, these are #2 and 3 by cost) via our ISPs. Each of the sets mentioned earlier connects to the corresponding set in Colorado, and they load balance traffic between in the failover situation. We could make both sets connect to both sets and have 4 paths but, well, whatever.  Moving on.&lt;/p&gt;

&lt;h3 id=&quot;load-balancers-haproxy&quot;&gt;Load Balancers (HAProxy)&lt;/h3&gt;

&lt;p&gt;The load balancers are running &lt;a href=&quot;https://www.haproxy.org/&quot;&gt;HAProxy&lt;/a&gt; 1.5.15 on &lt;a href=&quot;https://www.centos.org/&quot;&gt;CentOS 7&lt;/a&gt;, our preferred flavor of Linux. TLS (SSL) traffic is also terminated in HAProxy. We’ll be looking hard at HAProxy 1.7 soon for &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/2&quot;&gt;HTTP/2&lt;/a&gt; support.&lt;/p&gt;

&lt;p&gt;Unlike all other servers with a dual 10Gbps LACP network link, each load balancer has 2 pairs of 10Gbps: one for the external network and one for the DMZ. These boxes run 64GB or more of memory to more efficiently handle SSL negotiation. When we can cache more TLS sessions in memory for reuse, there’s less to recompute on subsequent connections to the same client. This means we can resume sessions both faster and cheaper. Given that RAM is pretty cheap dollar-wise, it’s an easy choice.&lt;/p&gt;

&lt;p&gt;The load balancers themselves are a pretty simple setup. We listen to different sites on various IPs (mostly for certificate concerns and DNS management) and route to various backends based mostly on the host header. The only things of note we do here is rate limiting and some header captures (sent from our web tier) into the &lt;a href=&quot;https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#3.1-log&quot;&gt;HAProxy syslog message&lt;/a&gt; so we can record performance metrics for every single request. We’ll &lt;a href=&quot;https://trello.com/c/1Oc9cC6u/11-monitoring&quot;&gt;cover that later too&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;web-tier-iis-85-aspnet-mvc-523-and-net-461&quot;&gt;Web Tier (IIS 8.5, ASP.Net MVC 5.2.3, and .Net 4.6.1)&lt;/h3&gt;

&lt;p&gt;The load balancers feed traffic to 9 servers we refer to as “primary” (01-09) and 2 “dev/meta” (10-11, our staging environment) web servers. The primary servers run things like Stack Overflow, Careers, and all Stack Exchange sites except &lt;a href=&quot;https://meta.stackoverflow.com/&quot;&gt;meta.stackoverflow.com&lt;/a&gt; and &lt;a href=&quot;https://meta.stackexchange.com/&quot;&gt;meta.stackexchange.com&lt;/a&gt;, which run on the last 2 servers. The primary Q&amp;amp;A Application itself is multi-tenant. This means that a single application serves the requests for all Q&amp;amp;A sites. Put another way: we can run the entire Q&amp;amp;A network off of a single application pool on a single server. Other applications like Careers, API v2, Mobile API, etc. are separate. Here’s what the primary and dev tiers look like in IIS:&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
  &lt;p&gt;&lt;a href=&quot;/blog/content/SO-Architecture-IIS-NY-WEB01.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture-IIS-NY-WEB01.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Architecture-IIS-NY-WEB01.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture-IIS-NY-WEB01.png&quot; loading=&quot;lazy&quot; alt=&quot;IIS in NY-WEB01&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;
&lt;a href=&quot;/blog/content/SO-Architecture-IIS-NY-WEB10.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture-IIS-NY-WEB10.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Architecture-IIS-NY-WEB10.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture-IIS-NY-WEB10.png&quot; loading=&quot;lazy&quot; alt=&quot;IIS in NY-WEB10&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Here’s what Stack Overflow’s distribution across the web tier looks like in &lt;a href=&quot;https://github.com/Opserver/Opserver&quot;&gt;Opserver&lt;/a&gt; (our internal monitoring dashboard):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Architecture-Opserver-HAProxy.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture-Opserver-HAProxy.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Architecture-Opserver-HAProxy.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture-Opserver-HAProxy.png&quot; loading=&quot;lazy&quot; alt=&quot;HAProxy in Opserver&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…and here’s what those web servers look like from a utilization perspective:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Architecture-Opserver-WebTier.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture-Opserver-WebTier.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Architecture-Opserver-WebTier.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture-Opserver-WebTier.png&quot; loading=&quot;lazy&quot; alt=&quot;Web Tier in Opserver&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ll go into why we’re so overprovisioned in future posts, but the highlight items are: rolling builds, headroom, and redundancy.&lt;/p&gt;

&lt;h3 id=&quot;service-tier-iis-aspnet-mvc-523-net-461-and-httpsys&quot;&gt;Service Tier (IIS, ASP.Net MVC 5.2.3, .Net 4.6.1, and HTTP.SYS)&lt;/h3&gt;

&lt;p&gt;Behind those web servers is the very similar “service tier.” It’s also running IIS 8.5 on Windows 2012R2. This tier runs internal services to support the production web tier and other internal systems. The two big players here are “Stack Server” which runs the tag engine and is based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http.sys&lt;/code&gt; (not behind IIS) and the Providence API (IIS-based). Fun fact: I have to set affinity on each of these 2 processes to land on separate sockets because Stack Server just steamrolls the L2 and L3 cache when refreshing question lists on a 2-minute interval.&lt;/p&gt;

&lt;p&gt;These service boxes do heavy lifting with the tag engine and backend APIs where we need redundancy, but not 9x redundancy. For example, loading all of the posts and their tags that change every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; minutes from the database (currently 2) isn’t that cheap. We don’t want to do that load 9 times on the web tier; 3 times is enough and gives us enough safety. We also configure these boxes differently on the hardware side to be better optimized for the different computational load characteristics of the tag engine and elastic indexing jobs (which also run here). The “tag engine” is a relatively complicated topic in itself and will be a &lt;a href=&quot;https://trello.com/c/DqklJDSF/29-tag-engine&quot;&gt;dedicated post&lt;/a&gt;. The basics are: when you visit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/questions/tagged/java&lt;/code&gt;, you’re hitting the tag engine to see which questions match. It does &lt;em&gt;all&lt;/em&gt; of our tag matching outside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/search&lt;/code&gt;, so the &lt;a href=&quot;https://meta.stackoverflow.com/questions/308875/new-navigation-release-candidate&quot;&gt;new navigation&lt;/a&gt;, etc. are all using this service for data.&lt;/p&gt;

&lt;h3 id=&quot;cache--pubsub-redis&quot;&gt;Cache &amp;amp; Pub/Sub (Redis)&lt;/h3&gt;

&lt;p&gt;We use &lt;a href=&quot;https://Redis.io/&quot;&gt;Redis&lt;/a&gt; for a few things here and it’s rock solid. Despite doing about 160 billion ops a month, every instance is below 2% CPU. Usually much lower:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Architecture-Redis-Utilization.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture-Redis-Utilization.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Architecture-Redis-Utilization.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture-Redis-Utilization.png&quot; loading=&quot;lazy&quot; alt=&quot;Redis in Bosun&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have an L1/L2 cache system with Redis. “L1” is HTTP Cache on the web servers or whatever application is in play. “L2” is falling back to Redis and fetching the value out. Our values are stored in the &lt;a href=&quot;https://developers.google.com/protocol-buffers/&quot;&gt;Protobuf format&lt;/a&gt;, via &lt;a href=&quot;https://github.com/mgravell/protobuf-net&quot;&gt;protobuf-dot-net&lt;/a&gt; by Marc Gravell. For a client, we’re using &lt;a href=&quot;https://github.com/StackExchange/StackExchange.Redis&quot;&gt;StackExchange.Redis&lt;/a&gt;—written in-house and open source. When one web server gets a cache miss in both L1 and L2, it fetches the value from source (a database query, API call, etc.) and puts the result in both local cache and Redis. The next server wanting the value may miss L1, but would find the value in L2/Redis, saving a database query or API call.&lt;/p&gt;

&lt;p&gt;We also run many Q&amp;amp;A sites, so each site has its own L1/L2 caching: by key prefix in L1 and by database ID in L2/Redis. We’ll go deeper on this in a &lt;a href=&quot;https://trello.com/c/OztwfkG7/16-caching-Redis&quot;&gt;future post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alongside the 2 main Redis servers (master/slave) that run all the site instances, we also have a machine learning instance slaved across 2 more dedicated servers (due to memory). This is used for recommending questions on the home page, better matching to jobs, etc. It’s a platform called Providence, &lt;a href=&quot;https://kevinmontrose.com/2015/01/27/providence-machine-learning-at-stack-exchange/&quot;&gt;covered by Kevin Montrose here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main Redis servers have 256GB of RAM (about 90GB in use) and the Providence servers have 384GB of RAM (about 125GB in use).&lt;/p&gt;

&lt;p&gt;Redis isn’t just for cache though, it also has a publish &amp;amp; subscriber mechanism where one server can publish a message and all other subscribers receive it—including downstream clients on Redis slaves. We use this mechanism to clear L1 caches on other servers when one web server does a removal for consistency, but there’s another great use: websockets.&lt;/p&gt;

&lt;h3 id=&quot;websockets-httpsgithubcomstackexchangenetgain&quot;&gt;Websockets (https://github.com/StackExchange/NetGain)&lt;/h3&gt;

&lt;p&gt;We use websockets to push real-time updates to users such as notifications in the top bar, vote counts, &lt;a href=&quot;https://meta.stackoverflow.com/questions/308875/new-navigation-release-candidate&quot;&gt;new nav&lt;/a&gt; counts, new answers and comments, and a few other  bits.&lt;/p&gt;

&lt;p&gt;The socket servers themselves are using raw sockets running on the web tier. It’s a very thin application on top of our open source library: &lt;a href=&quot;https://github.com/StackExchange/NetGain&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StackExchange.NetGain&lt;/code&gt;&lt;/a&gt;. During peak, we have about 500,000 &lt;strong&gt;concurrent&lt;/strong&gt; websocket connections open. That’s a lot of browsers. Fun fact: some of those browsers have been open for over 18 months. We’re not sure why. Someone should go check if those developers are still alive. Here’s what this week’s concurrent websocket pattern looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Architecture-Bosun-Websockets.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture-Bosun-Websockets.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Architecture-Bosun-Websockets.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture-Bosun-Websockets.png&quot; loading=&quot;lazy&quot; alt=&quot;Websocket connection counts from Bosun&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why websockets? They’re tremendously more efficient than polling at our scale. We can simply push more data with fewer resources this way, while being more instant to the user. They’re not without issues though—ephemeral port and file handle exhaustion on the load balancer are fun issues &lt;a href=&quot;https://trello.com/c/7nv66g78/58-websockets&quot;&gt;we’ll cover later&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;search-elasticsearch&quot;&gt;Search (Elasticsearch)&lt;/h3&gt;

&lt;p&gt;Spoiler: there’s not a lot to get excited about here. The web tier is doing pretty vanilla searches against &lt;a href=&quot;https://www.elastic.co/products/elasticsearch&quot;&gt;Elasticsearch&lt;/a&gt; 1.4, using the very slim high-performance &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StackExchange.Elastic&lt;/code&gt; client. Unlike most things, we have no plans to open source this simply because it only exposes a very slim subset of the API we use. I strongly believe releasing it would do more harm than good with developer confusion. We’re using elastic for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/search&lt;/code&gt;, calculating related questions, and suggestions when asking a question.&lt;/p&gt;

&lt;p&gt;Each Elastic cluster (there’s one in each data center) has 3 nodes, and each site has its own index. Careers has an additional few indexes. What makes our setup a little non-standard in the elastic world: our 3 server clusters are a bit beefier than average with all SSD storage, 192GB of RAM, and dual 10Gbps network each.&lt;/p&gt;

&lt;p&gt;The same application domains (yeah, we’re screwed with .Net Core here…) in Stack Server that host the tag engine also continually index items in Elasticsearch. We do some simple tricks here such as &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/ms182776.aspx&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ROWVERSION&lt;/code&gt; in SQL Server&lt;/a&gt; (the data source) compared against a “last position” document in Elastic. Since it behaves like a sequence, we can simply grab and index any items that have changed since the last pass.&lt;/p&gt;

&lt;p&gt;The main reason we’re on Elasticsearch instead of something like SQL full-text search is scalability and better allocation of money. SQL CPUs are comparatively very expensive, Elastic is cheap and has far more features these days. Why not &lt;a href=&quot;https://lucene.apache.org/solr/&quot;&gt;Solr&lt;/a&gt;? We want to search across the entire network (many indexes at once), and this wasn’t supported at decision time. The reason we’re not on 2.x yet is &lt;a href=&quot;https://github.com/elastic/elasticsearch/issues/8870&quot;&gt;a major change to “types”&lt;/a&gt; means we need to reindex everything to upgrade. I just don’t have enough time to make the needed changes and migration plan yet.&lt;/p&gt;

&lt;h3 id=&quot;databases-sql-server&quot;&gt;Databases (SQL Server)&lt;/h3&gt;

&lt;p&gt;We’re using SQL Server as our &lt;a href=&quot;https://en.wikipedia.org/wiki/Single_source_of_truth&quot;&gt;single source of truth&lt;/a&gt;. All data in Elastic and Redis comes from SQL Server. We run 2 SQL Server clusters with &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/hh510230.aspx&quot;&gt;AlwaysOn Availability Groups&lt;/a&gt;. Each of these clusters has 1 master (taking almost all of the load) and 1 replica in New York. Additionally, they have 1 replica in Colorado (our DR data center). All replicas are asynchronous.&lt;/p&gt;

&lt;p&gt;The first cluster is a set of Dell R720xd servers, each with 384GB of RAM, 4TB of PCIe SSD space, and 2x 12 cores. It hosts the Stack Overflow, Sites (bad name, I’ll explain later), PRIZM, and Mobile databases.&lt;/p&gt;

&lt;p&gt;The second cluster is a set of Dell R730xd servers, each with 768GB of RAM, 6TB of PCIe SSD space, and 2x 8 cores. This cluster runs &lt;em&gt;everything else&lt;/em&gt;. That list includes &lt;a href=&quot;https://talent.stackoverflow.com&quot;&gt;Talent&lt;/a&gt;, &lt;a href=&quot;https://openid.stackexchange.com/&quot;&gt;Open ID&lt;/a&gt;, &lt;a href=&quot;https://chat.stackoverflow.com/&quot;&gt;Chat&lt;/a&gt;, &lt;a href=&quot;https://github.com/NickCraver/StackExchange.Exceptional&quot;&gt;our Exception log&lt;/a&gt;, and every other Q&amp;amp;A site (e.g. &lt;a href=&quot;https://superuser.com/&quot;&gt;Super User&lt;/a&gt;, &lt;a href=&quot;https://serverfault.com/&quot;&gt;Server Fault&lt;/a&gt;, etc.).&lt;/p&gt;

&lt;p&gt;CPU utilization on the database tier is something we like to keep very low, but it’s actually a little high at the moment due to some plan cache issues we’re addressing. As of right now, NY-SQL02 and 04 are masters, 01 and 03 are replicas we just restarted today during some SSD upgrades. Here’s what the past 24 hours looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/blog/content/SO-Architecture-Opserver-DBTier.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/SO-Architecture-Opserver-DBTier.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/SO-Architecture-Opserver-DBTier.png&quot; /&gt;&lt;img src=&quot;/blog/content/SO-Architecture-Opserver-DBTier.png&quot; loading=&quot;lazy&quot; alt=&quot;DB Tier in Opserver&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our usage of SQL is pretty simple. Simple is fast. Though some queries can be crazy, our interaction with SQL itself is fairly vanilla. We have some legacy &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/bb425822.aspx&quot;&gt;Linq2Sql&lt;/a&gt;, but all new development is using &lt;a href=&quot;https://github.com/StackExchange/dapper-dot-net&quot;&gt;Dapper&lt;/a&gt;, our open source Micro-ORM using &lt;a href=&quot;https://en.wikipedia.org/wiki/Plain_Old_CLR_Object&quot;&gt;POCOs&lt;/a&gt;. Let me put this another way: Stack Overflow has only 1 stored procedure in the database and I intend to move that last vestige into code.&lt;/p&gt;

&lt;h3 id=&quot;libraries&quot;&gt;Libraries&lt;/h3&gt;

&lt;p&gt;Okay, let’s change gears to something that can more directly help &lt;em&gt;you&lt;/em&gt;. I’ve mentioned a few of these up above, but I’ll provide a list here of many open-source .Net libraries we maintain for the world to use. We open sourced them because they have no core business value but can help the world of developers. I hope you find these useful today:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/dapper-dot-net&quot;&gt;Dapper&lt;/a&gt; (.Net Core) - High-performance Micro-ORM for ADO.Net&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/StackExchange.Redis&quot;&gt;StackExchange.Redis&lt;/a&gt; - High-performance Redis client&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://miniprofiler.com/&quot;&gt;MiniProfiler&lt;/a&gt; - Lightweight profiler we run on every page (also supports Ruby, Go, and Node)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/NickCraver/StackExchange.Exceptional&quot;&gt;Exceptional&lt;/a&gt; - Error logger for SQL, JSON, MySQL, etc.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kevin-montrose/Jil&quot;&gt;Jil&lt;/a&gt; - High-performance JSON (de)serializer&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kevin-montrose/sigil&quot;&gt;Sigil&lt;/a&gt; - A .Net CIL generation helper (for when C# isn’t fast enough)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/StackExchange/NetGain&quot;&gt;NetGain&lt;/a&gt; - High-performance websocket server&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/opserver/Opserver/tree/overhaul&quot;&gt;Opserver&lt;/a&gt; - Monitoring dashboard polling most systems directly and feeding from Orion, Bosun, or WMI as well.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://bosun.org/&quot;&gt;Bosun&lt;/a&gt; - Backend monitoring system, written in Go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next up is a detailed current hardware list of what runs our code. After that, we go down &lt;a href=&quot;https://trello.com/b/0zgQjktX/blog-post-queue-for-stack-overflow-topics&quot;&gt;the list&lt;/a&gt;. Stay tuned.&lt;/p&gt;
</description>
        <pubDate>Wed, 17 Feb 2016 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Stack Overflow: A Technical Deconstruction</title>
        <link>https://nickcraver.com/blog/2016/02/03/stack-overflow-a-technical-deconstruction/</link>
        <description>&lt;blockquote&gt;
  &lt;p&gt;As new posts in the series appear, I’ll add them here to serve as a master list:&lt;br /&gt;
#1: &lt;a href=&quot;/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/&quot;&gt;Stack Overflow: The Architecture - 2016 Edition&lt;/a&gt;&lt;br /&gt;
#2: &lt;a href=&quot;/blog/2016/03/29/stack-overflow-the-hardware-2016-edition/&quot;&gt;Stack Overflow: The Hardware - 2016 Edition&lt;/a&gt;&lt;br /&gt;
#3: &lt;a href=&quot;/blog/2016/05/03/stack-overflow-how-we-do-deployment-2016-edition/&quot;&gt;Stack Overflow: How We Do Deployment - 2016 Edition&lt;/a&gt;&lt;br /&gt;
#4: &lt;a href=&quot;/blog/2018/11/29/stack-overflow-how-we-do-monitoring/&quot;&gt;Stack Overflow: How We Do Monitoring - 2018 Edition&lt;/a&gt;&lt;br /&gt;
#5: &lt;a href=&quot;/blog/2019/08/06/stack-overflow-how-we-do-app-caching/&quot;&gt;Stack Overflow: How We Do App Caching - 2019 Edition&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the reasons I love working at Stack Overflow is we’re &lt;s&gt;allowed&lt;/s&gt; &lt;em&gt;encouraged&lt;/em&gt; to talk about almost anything out in the open. Except for things companies &lt;em&gt;always&lt;/em&gt; keep private like financials and the nuclear launch codes, everything else is fair game. That’s an awesome thing that we haven’t taken advantage of on the technical side lately. I think it’s time for an experiment in extreme openness.&lt;/p&gt;

&lt;p&gt;By sharing what we do (and I mean &lt;em&gt;all&lt;/em&gt; of us), we better our world. Everyone that works at Stack shares at least one passion: improving life for all developers. Sharing how we do things is one of the best and biggest ways we can do that. It helps you. It helps me. It helps all of us.&lt;/p&gt;

&lt;p&gt;When I tell you how we do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;something&amp;gt;&lt;/code&gt;, a few things happen:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You might learn something cool you didn’t know about.&lt;/li&gt;
  &lt;li&gt;We might learn we’re doing it wrong.&lt;/li&gt;
  &lt;li&gt;We’ll both find a better way, together…and we share that too.&lt;/li&gt;
  &lt;li&gt;It helps eliminate the perception that “the big boys” always do it right. No, we screw up too.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s nothing to lose here and there’s no reason to keep things to yourself unless you’re afraid of being wrong. Good news: that’s not a problem. We get it wrong all the time anyway, so I’m not really worried about that one. Failure is &lt;em&gt;always&lt;/em&gt; an option. The best any of us can do is live, learn, move on, and do it better next time.
&lt;!--more--&gt;&lt;/p&gt;

&lt;h2 id=&quot;heres-where-i-need-your-help&quot;&gt;Here’s where I need your help&lt;/h2&gt;
&lt;p&gt;I need you to tell me: what do you want to hear about? My intention is to get to a great many things, but it will take some time. What are people most interested in? How do I decide which topic to blog about next? The answer: I don’t know and I can’t decide. That’s where you come in. Please, tell me.&lt;/p&gt;

&lt;p&gt;I put together this Trello board: &lt;a href=&quot;https://trello.com/b/0zgQjktX/blog-post-queue-for-stack-overflow-topics&quot;&gt;Blog post queue for Stack Overflow topics&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m also embedding it here for ease, hopefully this adds a lot of concreteness to the adventure:&lt;/p&gt;
&lt;iframe src=&quot;https://trello.com/b/0zgQjktX.html&quot; frameborder=&quot;0&quot; width=&quot;100%&quot; height=&quot;600&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;It’s public. You can comment and vote on topics as well as suggest new topics either on the board itself or shoot me a tweet: &lt;a href=&quot;https://twitter.com/Nick_Craver&quot;&gt;@Nick_Craver&lt;/a&gt;. Please, help me out by simply voting for what you want to know so I can prioritize the queue. If you see a topic and have specific questions, please comment on the card so I make sure to answer it in the post.&lt;/p&gt;

&lt;p&gt;The first post won’t be vote-driven. I think it &lt;em&gt;has&lt;/em&gt; to be the architecture overview so all future references make sense. After that, I’ll go down the board and blog the highest-voted topic each time.&lt;/p&gt;

&lt;p&gt;I’ve missed blogging due to spending my nights entirely in open source lately. I don’t believe that’s necessarily the &lt;em&gt;best&lt;/em&gt; or &lt;em&gt;only&lt;/em&gt; way for me to help developers. Having votes for topics gives me real motivation to dedicate the time to writing them up, pulling the stats, and making the pretty pictures. For that, I thank everyone participating.&lt;/p&gt;

&lt;p&gt;If you’re curious about my writing style and what to expect, check out some of my previous posts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.serverfault.com/2015/03/05/how-we-upgrade-a-live-data-center/&quot;&gt;How we upgrade a live data center&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2013/11/22/what-it-takes-to-run-stack-overflow/&quot;&gt;What it takes to run Stack Overflow&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/blog/2013/04/23/stackoverflow-com-the-road-to-ssl/&quot;&gt;Stackoverflow.com: the road to SSL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Am I crazy? Yep, probably - that’s already a lot of topics. But I think it’ll be fun and engaging. Let’s go on this adventure together.&lt;/p&gt;
</description>
        <pubDate>Wed, 03 Feb 2016 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2016/02/03/stack-overflow-a-technical-deconstruction/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Why you should wait on upgrading to .Net 4.6</title>
        <link>https://nickcraver.com/blog/2015/07/27/why-you-should-wait-on-dotnet-46/</link>
        <description>&lt;p&gt;&lt;strong&gt;Update (August 11th):&lt;/strong&gt; A patch for this bug has been released by Microsoft. Here’s their update to the advisory:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We released an updated version of RyuJIT today, which resolves this advisory. The update was released as &lt;a href=&quot;https://technet.microsoft.com/library/security/ms15-092.aspx&quot;&gt;Microsoft Security Bulletin MS15-092&lt;/a&gt; and is available on Windows Update or via direct download as &lt;a href=&quot;https://support.microsoft.com/kb/3086251&quot;&gt;KB3086251&lt;/a&gt;. The update resolves: &lt;a href=&quot;https://github.com/dotnet/coreclr/issues/1296&quot;&gt;CoreCLR #1296&lt;/a&gt;, &lt;a href=&quot;https://github.com/dotnet/coreclr/issues/1299&quot;&gt;CoreCLR #1299&lt;/a&gt;, and &lt;a href=&quot;https://github.com/Microsoft/visualfsharp/issues/536&quot;&gt;VisualFSharp #536&lt;/a&gt;. Major thanks to the developers who reported these issues. Thanks to everyone for their patience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;original-post&quot;&gt;Original Post&lt;/h3&gt;
&lt;p&gt;What follows is the work of several people: &lt;a href=&quot;http://blog.marcgravell.com/&quot;&gt;Marc Gravell&lt;/a&gt; and I have taken lead on this at Stack Overflow and we continue to coordinate with Microsoft on a resolution. They have fixed the bug internally, but not for users. Given the severity, we can’t in good conscience let such a subtle yet high-impact bug linger silently. &lt;strong&gt;We are not upgrading Stack Overflow to .Net 4.6&lt;/strong&gt;, and you shouldn’t upgrade yet either. You can find &lt;a href=&quot;https://github.com/dotnet/coreclr/issues/1296&quot;&gt;the issue we opened on GitHub (for public awareness) here&lt;/a&gt;. &lt;strong&gt;A fix has been released, see Update 5 below&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update #1 (July 27th):&lt;/strong&gt; &lt;a href=&quot;https://github.com/dotnet/coreclr/pull/1298&quot;&gt;A pull request has been posted by Matt Michell (Microsoft)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update #2 (July 28th):&lt;/strong&gt; There are several smaller repros now (&lt;a href=&quot;https://github.com/dotnet/coreclr/issues/1296#issuecomment-125568026&quot;&gt;including a small console app&lt;/a&gt;). Microsoft has confirmed they are working on an expedited hotfix release but we don’t have details yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update #3 (July 28th):&lt;/strong&gt; Microsoft’s Rich Lander has posted an update: &lt;a href=&quot;https://blogs.msdn.microsoft.com/dotnet/2015/07/28/ryujit-bug-advisory-in-the-net-framework-4-6/&quot;&gt;RyuJIT Bug Advisory in the .NET Framework 4.6&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update #4 (July 29th):&lt;/strong&gt; There’s &lt;a href=&quot;https://github.com/dotnet/coreclr/issues/1299&quot;&gt;another subtle bug&lt;/a&gt; found by Andrey Akinshin and the F# Engine Exception &lt;a href=&quot;https://github.com/Microsoft/visualfsharp/issues/536#issuecomment-125384182&quot;&gt;is confirmed to be a separate issue&lt;/a&gt;. I still recommend disabling RyuJIT in production given &lt;a href=&quot;https://github.com/dotnet/coreclr/issues?utf8=%E2%9C%93&amp;amp;q=is%3Aissue+RyuJIT&quot;&gt;the increasing bug count&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update #5 (August 11th):&lt;/strong&gt; A patch for this bug has been released by Microsoft, see above.&lt;/p&gt;

&lt;p&gt;This critical bug is specific to .Net 4.6 and RyuJIT (64-bit). I’ll make this big and bold so we get to the point quickly:&lt;/p&gt;

&lt;h3 id=&quot;the-methods-you-call-can-get-different-parameter-values-than-you-passed-in&quot;&gt;The methods you call can get different parameter values than you passed in.&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The JIT (Just-in-Time compiler) in .Net (and many platforms) does something called Tail Call optimization. This happens to alleviate stack load on the last-called method in a chain. I won’t go into what a tail call is &lt;a href=&quot;https://blogs.msdn.microsoft.com/davbr/2007/06/20/enter-leave-tailcall-hooks-part-2-tall-tales-of-tail-calls/&quot;&gt;because there’s already an excellent write up by David Broman&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The issue here is a bug in how RyuJIT x64 implements this optimization in certain situations. &lt;!--more--&gt;Let’s look at the specific example we hit at Stack Overflow (&lt;a href=&quot;https://github.com/StackExchange/RyuJIT-TailCallBug&quot;&gt;we have uploaded a minimal version of this reproduction to GitHub&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We noticed that &lt;a href=&quot;http://miniprofiler.com/&quot;&gt;MiniProfiler&lt;/a&gt; (which we use to track performance) was showing only on the first page load. The profiler then failed to show again until an application recycle. This turned out to be a caching bug based on the HTTP Cache usage locally. HTTP Cache is our “L1” cache at Stack Overflow; &lt;a href=&quot;https://redis.io/&quot;&gt;redis&lt;/a&gt; is typically the “L2.” After over a day of debugging (and sanity checking), we tracked the crazy to here:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-csharp&quot; data-lang=&quot;csharp&quot;&gt;&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;broadcastRemoveFromCache&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SetWithPriority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheItemPriority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SetWithPriority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheItemPriority&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;KeyInContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;RawSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RawSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheItemPriority&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;absolute&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HasValue&lt;/span&gt; 
                   &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UtcNow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSeconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
                   &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NoAbsoluteExpiration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HasValue&lt;/span&gt; 
                  &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
                  &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NoSlidingExpiration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;HttpRuntime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;absolute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;What was happening? We were setting the MiniProfiler cache duration (passed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Set&amp;lt;T&amp;gt;&lt;/code&gt;) as 3600 seconds. But often (~98% of the time), we were seeing it immediately expire from HTTP cache. Next we narrowed this down to being a bug &lt;strong&gt;only when optimizations are enabled&lt;/strong&gt; (the “Optimize Code” checkbox on your project’s build properties). At this point sanity is out the window and you debug &lt;em&gt;everything&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here’s what that code looks like now. Note: I have slightly shortened it to fit this page. &lt;a href=&quot;https://github.com/StackExchange/RyuJIT-TailCallBug/blob/master/StackRedis/Caches.Local.cs#L403&quot;&gt;The unaltered code is on GitHub here&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-csharp&quot; data-lang=&quot;csharp&quot;&gt;&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;broadcastRemoveFromCache&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LocalCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OnLogDuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;LocalCache.Set&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SetWithPriority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheItemPriority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SetWithPriority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheItemPriority&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LocalCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OnLogDuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;LocalCache.SetWithPriority&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;KeyInContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;RawSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RawSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CacheItemPriority&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LocalCache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OnLogDuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;RawSet&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;absolute&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HasValue&lt;/span&gt; 
                   &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UtcNow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSeconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
                   &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NoAbsoluteExpiration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HasValue&lt;/span&gt; 
                  &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
                  &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NoSlidingExpiration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;HttpRuntime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;absolute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Removed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;evt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Added&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;evt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;absolute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;durationSecs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isSliding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;This is nothing fancy, all we have is some methods calling each other. Here’s the scary result of those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalCache.OnLogDuration&lt;/code&gt; calls:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;LocalCache.Set: 3600&lt;/li&gt;
  &lt;li&gt;LocalCache.SetWithPriority: 3600&lt;/li&gt;
  &lt;li&gt;RawSet: &lt;em&gt;null&lt;/em&gt;, or 114, or 97, or some other seemingly random value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s an example test run from &lt;a href=&quot;https://github.com/StackExchange/RyuJIT-TailCallBug&quot;&gt;the GitHub repo&lt;/a&gt;:
 &lt;a href=&quot;/blog/content/Blog-RyuJIT-Results.png&quot; target=&quot;_blank&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;/blog/content/Blog-RyuJIT-Results.webp&quot; /&gt;&lt;source type=&quot;image/png&quot; srcset=&quot;/blog/content/Blog-RyuJIT-Results.png&quot; /&gt;&lt;img src=&quot;/blog/content/Blog-RyuJIT-Results.png&quot; loading=&quot;lazy&quot; alt=&quot;RyuJIT Tail Tests&quot; /&gt;&lt;/picture&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The method we called did not get the parameters we passed&lt;/strong&gt;. That’s it. The net result of this is that local cache (which we use &lt;em&gt;very&lt;/em&gt; heavily) is either unreliable or non-existent. This would add a tremendous amount of load to our entire infrastructure, making Stack Overflow much slower and likely leading to a full outage.&lt;/p&gt;

&lt;p&gt;That’s not why we’re telling you about this though. Let’s step back and look at the big picture. What are some other variable names we could use?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;amountToWithdraw&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qtyOfStockToBuy&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;carbonCopyToAccountId&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;voltage&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;targetAltitude&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rotorVelocity&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oxygenPressure&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dosageMilliliters&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Does that help put things in perspective?&lt;/p&gt;

&lt;p&gt;This bug is not obvious for several reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It only happens with optimizations enabled. For most developers and projects, that’s not in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DEBUG&lt;/code&gt; and won’t show locally.
    &lt;ul&gt;
      &lt;li&gt;That means you’ll only see this in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RELEASE&lt;/code&gt;, which for most people is &lt;strong&gt;only production&lt;/strong&gt;.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Attaching a debugger alters the behavior. This almost always hides the issue.&lt;/li&gt;
  &lt;li&gt;Adding a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Debug.WriteLine()&lt;/code&gt; will often fix the issue because of the tail change.&lt;/li&gt;
  &lt;li&gt;It won’t reproduce in certain scenarios (e.g. we can’t repro this in a console application or VS hosting, only IIS).&lt;/li&gt;
  &lt;li&gt;Given the nature of the bug, as far as we can tell, it can equally affect any framework library as well.&lt;/li&gt;
  &lt;li&gt;It can happen in a NuGet library (most of which are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RELEASE&lt;/code&gt;); the issue may not be in your code at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To address an obvious question: is this a security issue? Answer: it can be. It’s not something you could actively exploit in almost all cases, since stack variables are the things being swapped around here. However, it can be an issue indirectly. For example, if your code makes an assumption with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; param like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if (user == null) { user = SystemUser; }&lt;/code&gt;, then a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; passed in will certainly be a problem, giving other users that access sporadically. A more common example of this would be a value for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Role&lt;/code&gt; enum being passed incorrectly.&lt;/p&gt;

&lt;h2 id=&quot;recommendations&quot;&gt;Recommendations&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;Do not install .Net 4.6 in production.&lt;/li&gt;
  &lt;li&gt;If you have installed .Net 4.6, disable RyuJIT immediately (&lt;strong&gt;this is a temporary fix and should be removed when an update is released&lt;/strong&gt;). You can disable RyuJIT via a registry setting (note: this requires an application pool recycle to take effect).
    &lt;ul&gt;
      &lt;li&gt;Under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework&lt;/code&gt; add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useLegacyJit&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DWORD&lt;/code&gt; with a value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Or via PowerShell:&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;
  &lt;pre&gt;&lt;code class=&quot;language-powershell&quot; data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;n&quot;&gt;Set-ItemProperty&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;HKLM:\Software\Microsoft\.NETFramework&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;useLegacyJit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Type&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DWord&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Value&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/figure&gt;

&lt;p&gt;Be aware, &lt;a href=&quot;https://github.com/Microsoft/dotnet/blob/master/docs/testing-with-ryujit.md&quot;&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web.config&lt;/code&gt; method (#3)&lt;/a&gt; of disabling RyuJIT &lt;strong&gt;does not work&lt;/strong&gt;. Outside of IIS hosting, applying this fix via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.config&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; work.&lt;/p&gt;

&lt;p&gt;We are talking with and pushing Microsoft to get a fix for this shipped ASAP. We recognize releasing a fix for .Net on the Microsoft side isn’t a small deal. Our disclosure is prompted by the reality that a fix cannot be released, distributed, and applied immediately.&lt;/p&gt;

</description>
        <pubDate>Mon, 27 Jul 2015 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/blog/2015/07/27/why-you-should-wait-on-dotnet-46/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>(tiny) Life At Stack Overflow: My Developers Are Smarter Than Your DBAs</title>
        <link>https://nickcraver.com/talks/tiny/developers-and-dbas</link>
        <description>&lt;section&gt;
  &lt;h1&gt;My Developers Are Smarter Than Your DBAs&lt;/h1&gt;
  &lt;p&gt;
&lt;svg class=&quot;stackoverflow-logo&quot; viewBox=&quot;0 0 400 116&quot; enable-background=&quot;new 0 0 400 116&quot; height=&quot;100px&quot;&gt;
  &lt;g class=&quot;so-text&quot;&gt;
    &lt;path d=&quot;M94.4,92.2c-4.6,0-8.2-1.1-11.2-3.9l2.7-2.7c2.2,2.3,5.1,3.2,8.5,3.2c4.5,0,7.2-1.6,7.2-4.8
        c0-2.4-1.4-3.7-4.5-4l-4.5-0.4c-5.3-0.4-8.1-2.8-8.1-7.3c0-5,4.2-8,10-8c3.9,0,7.4,0.9,9.8,2.9l-2.6,2.6c-1.9-1.5-4.4-2.2-7.2-2.2
        c-4,0-6.1,1.7-6.1,4.5c0,2.3,1.3,3.7,4.8,4l4.4,0.4c4.8,0.4,8,2.3,8,7.4C105.5,89.2,101,92.2,94.4,92.2z&quot;/&gt;
    &lt;path d=&quot;M117.6,91.9c-4.6,0-7-3.3-7-7.3V67.9h-3.4v-3h3.4v-8.5h4v8.5h5.8v3h-5.8v16.6c0,2.5,1.2,4,3.7,4h2.1v3.4
        H117.6z&quot;/&gt;
    &lt;path d=&quot;M141,91.9v-2.6c-2.2,2.2-4.2,2.9-7.9,2.9c-3.8,0-6-0.7-7.7-2.5c-1.3-1.3-2-3.4-2-5.5c0-4.8,3.3-7.9,9.4-7.9
        h8.2v-2.6c0-4-2-6-7-6c-3.5,0-5.3,0.8-6.9,3.1l-2.7-2.5c2.4-3.2,5.2-4,9.6-4c7.3,0,10.9,3.2,10.9,9.1v18.5H141z M141,79.3h-7.6
        c-4.1,0-6.1,1.7-6.1,4.8c0,3.2,1.9,4.6,6.3,4.6c2.3,0,4.4-0.2,6.1-1.8c0.9-0.9,1.4-2.4,1.4-4.7V79.3z&quot;/&gt;
    &lt;path d=&quot;M160.6,92.2c-6.9,0-12.1-4.6-12.1-13.9c0-9.3,5.2-13.9,12.1-13.9c3.8,0,6.1,1,9,4l-2.7,2.5
        c-2-2.3-3.7-3-6.3-3c-2.7,0-4.9,1.1-6.3,3.1c-1.3,1.8-1.8,3.9-1.8,7.3c0,3.4,0.5,5.5,1.8,7.3c1.4,2,3.7,3.1,6.3,3.1
        c2.6,0,4.2-0.8,6.3-3.1l2.7,2.6C166.7,91.2,164.4,92.2,160.6,92.2z&quot;/&gt;
    &lt;path d=&quot;M191.4,91.9l-8.6-13.9l-5.3,6.1v7.9h-4V52.5h4v26.3l12.3-14.2h5l-9.2,10.4l10.8,16.8H191.4z&quot;/&gt;
    &lt;path d=&quot;M217.7,88.9c-1.7,1.8-4.5,3.4-8.5,3.4c-4,0-6.8-1.6-8.4-3.4c-2.5-2.6-3.1-5.7-3.1-10.6
        c0-4.9,0.6-8,3.1-10.6c1.7-1.8,4.4-3.4,8.4-3.4c4,0,6.8,1.6,8.5,3.4c2.5,2.6,3.1,5.7,3.1,10.6C220.8,83.2,220.1,86.3,217.7,88.9z
         M212.4,71.6c-0.8-0.8-1.8-1.2-3.2-1.2s-2.4,0.4-3.1,1.2c-1.4,1.4-1.6,3.8-1.6,6.6c0,2.8,0.2,5.2,1.6,6.7c0.8,0.8,1.8,1.2,3.1,1.2
        s2.4-0.4,3.2-1.2c1.4-1.4,1.6-3.8,1.6-6.7C213.9,75.4,213.8,73,212.4,71.6z&quot;/&gt;
    &lt;path d=&quot;M236.1,91.9h-5.3l-10.1-27.3h7.2l5.6,16.9l5.5-16.9h7.2L236.1,91.9z&quot;/&gt;
    &lt;path d=&quot;M252.5,80.4c0,3.5,2.1,6.1,6,6.1c3,0,4.5-0.8,6.2-2.6l4.1,4c-2.8,2.8-5.5,4.3-10.4,4.3
        c-6.5,0-12.6-2.9-12.6-14c0-8.9,4.8-13.9,11.9-13.9c7.6,0,11.9,5.6,11.9,13.1v3H252.5z M262.3,72.8c-0.7-1.6-2.3-2.8-4.6-2.8
        c-2.3,0-3.8,1.2-4.6,2.8c-0.4,1-0.6,1.7-0.6,2.9h10.4C262.8,74.5,262.7,73.8,262.3,72.8z&quot;/&gt;
    &lt;path d=&quot;M287.8,72c-1-1-1.9-1.6-3.6-1.6c-2.1,0-4.4,1.6-4.4,5v16.5H273V64.6h6.7v2.6c1.3-1.6,3.9-2.9,6.9-2.9
        c2.7,0,4.6,0.7,6.5,2.6L287.8,72z&quot;/&gt;
    &lt;path d=&quot;M304.5,69.8v22.1h-6.8V69.8h-2.8v-5.2h2.8v-3.4c0-3.9,2.4-7.8,8-7.8h3.9v5.8h-2.6c-1.6,0-2.4,0.9-2.4,2.5v3
        h5v5.2H304.5z M320.8,91.9c-5.6,0-8-3.9-8-7.8V53.4h6.8v30.3c0,1.6,0.7,2.5,2.4,2.5h2.6v5.8H320.8z&quot;/&gt;
    &lt;path d=&quot;M345.6,88.9c-1.7,1.8-4.5,3.4-8.5,3.4c-4,0-6.8-1.6-8.4-3.4c-2.5-2.6-3.1-5.7-3.1-10.6
        c0-4.9,0.6-8,3.1-10.6c1.7-1.8,4.4-3.4,8.4-3.4c4,0,6.8,1.6,8.5,3.4c2.5,2.6,3.1,5.7,3.1,10.6C348.7,83.2,348.1,86.3,345.6,88.9z
         M340.3,71.6c-0.8-0.8-1.8-1.2-3.2-1.2c-1.4,0-2.4,0.4-3.1,1.2c-1.4,1.4-1.6,3.8-1.6,6.6c0,2.8,0.2,5.2,1.6,6.7
        c0.8,0.8,1.8,1.2,3.1,1.2c1.4,0,2.4-0.4,3.2-1.2c1.4-1.4,1.6-3.8,1.6-6.7C341.9,75.4,341.8,73,340.3,71.6z&quot;/&gt;
    &lt;path d=&quot;M379.3,91.9h-5.6L368,74.8l-5.7,17.1h-5.6l-8.4-27.3h7.2l4.4,16.9l5.6-16.9h5l5.6,16.9l4.4-16.9h7.2
        L379.3,91.9z&quot;/&gt;
  &lt;/g&gt;
  &lt;g&gt;
    &lt;rect x=&quot;26.3&quot; y=&quot;58&quot; transform=&quot;matrix(0.9718 0.2357 -0.2357 0.9718 15.6501 -8.3368)&quot; fill=&quot;#BF9452&quot; width=&quot;32.8&quot; height=&quot;6.6&quot;/&gt;
    &lt;rect x=&quot;30.5&quot; y=&quot;47&quot; transform=&quot;matrix(0.8905 0.455 -0.455 0.8905 28.0333 -15.8267)&quot; fill=&quot;#D28B29&quot; width=&quot;32.8&quot; height=&quot;6.6&quot;/&gt;
    &lt;rect x=&quot;37.9&quot; y=&quot;36.2&quot; transform=&quot;matrix(0.7531 0.6579 -0.6579 0.7531 39.4192 -25.9804)&quot; fill=&quot;#F68A1F&quot; width=&quot;32.8&quot; height=&quot;6.6&quot;/&gt;
    &lt;rect x=&quot;48.4&quot; y=&quot;27.1&quot; transform=&quot;matrix(0.5616 0.8274 -0.8274 0.5616 53.5922 -40.3072)&quot; fill=&quot;#F47920&quot; width=&quot;32.8&quot; height=&quot;6.6&quot;/&gt;
    &lt;rect x=&quot;24.7&quot; y=&quot;68.9&quot; transform=&quot;matrix(0.9985 5.452314e-02 -5.452314e-02 0.9985 3.9961 -2.1364)&quot; fill=&quot;#A68A6E&quot; width=&quot;32.8&quot; height=&quot;6.6&quot;/&gt;
    &lt;polygon fill=&quot;#818185&quot; points=&quot;64.2,91.8 64.2,64.6 70.7,64.6 70.7,98.4 11.4,98.4 11.4,64.6 17.9,64.6 17.9,91.8     &quot;/&gt;
    &lt;rect x=&quot;24.5&quot; y=&quot;78.7&quot; fill=&quot;#818185&quot; width=&quot;33.1&quot; height=&quot;6.6&quot;/&gt;
  &lt;/g&gt;
&lt;/svg&gt;
  &lt;/p&gt;
  &lt;p&gt;
  &lt;small&gt;by &lt;a href=&quot;https://twitter.com/Nick_Craver&quot;&gt;@Nick_Craver&lt;/a&gt;&lt;/small&gt;
  &lt;/p&gt;
&lt;/section&gt;

&lt;section&gt;
    &lt;h2&gt;What would you say...you do here?&lt;h2&gt;
    &lt;h4&gt;Last month at Stack Overflow:&lt;/h4&gt;
    &lt;ul&gt;
        &lt;li&gt;1,468,389,303 Page Views&lt;/li&gt;
        &lt;li&gt;5,183,954,727 HTTP Hits&lt;/li&gt;
        &lt;li&gt;71,562,833,811,315 Bytes Sent&lt;/li&gt;
        &lt;li&gt;3,202,505,376 CDN Hits&lt;/li&gt;
        &lt;li&gt;54,400,000,000,000 CDN Bytes&lt;/li&gt;
        &lt;li&gt;19,532,899,854 SQL Queries&lt;/li&gt;
        &lt;li&gt;81,505,688,410 Redis Ops&lt;/li&gt;
        &lt;li&gt;18.2ms Average Render Time&lt;/li&gt;
        &lt;li&gt;...at roughly 5-10% capacity&lt;/li&gt;
    &lt;/ul&gt;
&lt;/section&gt;

&lt;section&gt;
  &lt;section&gt;    
    &lt;h2&gt;How do we do that?&lt;h2&gt;
  &lt;/section&gt;
  &lt;section&gt;
    &lt;h2&gt;Go that way, really fast.&lt;/h2&gt;
    &lt;h2 class=&quot;fragment&quot;&gt;If something gets in your way...turn.&lt;/h2&gt;    
  &lt;/section&gt;
  &lt;section&gt;
    &lt;h2&gt;We're not ready to turn yet.&lt;/h2&gt;
  &lt;/section&gt;
&lt;/section&gt;

&lt;section&gt;
  &lt;section&gt;
    &lt;h2&gt;Being part of a team&lt;/h2&gt;
  &lt;/section&gt;
  &lt;section&gt;
    &lt;h2&gt;Disadvantages:&lt;/h2&gt;
    &lt;p class=&quot;fragment&quot;&gt;You no longer know all the things&lt;/p&gt;
    &lt;p class=&quot;fragment&quot;&gt;You don't have time to learn all the things&lt;/p&gt;
    &lt;p class=&quot;fragment&quot;&gt;Merge conflicts&lt;/p&gt;
  &lt;/section&gt;
  &lt;section&gt;
    &lt;h2&gt;Advantages:&lt;/h2&gt;
    &lt;p class=&quot;fragment&quot;&gt;You have help&lt;/p&gt;
    &lt;p class=&quot;fragment&quot;&gt;Lots of help&lt;/p&gt;
    &lt;p class=&quot;fragment&quot;&gt;More victims for the wheel of blame&lt;/p&gt;
  &lt;/section&gt;
&lt;/section&gt;

&lt;section&gt;
  &lt;section&gt;
    &lt;h2&gt;Pipelines&lt;/h2&gt;
  &lt;/section&gt;
  &lt;section&gt;
    &lt;h2&gt;Most interactions with a DBA require 2 people&lt;/h2&gt;
    &lt;p&gt;This is true of anyone you're asking for a service&lt;/p&gt;
  &lt;/section&gt;
&lt;/section&gt;

&lt;section&gt;
  &lt;section&gt;
    &lt;h2&gt;Perspective &amp; Scope&lt;/h2&gt;
  &lt;/section&gt;
  &lt;section&gt;
    &lt;h2&gt;We know the things we know&lt;/h2&gt;
    &lt;p class=&quot;fragment&quot;&gt;Except knowing what we know&lt;/p&gt;
  &lt;/section&gt;
  &lt;section&gt;
    &lt;h2&gt;Insight and decisions are based on our world&lt;/h2&gt;
    &lt;p class=&quot;fragment&quot;&gt;Specifically, the world as we see it&lt;/p&gt;
  &lt;/section&gt;
  &lt;section&gt;
    &lt;h2&gt;Fog of war&lt;/h2&gt;
  &lt;/section&gt;
  &lt;section data-background=&quot;/talks/content/developers-and-dbas/dba.svg&quot;&gt;&lt;/section&gt;
  &lt;section data-background=&quot;/talks/content/developers-and-dbas/developer.svg&quot;&gt;&lt;/section&gt;
  &lt;section data-background=&quot;/talks/content/developers-and-dbas/overview.svg&quot;&gt;&lt;/section&gt;
&lt;/section&gt;

&lt;section&gt;
  &lt;section&gt;
    &lt;h2&gt;What does this allow us to do?&lt;/h2&gt;
    &lt;p class=&quot;fragment&quot;&gt;Tag Engine&lt;/p&gt; 
    &lt;p class=&quot;fragment&quot;&gt;Elasticsearch&lt;/p&gt;
    &lt;p class=&quot;fragment&quot;&gt;Moving /users into redis&lt;/p&gt;
    &lt;p class=&quot;fragment&quot; style=&quot;font-style: italic;&quot;&gt;Anything we want&lt;/p&gt;
  &lt;/section&gt;
&lt;/section&gt;

&lt;section&gt;
  &lt;h2&gt;Q&amp;amp;A?&lt;/h2&gt;
  &lt;p&gt;We love Q&amp;amp;A.&lt;/p&gt;
&lt;/section&gt;</description>
        <pubDate>Fri, 24 Jul 2015 00:00:00 +0000</pubDate>
        <guid isPermaLink="true">https://nickcraver.com/talks/tiny/developers-and-dbas</guid>
        
        
        <category>talks</category>
        
      </item>
    
  </channel>
</rss>
