<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>How GitHub approaches infrastructure - The GitHub Blog</title>
	<atom:link href="https://github.blog/engineering/infrastructure/feed/" rel="self" type="application/rss+xml" />
	<link>https://github.blog/engineering/infrastructure/</link>
	<description>Updates, ideas, and inspiration from GitHub to help developers build and design software.</description>
	<lastBuildDate>Wed, 15 Apr 2026 23:44:24 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://github.blog/wp-content/uploads/2019/01/cropped-github-favicon-512.png?fit=32%2C32</url>
	<title>How GitHub approaches infrastructure - The GitHub Blog</title>
	<link>https://github.blog/engineering/infrastructure/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">153214340</site>	<item>
		<title>How GitHub uses eBPF to improve deployment safety</title>
		<link>https://github.blog/engineering/infrastructure/how-github-uses-ebpf-to-improve-deployment-safety/</link>
		
		<dc:creator><![CDATA[Lawrence Gripper]]></dc:creator>
		<pubDate>Thu, 16 Apr 2026 16:00:00 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[eBPF]]></category>
		<category><![CDATA[platform engineering]]></category>
		<guid isPermaLink="false">https://github.blog/?p=95074</guid>

					<description><![CDATA[<p>Learn how Github uses eBPF to detect and prevent circular dependencies in its deployment tooling. </p>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-uses-ebpf-to-improve-deployment-safety/">How GitHub uses eBPF to improve deployment safety</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p class="wp-block-paragraph">Did you know that, at GitHub, we host all of our own source code on <a href="http://github.com">github.com</a>? We do this because we&rsquo;re our own biggest customer&mdash;testing out changes internally before they go to users. However, there&rsquo;s one downside: If github.com were ever to go down, we wouldn&rsquo;t be able to access our own source code.</p>



<p class="wp-block-paragraph">This is what you&rsquo;d call a very simple circular dependency: to deploy GitHub, we needed GitHub. If GitHub is down, then we wouldn&rsquo;t be able to deploy something to fix it. We mitigate this by maintaining a mirror of our code for fixing forward and built assets for rolling back.</p>



<p class="wp-block-paragraph">So we&rsquo;re done, right? Problem solved? Nope, there are more circular dependencies to consider. For example, how do you stop a deployment script introducing a circular dependency of its own on an internal service or downloading a binary from GitHub?</p>



<p class="wp-block-paragraph">When we started to design our new host-based deployment system, we evaluated some new approaches to prevent deployment code from creating circular dependencies. We found that using eBPF, we could selectively monitor and block those calls. In this blog post, we&rsquo;ll take you through our findings and show how you can get started writing your own eBPF programs.</p>



<h2 class="wp-block-heading" id="h-types-of-circular-dependencies">Types of circular dependencies</h2>



<p class="wp-block-paragraph">Let&rsquo;s start by looking at the types of circular dependencies through a hypothetical scenario.</p>



<p class="wp-block-paragraph">Suppose a MySQL outage occurs, which causes GitHub to be unable to serve <code>release</code> data from repositories. To resolve the incident, we need to roll out a configuration change to the stateful MySQL nodes that are impacted. This configuration change is applied by executing a deploy script on each node.</p>



<p class="wp-block-paragraph">Now, let&rsquo;s look at the different types of circular dependencies that could impact GitHub during this scenario.</p>



<ol start="1" class="wp-block-list">
<li><strong>Direct dependency</strong>: The MySQL deploy script attempts to pull the latest release of an&nbsp;open source&nbsp;tool from GitHub. Since GitHub&nbsp;can&rsquo;t&nbsp;serve the release data (due to the outage), the script&nbsp;can&rsquo;t&nbsp;complete.&nbsp;&nbsp;</li>
</ol>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="1433" height="194" src="https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.24-PM.png?resize=1433%2C194" alt="Diagram showing a MySQL deploy script fails after attempting to pull the latest release of an&nbsp;open&nbsp;source&nbsp;tool from GitHub." class="wp-image-95085" srcset="https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.24-PM.png?w=1433 1433w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.24-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.24-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.24-PM.png?w=1024 1024w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure>



<ol start="2" class="wp-block-list">
<li><strong>Hidden dependencies</strong>: The MySQL deploy script uses a servicing tool that is already present on the machine&rsquo;s disk. However, when the tool runs, it checks GitHub to see if an update is available. If it&rsquo;s unable to contact GitHub (due to the outage), the script may fail or hang, depending on how the tool handles the error when checking for updates.</li>
</ol>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="1439" height="363" src="https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.34-PM.png?resize=1439%2C363" alt="Diagram showing a script failing after being unable to contact GitHub (due to the outage)." class="wp-image-95086" srcset="https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.34-PM.png?w=1439 1439w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.34-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.34-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.34-PM.png?w=1024 1024w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure>



<ol start="3" class="wp-block-list">
<li><strong>Transient dependencies</strong>: The MySQL deploy script calls, via an API, another internal service (for example, a migrations service), which in turn attempts to fetch the latest release of an open source tool from GitHub to use the new binary. The failure propagates back to the deploy script.</li>
</ol>



<figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="1450" height="202" src="https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.41-PM.png?resize=1450%2C202" alt="Diagram showing a MySQL deploy script calling, via an API, another internal service, which in turn attempts to fetch the latest release of an open source tool from GitHub to use the new binary. The failure propagates back to the deploy script." class="wp-image-95088" srcset="https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.41-PM.png?w=1450 1450w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.41-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.41-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/04/Screenshot-2026-04-06-at-6.36.41-PM.png?w=1024 1024w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure>



<h2 class="wp-block-heading" id="h-how-do-you-solve-these-circular-dependencies">How do you solve these circular dependencies?</h2>



<p class="wp-block-paragraph">Until recently, the onus has been on every team who that owns stateful hosts to review their deployment scripts and identify circular dependencies.</p>



<p class="wp-block-paragraph">In practice, however, many dependencies aren&rsquo;t identified until an incident occurs, which can delay recovery.</p>



<p class="wp-block-paragraph">The obvious route would be to block access to github.com from the machines to validate that the system can deploy without it. But these hosts are stateful and serve customer traffic even during rolling deploys, drains, or restarts. Blocking github.com entirely would impact their ability to handle production requests.</p>



<p class="wp-block-paragraph">This is where we started to look at eBPF, which lets you load custom programs into the Linux kernel and hook into core system primitives like networking.</p>



<p class="wp-block-paragraph">We were particularly interested in the <a href="https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_CGROUP_SKB/"><code>BPF_PROG_TYPE_CGROUP_SKB</code> program type</a> because it lets you hook network egress from a particular cGroup.</p>



<p class="wp-block-paragraph">A <a href="https://en.wikipedia.org/wiki/Cgroups">cGroup</a> is a Linux primitive (used heavily by Docker but not limited to it) that enforces resource limits and isolation for sets of processes. You can create a cGroup, configure it, and move processes into it&mdash;no Docker required.</p>



<p class="wp-block-paragraph">This started to look very promising. Could we create a cGroup, place only the deployment script inside it, and then limit the outbound network access of only that script? It certainly looked possible, so we started to build a proof of concept.</p>



<h2 class="wp-block-heading" id="h-building-out-per-process-conditional-network-filtering-with-ebpf">Building out per-process conditional network filtering with eBPF</h2>



<p class="wp-block-paragraph">We started on a proof of concept in <code>go</code> that used the <code><a href="https://github.com/cilium/ebpf">cilium/ebpf</a></code> library.</p>



<p class="wp-block-paragraph">ebpf-go is a pure-Go library to read, modify, and load eBPF programs and attach them to various hooks in the Linux kernel.</p>



<p class="wp-block-paragraph">It massively simplifies the process of authoring, building, and running programs that use eBPF. For example, to hook the <a href="https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_CGROUP_SKB/"><code>BPF_PROG_TYPE_CGROUP_SKB</code> program type</a>, we can do this as follows: &#128071;</p>


<div class="wp-block-code-wrapper">
<pre class="wp-block-code"><code>//go:generate go tool bpf2go -tags linux bpf cgroup_skb.c -- -I../headers 

 

func main() { 

   // Load pre-compiled programs and maps into the kernel. 

   objs := bpfObjects{} 

   if err := loadBpfObjects(&amp;objs, nil); err != nil { 

       log.Fatalf("loading objects: %v", err) 

   } 

   defer objs.Close() 

 

   // Link the count_egress_packets program to the cgroup. 

   l, err := link.AttachCgroup(link.CgroupOptions{ 

       Path:    "/sys/fs/cgroup/system.slice", 

       Attach:  ebpf.AttachCGroupInetEgress, 

       Program: objs.CountEgressPackets, 

   }) 

   if err != nil { 

       log.Fatal(err) 

   } 

   defer l.Close() 

 

   log.Println("Counting packets...") 

 

   // Read loop reporting the total amount of times the kernel 

   // function was entered, once per second. 

   ticker := time.NewTicker(1 * time.Second) 

   defer ticker.Stop() 

 

   for range ticker.C { 

       var value uint64 

       if err := objs.PktCount.Lookup(uint32(0), &amp;value); err != nil { 

           log.Fatalf("reading map: %v", err) 

       } 

       log.Printf("number of packets: %d\n", value) 

   } 

} </code></pre>
<clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value='//go:generate go tool bpf2go -tags linux bpf cgroup_skb.c -- -I../headers 

 

func main() { 

   // Load pre-compiled programs and maps into the kernel. 

   objs := bpfObjects{} 

   if err := loadBpfObjects(&amp;objs, nil); err != nil { 

       log.Fatalf("loading objects: %v", err) 

   } 

   defer objs.Close() 

 

   // Link the count_egress_packets program to the cgroup. 

   l, err := link.AttachCgroup(link.CgroupOptions{ 

       Path:    "/sys/fs/cgroup/system.slice", 

       Attach:  ebpf.AttachCGroupInetEgress, 

       Program: objs.CountEgressPackets, 

   }) 

   if err != nil { 

       log.Fatal(err) 

   } 

   defer l.Close() 

 

   log.Println("Counting packets...") 

 

   // Read loop reporting the total amount of times the kernel 

   // function was entered, once per second. 

   ticker := time.NewTicker(1 * time.Second) 

   defer ticker.Stop() 

 

   for range ticker.C { 

       var value uint64 

       if err := objs.PktCount.Lookup(uint32(0), &amp;value); err != nil { 

           log.Fatalf("reading map: %v", err) 

       } 

       log.Printf("number of packets: %d\n", value) 

   } 

}' tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div>


<p class="wp-block-paragraph">With the eBPF program:</p>


<div class="wp-block-code-wrapper">
<pre class="wp-block-code"><code>//go:build ignore 

 

#include "common.h" 

 

char __license[] SEC("license") = "Dual MIT/GPL"; 

 

struct { 

   __uint(type, BPF_MAP_TYPE_ARRAY); 

   __type(key, u32); 

   __type(value, u64); 

   __uint(max_entries, 1); 

} pkt_count SEC(".maps"); 

 

SEC("cgroup_skb/egress") 

int count_egress_packets(struct __sk_buff *skb) { 

   u32 key      = 0; 

   u64 init_val = 1; 

 

   u64 *count = bpf_map_lookup_elem(&amp;pkt_count, &amp;key); 

   if (!count) { 

       bpf_map_update_elem(&amp;pkt_count, &amp;key, &amp;init_val, BPF_ANY); 

       return 1; 

   } 

   __sync_fetch_and_add(count, 1); 

 

   return 1; 

} </code></pre>
<clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value='//go:build ignore 

 

#include "common.h" 

 

char __license[] SEC("license") = "Dual MIT/GPL"; 

 

struct { 

   __uint(type, BPF_MAP_TYPE_ARRAY); 

   __type(key, u32); 

   __type(value, u64); 

   __uint(max_entries, 1); 

} pkt_count SEC(".maps"); 

 

SEC("cgroup_skb/egress") 

int count_egress_packets(struct __sk_buff *skb) { 

   u32 key      = 0; 

   u64 init_val = 1; 

 

   u64 *count = bpf_map_lookup_elem(&amp;pkt_count, &amp;key); 

   if (!count) { 

       bpf_map_update_elem(&amp;pkt_count, &amp;key, &amp;init_val, BPF_ANY); 

       return 1; 

   } 

   __sync_fetch_and_add(count, 1); 

 

   return 1; 

}' tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div>


<p class="wp-block-paragraph">The <code>//go:generate</code> line handles compiling the eBPF C code and auto-generating the <code>bpfObjects</code> struct, which allows us to attach and interact with the program. This means a simple <code>go build</code> is all you need. &#129395;</p>



<p class="wp-block-paragraph">(<code>cilium/ebpf</code> has a great set of examples to get started. <a href="https://github.com/cilium/ebpf/tree/main/examples/cgroup_skb">Review the full code from above</a>).</p>



<p class="wp-block-paragraph">There was still a missing piece though: <code>CGROUP_SKB</code> operates on IP addresses. Given the breadth of GitHub&rsquo;s systems and rate of change, keeping an up-to-date block IP list would be very hard.</p>



<p class="wp-block-paragraph">Could we use more eBPF to create a DNS-based blocked list? Yes, it turns out we could.</p>



<p class="wp-block-paragraph">An eBPF <a href="https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_CGROUP_SOCK_ADDR/">program type of <code>BPF_PROG_TYPE_CGROUP_SOCK_ADDR</code></a> allows you to hook syscalls to create sockets <strong>and change the destination IP</strong>.</p>



<p class="wp-block-paragraph">Here is a simplified example where we rewrite any <code>connect4</code> syscall targeting DNS (Port 53) to <code>localhost:53</code>.</p>


<div class="wp-block-code-wrapper">
<pre class="wp-block-code"><code>cgroupLink, err := link.AttachCgroup(link.CgroupOptions{ 

       Path:    cgroup.Name(), 

       Attach:  ebpf.AttachCGroupInet4Connect, 

       Program: obj.Connect4, 

   }) 

   if err != nil { 

       return nil, fmt.Errorf("attaching eBPF program Connect4 to cgroup: %w", err) 

   } </code></pre>
<clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value='cgroupLink, err := link.AttachCgroup(link.CgroupOptions{ 

       Path:    cgroup.Name(), 

       Attach:  ebpf.AttachCGroupInet4Connect, 

       Program: obj.Connect4, 

   }) 

   if err != nil { 

       return nil, fmt.Errorf("attaching eBPF program Connect4 to cgroup: %w", err) 

   }' tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div>

<div class="wp-block-code-wrapper">
<pre class="wp-block-code"><code>/* This is the hexadecimal representation of 127.0.0.1 address */ 

const __u32 ADDRESS_LOCALHOST_NETBYTEORDER = bpf_htonl(0x7f000001); 

 

SEC("cgroup/connect4") 

int connect4(struct bpf_sock_addr *ctx) { 

 __be32 original_ip = ctx-&gt;user_ip4; 

 __u16 original_port = bpf_ntohs(ctx-&gt;user_port); 

 

 if (ctx-&gt;user_port == bpf_htons(53)) { 

   /* For DNS Query (*:53) rewire service to backend 

    * 127.0.0.1:const_dns_proxy_port */ 

   ctx-&gt;user_ip4 = const_mitm_proxy_address; 

   ctx-&gt;user_port = bpf_htons(const_dns_proxy_port); 

 } 

 

 return 1; 

} </code></pre>
<clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value='/* This is the hexadecimal representation of 127.0.0.1 address */ 

const __u32 ADDRESS_LOCALHOST_NETBYTEORDER = bpf_htonl(0x7f000001); 

 

SEC("cgroup/connect4") 

int connect4(struct bpf_sock_addr *ctx) { 

 __be32 original_ip = ctx-&gt;user_ip4; 

 __u16 original_port = bpf_ntohs(ctx-&gt;user_port); 

 

 if (ctx-&gt;user_port == bpf_htons(53)) { 

   /* For DNS Query (*:53) rewire service to backend 

    * 127.0.0.1:const_dns_proxy_port */ 

   ctx-&gt;user_ip4 = const_mitm_proxy_address; 

   ctx-&gt;user_port = bpf_htons(const_dns_proxy_port); 

 } 

 

 return 1; 

}' tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div>


<p class="wp-block-paragraph">We used this to intercept DNS queries from the cGroup and forward them to a userspace DNS proxy we run.</p>



<p class="wp-block-paragraph">Now, any DNS queries initiated by the deployment script are routed through our DNS proxy. Our proxy evaluates each requested domain against our block list and uses <a href="https://docs.ebpf.io/linux/concepts/maps/">eBPF Maps</a> to communicate with the <code>CGROUP_SKB</code> program, allowing or denying the request accordingly.</p>



<p class="wp-block-paragraph">If you&rsquo;d like to dig into the code, here&rsquo;s <a href="https://github.com/lawrencegripper/ebpf-cgroup-firewall/">an early proof of concept</a> we put together. Our current implementation has progressed since then, but this should serve as a good intro.</p>



<p class="wp-block-paragraph">Like any fun project, the deeper we got, the more we realized we could do.</p>



<p class="wp-block-paragraph">For example, could we correlate blocked DNS requests back to the specific command or process that triggered them, so teams could more easily debug and fix issues? Yes, we can!</p>



<p class="wp-block-paragraph">Inside the <a href="https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_CGROUP_SKB/"><code>BPF_PROG_TYPE_CGROUP_SKB</code> program type</a>, we have <a href="https://docs.ebpf.io/linux/program-context/__sk_buff/">the <code>skb_buff</code></a> from which we can pull the <a href="https://beta.computer-networking.info/syllabus/default/protocols/dns.html">DNS transaction ID</a> and also <a href="https://docs.ebpf.io/linux/helper-function/bpf_get_current_pid_tgid/">capture the Process ID</a> (PID) that initiated the request. We place this information into another eBPF Map tracking <code>DNS Transaction ID -&gt; Process ID</code>.</p>



