
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Mon, 25 May 2026 19:48:05 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Our billing pipeline was suddenly slow. The culprit was a hidden bottleneck in ClickHouse]]></title>
            <link>https://blog.cloudflare.com/clickhouse-query-plan-contention/</link>
            <pubDate>Thu, 14 May 2026 13:00:00 GMT</pubDate>
            <description><![CDATA[ When a partitioning change to our petabyte-scale ClickHouse cluster caused critical billing jobs to stall, standard metrics showed no obvious errors. This post explores how we identified severe lock contention in ClickHouse's query planner and built upstream patches to fix it. ]]></description>
            <content:encoded><![CDATA[ <p>At Cloudflare, we are heavy users of ClickHouse, an open source online analytical processing (OLAP) database. Every day, we make millions of calls to ClickHouse to determine how much users should be billed for their usage of Cloudflare products. If we don't finish those jobs in a timely fashion, the invoices become very difficult to reconcile.</p><p>This pipeline powers hundreds of millions of dollars in usage revenue, fraud systems, and more, so being delayed has major downstream implications.</p><p>Which is why it was a big problem when the daily aggregation jobs in ClickHouse – responsible for ensuring Cloudflare’s bills go out – had slowed way down, following a migration. All the usual suspects looked clean: I/O, memory, rows scanned, parts read. Everything we would normally check when a ClickHouse query is slow appeared to be normal. </p><p>This is the story of how we discovered a hidden bottleneck buried deep within ClickHouse’s internals, and the three patches we wrote to fix it.</p>
    <div>
      <h2>The setup: a petabyte-scale analytics platform</h2>
      <a href="#the-setup-a-petabyte-scale-analytics-platform">
        
      </a>
    </div>
    <p>We use ClickHouse to store over a hundred petabytes of data across a few dozen clusters. To simplify onboarding for our many internal teams, we built a system called "Ready-Analytics" in early 2022.</p><p>The premise is simple: instead of designing new tables, teams can stream data into a single, massive table. Datasets are disambiguated by a <code>namespace</code>, and each record uses a standard schema (e.g., 20 float fields, 20 string fields, a timestamp, and an <code>indexID</code>). </p><p>In ClickHouse, the way data is sorted is crucial to query performance. This is where the <code>indexID</code> comes into play. It’s a string field, which forms part of the primary key, meaning that every individual namespace can have its data sorted in a way that is optimal for the queries the owners of that namespace expect to be running. Altogether, we end up with a primary key that looks like this: (<code>namespace</code>, <code>indexID</code>, <code>timestamp</code>).</p><p>This system is popular, with hundreds of applications using it. It had already grown to more than 2PiB of data by December 2024, and an ingestion rate of millions of rows per second. But it had one critical flaw: its retention policy.</p>
    <div>
      <h2>The problem: one retention policy to rule them all</h2>
      <a href="#the-problem-one-retention-policy-to-rule-them-all">
        
      </a>
    </div>
    <p>Cloudflare has been using ClickHouse for many years, since before it had native Time-to-Live (TTL) features. Consequently, we built our own retention system based on partitioning. The Ready-Analytics table was partitioned by <code>day</code>, and our retention job simply dropped partitions older than 31 days.</p><p>This "one-size-fits-all" 31-day retention was a major limitation. Some teams needed to store data for years due to legal or contractual obligations, while others needed only a few days. This restriction meant these use cases couldn't use Ready-Analytics and had to opt for a conventional setup, which has a far more complex onboarding process.</p><p>We needed a new system that allowed <b>per-namespace retention</b>.</p>
    <div>
      <h2>The solution: a new partitioning scheme</h2>
      <a href="#the-solution-a-new-partitioning-scheme">
        
      </a>
    </div>
    <p>We considered two main approaches:</p><ol><li><p><b>A Table-per-Namespace:</b> This would naturally solve the retention problem but would require significant new automation to manage thousands of tables on demand.</p></li><li><p><b>A New Partitioning Key:</b> We could change the partitioning key from just <code>(day)</code> to <code>(namespace, day)</code>.</p></li></ol><p>We chose the second option. This would allow our existing retention system to continue managing partitions, but now with per-namespace granularity.</p><p>We knew this would increase the total number of data parts in the table, but we made a key assumption: <b>since every query is filtered by a specific namespace, the </b><b><i>number of parts read by any single query</i></b><b> shouldn't change.</b> We believed this meant performance would be unaffected.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6d3ywoUP3OE2DCRAJtAgF7/5aa62c5702a923e30889ebf787925ae8/BLOG-3299_image4.png" />
          </figure><p><sup><i>This shows how we changed the partitioning, allowing us to cheaply drop data for a single namespace</i></sup></p><p>This new system also allowed us to build a sophisticated storage management layer. Using the<a href="https://en.wikipedia.org/wiki/Max-min_fairness"> <u>max-min fairness algorithm</u></a>, we could set a target disk utilization (e.g., 90%) and automatically "share" available space. Namespaces using less than their fair share would cede their unused capacity to those that needed more. This allowed us to confidently run our clusters at 90% utilization.</p><p>We began the migration in January 2025. Using ClickHouse's <code>Merge</code> table feature, we combined the old and new tables, writing all new data to the new partitioned table while the old data aged out.</p>
    <div>
      <h2>The mystery: when billing starts to break</h2>
      <a href="#the-mystery-when-billing-starts-to-break">
        
      </a>
    </div>
    <p>Two months later, in late March 2025, our billing team reported that their daily aggregation jobs were slowing down. These jobs are time-critical; if they don't finish, bills don't go out. The jobs were getting progressively slower, and we were approaching a deadline.</p><p>We investigated, but none of the usual suspects were to blame. I/O was fine. Memory was fine. The metrics for individual queries showed they were <i>not</i> reading more data or more parts than before. Our initial assumption seemed correct, yet the system was grinding to a halt.</p><p>It took several days before we even had a theory. Finally, we made a plot of query duration against the <i>total part count</i> in the cluster. The correlation was undeniable.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/69SY5BVn3bPW5O9n5LrVjU/c7b99fda71d57156fa2784947db675f4/BLOG-3299_image2.png" />
          </figure><p><sup><i>Average SELECT Query Durations on the Ready Analytics ClickHouse Cluster, showing progressive performance degradation.</i></sup></p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2mt08KB04Y3n0vsNJAfK81/86da06fe572126e0e777f7dc4c00d7fb/BLOG-3299_image1.png" />
          </figure><p><sup><i>Linear Growth in Total Data Part Count per Table Replica, following the new (namespace, day) partitioning scheme.</i></sup></p><p>But <i>why</i>? If we weren't <i>reading</i> the extra parts, why did their mere existence slow us down?</p>
    <div>
      <h2>The investigation: hunting bottlenecks with flame graphs</h2>
      <a href="#the-investigation-hunting-bottlenecks-with-flame-graphs">
        
      </a>
    </div>
    <p>We turned to ClickHouse's built-in <a href="https://clickhouse.com/docs/operations/system-tables/trace_log"><code><u>trace_log</u></code></a> to generate flame graphs. This is a built-in table that records traces from the running ClickHouse server. It not only includes traces of what code is being executed, but it associates these with specific users, query IDs and other metadata, meaning you can filter down to quite precise sets of events if necessary. In our case, we wanted to look specifically at <i>leaf SELECT queries</i>. This was easy thanks to the available metadata in this table.</p><p>The first CPU-based flame graph quickly confirmed our suspicion: a huge amount of time was being spent in <b>query planning</b>. This is the phase <i>before</i> execution when ClickHouse decides which parts to read.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4Tppo6pF4ePCWTTC1lWi03/a1582ce51ef60116a1848cab83fe9516/BLOG-3299_image7.png" />
          </figure><p><sup><i>Flame graph showing that 45% of leaf query CPU time is spent filtering a vector of parts based on the partition ID</i></sup></p><p>The flame graph was clear: 45% of the sampled CPU time was being spent in a single function called <code>filterPartsByPartition</code>.</p><p>Our first attempt at a fix was a small patch to this exact code path. The planner evaluates heuristics to prune parts, and we believed they weren't being evaluated in the optimal order for our table. Our patch changed the order, yielding a small 5% improvement. We were on the right path, but we'd missed the real problem.</p><p>We had been generating "CPU" traces, which only sample active threads. We switched to "Real" traces, which sample <i>all</i> threads, including those that are inactive or waiting. The new flame graph was a revelation.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/715i8uMnak2T9Sdf4Jtcsy/1c962dbef3692257679136f6c3d36dca/BLOG-3299_image10.png" />
          </figure><p><sup><i>Flame graph showing that more than half of leaf query duration is spent waiting for a mutex that protects the list of active parts</i></sup></p><p>The problem wasn't CPU-bound work; it was <b>massive lock contention</b>. More than half of our query duration was spent <i>waiting</i> to acquire a single mutex (<code>MergeTreeData</code>) that protects the table's list of parts. To plan a query, every single thread had to:</p><ol><li><p>Acquire an <b>exclusive lock</b> on this mutex.</p></li><li><p>Make a complete copy of the list of <i>all</i> parts in the table.</p></li><li><p>Release the lock.</p></li><li><p>Filter that list down to the relevant parts.</p></li></ol><p>With tens of thousands of parts and hundreds of concurrent queries, they were all just standing in a single-file line.</p>
    <div>
      <h2>The fixes: a trio of patches</h2>
      <a href="#the-fixes-a-trio-of-patches">
        
      </a>
    </div>
    <p>This insight helped us plan a series of optimizations to alleviate these hotspots. As with all the patches we make to ClickHouse, we try to make them generic, and eventually get them contributed to the upstream codebase. This makes it easier for us to maintain our fork, and means the community benefits from the changes we make too!</p>
    <div>
      <h3>Optimization 1: use a shared lock</h3>
      <a href="#optimization-1-use-a-shared-lock">
        
      </a>
    </div>
    <p>The query planner doesn't <i>modify</i> the parts list; it just <i>reads</i> it. It had no business using an exclusive lock.</p><p><b>The Fix:</b> We modified the code to acquire a <b>shared lock</b> (<code>std::shared_lock</code>) instead. This allowed all query planners to enter the critical section concurrently.</p><p><b>The Result:</b> A massive, immediate drop in query duration. The lock contention vanished.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5Kj9AqPYFnhQjQya9C1U5y/122aff70ba4aae88c7fdbc479edd0147/BLOG-3299_image8.png" />
          </figure><p><sup><i>Immediate Impact of the Shared Lock Optimization (Optimization 1) on Average SELECT Query Durations, demonstrating the resolution of lock contention.</i></sup></p>
    <div>
      <h3>Optimization 2: stop copying the vector</h3>
      <a href="#optimization-2-stop-copying-the-vector">
        
      </a>
    </div>
    <p>Performance was significantly better, but still not back to baseline. We went back to the trace log and made another ‘Real’ flame graph.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/77AAAaeIqq6wRHaG8LjVeI/6dccdbf2c8b0c334c1d05b930572ab05/BLOG-3299_image5.png" />
          </figure><p><sup><i>Flame graph showing that we spend a quarter of leaf query duration copying the vector of all parts, and another quarter filtering through it (copying again).</i></sup></p><p>The new flame graph showed the bottleneck had simply moved. Now, time was being spent <i>copying</i> the giant vector of parts, even with the shared lock. Intuitively, copying a vector sounds cheap, but when it contains tens of thousands of elements, and you do it hundreds of times a second, it adds up.</p><p><b>The Fix:</b> We deferred the copy entirely. We created a "shared copy" of the parts list. Read-only operations (like query planning) just read from this copy. Any operation that <i>modifies</i> the set of parts (like a new insert) regenerates the cache. Planners now only copy the <i>filtered</i> list of parts they actually need.</p><p><b>The Result:</b> Another significant performance improvement.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1d63suwP2kJZPqbzMQN1XQ/8ed1c519d1929f27d920d5a740b6b09f/BLOG-3299_image6.png" />
          </figure><p><sup><i>Further Performance Improvement After Rolling Out the Vector Copy Optimization (Optimization 2).</i></sup></p><p>After seeing these massive savings internally, we decided to bring these changes to the community. After some small design iterations with the maintainers at ClickHouse Inc., we got the changes merged under<a href="https://github.com/ClickHouse/ClickHouse/pull/85535"> <u>PR #85535</u></a><i>.</i> They have been available since <a href="https://clickhouse.com/docs/whats-new/changelog/2025#performance-improvement-1"><u>ClickHouse version 25.11</u></a>.</p>
    <div>
      <h3>Optimization 3: binary search for parts</h3>
      <a href="#optimization-3-binary-search-for-parts">
        
      </a>
    </div>
    <p>We're still not done. As part counts grow, performance <i>still</i> degrades, just much more slowly. The correlation with part count was still there. Coming back to this after a few months, a new flame graph (looking the same as Figure 3) shows the time is spent in the filtering code path (the one we tried to fix first). This code performs a <b>linear scan</b> over all parts, evaluating predicates against each one. Over a few months, we were back to select durations from before the optimizations.</p><p>But we know this list of parts is sorted by the partitioning key. Remember that the first column of the partition key is namespace, which the vast majority of queries filter on, because it identifies the “tenant.” How can we make use of this?</p><p><b>The Fix:</b> We implemented a binary search based on the <code>namespace</code> part of the partition ID. This works because the vector is sorted, so you can filter out a lot of the entries without actually looking at them. This is particularly effective since the <code>namespace</code> is the first part of that sorting key. After this first-pass of binary search, we have a much smaller range of parts we need to examine, and for those we still step through each one, applying the same logic as before to exclude parts based on other conditions.</p><p><b>The Result:</b> After deploying this patch in March 2026, query durations dropped by 50% (see Figure 8). More importantly, this finally breaks correlation of query durations with the number of parts. Unfortunately, this solution doesn’t generalize that well for arbitrary query conditions (e.g. conditions such as <code>namespace in (5,10)</code>). We are looking into more generic approaches like extending the <a href="https://clickhouse.com/blog/introducing-the-clickhouse-query-condition-cache"><u>query condition cache</u></a> to cover part filtering.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4leNlnUeD3RIQVPT2VN1IB/90b76933cfdd24955c51d4c00287b9b0/BLOG-3299_image3.png" />
          </figure><p><sup><i>Sustained Latency Reduction Following the Implementation of Binary Search for Part Pruning (Optimization 3).</i></sup></p>
    <div>
      <h2>An uneasy truce</h2>
      <a href="#an-uneasy-truce">
        
      </a>
    </div>
    <p>These optimizations resolved the immediate crisis with the billing system. But this journey exposed the deep, non-obvious costs of our partitioning choice.</p><p>Other problems remain. In this blog post we’ve only described the problems increasing part counts had on our select durations, but it has also caused problems for ZooKeeper, which tracks metadata for all the parts in ClickHouse. Perhaps one day we’ll tell the story of the 100 gigabyte ZooKeeper cluster.</p><p>We've bought ourselves significant breathing room, but the fundamental question remains: Was this partitioning scheme the right long-term choice? Or will we eventually need to bite the bullet and move to a different architecture? For now, our patches are holding, but the experience was a clear example of how even a well-planned change can fall victim to incorrect assumptions.</p><p>When the billing team first reported this problem we had 30,000 parts per replica. The part rate never stopped growing, and a year later we hit 160k parts per replica, but query durations have been stable thanks to the optimizations we made here.</p><p>At Cloudflare, we solve complex engineering problems at a massive scale. If the debugging and optimizations we described here sound like the type of challenge you’re looking for, check out some of the <a href="https://www.cloudflare.com/careers/jobs/?department=Engineering"><u>open roles</u></a> we are hiring for.</p> ]]></content:encoded>
            <category><![CDATA[ClickHouse]]></category>
            <category><![CDATA[Engineering]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Database]]></category>
            <category><![CDATA[Open Source]]></category>
            <guid isPermaLink="false">65sE8zefBt1cYbclYLhqjA</guid>
            <dc:creator>James Morrison</dc:creator>
            <dc:creator>Christian Endres</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare incident on November 14, 2024, resulting in lost logs]]></title>
            <link>https://blog.cloudflare.com/cloudflare-incident-on-november-14-2024-resulting-in-lost-logs/</link>
            <pubDate>Tue, 26 Nov 2024 16:00:00 GMT</pubDate>
            <description><![CDATA[ On November 14, 2024, Cloudflare experienced a Cloudflare Logs outage, impacting the majority of customers using these products. During the ~3.5 hours that these services were impacted, about 55% of the logs we normally send to customers were not sent and were lost. The details of what went wrong and why are interesting both for customers and practitioners. ]]></description>
            <content:encoded><![CDATA[ <p>On November 14, 2024, Cloudflare experienced an incident which impacted the majority of customers using <a href="https://developers.cloudflare.com/logs"><u>Cloudflare Logs</u></a>. During the roughly 3.5 hours that these services were impacted, about 55% of the logs we normally send to customers were not sent and were lost. We’re very sorry this happened, and we are working to ensure that a similar issue doesn't happen again.</p><p>This blog post explains what happened and what we’re doing to prevent recurrences. Also, the systems involved and the particular class of failure we experienced will hopefully be of interest to engineering teams beyond those specifically using these products.</p><p>Failures within systems at scale are inevitable, and it’s essential that subsystems protect themselves from failures in other parts of the larger system to prevent cascades. In this case, a misconfiguration in one part of the system caused a cascading overload in another part of the system, which was itself misconfigured. Had it been properly configured, it could have prevented the loss of logs.</p>
    <div>
      <h2>Background</h2>
      <a href="#background">
        
      </a>
    </div>
    <p>Cloudflare’s network is a globally distributed system enabling and supporting a wide variety of services. Every part of this system generates event logs which contain detailed metadata about what’s happening with our systems around the world. For example, an event log is generated for every request to Cloudflare’s CDN. <a href="https://developers.cloudflare.c/om/logs"><u>Cloudflare Logs</u></a> makes these event logs available to customers, who use them in a number of ways, including compliance, observability, and accounting.</p><p>On a typical day, Cloudflare sends about 4.5 trillion individual event logs to customers. Although this represents less than 10% of the over 50 trillion total customer event logs processed, it presents unique challenges of scale when building a reliable and fault-tolerant system.</p>
    <div>
      <h2>System architecture</h2>
      <a href="#system-architecture">
        
      </a>
    </div>
    <p>Cloudflare’s network is composed of tens of thousands of individual servers, network hardware components, and specialized software programs located in over 330 cities around the world. Although Cloudflare’s <a href="https://developers.cloudflare.com/logs/edge-log-delivery/"><u>Edge Log Delivery</u></a> product will send customers their event logs directly from each server, most customers opt not to do this because doing so will create significant complication and cost at the receiving end.</p><p>By analogy, imagine the postal service ringing your doorbell once for each letter instead of once for each packet of letters. With thousands or millions of letters each second, the number of separate transactions that would entail becomes prohibitive.</p><p>Fortunately, we also offer <a href="https://developers.cloudflare.com/logs/about/"><u>Logpush</u></a>, which collects and pushes logs to customers in more predictable file sizes and which scales automatically with usage. In order to provide this feature several services work together to collect and push the logs, as illustrated in the diagram below:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6pRdzUUrMbwG3AWsPncB3w/75146d1e379ccb126d8cd0210a6c12b8/image2.png" />
          </figure>
    <div>
      <h3>Logfwdr</h3>
      <a href="#logfwdr">
        
      </a>
    </div>
    <p>Logfwdr is an internal service written in Golang that accepts event logs from internal services running across Cloudflare’s global network and forwards them in batches to a service called Logreceiver. Logfwdr handles many different types of event logs, and one of its responsibilities is to determine which event logs should be forwarded and where they should be sent based on the type of event log, which customers it represents, and associated rules about where it should be processed. Configuration is provided to Logfwdr to enable it to make these determinations.</p>
    <div>
      <h3>Logreceiver</h3>
      <a href="#logreceiver">
        
      </a>
    </div>
    <p>Logreceiver (also written in Golang) accepts the batches of logs from across Cloudflare’s global network and further sorts them depending on the type of event and its purpose. For Cloudflare Logs, Logreceiver demultiplexes the batches into per-customer batches and forwards them to be buffered by Buftee. Currently, Logreceiver is handling about 45 PB (uncompressed) of customer event logs each day.</p>
    <div>
      <h3>Buftee</h3>
      <a href="#buftee">
        
      </a>
    </div>
    <p>It’s common for data pipelines to include a buffer. Producers and consumers of the data might be operating at different cadences, and parts of the pipeline will experience variances in how quickly they can process information. Using a <a href="https://en.wikipedia.org/wiki/Data_buffer"><u>buffer</u></a> makes it easier to manage these situations, and helps to prevent data loss if downstream consumers are broken. It’s also convenient to have a buffer that supports multiple downstream consumers with different cadences (<a href="https://en.wikipedia.org/wiki/Tee_(command)"><u>like the pipe fitting function of a tee</u></a>.)</p><p>At Cloudflare, we use an internal system called Buftee (written in Golang) to support this combined function. Buftee is a highly distributed system which supports a large number of named “buffers”. It supports operating on named “prefixes” (collections of buffers) as well as multiple representations/resolutions of the same time-indexed dataset. Using Buftee makes it possible for Cloudflare to handle extremely high throughput very efficiently.</p><p>For Cloudflare Logs, Buftee provides buffers for each Logpush job, containing 100% of the logs generated by the zone or account referenced by each job. This means that failure to process one customer’s job will not affect progress on another customer’s job. Handling buffers in this way avoids “head of line” blocking and also enables us to encrypt and delete each customer’s data separately if needed.</p><p>Buftee typically handles over 1 million buffers globally. The following is a snapshot of the number of buffers managed by Buftee servers in the period just prior to the incident.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5g4Ev1CX8pGqtpNUY3vZOo/50d8dfa7a1e9c492a537e4822d801625/image5.png" />
          </figure>
    <div>
      <h3>Logpush</h3>
      <a href="#logpush">
        
      </a>
    </div>
    <p>Logpush is a Golang service which reads logs from Buftee buffers and pushes the results in batches to various destinations configured by customers. A batch could end up, for example, as a file in R2. Each job has a unique configuration, and only jobs that are active and configured will be pushed. Currently, we push over 600 million such batches each day.</p>
    <div>
      <h2>What happened</h2>
      <a href="#what-happened">
        
      </a>
    </div>
    <p>On November 14, 2024, we made a change to support an additional <a href="https://developers.cloudflare.com/logs/reference/log-fields/#datasets"><u>dataset</u></a> for Logpush. This required adding a new configuration to be provided to Logfwdr in order for it to know which customers’ logs to forward for this new stream. Every few minutes, a separate system re-generates the configuration used by Logfwdr to decide which logs need to be forwarded. A bug in this system resulted in a blank configuration being provided to Logfwdr.</p><p>This bug essentially informed Logfwdr that <b>no customers</b> had logs configured to be pushed. The team quickly noticed the mistake and reverted the change in under five minutes.</p><p>Unfortunately, this first mistake triggered a second, latent bug in Logfwdr itself. A failsafe introduced in the early days of this feature, when traffic was much lower, was configured to “fail open”. This failsafe was designed to protect against a situation when this specific Logfwdr configuration was unavailable (as in this case) by transmitting events for <b>all customers</b> instead of just those who had configured a Logpush job. This was intended to prevent the loss of logs at the expense of sending more logs than strictly necessary when individual hosts were prevented from getting the configuration due to intermittent networking errors, for example.</p><p>When this failsafe was first introduced, the potential list of customers was smaller than it is today. This small window of less than five minutes resulted in a massive spike<b> </b>in the number of customers whose logs were sent by Logfwdr.</p><p>Even given this massive overload, our systems would have continued to send logs if not for one additional problem. Remember that Buftee creates a separate buffer for each customer with their logs to be pushed. When Logfwdr began to send event logs for all customers, Buftee began to create buffers for each one as those logs arrived, and each buffer requires resources as well as the bookkeeping to maintain them. This massive increase, resulting in roughly 40 times more buffers, is not something we’ve provisioned Buftee clusters to handle. In the lead-up to impact, Buftee was managing 40 million buffers globally, as shown in the figure below.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7HPwhkvRxiAQtjVbdVxRUN/a1d4aa174961f6a163e884011c8c18ad/image3.png" />
          </figure><p>A short temporary misconfiguration lasting just five minutes created a massive overload that took us several hours to fix and recover from. Because our backstops were not properly configured, the underlying systems became so overloaded that we could not interact with them normally.  A full reset and restart was required.</p>
    <div>
      <h2>Root causes</h2>
      <a href="#root-causes">
        
      </a>
    </div>
    <p>The bug in the Logfwdr configuration system was easy to fix, but it’s the type of bug that was likely to happen at some point.  We had planned for it by designing the original “fail open” behavior.  However, we neglected to regularly test that the broader system was capable of handling a fail open event.</p><p>The bigger failure was that Buftee became unresponsive.  Buftee’s purpose is to be a safeguard against bugs like this one.  A huge increase in the number of buffers is a failure mode that we had predicted, and had put mechanisms in Buftee to prevent this failure from cascading.  Our failure in this case was that we had not configured these mechanisms.  Had they been configured correctly, Buftee would not have been overwhelmed.</p><p>It's like having a seatbelt in a car, yet not fastening it. The seatbelt is there to protect you in case of an accident but if you don't actually buckle it up, it's not going to do its job when you need it. Similarly, while we had the safeguard of Buftee in place, we hadn't 'buckled it up' by configuring the necessary settings. We’re very sorry this happened and are taking steps to prevent a recurrence as described below.</p>
    <div>
      <h2>Going forward</h2>
      <a href="#going-forward">
        
      </a>
    </div>
    <p>We’re creating alerts to ensure that these particular misconfigurations will be impossible to miss, and we are also addressing the specific bug and the associated tests that triggered this incident.</p><p>Just as importantly, we accept that mistakes and misconfigurations are inevitable. All our systems at Cloudflare need to respond to these predictably and gracefully. Currently, we conduct regular “cut tests” to ensure that these systems will cope with the loss of a datacenter or a network failure. In the future, we’ll also conduct regular “overload tests” to simulate the kind of cascade which happened in this incident to ensure that our production systems will handle them gracefully.</p><p>Logpush is a robust and flexible platform for customers who need to integrate their own logging and monitoring systems with Cloudflare. Different Logpush jobs can be deployed to support multiple destinations or, with filtering, multiple subsets of logs.</p> ]]></content:encoded>
            <category><![CDATA[Logs]]></category>
            <category><![CDATA[Data]]></category>
            <category><![CDATA[Log Push]]></category>
            <guid isPermaLink="false">3SNSdDbbVziSrdGxDq4AzH</guid>
            <dc:creator>Jamie Herre</dc:creator>
            <dc:creator>Tom Walwyn</dc:creator>
            <dc:creator>Christian Endres</dc:creator>
            <dc:creator>Gabriele Viglianisi</dc:creator>
            <dc:creator>Mik Kocikowski</dc:creator>
            <dc:creator>Rian van der Merwe</dc:creator>
        </item>
    </channel>
</rss>