<p class="wp-block-paragraph">Here is a simplified version of the eBPF code (see this <a href="https://github.com/lawrencegripper/ebpf-cgroup-firewall/blob/main/pkg/ebpf/bpf.c#L338-L360">PoC code</a> for full example):</p>


<div class="wp-block-code-wrapper">
<pre class="wp-block-code"><code>  __u32 pid = bpf_get_current_pid_tgid() &gt;&gt; 32; 

     __u16 skb_read_offset = sizeof(struct iphdr) + sizeof(struct udphdr); 

     __u16 dns_transaction_id = 

         get_transaction_id_from_dns_header(skb, skb_read_offset); 

 

     if (pid &amp;&amp; dns_transaction_id != 0) { 

       bpf_map_update_elem(&amp;dns_transaction_id_to_pid, &amp;dns_transaction_id, 

                           pid, BPF_ANY); 

     } </code></pre>
<clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="__u32 pid = bpf_get_current_pid_tgid() &gt;&gt; 32; 

     __u16 skb_read_offset = sizeof(struct iphdr) + sizeof(struct udphdr); 

     __u16 dns_transaction_id = 

         get_transaction_id_from_dns_header(skb, skb_read_offset); 

 

     if (pid &amp;&amp; dns_transaction_id != 0) { 

       bpf_map_update_elem(&amp;dns_transaction_id_to_pid, &amp;dns_transaction_id, 

                           pid, BPF_ANY); 

     }" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div>


<p class="wp-block-paragraph">As we&rsquo;re redirecting all DNS calls to our userspace DNS proxy, we can look at the transaction ID of each request, find the domain being resolved, and lookup in the eBPF Map to see which process made the request. By reading <code>/proc/{PID}/cmdline</code>, we can even extract the full command line that triggered the request.</p>



<p class="wp-block-paragraph">Then we can output a log line with all the information:</p>


<div class="wp-block-code-wrapper">
<pre class="wp-block-code"><code>&gt; WARN DNS BLOCKED reason=FromDNSRequest blocked=true blockedAt=dns domain=github.com. pid=266767 cmd="curl github.com " firewallMethod=blocklist</code></pre>
<clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value='&gt; WARN DNS BLOCKED reason=FromDNSRequest blocked=true blockedAt=dns domain=github.com. pid=266767 cmd="curl github.com " firewallMethod=blocklist' tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div>


<p class="wp-block-paragraph">With that, we&rsquo;re done.</p>



<p class="wp-block-paragraph">We can now:</p>



<ul class="wp-block-list">
<li>Conditionally block domains that would cause circular dependencies from deployment scripts.</li>



<li>Inform the owning team which command triggered the blocked request.</li>



<li>Provide an audit list of all domains contacted during a deployment.</li>



<li>Use the cGroups to enforce CPU and memory limits on deploy scripts, preventing runaway resource usage from impacting workloads.</li>
</ul>



<h2 class="wp-block-heading" id="h-what-s-next">What&rsquo;s next?</h2>



<p class="wp-block-paragraph">Our new circular dependency detection process is live after a six-month rollout.</p>



<p class="wp-block-paragraph">Now, if a team accidentally adds a problematic dependency, or if an existing binary tool we use takes a new dependency, the tooling will detect that problem and flag it to the team.</p>



<p class="wp-block-paragraph">The net result is a more stable GitHub and faster mean time to recovery during incidents (due to the removal of these circular dependencies).</p>



<p class="wp-block-paragraph">Are there ways for circular dependencies to still trip things up? You bet&mdash;and we&rsquo;ll look to improve the tool as we discover them.</p>



<h2 class="wp-block-heading" id="h-want-to-dive-in">Want to dive in?</h2>



<p class="wp-block-paragraph">Has this piqued your interest in what you might be able to do with eBPF?</p>



<p class="wp-block-paragraph">Get started by having a look through the examples in <a href="https://github.com/cilium/ebpf/tree/main/examples">cilium/ebpf</a> and the great documentation on the <a href="http://docs.ebpf.io">docs.ebpf.io</a> site.</p>



<p class="wp-block-paragraph">If you&rsquo;re not quite ready to start writing your own eBPF tools, try open source tools powered by eBPF, like <a href="https://bpftrace.org/tutorial-one-liners#lesson-3-file-opens">bpftrace for deep tracing</a> or <a href="https://github.com/mozillazg/ptcpdump">ptcpdump to get TCP dumps</a> with container-level metadata.</p>
</body></html>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-uses-ebpf-to-improve-deployment-safety/">How GitHub uses eBPF to improve deployment safety</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">95074</post-id>	</item>
		<item>
		<title>When protections outlive their purpose: A lesson on managing defense systems at scale</title>
		<link>https://github.blog/engineering/infrastructure/when-protections-outlive-their-purpose-a-lesson-on-managing-defense-systems-at-scale/</link>
		
		<dc:creator><![CDATA[Thomas Kjær Aabo]]></dc:creator>
		<pubDate>Thu, 15 Jan 2026 20:54:32 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[developer experience]]></category>
		<category><![CDATA[incident response]]></category>
		<category><![CDATA[observability]]></category>
		<category><![CDATA[site reliability]]></category>
		<guid isPermaLink="false">https://github.blog/?p=93309</guid>

					<description><![CDATA[<p>User feedback led us to clean up outdated mitigations. See why observability and lifecycle management are critical for defense systems.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/when-protections-outlive-their-purpose-a-lesson-on-managing-defense-systems-at-scale/">When protections outlive their purpose: A lesson on managing defense systems at scale</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p class="wp-block-paragraph">To keep a platform like GitHub available and responsive, it&rsquo;s critical to build defense mechanisms. A whole lot of them. Rate limits, traffic controls, and protective measures spread across multiple layers of infrastructure. These all play a role in keeping the service healthy during abuse or attacks.</p>



<p class="wp-block-paragraph">We recently ran into a challenge: Those same protections can quietly outlive their usefulness and start blocking legitimate users. This is especially true for protections added as emergency responses during incidents, when responding quickly means accepting broader controls that aren&rsquo;t necessarily meant to be long-term. User feedback led us to clean up outdated mitigations and reinforced that observability is just as critical for defenses as it is for features.</p>



<p class="wp-block-paragraph"><strong>We apologize for the disruption. </strong>We should have caught and removed these protections sooner. Here&rsquo;s what happened.</p>



<h2 class="wp-block-heading" id="h-what-users-reported">What users reported</h2>



<p class="wp-block-paragraph">We saw reports on social media from people getting &ldquo;too many requests&rdquo; errors during normal, low-volume browsing, such as when following a GitHub link from another service or app, or just browsing around with no obvious pattern of abuse.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="659" width="1024" src="https://github.blog/wp-content/uploads/2026/01/1.png?resize=1024%2C659" alt="Screenshot of a 'Too many requests' screen encountered by users." class="wp-image-93311" srcset="https://github.blog/wp-content/uploads/2026/01/1.png?w=1240 1240w, https://github.blog/wp-content/uploads/2026/01/1.png?w=300 300w, https://github.blog/wp-content/uploads/2026/01/1.png?w=768 768w, https://github.blog/wp-content/uploads/2026/01/1.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption"><em>Users encountered a &ldquo;Too many requests&rdquo; error during normal browsing.</em></figcaption></figure>



<p class="wp-block-paragraph">These were users making a handful of normal requests hitting rate limits that shouldn&rsquo;t have applied to them.</p>



<h2 class="wp-block-heading" id="what-we-found">What we found</h2>



<p class="wp-block-paragraph">Investigating these reports, we discovered the root cause: Protection rules added during past abuse incidents had been left in place. These rules were based on patterns that had been strongly associated with abusive traffic when they were created. The problem is that those same patterns were also matching some logged-out requests from legitimate clients.</p>



<p class="wp-block-paragraph">These patterns are combinations of industry-standard fingerprinting techniques alongside platform-specific business logic &mdash; composite signals that help us distinguish legitimate usage from abuse. But, unfortunately, composite signals can occasionally produce false positives.</p>



<p class="wp-block-paragraph">The composite approach did provide filtering. Among requests that matched the suspicious fingerprints, only about 0.5&ndash;0.9% were actually blocked; specifically, those that also triggered the business-logic rules. Requests that matched both criteria were blocked 100% of the time.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="509" width="1024" src="https://github.blog/wp-content/uploads/2026/01/2.png?resize=1024%2C509" alt="Chart showing percentage of fingerprint matches that were blocked by also triggering business-logic rules, fluctuating between 0.5-0.9% over 60 minutes" class="wp-image-93312" srcset="https://github.blog/wp-content/uploads/2026/01/2.png?w=1184 1184w, https://github.blog/wp-content/uploads/2026/01/2.png?w=300 300w, https://github.blog/wp-content/uploads/2026/01/2.png?w=768 768w, https://github.blog/wp-content/uploads/2026/01/2.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption"><em>Not all fingerprint matches resulted in blocks &mdash; only those also matching business logic patterns.</em></figcaption></figure>



<p class="wp-block-paragraph">The overall impact was small but consistent; however, for the customers who were affected, we recognize that any incorrect blocking is unacceptable and can be disruptive. To put all of this in perspective, the following shows the false-positive rate relative to total traffic.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="508" width="1024" src="https://github.blog/wp-content/uploads/2026/01/3.png?resize=1024%2C508" alt="Chart showing false positives as approximately 0.003-0.004% of total traffic, with a reference line at 100%" class="wp-image-93313" srcset="https://github.blog/wp-content/uploads/2026/01/3.png?w=1184 1184w, https://github.blog/wp-content/uploads/2026/01/3.png?w=300 300w, https://github.blog/wp-content/uploads/2026/01/3.png?w=768 768w, https://github.blog/wp-content/uploads/2026/01/3.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption"><em>False positives represented roughly 0.003-0.004% of total traffic.</em></figcaption></figure>



<p class="wp-block-paragraph">Although the percentage was low, it still meant that real users were incorrectly blocked during normal browsing, which is not acceptable. The chart below zooms in specifically on this false-positive pattern over time.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="509" width="1024" src="https://github.blog/wp-content/uploads/2026/01/4.png?resize=1024%2C509" alt="Chart showing false positive rate over 60 minutes, hovering around 0.003-0.004%" class="wp-image-93314" srcset="https://github.blog/wp-content/uploads/2026/01/4.png?w=1184 1184w, https://github.blog/wp-content/uploads/2026/01/4.png?w=300 300w, https://github.blog/wp-content/uploads/2026/01/4.png?w=768 768w, https://github.blog/wp-content/uploads/2026/01/4.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption"><em>In the hour before cleanup, approximately 3-4 requests per 100,000 (0.003-0.004%) were incorrectly blocked.</em></figcaption></figure>



<p class="wp-block-paragraph">This is a common challenge when defending platforms at scale. During active incidents, you need to respond quickly, and you accept some tradeoffs to keep the service available. The mitigations are correct and necessary <em>at that moment</em>. Those emergency controls don&rsquo;t age well as threat patterns evolve and legitimate tools and usage change.</p>



<p class="wp-block-paragraph">Without active maintenance, temporary mitigations become permanent, and their side effects compound quietly.</p>



<h3 class="wp-block-heading" id="tracing-through-the-stack">Tracing through the stack</h3>



<p class="wp-block-paragraph">The investigation itself highlighted why these issues can persist. When users reported errors, we traced requests across multiple layers of infrastructure to identify where the blocks occurred.</p>



<p class="wp-block-paragraph">To understand why this tracing is necessary, it helps to see how protection mechanisms are applied throughout our infrastructure. We&rsquo;ve built a custom, multi-layered protection infrastructure tailored to GitHub&rsquo;s unique operational requirements and scale, building upon the flexibility and extensibility of open-source projects like HAProxy. Here&rsquo;s a simplified view of how requests flow through these defense layers <em>(simplified to avoid disclosing specific defense mechanisms and to keep the concepts broadly applicable)</em>:</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img data-recalc-dims="1" loading="lazy" decoding="async" height="1024" width="923" src="https://github.blog/wp-content/uploads/2026/01/5.png?resize=923%2C1024" alt="Diagram showing user requests flowing through multiple infrastructure layers (Edge, Application, Service, Backend), with protection mechanisms at each layer including DDoS protection, rate limits, authentication, and access controls." class="wp-image-93315" style="width:500px" srcset="https://github.blog/wp-content/uploads/2026/01/5.png?w=1309 1309w, https://github.blog/wp-content/uploads/2026/01/5.png?w=270 270w, https://github.blog/wp-content/uploads/2026/01/5.png?w=768 768w, https://github.blog/wp-content/uploads/2026/01/5.png?w=923 923w" sizes="auto, (max-width: 923px) 100vw, 923px" /></figure>



<p class="wp-block-paragraph">Each layer has legitimate reasons to rate-limit or block requests. During an incident, a protection might be added at any of these layers depending on where the abuse is best mitigated and what controls are fastest to deploy.</p>



<p class="wp-block-paragraph">The challenge: When a request gets blocked, tracing which layer made that decision requires correlating logs across multiple systems, each with different schemas.</p>



<p class="wp-block-paragraph">In this case, we started with user reports and worked backward:</p>



<ol class="wp-block-list">
<li><strong>User reports</strong> provided timestamps and approximate behavior patterns.</li>



<li><strong>Edge tier logs</strong> showed the requests reaching our infrastructure.</li>



<li><strong>Application tier logs</strong> revealed 429 &ldquo;Too Many Requests&rdquo; responses.</li>



<li><strong>Protection rule analysis</strong> ultimately identified which rules matched these requests.</li>
</ol>



<p class="wp-block-paragraph">The investigation took us from external reports to distributed logs to rule configurations, demonstrating that maintaining comprehensive visibility into what&rsquo;s actually blocking requests and where is essential.</p>



<h3 class="wp-block-heading" id="the-lifecycle-of-incident-mitigations">The lifecycle of incident mitigations</h3>



<p class="wp-block-paragraph">Here&rsquo;s how these protections outlived their purpose:</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="115" width="1024" src="https://github.blog/wp-content/uploads/2026/01/6.png?resize=1024%2C115" alt="Diagram showing incident mitigation lifecycle: control added during incident, works initially, remains active over time without review, eventually blocks legitimate traffic." class="wp-image-93316" srcset="https://github.blog/wp-content/uploads/2026/01/6.png?w=1600 1600w, https://github.blog/wp-content/uploads/2026/01/6.png?w=300 300w, https://github.blog/wp-content/uploads/2026/01/6.png?w=768 768w, https://github.blog/wp-content/uploads/2026/01/6.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/01/6.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></figure>



<p class="wp-block-paragraph">Each mitigation was necessary when added. But the controls where we didn&rsquo;t consistently apply lifecycle management (setting expiration dates, conducting post-incident rule reviews, or monitoring impact) became technical debt that accumulated until users noticed.</p>



<h2 class="wp-block-heading" id="what-we-did">What we did</h2>



<p class="wp-block-paragraph">We reviewed these mitigations, analyzing what each one was blocking today versus what it was meant to block when created. We removed the rules that were no longer serving their purpose, and kept protections against ongoing threats.</p>



<h2 class="wp-block-heading" id="what-were-building">What we&rsquo;re building</h2>



<p class="wp-block-paragraph">Beyond the immediate fix, we&rsquo;re improving the lifecycle management of protective controls:</p>



<ul class="wp-block-list">
<li>Better visibility across all protection layers to trace the source of rate limits and blocks.</li>



<li>Treating incident mitigations as temporary by default. Making them permanent should require an intentional, documented decision.</li>



<li>Post-incident practices that evaluate emergency controls and evolve them into sustainable, targeted solutions.</li>
</ul>



<p class="wp-block-paragraph">Defense mechanisms &ndash; even those deployed quickly during incidents &ndash; need the same care as the systems they protect. They need observability, documentation, and active maintenance. When protections are added during incidents and left in place, they become technical debt that quietly accumulates.</p>



<p class="wp-block-paragraph">Thanks to everyone who reported issues publicly! <strong>Your feedback directly led to these improvements.</strong> And thanks to the teams across GitHub who worked on the investigation and are building better lifecycle management into how we operate. Our platform, team, and community are better together!</p>
</body></html>
<p>The post <a href="https://github.blog/engineering/infrastructure/when-protections-outlive-their-purpose-a-lesson-on-managing-defense-systems-at-scale/">When protections outlive their purpose: A lesson on managing defense systems at scale</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">93309</post-id>	</item>
		<item>
		<title>How GitHub engineers tackle platform problems</title>
		<link>https://github.blog/engineering/infrastructure/how-github-engineers-tackle-platform-problems/</link>
		
		<dc:creator><![CDATA[Fabian Aguilar Gomez]]></dc:creator>
		<pubDate>Tue, 10 Jun 2025 16:00:00 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<guid isPermaLink="false">https://github.blog/?p=88639</guid>

					<description><![CDATA[<p>Our best practices for quickly identifying, resolving, and preventing issues at scale.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-engineers-tackle-platform-problems/">How GitHub engineers tackle platform problems</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p class="wp-block-paragraph">In my spare time I enjoy building <a href="https://en.wikipedia.org/wiki/Gundam">Gundam</a> models, which are model kits to build iconic mechas from the Gundam universe. You might be wondering what this has to do with software engineering. Product engineers can be seen as the engineers who take these kits and build the Gundam itself. They are able to utilize all pieces and build a working product that is fun to collect or even play with!</p>



<p class="wp-block-paragraph">Platform engineers, on the other hand, supply the tools needed to build these kits (like clippers and files) and maybe even build a cool display so everyone can see the final product. They ensure that whoever is constructing it has all the necessary tools, even if they don&rsquo;t physically build the Gundam themselves.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="840" width="1024" src="https://github.blog/wp-content/uploads/2025/06/gundam.jpg?resize=1024%2C840" alt="A photograph of several Gundam models on a shelf." class="wp-image-88641" srcset="https://github.blog/wp-content/uploads/2025/06/gundam.jpg?w=1600 1600w, https://github.blog/wp-content/uploads/2025/06/gundam.jpg?w=300 300w, https://github.blog/wp-content/uploads/2025/06/gundam.jpg?w=768 768w, https://github.blog/wp-content/uploads/2025/06/gundam.jpg?w=1024 1024w, https://github.blog/wp-content/uploads/2025/06/gundam.jpg?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></figure>



<p class="wp-block-paragraph">About a year ago, my team at GitHub moved to the infrastructure organization, inheriting new roles and Areas of Responsibility (AoRs). Previously, the team had tackled external customer problems, such as building the new <a href="https://github.blog/changelog/2024-04-25-github-actions-deployment-views-across-environments-ga/">deployment views across environments</a>. This involved interacting with users who depend on GitHub to address challenges within their respective industries. Our new customers as a platform engineering team are <em>internal</em>, which makes our responsibilities different from the product-focused engineering work we were doing before.</p>



<p class="wp-block-paragraph">Going back to my Gundam example, rather than constructing kits, we&rsquo;re now responsible for building the components of the kits. Adapting to this change meant I had to rethink my approach to code testing and problem solving.</p>



<p class="wp-block-paragraph">Whether you&rsquo;re working on product engineering or on the platform side, here are a few best practices to tackle platform problems.</p>



<h2 class="wp-block-heading" id="h-understanding-your-domain">Understanding your domain</h2>



<p class="wp-block-paragraph">One of the most critical steps before tackling problems is understanding the domain. A &ldquo;domain&rdquo; is the business and technical subject area in which a team and platform organization operate. This requires gaining an understanding of technical terms and how these systems interact to provide fast and reliable solutions. Here&rsquo;s how to get up to speed:&nbsp;</p>



<ul class="wp-block-list">
<li><strong>Talk to your neighbors: </strong>Arrange a handover meeting with a team that has more knowledge and experience with the subject matter. This meeting provides an opportunity to ask questions about terminology and gain a deeper understanding of the problems the team will be addressing.&nbsp;</li>



<li><strong>Investigate old issues: </strong>If there is a backlog of issues that are either stale or still persistent, they may give you a better understanding of the system&rsquo;s current limitations and potential areas for improvement.</li>



<li><strong>Read the docs: </strong>Documentation is a goldmine of knowledge that can help you understand how the system works.&nbsp;</li>
</ul>



<h2 class="wp-block-heading" id="h-bridging-concepts-to-platform-specific-skills">Bridging concepts to platform-specific skills</h2>



<p class="wp-block-paragraph">While the preceding advice offers general guidance applicable to both product and platform teams, platform teams &mdash; serving as the foundational layer &mdash; necessitate a more in-depth understanding.</p>



<ul class="wp-block-list">
<li><strong>Networks</strong>: Understanding network fundamentals is crucial for all engineers, even those not directly involved in network operations. This includes concepts like TCP, UDP, and L4 load balancing, as well as debugging tools such as dig. A solid grasp of these areas is essential to comprehend how network traffic impacts your platform.</li>



<li><strong>Operating systems and hardware:</strong> Selecting appropriate virtual machines (VMs) or physical hardware is vital for both scalability and cost management. Making well-informed choices for particular applications requires a strong grasp of both. This is closely linked to choosing the right operating system for your machines, which is important to avoid systems with vulnerabilities or those nearing end of life.</li>



<li><strong>Infrastructure as Code (IaC): </strong>Automation tools like Terraform, Ansible, and Consul are becoming increasingly essential. Proficiency in these tools is becoming a necessity as they significantly decrease human error during infrastructure provisioning and modifications.&nbsp;</li>



<li><strong>Distributed systems</strong>: Dealing with platform issues, particularly in distributed systems, necessitates a deep understanding that failures are inevitable. Consequently, employing proactive solutions like failover and recovery mechanisms is crucial for preserving system reliability and preventing adverse user experiences. The optimal approach for this depends entirely on the specific problem and the desired system behavior.</li>
</ul>



<h2 class="wp-block-heading" id="h-knowledge-sharing">Knowledge sharing</h2>



<p class="wp-block-paragraph">By sharing lessons and ideas, engineers can introduce new perspectives that lead to breakthroughs and innovations. Taking the time to understand why a project or solution did or didn&rsquo;t work and sharing those findings provides new perspectives that we can use going forward.</p>



<p class="wp-block-paragraph">Here are three reasons why knowledge sharing is so important:&nbsp;</p>



<ul class="wp-block-list">
<li><strong>Teamwork makes the dream work: </strong>Collaboration often results in quicker problem resolution and fosters new solution innovation, as engineers have the opportunity to learn from each other and expand upon existing ideas.</li>



<li><strong>Prevent lost knowledge</strong>: If we don&rsquo;t share our lessons learned, we prevent the information from being disseminated across the team or organization. This becomes a problem if an engineer leaves the company or is simply unavailable.</li>



<li><strong>Improve our customer success: </strong>As engineers, our solutions should effectively serve our customers. By sharing our knowledge and lessons learned, we can help the team build reliable, scalable, and secure platforms, which will enable us to create better products that meet customer needs and expectations!</li>
</ul>



<p class="wp-block-paragraph">But big differences start to appear between product engineering and infrastructure engineering when it comes to the impact radius and the testing process.</p>



<h2 class="wp-block-heading" id="h-impact-radius">Impact radius</h2>



<p class="wp-block-paragraph">With platforms being the fundamental building blocks of a system, any change (small or large) can affect a wide range of products. Our team is responsible for DNS, a foundational service that impacts numerous products. Even a minor alteration to this service can have extensive repercussions, potentially disrupting access to content across our site and affecting products ranging from GitHub Pages to GitHub Copilot.&nbsp;</p>



<ul class="wp-block-list">
<li><strong>Understand the radius: </strong>Or understand the downstream dependencies. Direct communication with teams that depend on our service provides valuable insights into how proposed changes may affect other services.</li>



<li><strong>Postmortems: </strong>By looking at past incidents related to our platform and asking &ldquo;<em>What is the impact of this incident?&rdquo;, </em>we can form more context around what change or failure was introduced, how our platform played a role in it, and how it was fixed.</li>



<li><strong>Monitoring and telemetry: </strong>Condense important monitoring and logging into a small and quickly digestible medium to give you the general health of the system. This could be a Single Availability Metric (SAM), for example. The ability to quickly glance at a single dashboard allows engineers to rapidly pinpoint the source of an issue and streamlines the debugging and incident mitigation process, as compared to searching through and interpreting detailed monitors or log messages.</li>
</ul>



<h2 class="wp-block-heading" id="h-testing-changes">Testing changes</h2>



<p class="wp-block-paragraph">Testing changes in a distributed environment can be challenging, especially for services like DNS. A crucial step in solving this issue is utilizing a test site as a &ldquo;real&rdquo; machine where you can implement and assess all your changes.&nbsp;</p>



<ul class="wp-block-list">
<li><strong>Infrastructure as Code (IaC): </strong>When using tools like Terraform or Ansible, it&rsquo;s crucial to test fundamental operations like provisioning and deprovisioning machines. There are circumstances where a machine will need to be re-provisioned. In these cases, we want to ensure the machine is not accidentally deleted and that we retain the ability to create a new one if needed.</li>



<li><strong>End-to-End (E2E): </strong>Begin directing some network traffic to these servers. Then the team can observe host behavior by directly interacting with it, or we can evaluate functionality by diverting a small portion of traffic.</li>



<li><strong>Self-healing</strong>: We want to test the platform&rsquo;s ability to recover from unexpected loads and identify bottlenecks before they impact our users. Early identification of bottlenecks or bugs is crucial for maintaining the health of our platform.</li>
</ul>



<p class="wp-block-paragraph">Ideally changes will be implemented on a host-by-host basis once testing is complete. This approach allows for individual machine rollback and prevents changes from being applied to unaffected hosts.</p>



<h2 class="wp-block-heading" id="h-what-to-remember">What to remember</h2>



<p class="wp-block-paragraph">Platform engineering can be difficult. The systems GitHub operates with are complex and there are a lot of services and moving parts. However, there&rsquo;s nothing like seeing everything come together. All the hard work our engineering teams do behind the scenes really pays off when the platform is running smoothly and teams are able to ship faster and more reliably &mdash; which allows GitHub to be the home to all developers.</p>



<div class="wp-block-group post-content-cta has-global-padding is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph"><strong>Want to dive deeper?</strong> Check out our <a href="https://github.blog/engineering/infrastructure/">infrastructure related blog posts</a>.</p>
</div>
</body></html>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-engineers-tackle-platform-problems/">How GitHub engineers tackle platform problems</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">88639</post-id>	</item>
		<item>
		<title>How GitHub reduced testing time for iOS apps with new runner features</title>
		<link>https://github.blog/engineering/infrastructure/how-github-reduced-testing-time-for-ios-apps-with-new-runner-features/</link>
		
		<dc:creator><![CDATA[Stephen Glass]]></dc:creator>
		<pubDate>Mon, 03 Jun 2024 16:45:06 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[GitHub Actions]]></category>
		<category><![CDATA[GitHub Mobile]]></category>
		<category><![CDATA[How GitHub builds GitHub]]></category>
		<guid isPermaLink="false">https://github.blog/?p=78252</guid>

					<description><![CDATA[<p>Learn how GitHub used macOS and Apple Silicon runners for GitHub Actions to build, test, and deploy our iOS app faster.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-reduced-testing-time-for-ios-apps-with-new-runner-features/">How GitHub reduced testing time for iOS apps with new runner features</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"><br />
<html><body></p>
<h2 id="github-actions-%f0%9f%a4%9d-github-for-ios" id="github-actions-%f0%9f%a4%9d-github-for-ios" ><a class="heading-link" href="#github-actions-%f0%9f%a4%9d-github-for-ios">GitHub Actions &#129309; GitHub for iOS<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>The GitHub iOS and GitHub Actions macOS runner teams are integral parts of each other&rsquo;s development inner loop. Each team partners on testing new runner images and hardware long before the features land in the hands of developers. GitHub Actions has been working hard at bringing the latest Mac hardware to the community. Apple silicon (M1) macOS runners are available for free in public repositories, along with larger options available for those jobs that need more performance.</p>
<div class="mod-vh position-relative" style="height: 0; padding-bottom: calc((9 / 16)*100%);">
			<iframe loading="lazy" class="position-absolute top-0 left-0 width-full height-full" src="https://www.youtube.com/embed/I2J2MzKjcqY?version=3&amp;rel=1&amp;showsearch=0&amp;showinfo=1&amp;iv_load_policy=1&amp;fs=1&amp;hl=en-US&amp;autohide=2&amp;wmode=transparent" title="YouTube video player" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0"></iframe>
		</div>
<p>The GitHub iOS team has been busy improving the user experience in the app, recently shipping such as <a href="https://github.blog/2024-05-07-github-copilot-chat-in-github-mobile/">GitHub Copilot Chat</a>, code search, localization for German and Korean, and making it easier to work with issues and projects. In this blog, we will discuss how the GitHub iOS team brings the app to developers around the world, the benefits of Apple silicon, and building on GitHub Actions using macOS runners.</p>
<h2 id="how-github-reduced-testing-time-for-ios-apps-with-new-runner-features" id="how-github-reduced-testing-time-for-ios-apps-with-new-runner-features" ><a class="heading-link" href="#how-github-reduced-testing-time-for-ios-apps-with-new-runner-features">How GitHub reduced testing time for iOS apps with new runner features<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>The GitHub iOS team previously used a single workflow with one job to build and test the entire codebase on GitHub Actions that took 38 minutes to complete with the prior generation runners. The GitHub iOS app consists of about 60 first-party modules, consisting of various targets, such as dynamic frameworks, static libraries, app extensions, or the GitHub app itself. These modules range from networking layers to design system components to entire features or products, helping us maintain the app.</p>
<h3 id="breaking-down-the-monolith" id="breaking-down-the-monolith" ><a class="heading-link" href="#breaking-down-the-monolith">Breaking down the monolith<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We decided to leverage the power of Apple silicon to speed up their testing process. We switched to M1 macOS runners (macos-14-xlarge YAML label) on GitHub Actions and split their test suite into separate jobs for each module. This way, they could build and test each module independently and get faster feedback. Some of the smallest modules completed their tests in <strong>as little as 2-3 minutes</strong> on M1 macOS runners, getting feedback to developers on their pull requests faster than ever before. This also made it easier to identify and fix failures on specific modules without waiting for a monolithic build to finish.</p>
<p>By using Apple silicon, we <strong>reduced their testing time by 60%, from 38 minutes to 15 minutes,</strong> and improved our productivity and efficiency. The figure below demonstrates how we broke down the monolith into small modules in order to improve our build times.</p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2024/05/image2_1be7aa.png?w=1024&#038;resize=1024%2C662" alt="Image demonstrates the monolith build on tip with the total CI time. The Image below it demonstrates how per-module builds are crafted and the reduction in CI time with the new approach." width="1024" height="662" class="aligncenter size-large wp-image-78253 width-fit" srcset="https://github.blog/wp-content/uploads/2024/05/image2_1be7aa.png?w=1999 1999w, https://github.blog/wp-content/uploads/2024/05/image2_1be7aa.png?w=300 300w, https://github.blog/wp-content/uploads/2024/05/image2_1be7aa.png?w=768 768w, https://github.blog/wp-content/uploads/2024/05/image2_1be7aa.png?w=1024 1024w, https://github.blog/wp-content/uploads/2024/05/image2_1be7aa.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<p>As each build is kicked off, GitHub Actions is behind the scenes preparing the required number of machines to execute the workflow. Each request is sent to the GitHub Actions service where it picks up a freshly reimaged virtual machine to execute the required number of jobs. The figure below shows how a request travels from our repository to the Actions Mac servers in Azure.</p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2024/05/image1.png?w=1024&#038;resize=1024%2C408" alt="Image displays the relationship between the request for workflow to run and how a machine is assigned to a job. From left to right, the flow starts at GitHub.com, then the request is sent to Actions. Actions then finds the available macOS VM to execute the workflow." width="1024" height="408" class="aligncenter size-large wp-image-78254 width-fit" srcset="https://github.blog/wp-content/uploads/2024/05/image1.png?w=1191 1191w, https://github.blog/wp-content/uploads/2024/05/image1.png?w=300 300w, https://github.blog/wp-content/uploads/2024/05/image1.png?w=768 768w, https://github.blog/wp-content/uploads/2024/05/image1.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<p>With shorter build times and a scaling CI fleet, Apple silicon hosts allowed the GitHub iOS team to scale their jobs out across many shorter, faster steps, with GitHub Actions abstracting over the complexity of distributing CI jobs.</p>
<h3 id="analyzing-ci-performance" id="analyzing-ci-performance" ><a class="heading-link" href="#analyzing-ci-performance">Analyzing CI performance<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We further investigated the CI performance and divided each module&rsquo;s CI into two separate steps, build and test, using xcodebuild&rsquo;s build-without-testing and test-without-building. This helped us identify unit tests that ran for a long time or highlighted fast unit tests that finished in seconds.</p>
<h3 id="native-development-and-test-environments" id="native-development-and-test-environments" ><a class="heading-link" href="#native-development-and-test-environments">Native development and test environments<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>With Apple silicon powering GitHub Actions runners and the developers&rsquo; laptops, our CI now had the same architecture as local development machines. Engineers could identify patterns that took a long time to compile or tests that failed due to the architecture from CI and fix them locally with confidence.</p>
<h2 id="benefits-of-apple-silicon" id="benefits-of-apple-silicon" ><a class="heading-link" href="#benefits-of-apple-silicon">Benefits of Apple silicon<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Apple silicon improves build performance, increases reliability, and lets iOS teams test natively for all Apple platforms throughout the software development lifecycle. They can avoid problems from cross-compilation or emulation and use the latest simulators on our GitHub Actions runner image. This ensures that their apps work well with the newest versions of iOS, iPadOS, watchOS, and tvOS. Our GitHub Actions M1 macOS runners help iOS teams leverage these benefits and deliver high-quality apps to their users faster and more efficiently. Additionally, GitHub Actions offers 50 concurrent runners for enterprise accounts and five for GitHub Free and Team plans. The GitHub for iOS team takes full advantage of these concurrent runners and initiates 50 jobs for every pull request to perform modular testing on the app in parallel.</p>
<h2 id="get-started-building-on-github-actions-using-macos-runners" id="get-started-building-on-github-actions-using-macos-runners" ><a class="heading-link" href="#get-started-building-on-github-actions-using-macos-runners">Get started building on GitHub Actions using macOS runners<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>GitHub-hosted macOS runners are YAML-driven, meaning they are accessed by updating the <code>runs on:</code> key in your workflow file.</p>
<ul>
<li><a href="https://docs.github.com/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories">Standard GitHub-hosted runners for Public repositories</a> </li>
<li><a href="https://docs.github.com/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-private-repositories">Standard GitHub-hosted runners for Private repositories</a> </li>
<li><a href="https://docs.github.com/actions/using-github-hosted-runners/about-larger-runners/about-larger-runners#about-macos-larger-runners">macOS larger runners</a> </li>
</ul>
<p></body></html></p>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-reduced-testing-time-for-ios-apps-with-new-runner-features/">How GitHub reduced testing time for iOS apps with new runner features</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">78252</post-id>	</item>
		<item>
		<title>Upgrading GitHub.com to MySQL 8.0</title>
		<link>https://github.blog/engineering/infrastructure/upgrading-github-com-to-mysql-8-0/</link>
		
		<dc:creator><![CDATA[Jiaqi Liu]]></dc:creator>
		<pubDate>Thu, 07 Dec 2023 22:00:36 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[availability]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">https://github.blog/?p=75736</guid>

					<description><![CDATA[<p>GitHub uses MySQL to store vast amounts of relational data. This is the story of how we seamlessly upgraded our production fleet to MySQL 8.0.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/upgrading-github-com-to-mysql-8-0/">Upgrading GitHub.com to MySQL 8.0</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"><br />
<html><body></p>
<p>Over 15 years ago, GitHub started as a Ruby on Rails application with a single MySQL database. Since then, GitHub has evolved its MySQL architecture to meet the scaling and resiliency needs of the platform&mdash;including <a href="https://github.blog/2018-06-20-mysql-high-availability-at-github/">building for high availability</a>, <a href="https://github.blog/2017-07-06-mysql-testing-automation-at-github/">implementing testing automation</a>, and <a href="https://github.blog/2021-09-27-partitioning-githubs-relational-databases-scale/">partitioning the data</a>. Today, MySQL remains a core part of GitHub&rsquo;s infrastructure and our relational database of choice.</p>
<p>This is the story of how we upgraded our fleet of 1200+ MySQL hosts to 8.0. Upgrading the fleet with no impact to our Service Level Objectives (SLO) was no small feat&ndash;planning, testing and the upgrade itself took over a year and collaboration across multiple teams within GitHub.</p>
<h2 id="motivation-for-upgrading" id="motivation-for-upgrading" ><a class="heading-link" href="#motivation-for-upgrading">Motivation for upgrading<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Why upgrade to MySQL 8.0? With <a href="https://dev.mysql.com/doc/refman/8.0/en/faqs-general.html">MySQL 5.7 nearing end of life</a>, we upgraded our fleet to the next major version, MySQL 8.0. We also wanted to be on a version of MySQL that gets the latest security patches, bug fixes, and performance enhancements. There are also new features in 8.0 that we want to test and benefit from, including Instant DDLs, invisible indexes, and compressed bin logs, among others.</p>
<h2 id="githubs-mysql-infrastructure" id="githubs-mysql-infrastructure" ><a class="heading-link" href="#githubs-mysql-infrastructure">GitHub&rsquo;s MySQL infrastructure<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Before we dive into how we did the upgrade, let&rsquo;s take a 10,000-foot view of our MySQL infrastructure:</p>
<ul>
<li>Our fleet consists of 1200+ hosts. It&rsquo;s a combination of Azure Virtual Machines and bare metal hosts in our data center.</li>
<li>We store 300+ TB of data and serve 5.5 million queries per second across 50+ database clusters.</li>
<li>Each cluster is <a href="https://github.blog/2018-06-20-mysql-high-availability-at-github/">configured for high availability</a> with a primary plus replicas cluster setup.</li>
<li>Our data is partitioned. We leverage both horizontal and vertical sharding to scale our MySQL clusters. We have MySQL clusters that store data for specific product-domain areas. We also have horizontally sharded <a href="https://vitess.io/">Vitess</a> clusters for large-domain areas that outgrew the single-primary MySQL cluster.</li>
<li>We have a large ecosystem of tools consisting of Percona Toolkit, <a href="https://github.com/github/gh-ost">gh-ost</a>, <a href="https://github.com/openark/orchestrator">orchestrator</a>,<a href="https://github.com/github/freno"> freno</a>, and in-house automation used to operate the fleet.</li>
</ul>
<p>All this sums up to a diverse and complex deployment that needs to be upgraded while maintaining our SLOs.</p>
<h2 id="preparing-the-journey" id="preparing-the-journey" ><a class="heading-link" href="#preparing-the-journey">Preparing the journey<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>As the primary data store for GitHub, we hold ourselves to a high standard for availability. Due to the size of our fleet and the criticality of MySQL infrastructure, we had a few requirements for the upgrade process:</p>
<ul>
<li>We must be able to upgrade each MySQL database while adhering to our Service Level Objectives (SLOs) and Service Level Agreements (SLAs).</li>
<li>We are unable to account for all failure modes in our testing and validation stages. So, in order to remain within SLO, we needed to be able to roll back to the prior version of MySQL 5.7 without a disruption of service.</li>
<li>We have a very diverse workload across our MySQL fleet. To reduce risk, we needed to upgrade each database cluster atomically and schedule around other major changes. This meant the upgrade process would be a long one. Therefore, we knew from the start we needed to be able to sustain operating a mixed-version environment.</li>
</ul>
<p>Preparation for the upgrade started in July 2022 and we had several milestones to reach even before upgrading a single production database.</p>
<h3 id="prepare-infrastructure-for-upgrade" id="prepare-infrastructure-for-upgrade" ><a class="heading-link" href="#prepare-infrastructure-for-upgrade">Prepare infrastructure for upgrade<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We needed to determine appropriate default values for MySQL 8.0 and perform some baseline performance benchmarking. Since we needed to operate two versions of MySQL, our tooling and automation needed to be able to handle mixed versions and be aware of new, different, or deprecated syntax between 5.7 and 8.0.</p>
<h3 id="ensure-application-compatibility" id="ensure-application-compatibility" ><a class="heading-link" href="#ensure-application-compatibility">Ensure application compatibility<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We added MySQL 8.0 to Continuous Integration (CI) for all applications using MySQL. We ran MySQL 5.7 and 8.0 side-by-side in CI to ensure that there wouldn&rsquo;t be regressions during the prolonged upgrade process. We detected a variety of bugs and incompatibilities in CI, helping us remove any unsupported configurations or features and escape any new reserved keywords.</p>
<p>To help application developers transition towards MySQL 8.0, we also enabled an option to select a MySQL 8.0 prebuilt container in GitHub Codespaces for debugging and provided MySQL 8.0 development clusters for additional pre-prod testing.</p>
<h3 id="communication-and-transparency" id="communication-and-transparency" ><a class="heading-link" href="#communication-and-transparency">Communication and transparency<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We used GitHub Projects to create a rolling calendar to communicate and track our upgrade schedule internally. We created issue templates that tracked the checklist for both application teams and the database team to coordinate an upgrade.</p>
<p><figure id="attachment_75739"  class="wp-caption aligncenter mx-0"><img data-recalc-dims="1" decoding="async" class="width-fit size-large wp-image-75739 width-fit" src="https://github.blog/wp-content/uploads/2023/12/image2-1.png?w=1024&#038;h=533" alt="Project Board for tracking the MySQL 8.0 upgrade schedule"  srcset="https://github.blog/wp-content/uploads/2023/12/image2-1.png?w=1797 1797w, https://github.blog/wp-content/uploads/2023/12/image2-1.png?w=300 300w, https://github.blog/wp-content/uploads/2023/12/image2-1.png?w=768 768w, https://github.blog/wp-content/uploads/2023/12/image2-1.png?w=1024 1024w, https://github.blog/wp-content/uploads/2023/12/image2-1.png?w=1536 1536w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="text-mono color-fg-muted mt-14px f5-mktg">Project Board for tracking the MySQL 8.0 upgrade schedule</figcaption></figure></p>
<h2 id="upgrade-plan" id="upgrade-plan" ><a class="heading-link" href="#upgrade-plan">Upgrade plan<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>To meet our availability standards, we had a gradual upgrade strategy that allowed for checkpoints and rollbacks throughout the process.</p>
<h3 id="step-1-rolling-replica-upgrades" id="step-1-rolling-replica-upgrades" ><a class="heading-link" href="#step-1-rolling-replica-upgrades">Step 1: Rolling replica upgrades<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We started with upgrading a single replica and monitoring while it was still offline to ensure basic functionality was stable. Then, we enabled production traffic and continued to monitor for query latency, system metrics, and application metrics. We gradually brought 8.0 replicas online until we upgraded an entire data center and then iterated through other data centers. We left enough 5.7 replicas online in order to rollback, but we disabled production traffic to start serving all read traffic through 8.0 servers.</p>
<p><figure id="attachment_75740"  class="wp-caption aligncenter mx-0"><img data-recalc-dims="1" decoding="async" class="width-fit size-large wp-image-75740 width-fit" src="https://github.blog/wp-content/uploads/2023/12/image4.png?w=1024&#038;h=509" alt="The replica upgrade strategy involved gradual rollouts in each data center (DC)."  srcset="https://github.blog/wp-content/uploads/2023/12/image4.png?w=1972 1972w, https://github.blog/wp-content/uploads/2023/12/image4.png?w=300 300w, https://github.blog/wp-content/uploads/2023/12/image4.png?w=768 768w, https://github.blog/wp-content/uploads/2023/12/image4.png?w=1024 1024w, https://github.blog/wp-content/uploads/2023/12/image4.png?w=1536 1536w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="text-mono color-fg-muted mt-14px f5-mktg">The replica upgrade strategy involved gradual rollouts in each data center (DC).</figcaption></figure></p>
<h3 id="step-2-update-replication-topology" id="step-2-update-replication-topology" ><a class="heading-link" href="#step-2-update-replication-topology">Step 2: Update replication topology<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>Once all the read-only traffic was being served via 8.0 replicas, we adjusted the replication topology as follows:</p>
<ul>
<li>An 8.0 primary candidate was configured to replicate directly under the current 5.7 primary.</li>
<li>Two replication chains were created downstream of that 8.0 replica:</li>
<li>A set of only 5.7 replicas (not serving traffic, but ready in case of rollback).</li>
<li>A set of only 8.0 replicas (serving traffic).</li>
<li>The topology was only in this state for a short period of time (hours at most) until we moved to the next step.</li>
</ul>
<p><figure id="attachment_75742"  class="wp-caption aligncenter mx-0"><img data-recalc-dims="1" decoding="async" class="width-fit size-large wp-image-75742 width-fit" src="https://github.blog/wp-content/uploads/2023/12/image3-1.png?w=928&#038;h=752" alt="To facilitate the upgrade, the topology was updated to have two replication chains."  srcset="https://github.blog/wp-content/uploads/2023/12/image3-1.png?w=928 928w, https://github.blog/wp-content/uploads/2023/12/image3-1.png?w=300 300w, https://github.blog/wp-content/uploads/2023/12/image3-1.png?w=768 768w" sizes="(max-width: 928px) 100vw, 928px" /><figcaption class="text-mono color-fg-muted mt-14px f5-mktg">To facilitate the upgrade, the topology was updated to have two replication chains.</figcaption></figure></p>
<h3 id="step-3-promote-mysql-8-0-host-to-primary" id="step-3-promote-mysql-8-0-host-to-primary" ><a class="heading-link" href="#step-3-promote-mysql-8-0-host-to-primary">Step 3: Promote MySQL 8.0 host to primary<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We opted not to do direct upgrades on the primary database host. Instead, we would promote a MySQL 8.0 replica to primary through a graceful failover performed with <a href="https://github.com/openark/orchestrator">Orchestrator</a>. At that point, the replication topology consisted of an 8.0 primary with two replication chains attached to it: an offline set of 5.7 replicas in case of rollback and a serving set of 8.0 replicas.</p>
<p>Orchestrator was also configured to blacklist 5.7 hosts as potential failover candidates to prevent an accidental rollback in case of an unplanned failover.</p>
<p><figure id="attachment_75743"  class="wp-caption aligncenter mx-0"><img data-recalc-dims="1" decoding="async" class="width-fit size-large wp-image-75743 width-fit" src="https://github.blog/wp-content/uploads/2023/12/image1-1.png?w=1024&#038;h=465" alt="Primary failover and additional steps to finalize MySQL 8.0 upgrade for a database"  srcset="https://github.blog/wp-content/uploads/2023/12/image1-1.png?w=1982 1982w, https://github.blog/wp-content/uploads/2023/12/image1-1.png?w=300 300w, https://github.blog/wp-content/uploads/2023/12/image1-1.png?w=768 768w, https://github.blog/wp-content/uploads/2023/12/image1-1.png?w=1024 1024w, https://github.blog/wp-content/uploads/2023/12/image1-1.png?w=1536 1536w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="text-mono color-fg-muted mt-14px f5-mktg">Primary failover and additional steps to finalize MySQL 8.0 upgrade for a database</figcaption></figure></p>
<h3 id="step-4-internal-facing-instance-types-upgraded" id="step-4-internal-facing-instance-types-upgraded" ><a class="heading-link" href="#step-4-internal-facing-instance-types-upgraded">Step 4: Internal facing instance types upgraded<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We also have ancillary servers for backups or non-production workloads. Those were subsequently upgraded for consistency.</p>
<h3 id="step-5-cleanup" id="step-5-cleanup" ><a class="heading-link" href="#step-5-cleanup">Step 5: Cleanup<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>Once we confirmed that the cluster didn&rsquo;t need to rollback and was successfully upgraded to 8.0, we removed the 5.7 servers. Validation consisted of at least one complete 24 hour traffic cycle to ensure there were no issues during peak traffic.</p>
<h2 id="ability-to-rollback" id="ability-to-rollback" ><a class="heading-link" href="#ability-to-rollback">Ability to Rollback<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>A core part of keeping our upgrade strategy safe was maintaining the ability to rollback to the prior version of MySQL 5.7. For read-replicas, we ensured enough 5.7 replicas remained online to serve production traffic load, and rollback was initiated by disabling the 8.0 replicas if they weren&rsquo;t performing well. For the primary, in order to roll back without data loss or service disruption, we needed to be able to maintain backwards data replication between 8.0 and 5.7.</p>
<p>MySQL supports replication from one release to the next higher release but does not explicitly support the reverse (<a href="https://dev.mysql.com/doc/refman/8.0/en/replication-compatibility.html">MySQL Replication compatibility</a>). When we tested promoting an 8.0 host to primary on our staging cluster, we saw replication break on all 5.7 replicas. There were a couple of problems we needed to overcome:</p>
<ol>
<li>In MySQL 8.0, <code>utf8mb4</code> is the default character set and uses a more modern <code>utf8mb4_0900_ai_ci</code> collation as the default. The prior version of MySQL 5.7 supported the <code>utf8mb4_unicode_520_ci</code> collation but not the latest version of Unicode <code>utf8mb4_0900_ai_ci</code>.</li>
<li>MySQL 8.0<a href="https://dev.mysql.com/doc/refman/8.0/en/roles.html"> introduces roles</a> for managing privileges but this feature did not exist in MySQL 5.7. When an 8.0 instance was promoted to be a primary in a cluster, we encountered problems. Our configuration management was expanding certain permission sets to include role statements and executing them, which broke downstream replication in 5.7 replicas. We solved this problem by temporarily adjusting defined permissions for affected users during the upgrade window.</li>
</ol>
<p>To address the character collation incompatibility, we had to set the default character encoding to <code>utf8</code> and collation to <code>utf8_unicode_ci</code>.</p>
<p>For the GitHub.com monolith, our Rails configuration ensured that character collation was consistent and made it easier to standardize client configurations to the database. As a result, we had high confidence that we could maintain backward replication for our most critical applications.</p>
<h2 id="challenges" id="challenges" ><a class="heading-link" href="#challenges">Challenges<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Throughout our testing, preparation and upgrades, we encountered some technical challenges.</p>
<h3 id="what-about-vitess" id="what-about-vitess" ><a class="heading-link" href="#what-about-vitess">What about Vitess?<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We use Vitess for horizontally sharding relational data. For the most part, upgrading our Vitess clusters was not too different from upgrading the MySQL clusters. We were already running Vitess in CI, so we were able to validate query compatibility. In our upgrade strategy for sharded clusters, we upgraded one shard at a time. VTgate, the Vitess proxy layer, advertises the version of MySQL and some client behavior depends on this version information. For example, one application used a Java client that disabled the query cache for 5.7 servers&mdash;since the query cache was removed in 8.0, it generated blocking errors for them. So, once a single MySQL host was upgraded for a given keyspace, we had to make sure we also updated the VTgate setting to advertise 8.0.</p>
<h3 id="replication-delay" id="replication-delay" ><a class="heading-link" href="#replication-delay">Replication delay<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We use read-replicas to scale our read availability. GitHub.com requires low replication delay in order to serve up-to-date data.</p>
<p>Earlier on in our testing, we encountered a replication bug in MySQL that was <a href="https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-28.html#mysqld-8-0-28-bug">patched on 8.0.28</a>:</p>
<blockquote><p>
  Replication: If a replica server with the system variable <code>replica_preserve_commit_order</code> = 1 set was used under intensive load for a long period, the instance could run out of commit order sequence tickets. Incorrect behavior after the maximum value was exceeded caused the applier to hang and the applier worker threads to wait indefinitely on the commit order queue. The commit order sequence ticket generator now wraps around correctly. Thanks to Zhai Weixiang for the contribution. (Bug #32891221, Bug #103636)
</p></blockquote>
<p>We happen to meet all the criteria for hitting this bug.</p>
<ul>
<li>We use <code>replica_preserve_commit_order</code> because we use GTID based replication.</li>
<li>We have intensive load for long periods of time on many of our clusters and certainly for all of our most critical ones. Most of our clusters are very write-heavy.</li>
</ul>
<p>Since this bug was already patched upstream, we just needed to ensure we are deploying a version of MySQL higher than 8.0.28.</p>
<p>We also observed that the heavy writes that drove replication delay were exacerbated in MySQL 8.0. This made it even more important that we avoid heavy bursts in writes. At GitHub, we use <a href="https://github.com/github/freno">freno</a> to throttle write workloads based on replication lag.</p>
<h3 id="queries-would-pass-ci-but-fail-on-production" id="queries-would-pass-ci-but-fail-on-production" ><a class="heading-link" href="#queries-would-pass-ci-but-fail-on-production">Queries would pass CI but fail on production<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We knew we would inevitably see problems for the first time in production environments&mdash;hence our gradual rollout strategy with upgrading replicas. We encountered queries that passed CI but would fail on production when encountering real-world workloads. Most notably, we encountered a problem where queries with large <code>WHERE IN</code> clauses would crash MySQL. We had large <code>WHERE IN</code> queries containing over tens of thousands of values. In those cases, we needed to rewrite the queries prior to continuing the upgrade process. Query sampling helped to track and detect these problems. At GitHub, we use <a href="https://www.solarwinds.com/database-performance-monitor">Solarwinds DPM (VividCortex)</a>, a SaaS database performance monitor, for query observability.</p>
<h2 id="learnings-and-takeaways" id="learnings-and-takeaways" ><a class="heading-link" href="#learnings-and-takeaways">Learnings and takeaways<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Between testing, performance tuning, and resolving identified issues, the overall upgrade process took over a year and involved engineers from multiple teams at GitHub. We upgraded our entire fleet to MySQL 8.0 &ndash; including staging clusters, production clusters in support of GitHub.com, and instances in support of internal tools. This upgrade highlighted the importance of our observability platform, testing plan, and rollback capabilities. The testing and gradual rollout strategy allowed us to identify problems early and reduce the likelihood for encountering new failure modes for the primary upgrade.</p>
<p>While there was a gradual rollout strategy, we still needed the ability to rollback at every step and we needed the observability to identify signals to indicate when a rollback was needed. The most challenging aspect of enabling rollbacks was holding onto the backward replication from the new 8.0 primary to 5.7 replicas. We learned that consistency in the <a href="https://github.com/trilogy-libraries/trilogy">Trilogy client library</a> gave us more predictability in connection behavior and allowed us to have confidence that connections from the main Rails monolith would not break backward replication.</p>
<p>However, for some of our MySQL clusters with connections from multiple different clients in different frameworks/languages, we saw backwards replication break in a matter of hours which shortened the window of opportunity for rollback. Luckily, those cases were few and we didn&rsquo;t have an instance where the replication broke before we needed to rollback. But for us this was a lesson that there are benefits to having known and well-understood client-side connection configurations. It emphasized the value of developing guidelines and frameworks to ensure consistency in such configurations.</p>
<p>Prior efforts to <a href="https://github.blog/2021-09-27-partitioning-githubs-relational-databases-scale/">partition our data</a> paid off&mdash;it allowed us to have more targeted upgrades for the different data domains. This was important as one failing query would block the upgrade for an entire cluster and having different workloads partitioned allowed us to upgrade piecemeal and reduce the blast radius of unknown risks encountered during the process. The tradeoff here is that this also means that our MySQL fleet has grown.</p>
<p>The last time GitHub upgraded MySQL versions, we had five database clusters and now we have 50+ clusters. In order to successfully upgrade, we had to invest in observability, tooling, and processes for managing the fleet.</p>
<h2 id="conclusion" id="conclusion" ><a class="heading-link" href="#conclusion">Conclusion<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>A MySQL upgrade is just one type of routine maintenance that we have to perform &ndash; it&rsquo;s critical for us to have an upgrade path for any software we run on our fleet. As part of the upgrade project, we developed new processes and operational capabilities to successfully complete the MySQL version upgrade. Yet, we still had too many steps in the upgrade process that required manual intervention and we want to reduce the effort and time it takes to complete future MySQL upgrades.</p>
<p>We anticipate that our fleet will continue to grow as GitHub.com grows and we have goals to partition our data further which will increase our number of MySQL clusters over time. Building in automation for operational tasks and self-healing capabilities can help us scale MySQL operations in the future. We believe that investing in reliable fleet management and automation will allow us to scale github and keep up with required maintenance, providing a more predictable and resilient system.</p>
<p>The lessons from this project provided the foundations for our MySQL automation and will pave the way for future upgrades to be done more efficiently, but still with the same level of care and safety.</body></html></p>
<p>The post <a href="https://github.blog/engineering/infrastructure/upgrading-github-com-to-mysql-8-0/">Upgrading GitHub.com to MySQL 8.0</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">75736</post-id>	</item>
		<item>
		<title>How GitHub uses GitHub Actions and Actions larger runners to build and test GitHub.com</title>
		<link>https://github.blog/engineering/infrastructure/how-github-uses-github-actions-and-actions-larger-runners-to-build-and-test-github-com/</link>
		
		<dc:creator><![CDATA[Max Wagner]]></dc:creator>
		<pubDate>Tue, 26 Sep 2023 17:30:14 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[developer experience]]></category>
		<category><![CDATA[GitHub Actions]]></category>
		<category><![CDATA[How GitHub builds GitHub]]></category>
		<guid isPermaLink="false">https://github.blog/?p=74251</guid>

					<description><![CDATA[<p>Recently, we’ve been working to make our CI experience better by leveraging the newly released GitHub feature, Actions larger runners, to run our CI. </p>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-uses-github-actions-and-actions-larger-runners-to-build-and-test-github-com/">How GitHub uses GitHub Actions and Actions larger runners to build and test GitHub.com</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>The Developer Experience (DX) team at GitHub collaborated with a number of other teams to work on moving our continuous integration (CI) system to <a href="https://github.com/features/actions">GitHub Actions</a> to support the development and scaling demands of our engineering team. Our goal as a team is to enable our engineers to confidently and quickly ship software. To that end, we’ve worked on <a href="https://github.blog/2023-08-02-how-we-build-containerized-services-at-github-using-github/">providing paved paths</a>, a suite of automated tools and applications to streamline our development, runtime platforms, and deployments. Recently, we’ve been working to make our CI experience better by leveraging the newly released GitHub feature, <a href="https://docs.github.com/actions/using-github-hosted-runners/about-larger-runners">Actions larger runners</a>, to run our CI.</p>
<p>Read on to see how we run 15,000 CI jobs within an hour across 150,000 cores of compute!</p>
<h2 id="brief-history-of-ci-at-github"><a class="heading-link" href="#brief-history-of-ci-at-github">Brief history of CI at GitHub<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>GitHub has invested in a variety of different CI systems throughout its history. With each system, our aim has been to enhance the development experience for both GitHub engineers writing and deploying code and for engineers maintaining the systems.</p>
<p>However, with past CI systems we faced challenges with scaling the system to meet the needs of our engineering team to provide both stable and ephemeral build environments. Neither of these challenges allowed us to provide the optimal developer experience.</p>
<p>Then, GitHub released <a href="https://github.blog/2022-09-01-github-actions-introducing-the-new-larger-github-hosted-runners-beta/">GitHub Actions larger runners</a>. This gave us an opportunity not only to transition to a fully featured CI system, but also to develop, experience, and utilize the systems we are creating for our customers and to drive feedback to help build the product. For the GitHub DX team, this transition was a great opportunity to move away from maintaining our past CI systems while delivering a superior developer experience.</p>
<h2 id="what-are-larger-runners"><a class="heading-link" href="#what-are-larger-runners">What are larger runners?<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Larger runners are GitHub Actions runners that are hosted by GitHub. They are managed virtual machines (VMs) with more RAM, CPU, and disk space than standard GitHub-hosted runners. There are a variety of different machine sizes offered for the runners as well as some additional features compared to the standard GitHub-hosted runners.</p>
<p><div class="post-content-cta"><p>Larger runners are available to GitHub Team and GitHub Enterprise Cloud customers. Check out <a href="https://docs.github.com/actions/using-github-hosted-runners/about-larger-runners">these docs</a> to learn more about larger runners.</p>
</div></p>
<h3 id="why-did-we-pick-larger-runners"><a class="heading-link" href="#why-did-we-pick-larger-runners">Why did we pick larger runners?<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<h4 id="autoscaling-and-managed"><a class="heading-link" href="#autoscaling-and-managed">Autoscaling and managed<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h4>
<p>Coming from previous iterations of GitHub’s CI systems, we needed the ability to create CI machines on demand to meet the fast feedback cycles needed by GitHub engineers and to scale with the rate of change of the site.</p>
<p>With larger runners, we maintain the ability to autoscale our CI system because GitHub will automatically create multiple instances of a runner that scale up and down to match the job demands of our engineers. An added benefit is that the GitHub DX team no longer has to worry about the scaling of the runners since all of those complexities are handled by GitHub itself!</p>
<p>We wanted to share some raw numbers on our current peak utilization of larger runners:</p>
<ul>
<li>Uses 4,500 concurrent 32-core runners </li>
<li>Runs 125,000 build minutes per hour</li>
<li>Queues and runs approximately 15,000 jobs within an hour</li>
<li>Allocates around 150,000 cores of compute</li>
</ul>
<h4 id="beta-custom-vm-image-support"><a class="heading-link" href="#beta-custom-vm-image-support">(Beta) Custom VM image support<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h4>
<p>GitHub Actions provides runners with a lot of tools already baked in, which is sufficient and convenient for a variety of projects across the company. However, for some complex production GitHub services, the prebuilt runners did not satisfy all our requirements.</p>
<p>To maintain an efficient and fast CI system, the DX team needed the ability to provide machines with all the tools needed to build those production services. We didn’t want to spend extra time installing tools or compiling projects during CI jobs.</p>
<p>We are currently building features into larger runners so they have the ability to be launched from a custom VM image, called custom images. While this feature is still in beta, using custom images is a huge benefit to GitHub’s CI lifecycle for a couple of reasons.</p>
<p>First, custom images allows GitHub to bundle all the required software and tools needed to build and test complex production bearing services. Anything that is unique to GitHub or one of our projects can be pre-installed on the image before a GitHub Actions workflow even starts.</p>
<p>Second, custom images enable GitHub to dramatically speed up our GitHub Actions workflows by acting as a bootstrapping cache for some projects. During custom image creation, we bundle a pre-built version of a project&#8217;s source code into the image. Subsequently, when the project starts a GitHub Actions workflow, it can utilize a cached version of its source code, and any other build artifacts, to speed up its build process.</p>
<p>The cached project source code on the custom VM image can quickly become out of date due to the rapid rate of development within GitHub. This, in turn, causes workflow durations to increase. The DX team worked with the GitHub Actions engineering team to create an API on GitHub to regularly update the custom image multiple times a day to keep the project source up to date.</p>
<p>In practice, this has reduced the bootstrapping time of our projects significantly. Without custom images, our workflows would take around 50 minutes from start to finish, versus the 12 minutes they take today. This is a game changer for our engineers.</p>
<p>We’re working on a way to offer this functionality at scale. If you are interested in custom images for your CI/CD workflows, please reach out to your account manager to learn more!</p>
<h2 id="important-github-actions-features"><a class="heading-link" href="#important-github-actions-features">Important GitHub Actions features<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>There are thousands of projects at GitHub — from services that run production workloads to small tools that need to run CI to perform their daily operations. To make this a reality, GitHub leverages several important features in GitHub Actions that enable us to use the platform efficiently and securely across the company at scale.</p>
<h3 id="reusable-workflows"><a class="heading-link" href="#reusable-workflows">Reusable workflows<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>One of the DX team’s driving goals is to pave paths for all repositories to run CI without introducing unnecessary repetition across repositories. Prior to GitHub Actions, we created single job configurations that could be used across multiple projects. In GitHub Actions, this was not as easy because any repository can define its own workflows. Reusable workflows to the rescue!</p>
<p>The <a href="https://docs.github.com/actions/using-workflows/reusing-workflows">reusable workflows</a> feature in GitHub Actions provides a way to centrally manage a workflow in a repository that can be utilized by many other repositories in an organization. This was critical in our transition from our previous CI system to GitHub Actions. We were able to create several prebuilt workflows in a single repository, and many repositories could then use those workflows. This makes the process of adding CI to an existing or new project very much plug and play.</p>
<p>In our central repository hosting our reusable workflows, we can have workflows defined like:</p>
<pre><code>on:
  workflow_call:
    inputs:
      cibuild-script:
        description: 'Which cibuild script to run.'
        type: string
        required: false
        default: "script/cibuild"
    secrets:
      service-api-key:
        required: true

jobs:
  reusable_workflow_job:
    runs-on: gh-larger-runner-medium
    name: Simple Workflow Job
    timeout-minutes: 20
    steps:
      - name: Checkout Project
        uses: actions/checkout@v3
      - name: Run cibuild script
        run: |
          bash ${{ inputs.cibuild-script }}
        shell: bash
</code></pre>
<p>And in consuming repositories, they can simply utilize the reusable workflow, with just a few lines of code!</p>
<pre><code>name: my-new-project
on:
  workflow_dispatch:
  push:

jobs:
  call-reusable-workflow:
    uses: github/internal-actions/.github/workflows/default.yml@main
    with:
      cibuild-script: "script/cibuild-my-tests"
    secrets:
      service-api-key: ${{ secrets.SERVICE_API_KEY }}
</code></pre>
<p>Another great benefit of the reusable workflows feature is that the <code>runner</code> can be defined in the Reusable Workflow, meaning that we can guarantee all users of the workflow will run on our designated larger runner pool. Now, projects don’t need to worry about which runner they need to use!</p>
<h3 id="beta-reusing-previous-workflow-outcomes"><a class="heading-link" href="#beta-reusing-previous-workflow-outcomes">(Beta) Reusing previous workflow outcomes<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>To optimize our developer experience, the DX team worked with our engineering team to create a feature for GitHub Actions that allows workflows to reuse the outcome of a previous workflow run where the outcomes would be the same.</p>
<p>In some cases, the file contents of a repository are exactly the same between workflow runs that run on different commits. That is, the Git tree IDs for the current commit is the same as the previous commit (there are no file differences). In these cases, we can bypass CI checks by reusing the previous workflow outcomes and allow engineers to not have to wait for CI to run again.</p>
<p>This feature saves GitHub engineers from running anywhere from 300 to 500 workflows runs a day!</p>
<h2 id="other-challenges-faced"><a class="heading-link" href="#other-challenges-faced">Other challenges faced<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<h3 id="private-service-access"><a class="heading-link" href="#private-service-access">Private service access<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>During some internal GitHub Actions workflow runs, the workflows need the ability to access some GitHub private services, within a GitHub virtual private cloud (VPC), over the network. These could be resources such as artifact storage, application metadata services, and other services that enable invocation of our test harness.</p>
<p>When we moved to larger runners, this requirement to access private services became a top-of-mind concern. In previous iterations of our CI infrastructure, these private services were accessible through other cloud and network configurations. However, larger runners are isolated from other production environments, meaning they cannot access our private services.</p>
<p>Like all companies, we need to focus on both the security of our platform as well as the developer experience. To satisfy these two requirements, GitHub developed a remote access solution that allows clients residing outside of our VPCs (larger runners) to securely access select private services.</p>
<p>This remote access solution works on the principle of minting an <a href="https://docs.github.com/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect">OIDC token</a> in GitHub Actions, passing the OIDC token to a remote access gateway that authorizes the request by validating the OIDC token, and then proxying the request to the private service residing in a private network.</p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2023/09/actions-runners-remote-access.png?w=1024&#038;resize=1024%2C895" alt="Flow diagram showing an OIDC token being mined in GitHub Actions, passed to a remote access gateway that authorizes the request by validating the OIDC token, and then proxying the request to the private service residing in a private network." width="1024" height="895" class="aligncenter size-large wp-image-74253 width-fit" srcset="https://github.blog/wp-content/uploads/2023/09/actions-runners-remote-access.png?w=1600 1600w, https://github.blog/wp-content/uploads/2023/09/actions-runners-remote-access.png?w=300 300w, https://github.blog/wp-content/uploads/2023/09/actions-runners-remote-access.png?w=768 768w, https://github.blog/wp-content/uploads/2023/09/actions-runners-remote-access.png?w=1024 1024w, https://github.blog/wp-content/uploads/2023/09/actions-runners-remote-access.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<p>With this solution we are able to securely provide remote access from larger runners running GitHubActions to our private resources within our VPC.</p>
<p>GitHub has open sourced the basic scaffolding of this remote access gateway in the <a href="https://github.com/github/actions-oidc-gateway-example">github/actions-oidc-gateway-example</a> repository, so be sure to check it out!</p>
<h2 id="conclusion"><a class="heading-link" href="#conclusion">Conclusion<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>GitHub Actions provides a robust and smooth developer experience for GitHub engineers working on GitHub.com. We have been able to accomplish this by using the power of GitHub Actions features, such as reusable workflows and reusable workflow outcomes, and by leveraging the scalability and manageability of the GitHub Actions larger runners. We have also used this effort to enhance the GitHub Actions product. To put it simply, GitHub runs on GitHub.</p>
<p><aside class="p-4 p-md-6 post-aside--large"><p class="h5-mktg gh-aside-title">Learn more about GitHub Actions and Developer Experience</p></p>
<ul>
<li>Level up your GitHub Actions skillset with our <a href="https://resources.github.com/learn/pathways/">learning pathways on automation</a>.</li>
<li>Explore how investing in a <a href="https://github.blog/2023-06-08-developer-experience-what-is-it-and-why-should-you-care/">better developer experience</a> frees developers to do what matters most: building great software.</li>
<li>Learn <a href="https://github.blog/2023-06-13-survey-reveals-ais-impact-on-the-developer-experience/">how generative AI is changing the developer experience</a>.</li>
<li>Modernize your software development environment and <a href="https://github.blog/2023-07-03-leading-your-platform-migration-with-developer-experience/">transform your business with GitHub</a>.</li>
</ul>
</aside>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-uses-github-actions-and-actions-larger-runners-to-build-and-test-github-com/">How GitHub uses GitHub Actions and Actions larger runners to build and test GitHub.com</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">74251</post-id>	</item>
		<item>
		<title>Scaling merge-ort across GitHub</title>
		<link>https://github.blog/engineering/infrastructure/scaling-merge-ort-across-github/</link>
		
		<dc:creator><![CDATA[Matt Cooper]]></dc:creator>
		<pubDate>Thu, 27 Jul 2023 16:00:54 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[How GitHub builds GitHub]]></category>
		<category><![CDATA[scale]]></category>
		<guid isPermaLink="false">https://github.blog/?p=73390</guid>

					<description><![CDATA[<p>GitHub switched to performing merges and rebases using merge-ort. Come behind the scenes to see why and how we made this change.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/scaling-merge-ort-across-github/">Scaling merge-ort across GitHub</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>At GitHub, we perform a lot of merges and rebases in the background. For example, when you’re ready to merge your pull request, we already have the resulting merge assembled. Speeding up merge and rebase performance saves both user-visible time and backend resources. Git has recently <a href="https://github.blog/2021-08-16-highlights-from-git-2-33/#merge-ort-a-new-merge-strategy">learned some new tricks</a> which we’re using at scale across GitHub. This post walks through what’s changed and how the experience has improved.</p>
<h2 id="our-requirements-for-a-merge-strategy"><a class="heading-link" href="#our-requirements-for-a-merge-strategy">Our requirements for a merge strategy<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>There are a few non-negotiable parts of any merge strategy we want to employ:</p>
<ul>
<li><strong>It has to be fast.</strong> At GitHub’s scale, even a small slowdown is multiplied by the millions of activities going on in repositories we host each day.</li>
<li><strong>It has to be correct.</strong> For merge strategies, what’s “correct” is occasionally a matter of debate. In those cases, we try to match what users <em>expect</em> (which is often whatever the Git command line does).</li>
<li><strong>It can’t check out the repository.</strong> There are both scalability and security implications to having a working directory, so we simply don’t.</li>
</ul>
<p><a href="https://github.blog/2015-12-15-move-fast/">Previously</a>, we used <code>libgit2</code> to tick these boxes: it was faster than <a href="https://git-scm.com/docs/merge-strategies#Documentation/merge-strategies.txt-recursive">Git’s default merge strategy</a> and it didn’t require a working directory. On the correctness front, we either performed the merge <em>or</em> reported a merge conflict and halted. However, because of additional code related to<a href="https://public-inbox.org/git/539A25BF.4060501@alum.mit.edu/"> merge base selection</a>, sometimes a user’s local Git could easily merge what our implementation could not. This led to a steady stream of support tickets asking why the GitHub web UI couldn’t merge two files when the local command line could. We weren’t meeting those users’ expectations, so from their perspective, we weren’t correct.</p>
<h2 id="a-new-strategy-emerges"><a class="heading-link" href="#a-new-strategy-emerges">A new strategy emerges<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Two years ago, Git learned a new merge strategy, <code>merge-ort</code>. As the author <a href="https://lore.kernel.org/git/4a0f088f3669a95c7f75e885d06c0a3bdaf31f42.1628055482.git.gitgitgadget@gmail.com/">details on the mailing list</a>, <code>merge-ort</code> is fast, correct, and addresses many shortcomings of the older default strategy. Even better, unlike <code>merge-recursive</code>, it doesn’t need a working directory. <code>merge-ort</code> is much faster even than our optimized, <code>libgit2</code>-based strategy. What’s more, <code>merge-ort</code> has since become Git’s default. That meant our strategy would fall even further behind on correctness.</p>
<p>It was clear that GitHub needed to upgrade to <code>merge-ort</code>. We split this effort into two parts: first deploy <code>merge-ort</code> for merges, then deploy it for rebases.</p>
<h2 id="merge-ort-for-merges"><a class="heading-link" href="#merge-ort-for-merges"><code>merge-ort</code> for merges<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Last September, we <a href="https://github.blog/changelog/2022-09-12-merge-commits-now-created-using-the-merge-ort-strategy/">announced</a> that we’re using <code>merge-ort</code> for merge commits. We used <a href="https://github.blog/2016-02-03-scientist/">Scientist</a> to run <em>both</em> code paths in production so we can compare timing, correctness, etc. without risking much. The customer still gets the result of the old code path, while the GitHub feature team gets to compare and contrast the behavior of the new code path. Our process was:</p>
<ol>
<li>Create and enable a Scientist experiment with the new code path.</li>
<li>Roll it out to a fraction of traffic. In our case, we started with some GitHub-internal repositories first before moving to a percentage-based rollout across all of production.</li>
<li>Measure gains, check correctness, and fix bugs iteratively.</li>
</ol>
<p>We saw dramatic speedups across the board, especially on large, heavily-trafficked repositories. For our own <code>github/github</code> monolith, we saw a 10x speedup in both the average and P99 case. Across the entire experiment, our P50 saw the same 10x speedup and P99 case got nearly a 5x boost.</p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2023/07/merge-ort-1.png?w=1024&#038;resize=1024%2C459" alt="Chart showing experimental candidate versus control at P50. The candidate implementation fairly consistently stays below 0.1 seconds." width="1024" height="459" class="aligncenter size-large wp-image-73391 width-fit" srcset="https://github.blog/wp-content/uploads/2023/07/merge-ort-1.png?w=1124 1124w, https://github.blog/wp-content/uploads/2023/07/merge-ort-1.png?w=300 300w, https://github.blog/wp-content/uploads/2023/07/merge-ort-1.png?w=768 768w, https://github.blog/wp-content/uploads/2023/07/merge-ort-1.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2023/07/merge-ort-2.png?w=1024&#038;resize=1024%2C458" alt="Chart showing experimental candidate versus control at P99. The candidate implementation follows the same spiky pattern as the control, but its peaks are much lower." width="1024" height="458" class="aligncenter size-large wp-image-73392 width-fit" srcset="https://github.blog/wp-content/uploads/2023/07/merge-ort-2.png?w=1122 1122w, https://github.blog/wp-content/uploads/2023/07/merge-ort-2.png?w=300 300w, https://github.blog/wp-content/uploads/2023/07/merge-ort-2.png?w=768 768w, https://github.blog/wp-content/uploads/2023/07/merge-ort-2.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2023/07/merge-ort-3.png?w=1024&#038;resize=1024%2C216" alt="Dashboard widgets showing P50 average times for experimental candidate versus control. The control averages 71.07 milliseconds while the candidate averages 7.74 milliseconds." width="1024" height="216" class="aligncenter size-large wp-image-73393 width-fit" srcset="https://github.blog/wp-content/uploads/2023/07/merge-ort-3.png?w=1600 1600w, https://github.blog/wp-content/uploads/2023/07/merge-ort-3.png?w=300 300w, https://github.blog/wp-content/uploads/2023/07/merge-ort-3.png?w=768 768w, https://github.blog/wp-content/uploads/2023/07/merge-ort-3.png?w=1024 1024w, https://github.blog/wp-content/uploads/2023/07/merge-ort-3.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2023/07/merge-ort-4.png?w=1024&#038;resize=1024%2C215" alt="Dashboard widgets showing P99 average times for experimental candidate versus control. The control averages 1.63 seconds while the candidate averages 329.82 milliseconds." width="1024" height="215" class="aligncenter size-large wp-image-73394 width-fit" srcset="https://github.blog/wp-content/uploads/2023/07/merge-ort-4.png?w=1600 1600w, https://github.blog/wp-content/uploads/2023/07/merge-ort-4.png?w=300 300w, https://github.blog/wp-content/uploads/2023/07/merge-ort-4.png?w=768 768w, https://github.blog/wp-content/uploads/2023/07/merge-ort-4.png?w=1024 1024w, https://github.blog/wp-content/uploads/2023/07/merge-ort-4.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<h2 id="merge-ort-for-rebases"><a class="heading-link" href="#merge-ort-for-rebases"><code>merge-ort</code> for rebases<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Like merges, we also do a huge number of rebases. Customers may choose <a href="https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/keeping-your-pull-request-in-sync-with-the-base-branch#updating-your-pull-request-branch">rebase workflows</a> in their pull requests. We also perform test rebases and other “behind the scenes” operations, so we also<a href="https://github.blog/changelog/2023-06-28-rebase-commits-now-created-using-the-merge-ort-strategy/"> brought merge-ort to rebases</a>.</p>
<p>This time around, we powered rebases using a new Git subcommand: <code>git-replay</code>. <code>git replay</code> was written by the original author of <code>merge-ort</code>, <a href="https://github.com/newren">Elijah Newren</a> (a prolific Git contributor). With this tool, we could perform rebases using <code>merge-ort</code> and without needing a worktree. Once again, the path was pretty similar:</p>
<ol>
<li>Merge <code>git-replay</code> into our fork of Git. (We were running the experiment with Git 2.39, which didn’t include the <code>git-replay</code> feature.)</li>
<li>Before shipping, leverage our test suite to detect discrepancies between the old and the new implementations.</li>
<li>Write automation to flush out bugs by performing test rebases of all open pull requests in <code>github/github</code> and comparing the results.</li>
<li>Set up a Scientist experiment to measure the performance delta between <code>libgit2</code>-powered rebases and monitor for unexpected mismatches in behavior.</li>
<li>Measure gains, check correctness, and fix bugs iteratively.</li>
</ol>
<p>Once again, we were amazed at the results. The following is a  great anecdote from testing, as relayed by <a href="https://github.com/wincent">@wincent</a> (one of the GitHub engineers on this project):</p>
<blockquote><p>Another way to think of this is in terms of resource usage. We ran the experiment over 730k times. In that interval, our computers spent 2.56 hours performing rebases with <code>libgit2</code>, but under 10 minutes doing the same work with <code>merge-ort</code>. And this was running the experiment for 0.5% of actors. Extrapolating those numbers out to 100%, if we had done all rebases during that interval with <code>merge-ort</code>, it would have taken us 2,000 minutes, or about 33 hours. That same work done with <code>libgit2</code> would have taken 512 hours!</p></blockquote>
<h2 id="whats-next"><a class="heading-link" href="#whats-next">What’s next<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>While we’ve covered the most common uses, this is not the end of the story for <code>merge-ort</code> at GitHub. There are still other places in which we can leverage its superpowers to bring better performance, greater accuracy, and improved availability. Squashing and reverting are on our radar for the future, as well as considering what new product features it could unlock down the road.</p>
<h3 id="appreciation"><a class="heading-link" href="#appreciation">Appreciation<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>Many thanks to all the GitHub folks who worked on these two projects. Also, GitHub continues to be grateful for the hundreds of volunteer contributors to the Git open source project, including <a href="https://github.com/newren">Elijah Newren</a> for designing, implementing, and continually improving <code>merge-ort</code>.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/scaling-merge-ort-across-github/">Scaling merge-ort across GitHub</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">73390</post-id>	</item>
		<item>
		<title>Experiment: The hidden costs of waiting on slow build times</title>
		<link>https://github.blog/engineering/infrastructure/experiment-the-hidden-costs-of-waiting-on-slow-build-times/</link>
		
		<dc:creator><![CDATA[Natalie Somersall]]></dc:creator>
		<pubDate>Thu, 08 Dec 2022 18:00:02 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[developer experience]]></category>
		<category><![CDATA[GitHub-hosted runners]]></category>
		<guid isPermaLink="false">https://github.blog/?p=69029</guid>

					<description><![CDATA[<p>How much does it really cost to buy more powerful cloud compute resources for development work? A lot less than you think. </p>
<p>The post <a href="https://github.blog/engineering/infrastructure/experiment-the-hidden-costs-of-waiting-on-slow-build-times/">Experiment: The hidden costs of waiting on slow build times</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>The cost of hardware is one of the most common objections to providing more powerful computing resources to development teams—and that’s regardless of whether you’re talking about physical hardware in racks, managed cloud providers, or a software-as-a-service based (SaaS) compute resource. Paying for compute resources is an easy cost to “feel” as a business, especially if it’s a recurring operating expense for a managed cloud provider or SaaS solution.</p>
<p>When you ask a developer whether they’d prefer more or less powerful hardware, the answer is almost always the same: they want more powerful hardware. That’s because more powerful hardware means less time waiting on builds—and that means more time to build the next feature or fix a bug.</p>
<p>But even if the upfront cost is higher for higher-powered hardware, what’s the actual cost when you consider the impact on developer productivity?</p>
<p>To find out, I set up an experiment using GitHub’s new, <a href="https://docs.github.com/en/actions/using-github-hosted-runners/using-larger-runners">larger hosted runners</a>, which offer powerful cloud-based compute resources, to execute a large build at each compute tier from 2 cores to 64 cores. I wanted to see what the cost of each build time would be, and then compare that with the average hourly cost of a United States-based developer to figure out the actual operational expense for a business.</p>
<p>The results might surprise you.</p>
<p><aside class="p-4 p-md-6 post-aside--large"><p class="h5-mktg gh-aside-title">What are GitHub larger runners?</p><p>Larger runners bring high-powered, cloud-based compute resources to GitHub with up to 64 CPU cores and 256GB of RAM. These are virtual machines (VMs) used by GitHub&#8217;s CI/CD and automation solution GitHub Actions, and they&#8217;re available on the GitHub Team and GitHub Enterprise Cloud plans. Beyond being a lot more powerful than standard compute resources on GitHub, larger runners can be defined at the organizational level in terms of their hardware specifications and operating systems, and autoscale with an organization&#8217;s needs. You can <a href="https://docs.github.com/en/actions/using-github-hosted-runners/using-larger-runners">learn more about larger runners in our docs</a>.</p>
</aside>
<h2 id="testing-build-times-vs-cost-by-core-size-on-compute-resources"><a class="heading-link" href="#testing-build-times-vs-cost-by-core-size-on-compute-resources">Testing build times vs. cost by core size on compute resources<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>For my experiment, I used my own personal project where <a href="https://github.com/some-natalie/fedora-acs-override">I compile the Linux kernel</a> (seriously!) for Fedora 35 and Fedora 36. For background, I need a non-standard patch to play video games on my personal desktop without having to deal with dual booting.</p>
<p>Beyond being a fun project, it’s also a perfect case study for this experiment. As a software build, it takes a long time to run—and it’s a great proxy for more intensive software builds developers often navigate at work.</p>
<p>Now comes the fun part: our experiment. Like I said above, I’m going to initiate builds of this project at each compute tier from 2 cores to 64 cores, and then determine how long each build takes and its cost on GitHub’s larger runners. Last but not least: I’ll compare how much time we save during the build cycle and square that with how much more time developers would have to be productive to find the true business cost.</p>
<p>The logic here is that developers could either be waiting the entire time a build runs or end up context-switching to work on something else while a build runs. Both of these impact overall productivity (more on this below).</p>
<p>To simplify my calculations, I took the average runtimes of two builds per compute tier.</p>
<div data-target="content-table-wrap.container" class="content-table-wrap"><content-table-wrap></p>
<table style="border: 1px black">
<tbody>
<tr>
<td><strong>Pro tip: </strong> You can find my full spreadsheet for these calculations <a href="https://docs.google.com/spreadsheets/d/1ostvpK8jmC13U25bdyekyBV9uS8xydLV14q2ZwaH24k">here</a> if you want to copy it and play with the numbers yourself using other costs, times for builds, developer salaries, etc.</td>
</tr>
</tbody>
</table>
<p></content-table-wrap></div>
<h2 id="how-much-slow-build-times-cost-companies"><a class="heading-link" href="#how-much-slow-build-times-cost-companies">How much slow build times cost companies<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>In scenario number one of our experiment, we’ll assume that developers may just wait for a build to run and do nothing else during that time frame. That’s not a great outcome, but it happens.</p>
<p>So, what does this cost a business? <a href="https://survey.stackoverflow.co/2022/#salary-united-states">According to StackOverflow’s 2022 Developer Survey</a>, the average annual cost of a developer in the United States is approximately $150,000 per year including fringe benefits, taxes, and so on. That breaks down to around $75 (USD) an hour. In short, if a developer is waiting on a build to run for one hour and doing nothing in that timeframe, the business is still spending $75 on average for that developer’s time—and potentially losing out on time that developer could be focusing on building more code.</p>
<p>Now for the fun part: calculating the runtimes and cost to execute a build using each tier of compute power, plus the cost of a developer’s time spent waiting on the build. (And remember, I ran each of these twice at each tier and then averaged the results together.)</p>
<p>You end up with something like this:</p>
<div data-target="content-table-wrap.container" class="content-table-wrap"><content-table-wrap></p>
<table>
<tbody>
<tr>
<td><strong>Compute power</strong></td>
<td><strong>Fedora 35 build</strong></td>
<td><strong>Fedora 36 build</strong></td>
<td><strong>Average time</strong></p>
<p><strong>(minutes)</strong></td>
<td><strong>Cost/minute for compute</strong></td>
<td><strong>Total cost of 1 build</strong></td>
<td><strong>Developer cost</strong></p>
<p><strong>(1 dev)</strong></td>
<td><strong>Developer cost</strong></p>
<p><strong>(5 devs)</strong></td>
</tr>
<tr>
<td>2 core</td>
<td>5:24:27</td>
<td>4:54:02</td>
<td>310</td>
<td>$0.008</td>
<td>$2.48</td>
<td>$389.98</td>
<td>$1,939.98</td>
</tr>
<tr>
<td>4 core</td>
<td>2:46:33</td>
<td>2:57:47</td>
<td>173</td>
<td>$0.016</td>
<td>$2.77</td>
<td>$219.02</td>
<td>$1,084.02</td>
</tr>
<tr>
<td>8 core</td>
<td>1:32:13</td>
<td>1:30:41</td>
<td>92</td>
<td>$0.032</td>
<td>$2.94</td>
<td>$117.94</td>
<td>$577.94</td>
</tr>
<tr>
<td>16 core</td>
<td>0:54:31</td>
<td>0:54:14</td>
<td>55</td>
<td>$0.064</td>
<td>$3.52</td>
<td>$72.27</td>
<td>$347.27</td>
</tr>
<tr>
<td>32 core</td>
<td>0:36:21</td>
<td>0:32:21</td>
<td>35</td>
<td>$0.128</td>
<td>$4.48</td>
<td>$48.23</td>
<td>$223.23</td>
</tr>
<tr>
<td>64 core</td>
<td>0:29:25</td>
<td>0:24:24</td>
<td>27</td>
<td>$0.256</td>
<td>$6.91</td>
<td>$40.66</td>
<td>$175.66</td>
</tr>
</tbody>
</table>
<p></content-table-wrap></div>
<p>You can immediately see how much faster each build completes on more powerful hardware—and that’s hardly surprising. But it’s striking how much money, on average, a business would be paying their developers in the time it takes for a build to run.</p>
<p>When you plot this out, you end up with a pretty compelling case for spending more money on stronger hardware.</p>
<p><figure id="attachment_69031"  class="wp-caption aligncenter mx-0"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1200" height="742" class="width-fit size-large wp-image-69031 width-fit" src="https://github.blog/wp-content/uploads/2022/12/costofbuilds1.png?w=1024&#038;resize=1200%2C742" alt="A chart showing the cost of a build on servers of varying CPU power." srcset="https://github.blog/wp-content/uploads/2022/12/costofbuilds1.png?w=1200 1200w, https://github.blog/wp-content/uploads/2022/12/costofbuilds1.png?w=300 300w, https://github.blog/wp-content/uploads/2022/12/costofbuilds1.png?w=768 768w, https://github.blog/wp-content/uploads/2022/12/costofbuilds1.png?w=1024 1024w, https://github.blog/wp-content/uploads/2022/12/costofbuilds1.png?w=512 512w, https://github.blog/wp-content/uploads/2022/12/costofbuilds1.png?w=288 288w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="text-mono color-fg-muted mt-14px f5-mktg">A chart showing the cost of a build on servers of varying CPU power.</figcaption></figure></p>
<p><strong>The bottom line:</strong> The cost of hardware is much, much less than the total cost for developers, and giving your engineering teams more CPU power means they have more time to develop software instead of waiting on builds to complete. And the bigger the team you have in a given organization, the more upside you have to invest in more capable compute resources.</p>
<h2 id="how-much-context-switching-costs-companies"><a class="heading-link" href="#how-much-context-switching-costs-companies">How much context switching costs companies<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Now let’s change the scenario in our experiment: Instead of assuming that developers are sitting idly while waiting for a build to finish, let’s consider they instead start working on another task while a build runs.</p>
<p>This is a classic example of context switching, and it comes with a cost, too. Research has found that context switching is both distracting and an impediment to focused and productive work. In fact, Gloria Mark, a professor of informatics at the University of California, Irvine, has found <a href="https://www.fastcompany.com/944128/worker-interrupted-cost-task-switching">it takes about 23 minutes for someone to get back to their original task</a> after context switching—and that isn’t even specific to development work, which often entails deeply involved work.</p>
<p>Based on my own experience, switching from one focused task to another takes at least an hour so that’s what I used to run the numbers against. Now, let’s break down the data again:</p>
<div data-target="content-table-wrap.container" class="content-table-wrap"><content-table-wrap></p>
<table>
<tbody>
<tr>
<td><strong>Compute power</strong></td>
<td><strong>Minutes</strong></td>
<td><strong>Cost of 1 build</strong></td>
<td><strong>Partial developer cost</strong></p>
<p><strong>(1 dev)</strong></td>
<td><strong>Partial developer cost</strong></p>
<p><strong>(5 devs)</strong></td>
</tr>
<tr>
<td>2 core</td>
<td>310</td>
<td>$2.48</td>
<td>$77.48</td>
<td>$377.48</td>
</tr>
<tr>
<td>4 core</td>
<td>173</td>
<td>$2.77</td>
<td>$77.77</td>
<td>$377.77</td>
</tr>
<tr>
<td>8 core</td>
<td>92</td>
<td>$2.94</td>
<td>$77.94</td>
<td>$377.94</td>
</tr>
<tr>
<td>16 core</td>
<td>55</td>
<td>$3.52</td>
<td>$78.52</td>
<td>$378.52</td>
</tr>
<tr>
<td>32 core</td>
<td>35</td>
<td>$4.48</td>
<td>$79.48</td>
<td>$379.48</td>
</tr>
<tr>
<td>64 core</td>
<td>27</td>
<td>$6.91</td>
<td>$81.91</td>
<td>$381.91</td>
</tr>
</tbody>
</table>
<p></content-table-wrap></div>
<p>Here, the numbers tell a different story—that is, if you’re going to switch tasks anyways, the speed of build runs doesn’t significantly matter. Labor is much, much more expensive than compute resources. And that means spending a few more dollars to speed up the build is inconsequential in the long run.</p>
<p>Of course, this assumes it will take an hour for developers to get back on track after context switching. But according to the research we cited above, some people can get back on track in 23 minutes (and, additional research from Cornell found that <a href="https://assets.qatalog.com/language.work/qatalog-2021-workgeist-report.pdf">it sometimes takes as little as 10 minutes</a>).</p>
<p>To account for this, let’s try shortening the time frames to 30 minutes and 15 minutes:</p>
<div data-target="content-table-wrap.container" class="content-table-wrap"><content-table-wrap></p>
<table>
<tbody>
<tr>
<td><strong>Compute power</strong></td>
<td><strong>Minutes</strong></td>
<td><strong>Cost of 1 build</strong></td>
<td><strong>Partial dev cost</strong></p>
<p><strong>(1 dev, 30 mins)</strong></td>
<td><strong>Partial dev cost</strong></p>
<p><strong>(5 devs, 30 mins)</strong></td>
<td><strong>Partial dev cost</strong></p>
<p><strong>(1 dev, 15 mins)</strong></td>
<td><strong>Partial dev cost</strong></p>
<p><strong>(5 devs, 15 mins)</strong></td>
</tr>
<tr>
<td>2 core</td>
<td>310</td>
<td>$2.48</td>
<td>$39.98</td>
<td>$189.98</td>
<td>$21.23</td>
<td>$96.23</td>
</tr>
<tr>
<td>4 core</td>
<td>173</td>
<td>$2.77</td>
<td>$40.27</td>
<td>$190.27</td>
<td>$21.52</td>
<td>$96.52</td>
</tr>
<tr>
<td>8 core</td>
<td>92</td>
<td>$2.94</td>
<td>$40.44</td>
<td>$190.44</td>
<td>$21.69</td>
<td>$96.69</td>
</tr>
<tr>
<td>16 core</td>
<td>55</td>
<td>$3.52</td>
<td>$41.02</td>
<td>$191.02</td>
<td>$22.27</td>
<td>$97.27</td>
</tr>
<tr>
<td>32 core</td>
<td>35</td>
<td>$4.48</td>
<td>$41.98</td>
<td>$191.98</td>
<td>$23.23</td>
<td>$98.23</td>
</tr>
<tr>
<td>64 core</td>
<td>27</td>
<td>$6.91</td>
<td>$44.41</td>
<td>$194.41</td>
<td>$25.66</td>
<td>$100.66</td>
</tr>
</tbody>
</table>
<p></content-table-wrap></div>
<p>And when you visualize this data on a graph, the cost for a single developer waiting on a build or switching tasks looks like this:</p>
<p><figure id="attachment_69032"  class="wp-caption aligncenter mx-0"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1476" height="742" class="width-fit size-large wp-image-69032 width-fit" src="https://github.blog/wp-content/uploads/2022/12/costofbuilds2.png?w=1024&#038;resize=1476%2C742" alt="A chart showing how much it costs for developers to wait for a build to execute." srcset="https://github.blog/wp-content/uploads/2022/12/costofbuilds2.png?w=1476 1476w, https://github.blog/wp-content/uploads/2022/12/costofbuilds2.png?w=300 300w, https://github.blog/wp-content/uploads/2022/12/costofbuilds2.png?w=768 768w, https://github.blog/wp-content/uploads/2022/12/costofbuilds2.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="text-mono color-fg-muted mt-14px f5-mktg">A chart showing how much it costs for developers to wait for a build to execute.</figcaption></figure></p>
<p>When you assume the average hourly rate of a developer is $75 (USD), the graph above shows that it almost always makes sense to pay more for more compute power so your developers aren’t left waiting or context switching. Even the most expensive compute option—$15 an hour for 64 cores and 256GB of RAM—only accounts for a fifth of the hourly cost of a single developer’s time. As developer salaries increase, the cost of hardware decreases, or the time the job takes to run decreases—and this inverse ratio bolsters the case for buying better equipment.</p>
<p>That’s something to consider.</p>
<h2 id="the-bottom-line"><a class="heading-link" href="#the-bottom-line">The bottom line<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>It’s cheaper—and less frustrating for your developers—to pay more for better hardware to keep your team on track.</p>
<p>In this case, spending an extra $4-5 on build compute saves about $40 per build for an individual developer, or a little over $200 per build for a team of five, <em>and</em> the frustration of switching tasks with a productivity cost of about an hour. That’s not nothing. Of course, spending that extra $4-5 at scale can quickly compound—but so can the cost of sunk productivity.</p>
<p>Even though we used GitHub’s larger runners as an example here, these findings are applicable to any type of hardware—whether self-hosted or in the cloud. So remember: The upfront cost for more CPU power pays off over time. And your developers will thank you (trust us).</p>
<p><div class="post-content-cta"><p>Want to try our new high-performance GitHub-hosted runners? <a href="https://github.com/features/github-hosted-runners/signup">Sign up for the beta today</a>.</p>
</div></p>
<p>The post <a href="https://github.blog/engineering/infrastructure/experiment-the-hidden-costs-of-waiting-on-slow-build-times/">Experiment: The hidden costs of waiting on slow build times</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">69029</post-id>	</item>
		<item>
		<title>How GitHub converts previously encrypted and unencrypted columns to ActiveRecord encrypted columns</title>
		<link>https://github.blog/engineering/infrastructure/how-github-converts-previously-encrypted-and-unencrypted-columns-to-activerecord-encrypted-columns/</link>
		
		<dc:creator><![CDATA[Kylie Stradley]]></dc:creator>
		<pubDate>Thu, 03 Nov 2022 12:53:42 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[ActiveRecord Encryption]]></category>
		<category><![CDATA[Column Encryption]]></category>
		<category><![CDATA[Database Encryption]]></category>
		<guid isPermaLink="false">https://github.blog/?p=68413</guid>

					<description><![CDATA[<p>This post is the second part in a series about ActiveRecord::Encryption that shows how GitHub upgrades previously encrypted and unencrypted columns to ActiveRecord::Encryption.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-converts-previously-encrypted-and-unencrypted-columns-to-activerecord-encrypted-columns/">How GitHub converts previously encrypted and unencrypted columns to ActiveRecord encrypted columns</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2 id="background"><a class="heading-link" href="#background">Background<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>In the <a href="https://github.blog/2022-10-26-why-and-how-github-encrypts-sensitive-database-columns-using-activerecordencryption/">first post</a> in this series, we detailed how we designed our easy‐to‐use column encryption paved path. We found during the rollout that the bulk of time and effort was spent in robustly supporting the reading and upgrading of previous encryption formats/plaintext and key rotation. In this post, we’ll explain the design decisions we made in our migration plan and describe a simplified migration pattern you can use to encrypt (or re-encrypt) existing records in your Rails application.</p>
<p>We have two cases for encrypted columns data migration–upgrading plaintext or previously encrypted data to our new standard and key rotation.</p>
<p>Upon consulting the Rails documentation to see if there was any prior art we could use, we found the <a href="https://edgeguides.rubyonrails.org/active_record_encryption.html#support-for-previous-encryption-schemes">previous encryptor</a> strategy but <em>exactly</em> how to migrate existing data is, as they say, an “exercise left for the reader.”</p>
<p>Dear reader, lace up your sneakers because we are about to exercise. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f45f.png" alt="👟" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>To convert plaintext columns or columns encrypted with our deprecated internal encryption library, we used <code>ActiveRecord::Encryption</code>’s previous encryptor strategy, our existing feature flag mechanism and our own type of database migration called a transition. Transitions are used by GitHub to modify existing data, as opposed to migrations that are mainly used to add or change columns. To simplify things and save time, in the example migration strategy, we’ll rely on the Ruby gem, <a href="https://github.com/Shopify/maintenance_tasks">MaintenanceTasks</a>.</p>
<h3 id="previous-encryptor-strategy"><a class="heading-link" href="#previous-encryptor-strategy">Previous encryptor strategy<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p><code>ActiveRecord::Encryption</code> provides as a config option <a href="https://edgeguides.rubyonrails.org/active_record_encryption.html#config-active-record-encryption-support-unencrypted-data"><code>config.active_record.encryption.support_unencrypted_data</a></code> that allows plaintext values in an encrypted_attribute to be read without error. This is enabled globally and could be a good strategy to use if you are migrating only plaintext columns and you are going to migrate them all at once. We chose not to use this option because we want to migrate columns to <code>ActiveRecord::Encryption</code> without exposing the ciphertext of <em>other</em> columns if decryption fails. By using a previous encryptor, we can isolate this “plaintext mode” to a single model.</p>
<p>In addition to this, GitHub’s previous encryptor uses a schema validator and regex to make sure that the “plaintext” being returned does not have the same shape as Rails encrypted columns data.</p>
<h3 id="feature-flag-strategy"><a class="heading-link" href="#feature-flag-strategy">Feature flag strategy<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>We wanted to have fine-grained control to safely roll out our new encryption strategy, as well as the ability to completely disable it in case something went wrong, so we created our own <a href="https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute-label-Creating+Custom+Types">custom type</a> using the <a href="https://api.rubyonrails.org/classes/ActiveModel/Type/Value.html">ActiveModel::Type API</a>, which would only perform encryption when the <a href="https://github.blog/2021-04-27-ship-code-faster-safer-feature-flags/">feature flag</a> for our new column encryption strategy was <em>disabled</em>.</p>
<p>A common feature flag strategy would be to start a feature flag at 0% and gradually ramp it up to 100% while you observe and verify the effects on your application. Once a flag is verified at 100%, you would remove the feature flag logic and delete the flag. To gradually increase a flag on column encryption, we would need to have an encryption strategy that could handle plaintext and encrypted records both back and forth because there would be no way to know if a column was encrypted without attempting to read it first. This seemed like unnecessary additional and confusing work, so we knew we’d want to use flagging as an on/off switch.</p>
<p>While a feature flag should generally not be long running, we needed the feature flag <em>logic</em> to be long running because we want it to be available for GitHub developers who will want to upgrade existing columns to use <code>ActiveRecord::Encryption</code>.</p>
<p>This is why we chose to inverse the usual feature flag default to give us the flexibility to upgrade columns incrementally without introducing unnecessary long‐running feature flags. This means we set the flag at 100% to <em>prevent</em> records from being encrypted with the new standard and set it to 0% to cause them <em>to be</em> encrypted with our new standard. If for some reason we are unable to prioritize upgrading a column, other columns do not need to be flagged at 100% to continue to be encrypted on our new standard.</p>
<p>We added this logic to our monkeypatch of <code>ActiveRecord::Base::encrypts</code> method to ensure our feature flag serializer is used:</p>
<h4 id="code-sample-1"><a class="heading-link" href="#code-sample-1">Code sample 1<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h4>
<pre><code>self.attribute(attribute) do |cast_type|
    GitHub::Encryption::FeatureFlagEncryptedType.new(cast_type: cast_type, attribute_name: attribute, model_name: self.name)
end
</code></pre>
<p>Which instantiates our new ActiveRecord Type that checks for the flag in its serialize method:</p>
<h4 id="code-sample-2"><a class="heading-link" href="#code-sample-2">Code sample 2<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h4>
<pre><code># frozen_string_literal: true

module GitHub
  module Encryption
    class FeatureFlagEncryptedType &lt; ::ActiveRecord::Type::Text
      attr_accessor :cast_type, :attribute_name, :model_name


      # delegate: a method to make a call to `this_object.foo.bar` into `this_object.bar` for convenience
      # deserialize: Take a value from the database, and make it suitable for Rails
      # changed_in_place?: determine if the value has changed and needs to be rewritten to the database
      delegate :deserialize, :changed_in_place?
, to: :cast_type

      def initialize(cast_type:, attribute_name:, model_name:)
        raise RuntimeError, "Not an EncryptedAttributeType" unless cast_type.is_a?(ActiveRecord::Encryption::EncryptedAttributeType)

        @cast_type = cast_type
        @attribute_name = attribute_name
        @model_name = model_name
      end


      # Take a value from Rails and make it suitable for the database
      def serialize(value)
        if feature_flag_enabled?("encrypt_as_plaintext_#{model_name.downcase}_#{attribute_name.downcase}")
          # Fall back to plaintext (ignore the encryption serializer)
          cast_type.cast_type.serialize(value)
        else
          # Perform encryption via active record encryption serializer
          cast_type.serialize(value)
        end
      end
    end
  end
end
</code></pre>
<p>A caveat to this implementation is that we extended from <code>ActiveRecord::Type::Text</code> which extends from <a href="https://github.com/rails/rails/blob/9a263e9a0ffb82faa6d3153fd1f35b814a366cd5/activemodel/lib/active_model/type/string.rb#L8-L12">ActiveModel::Type:String</a>, which implements <code>changed_in_place?</code> by checking if the <code>new_value</code> is a string, and, if it is, does a string comparison to determine if the value was changed.</p>
<p>We ran into this caveat during our roll out of our new encrypted columns. When migrating a column previously encrypted with our internal encryption library, we found that  <code>changed_in_place?</code> would compare the decrypted plaintext value to the encrypted value stored in the database, always marking the record as changed in place as these were never equal. When we migrated one of our fields related to 2FA recovery codes, this had the unexpected side effect of causing them to all appear changed in our audit log logic and <a href="https://github.blog/changelog/2022-08-18-false-alert-flags-will-appear-in-users-security-log-due-to-a-bug-in-2fa-recovery-events/">created false-alerts in customer facing security logs</a>. Fortunately, though, there was no impact to data and our authentication team annotated the false alerts to indicate this to affected customers.</p>
<p>To address the cause, we delegated the <code>changed_in_place?</code> to the <code>cast_type</code>, which in this case will always be <code>ActiveRecord::Encryption::EncryptedAttributeType</code> that <a href="https://github.com/rails/rails/blob/main/activerecord/lib/active_record/encryption/encrypted_attribute_type.rb#L47-L50">attempts to deserialize</a> the previous value before comparing it to the new value.</p>
<h3 id="key-rotation"><a class="heading-link" href="#key-rotation">Key rotation<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p><code>ActiveRecord::Encryption</code> accommodates for a list of keys to be used so that the most recent one is used to encrypt records, but all entries in the <a href="https://github.com/rails/rails/blob/main/activerecord/lib/active_record/encryption/cipher.rb#L26">list will be tried</a> until there is a successful decryption or an ActiveRecord::DecryptionError is raised. On its own, this will ensure that when you add a new key, records that are updated <em>after</em> will automatically be re-encrypted with the new key.</p>
<p>This functionality allows us to reuse our migration strategy (see code sample 5) to re-encrypt all records on a model with the new encryption key. We do this simply by adding a new key and running the migration to re-encrypt.</p>
<h2 id="example-migration-strategy"><a class="heading-link" href="#example-migration-strategy">Example migration strategy<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>This section will describe a simplified version of our migration process you can replicate in your application. We use a previous encryptor to implement safe plaintext support and the <a href="https://github.com/Shopify/maintenance_tasks">maintanence_tasks</a> gem to backfill the existing records.</p>
<h3 id="set-up-activerecordencryption-and-create-a-previous-encryptor"><a class="heading-link" href="#set-up-activerecordencryption-and-create-a-previous-encryptor">Set up <code>ActiveRecord::Encryption</code> and create a previous encryptor<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>Because this is a simplified example of our own migration strategy, we recommend using a previous encryptor to restrict the “plaintext mode” of <code>ActiveRecord::Encryption</code> to the specific model(s) being migrated.</p>
<p>Set up <code>ActiveRecord::Encryption</code> by generating random key set:</p>
<pre><code>bin/rails db:encryption:init
</code></pre>
<p>And adding it to the encrypted Rails.application.credentials using:</p>
<pre><code>bin/rails credentials:edit
</code></pre>
<p>If you do not have a master.key, this command will generate one for you. Remember never to commit your master key!</p>
<p><strong>Create a previous encryptor.</strong> Remember, when you provide a previous strategy, <code>ActiveRecord::Encryption</code> will use the previous to <em>decrypt</em> and the current (in this case ActiveRecord’s default encryptor) to <em>encrypt</em> the records.</p>
<h4 id="code-sample-3"><a class="heading-link" href="#code-sample-3">Code sample 3<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h4>
<pre><code>app/lib/encryption/previous_encryptor.rb

# frozen_string_literal: true

module Encryption
  class PreviousEncryptor
    def encrypt(clear_text, key_provider: nil, cipher_options: {})
        raise NotImplementedError.new("This method should not be called")
    end

    def decrypt(previous_data, key_provider: nil, cipher_options: {})
      # JSON schema validation
        previous_data
    end
  end
end
</code></pre>
<p>Add the previous encryptor to the encrypted column</p>
<h4 id="code-sample-4"><a class="heading-link" href="#code-sample-4">Code sample 4<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h4>
<pre><code>app/models/secret.rb
class Secret &lt; ApplicationRecord
  encrypts :code, previous: { encryptor: Encryption::PreviousEncryptor.new }
end
</code></pre>
<p>The <code>PreviousEncryptor</code> will allow plaintext records to be read as plaintext but will encrypt all new records up until and while the task is running.</p>
<h3 id="install-the-maintenance-tasks-gem-and-create-a-task"><a class="heading-link" href="#install-the-maintenance-tasks-gem-and-create-a-task">Install the Maintenance Tasks gem and create a task<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>Install the <a href="[https://github.com/Shopify/maintenance_tasks](https://github.com/Shopify/maintenance_tasks)">Maintenance Tasks gem</a> per the instructions and you will be ready to create the maintenance task.</p>
<p>Create the task.</p>
<pre><code>bin/rails generate maintenance_tasks:task encrypt_plaintext_secrets
</code></pre>
<p>In day‐to‐day use, you shouldn’t ever need to call <code>secret.encrypt</code> because ActiveRecord handles the encryption before inserting into the database, but we can use this API in our task:</p>
<h4 id="code-sample-5"><a class="heading-link" href="#code-sample-5">Code sample 5<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h4>
<pre><code>app/tasks/maintenance/encrypt_plaintext_secrets_task.rb

# frozen_string_literal: true

module Maintenance
  class EncryptPlaintextSecretsTask &lt; MaintenanceTasks::Task
    def collection
      Secret.all
    end

    def process(element)
      element.encrypt
    end
      …
  end
end
</code></pre>
<h3 id="run-the-maintenance-task"><a class="heading-link" href="#run-the-maintenance-task">Run the Maintenance Task<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>Maintenance Tasks provides <a href="https://github.com/Shopify/maintenance_tasks#running-a-task">several options</a> to run the task, but we use the web UI in this example:</p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2022/11/maintenancetasks.png?w=1024&#038;resize=1024%2C643" alt="Screenshot of the Maintenance Tasks web UI." width="1024" height="643" class="aligncenter size-large wp-image-68415 width-fit" srcset="https://github.blog/wp-content/uploads/2022/11/maintenancetasks.png?w=1600 1600w, https://github.blog/wp-content/uploads/2022/11/maintenancetasks.png?w=300 300w, https://github.blog/wp-content/uploads/2022/11/maintenancetasks.png?w=768 768w, https://github.blog/wp-content/uploads/2022/11/maintenancetasks.png?w=1024 1024w, https://github.blog/wp-content/uploads/2022/11/maintenancetasks.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<h3 id="verify-your-encryption-and-cleanup"><a class="heading-link" href="#verify-your-encryption-and-cleanup">Verify your encryption and cleanup<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>You can verify encryption in Rails console, if you like:</p>
<p><img data-recalc-dims="1" loading="lazy" decoding="async" src="https://github.blog/wp-content/uploads/2022/11/verifyencryption.png?w=1024&#038;resize=1024%2C221" alt="Screenshot of the Rails console" width="1024" height="221" class="aligncenter size-large wp-image-68416 width-fit" srcset="https://github.blog/wp-content/uploads/2022/11/verifyencryption.png?w=1600 1600w, https://github.blog/wp-content/uploads/2022/11/verifyencryption.png?w=300 300w, https://github.blog/wp-content/uploads/2022/11/verifyencryption.png?w=768 768w, https://github.blog/wp-content/uploads/2022/11/verifyencryption.png?w=1024 1024w, https://github.blog/wp-content/uploads/2022/11/verifyencryption.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></p>
<p>And now you should be able to safely remove your previous encryptor leaving the model of your newly encrypted column looking like this:</p>
<h4 id="code-sample-6"><a class="heading-link" href="#code-sample-6">Code sample 6<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h4>
<pre><code>app/models/secret.rb

class Secret &lt; ApplicationRecord
  encrypts :code
end
</code></pre>
<h2 id="and-so-can-you"><a class="heading-link" href="#and-so-can-you">And so can you!<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Encrypting database columns is a valuable extra layer of security that can protect sensitive data during exploits, but it’s not always easy to migrate data in an existing application. We wrote <a href="https://github.blog/2022-10-26-why-and-how-github-encrypts-sensitive-database-columns-using-activerecordencryption/">this series</a> in the hope that more organizations will be able to plot a clear path forward to using <code>ActiveRecord::Encryption</code> to start encrypting existing sensitive values.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/how-github-converts-previously-encrypted-and-unencrypted-columns-to-activerecord-encrypted-columns/">How GitHub converts previously encrypted and unencrypted columns to ActiveRecord encrypted columns</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">68413</post-id>	</item>
		<item>
		<title>Why and how GitHub encrypts sensitive database columns using ActiveRecord::Encryption</title>
		<link>https://github.blog/engineering/infrastructure/why-and-how-github-encrypts-sensitive-database-columns-using-activerecordencryption/</link>
		
		<dc:creator><![CDATA[Kylie Stradley]]></dc:creator>
		<pubDate>Wed, 26 Oct 2022 14:00:25 +0000</pubDate>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[ActiveRecord Encryption]]></category>
		<category><![CDATA[Column Encryption]]></category>
		<category><![CDATA[Database Encryption]]></category>
		<guid isPermaLink="false">https://github.blog/?p=68159</guid>

					<description><![CDATA[<p>You may know that GitHub encrypts your source code at rest, but you may not have known that we encrypt sensitive database columns as well. Read about our column encryption strategy and our decision to adopt the Rails column encryption standard.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/why-and-how-github-encrypts-sensitive-database-columns-using-activerecordencryption/">Why and how GitHub encrypts sensitive database columns using ActiveRecord::Encryption</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><aside class="p-4 p-md-6 post-aside--large"><p>This blog post is the first part in a two‐part series on how GitHub is using <code>ActiveRecord::Encryption</code>.</p>
</aside>
<p>You may know that GitHub <a href="https://github.blog/changelog/2019-05-23-git-data-encryption-at-rest/">encrypts your source code at rest</a>, but you may not have known that we also encrypt sensitive database columns in our Ruby on Rails monolith. We do this to provide an additional layer of defense in depth to mitigate concerns, such as:</p>
<ul>
<li>Reading or tampering with sensitive fields if a database is inappropriately accessed</li>
<li>Accidentally exposing sensitive data in logs</li>
</ul>
<h2 id="motivation"><a class="heading-link" href="#motivation">Motivation<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>Until recently, we used an internal library called Encrypted Attributes. GitHub developers would declare a column should be encrypted using an API that might look familiar if you have used <a href="https://edgeguides.rubyonrails.org/active_record_encryption.html#declaration-of-encrypted-attributes"><code>ActiveRecord::Encryption</code></a>:</p>
<pre><code class="language-ruby">class TotpAppRegistration
  encrypted_attribute :encrypted_otp_secret, :plaintext_otp_secret
end
</code></pre>
<p>Given that we had an existing implementation, you may be wondering why we chose to take on the work of converting our columns to <code>ActiveRecord::Encryption</code>. Our main motivation was to ensure that developers did not have to learn a GitHub-specific pattern to encrypt their sensitive data.</p>
<p><p class="purple-text text-gradient-purple-coral mt-6 mb-6">We believe strongly that using familiar, intuitive patterns results in better adoption of security tools and, by extension, better security for our users.</p></p>
<p>In addition to exposing some of the implementation details of the underlying encryption, this API did not provide an easy way for developers to encrypt existing columns. Our internal library required a separate encryption key to be generated and stored in our secure environment variable configuration—for each new database column. This created a bottleneck, as most developers don’t work with encryption every day and needed support from the security team to make changes.</p>
<p>When assessing <code>ActiveRecord::Encryption</code>, we were particularly interested in its ease of use for developers. We wanted a developer to be able to write one line of code, and no matter if their column was previously plaintext or used our previous solution, their column would magically start using <code>ActiveRecord::Encryption</code>. The final API looks something like this:</p>
<pre><code class="language-ruby">class TotpAppRegistration
  encrypts :encrypted_otp_secret
end
</code></pre>
<p>This API is the exact same as what is used by traditional <code>ActiveRecord::Encryption</code> while hiding all the complexity of making it work at GitHub scale.</p>
<h2 id="how-we-implemented-this"><a class="heading-link" href="#how-we-implemented-this">How we implemented this<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>As part of implementing <code>ActiveRecord::Encryption</code>into our monolith, we worked with our architecture and infrastructure teams to make sure the solution met GitHub’s scalability and security requirements. Below is a brief list of some of the customizations we made to fit the implementation to our infrastructure.</p>
<p>As always, there are specific nuances that must be considered when modifying existing encryption implementations, and it is always a good practice to review any new cryptography code with a security team.</p>
<p><figure id="attachment_68163"  class="wp-caption aligncenter mx-0"><img data-recalc-dims="1" loading="lazy" decoding="async" width="958" height="555" src="https://github.blog/wp-content/uploads/2022/10/activerecord1.jpeg?w=958&#038;resize=958%2C555" alt="" class="width-fit size-large wp-image-68163 width-fit" srcset="https://github.blog/wp-content/uploads/2022/10/activerecord1.jpeg?w=958 958w, https://github.blog/wp-content/uploads/2022/10/activerecord1.jpeg?w=300 300w, https://github.blog/wp-content/uploads/2022/10/activerecord1.jpeg?w=768 768w" sizes="auto, (max-width: 958px) 100vw, 958px" /><figcaption class="text-mono color-fg-muted mt-14px f5-mktg">Diagram 1: Key access and derivation flow for GitHub’s `ActiveRecord::Encryption` implementation</figcaption></figure></p>
<h3 id="secure-primary-key-storage"><a class="heading-link" href="#secure-primary-key-storage">Secure primary key storage<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>By default, Rails uses its <a href="https://edgeguides.rubyonrails.org/security.html#custom-credentials">built-in credentials.yml.enc</a> file to securely store the primary key and static salt used for deriving the column encryption key in <code>ActiveRecord::Encryption</code>.</p>
<p>GitHub’s key management strategy for <code>ActiveRecord::Encryption</code> differs from the Rails default in two key ways: deriving a separate key per column and storing the key in our centralized secret management system.</p>
<h2 id="deriving-per-column-keys-from-a-single-primary-key"><a class="heading-link" href="#deriving-per-column-keys-from-a-single-primary-key">Deriving per-column keys from a single primary key<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>As explained above, one of the goals of this transition was to no longer bottleneck teams by managing keys manually. We did, however, want to maintain the security properties of separate keys. Thankfully, cryptography experts have created a primitive known as a Key Derivation Function (KDF) for this purpose. These functions take (roughly) three important parameters: the primary key, a unique salt, and a string termed “info” by the spec.</p>
<p>Our salt is simply the table name, an underscore, and the attribute name. So for <code>TotpAppRegistrations#encrypted_otp_secret</code> the salt would be <code>totp_app_registrations_encrypted_otp_secret</code>. This ensures the key is different per column.</p>
<p>Due to the specifics of the <code>ActiveRecord::Encryption</code> algorithm (AES256-GCM), we need to be careful not to encrypt too many values using the same key (to avoid <a href="https://github.com/christianlundkvist/blog/blob/master/2021_01_25_nonce_reuse_in_encryption/nonce_reuse_in_encryption.md">nonce reuse</a>). We use the “info” string parameter to ensure the key for each column changes automatically at least once per year. Therefore, we can populate the <a href="https://www.rfc-editor.org/rfc/rfc5869.html#section-3.2">info</a> input with the current year as <a href="https://en.wikipedia.org/wiki/Cryptographic_nonce">a nonce</a> during key derivation.</p>
<p>The applications that make up GitHub store secrets in <a href="https://www.vaultproject.io/">Hashicorp Vault</a>. To conform with this pre-existing pattern, we wanted to pull our primary key from Vault instead of the credentials.yml.enc file. To accommodate for this, we wrote a custom key provider that behaves similarly to the default <a href="https://edgeguides.rubyonrails.org/active_record_encryption.html#derivedsecretkeyprovider"><code>DerivedSecretKeyProvider</code></a>, retrieving the key from Vault and deriving the key with our KDF (see Diagram 1).</p>
<h2 id="making-new-behavior-the-default"><a class="heading-link" href="#making-new-behavior-the-default">Making new behavior the default<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>One of our team’s key principles is that solutions we develop should be intuitive and not require implementation knowledge on the part of the product developer. ActiveRecord::Encryption includes functionality to customize the Encryptor used to encrypt data for a given column. This functionality would allow developers to optionally use the strategies described above, but to make it the default for our monolith we needed to override the <code>encrypts</code> <a href="https://api.rubyonrails.org/classes/ActiveRecord/Encryption/EncryptableRecord.html#method-i-encrypts">model helper</a> to automatically select an appropriate GitHub-specific key provider for the user.</p>
<pre><code>{
def self.encrypts(*attributes, key_provider: nil, previous: nil, **options)
      # snip: ensure only one attribute is passed
# ...

    # pull out the sole attribute
    attribute = attributes.sole

      # snip: ensure if a key provider is passed, that it is a GitHubKeyProvider
      # ...

    # If no key provider is set, instantiate one
    kp = key_provider || GitHub::Encryption::GitHubKeyProvider.new(table: table_name.to_sym, attribute: attribute)

      # snip: logic to ensure previous encryption formats and plaintext are supported for smooth transition (see part 2)
      # github_previous = ...

    # call to rails encryption
    super(attribute, key_provider: kp, previous: github_previous, **options)
end
}
</code></pre>
<p>Currently, we only provide this API to developers working on our internal <code>github.com</code> codebase. As we work with the library, we are experimenting with upstreaming this strategy to <code>ActiveRecord::Encryption</code> by replacing the per-class encryption scheme with a per-column encryption scheme.</p>
<h3 id="turn-off-compression-by-default"><a class="heading-link" href="#turn-off-compression-by-default">Turn off compression by default<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h3>
<p>Compressing values prior to encryption can reveal some information about the content of the value. For example, a value with more repeated bytes, such as “abcabcabc,” will compress better than a string of the same length, such as “abcdefghi”. In addition to the common encryption property that ciphertext generally exposes the length, this exposes additional information about the entropy (randomness) of the underlying plaintext.</p>
<p><code>ActiveRecord::Encryption</code> compresses data by default for storage efficiency purposes, but since the values we are encrypting are relatively small, we did not feel this tradeoff was worth it for our use case. This is why we replaced the default to compress values before encryption with a flag that makes compression optional.</p>
<h2 id="migrating-to-a-new-encryption-standard-the-hard-parts"><a class="heading-link" href="#migrating-to-a-new-encryption-standard-the-hard-parts">Migrating to a new encryption standard: the hard parts<span class="heading-hash pl-2 text-italic text-bold" aria-hidden="true"></span></a></h2>
<p>This post illustrates some of the design decisions and tradeoffs we encountered when choosing ActiveRecord::Encryption, but it’s not quite enough information to guide developers of existing applications to start encrypting columns. In the next post in this series we’ll show you how we handled the hard parts—how to upgrade existing columns in your application from plaintext or possibly another encryption standard.</p>
<p>The post <a href="https://github.blog/engineering/infrastructure/why-and-how-github-encrypts-sensitive-database-columns-using-activerecordencryption/">Why and how GitHub encrypts sensitive database columns using ActiveRecord::Encryption</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">68159</post-id>	</item>
	</channel>
</rss>
