
<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>Thu, 09 Apr 2026 10:04:46 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Announcing Workers automatic tracing, now in open beta]]></title>
            <link>https://blog.cloudflare.com/workers-tracing-now-in-open-beta/</link>
            <pubDate>Tue, 28 Oct 2025 12:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare Workers' support for automatic tracing is now in open beta! Export traces to any OpenTelemetry-compatible provider for deeper application observability -- no code changes required ]]></description>
            <content:encoded><![CDATA[ <p></p><p>When your Worker slows down or starts throwing errors, finding the root cause shouldn't require hours of log analysis and trial-and-error debugging. You should have clear visibility into what's happening at every step of your application's request flow. This is feedback we’ve heard loud and clear from developers using Workers, and today we’re excited to announce an Open Beta for tracing on <a href="https://www.cloudflare.com/developer-platform/products/workers/"><u>Cloudflare Workers</u></a>! You can now:  </p><ul><li><p><b>Get automatic instrumentation for applications on the Workers platform: </b>No manual setup, complex instrumentation, or code changes. It works out of the box. </p></li><li><p><b>Explore and investigate traces in the Cloudflare dashboard:</b> Your traces are processed and available in the Workers Observability dashboard alongside your existing logs.</p></li><li><p><b>Export logs and traces to OpenTelemetry-compatible providers:</b> Send OpenTelemetry traces (and correlated logs) to your observability provider of choice. </p></li></ul><p>In 2024, <a href="https://blog.cloudflare.com/cloudflare-acquires-baselime-expands-observability-capabilities/"><u>we set out to build</u></a> the best first-party <a href="https://www.cloudflare.com/developer-platform/products/workers-observability/"><u>observability</u></a> of any cloud platform. We launched a new metrics dashboard to give better insights into how your Worker is performing, <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/#enable-workers-logs"><u>Workers Logs</u></a> to automatically ingest and store logs for your Workers, a <a href="https://developers.cloudflare.com/workers/observability/query-builder/"><u>query builder</u></a> to explore your data across any dimension and <a href="https://developers.cloudflare.com/workers/observability/logs/real-time-logs/"><u>real-time logs</u></a> to stream your logs in real time with advanced filtering capabilities. Starting today, you can get an even deeper understanding of your Workers applications by enabling automatic <b>tracing</b>!</p>
    <div>
      <h3>What is Workers Tracing? </h3>
      <a href="#what-is-workers-tracing">
        
      </a>
    </div>
    <p>Workers traces capture and emit OpenTelemetry-compliant spans to show you detailed metadata and timing information on every operation your Worker performs.<b> </b>It helps you identify performance bottlenecks, resolve errors, and understand how your Worker interacts with other services on the Workers platform. You can now answer questions like:</p><ul><li><p>Which calls are slowing down my application?</p></li><li><p>Which queries to my database take the longest? </p></li><li><p>What happened within a request that resulted in an error?</p></li></ul><p>Tracing provides a visualization of each invocation's journey through various operations. Each operation is captured as a span, a timed segment that shows what happened and how long it took. Child spans nest within parent spans to show sub-operations and dependencies, creating a hierarchical view of your invocation’s execution flow. Each span can include contextual metadata or attributes that provide details for debugging and filtering events.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4F7l3WSJ2hY0eu6kX47Rdp/c7c3934e9abbbb1f01ec979941d35b54/unnamed.png" />
          </figure>
    <div>
      <h3>Full automatic instrumentation, no code changes </h3>
      <a href="#full-automatic-instrumentation-no-code-changes">
        
      </a>
    </div>
    <p>Previously, instrumenting your application typically required an understanding of the <a href="https://opentelemetry.io/docs/specs/"><u>OpenTelemetry spec</u></a>, multiple <a href="https://opentelemetry.io/docs/concepts/instrumentation/libraries/"><u>OTel libraries</u></a>, and how they related to each other. Implementation was tedious and bloated your codebase with instrumentation code that obfuscated your application logic.</p><p>Setting up tracing typically meant spending hours integrating third-party SDKs, wrapping every database call and API request with instrumentation code, and debugging complex config files before you saw a single trace. This implementation overhead often makes observability an afterthought, leaving you without full visibility in production when issues arise.</p><p>What makes Workers Tracing truly magical is it’s <b>completely automatic – no set up, no code changes, no wasted time. </b>We took the approach of automatically instrumenting every I/O operation in your Workers, through a deep integration in <a href="https://github.com/cloudflare/workerd"><u>workerd</u></a>, our runtime, enabling us to capture the full extent of data flows through every invocation of your Workers.</p><p>You focus on your application logic. We take care of the instrumentation.</p>
    <div>
      <h4>What you can trace today</h4>
      <a href="#what-you-can-trace-today">
        
      </a>
    </div>
    <p>The operations covered today are: </p><ul><li><p><b>Binding calls:</b> Interactions with various <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/"><u>Worker bindings</u></a>. KV reads and writes, <a href="https://www.cloudflare.com/developer-platform/products/r2/">R2 object storage</a> operations, Durable Object invocations, and many more binding calls are automatically traced. This gives you complete visibility into how your Worker uses other services.</p></li><li><p><b>Fetch calls:</b> All outbound HTTP requests are automatically instrumented, capturing timing, status codes, and request metadata. This enables you to quickly identify which external dependencies are affecting your application's performance.</p></li><li><p><b>Handler calls:</b> Methods on a Worker that can receive and process external inputs, such as <a href="https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/"><u>fetch handlers</u></a>, <a href="https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/"><u>scheduled handlers</u></a>, and <a href="https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer"><u>queue handlers</u></a>. This gives you visibility into performance of how your Worker is being invoked.</p></li></ul>
    <div>
      <h4>Automatic attributes on every span </h4>
      <a href="#automatic-attributes-on-every-span">
        
      </a>
    </div>
    <p>Our automated instrumentation captures each operation as a span. For example, a span generated by an R2 binding call (like a <code>get</code> or <code>put</code> operation) will automatically contain any available attributes, such as the operation type, the error if applicable, the object key, and duration. These detailed attributes provide the context you need to answer precise questions about your application without needing to manually log every detail.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6n5j0AdsgouMuHacgrTW9Q/60e4f73dd66a591ee666d6acdbaafc03/unnamed2.png" />
          </figure><p>We will continue to add more detailed attributes to spans and add the ability to trace an invocation across multiple Workers or external services. Our <a href="http://developers.cloudflare.com/workers/observability/traces/spans-and-attributes/"><u>documentation</u></a> contains a complete list of all instrumented spans and their attributes.</p>
    <div>
      <h3>Investigate traces in the Workers dashboard</h3>
      <a href="#investigate-traces-in-the-workers-dashboard">
        
      </a>
    </div>
    <p>You can easily<a href="http://developers.cloudflare.com/workers/observability/traces/"><u> view traces directly within a specific Worker application</u></a> in the Cloudflare dashboard, giving you immediate visibility into your application's performance. You’ll find a list of all trace events within your desired time frame and a trace visualization of each invocation including duration of each call and any available attributes. You can also query across all Workers on your account, letting you pinpoint issues occurring on multiple applications. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7HU4vktx7zr3aflWK9cdsl/73dcd1f1f9702faa65430fec9c9da8c5/unnamed_3.png" />
          </figure><p>To get started viewing traces on your Workers application, you can set: </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5e7hIAgVnjxvZSshwBTpXf/a0571af6e43be414c85e25dba106848c/1.png" />
          </figure>
    <div>
      <h3>Export traces to OpenTelemetry compatible providers </h3>
      <a href="#export-traces-to-opentelemetry-compatible-providers">
        
      </a>
    </div>
    <p>However, we realize that some development teams need Workers data to live alongside other telemetry data in the <b>tools they are already using</b>. That’s why we’re also adding tracing exports, letting your team send, visualize and query data with your existing observability stack! Starting today, you can export traces directly to providers like <a href="https://www.honeycomb.io/"><u>Honeycomb</u></a>, <a href="https://grafana.com/"><u>Grafana</u></a>, <a href="https://sentry.io/welcome/"><u>Sentry</u></a> or any other <a href="http://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/#available-opentelemetry-destinations"><u>OpenTelemetry Protocol (OTLP) provider with an available endpoint</u></a>.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3GdiHZEL1bIpGn5BRhbyyT/0fbb6610a488f5f2c44f78ba3ecaf576/Export_traces_to_OpenTelemetry_compatible_providers_.png" />
          </figure>
    <div>
      <h4>Correlated logs and traces </h4>
      <a href="#correlated-logs-and-traces">
        
      </a>
    </div>
    <p>We also support exporting OTLP-formatted logs that share the same trace ID, enabling third-party platforms to automatically correlate log entries with their corresponding traces. This lets you easily jump between spans and related log messages.</p>
    <div>
      <h4>Set up your destination, enable exports, and go! </h4>
      <a href="#set-up-your-destination-enable-exports-and-go">
        
      </a>
    </div>
    <p>To start sending events to your destination of choice, first, configure your OTLP endpoint destination in the Cloudflare dashboard. For every destination you can specify a custom name and set custom headers to include API keys or app configuration. </p><p>Once you have your destination set up (e.g. <code>honeycomb-tracing</code>), set the following in your <code>wrangler.jsonc </code>and deploy: </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2xUbPsz307PvkZ6i15iUKe/452efaad0afdbeb2722f42726d0a849b/3.png" />
          </figure>
    <div>
      <h3>Coming up for Workers observability</h3>
      <a href="#coming-up-for-workers-observability">
        
      </a>
    </div>
    <p>This is just the beginning of Workers providing the workflows and tools to get you the telemetry data you want, where you want it. We’re improving our support both for native tracing in the dashboard and for exporting other types of telemetry to 3rd parties. In the upcoming months we’ll be launching: </p><ul><li><p><b>Support for more spans and attributes: </b>We are adding more automatic traces for every part of the Workers platform. While our first goal is to give you visibility into the duration of every operation within your request, we also want to add detailed attributes. Your feedback on what’s missing will be extremely valuable here. </p></li><li><p><b>Trace context propagation: </b>When building<b> </b><b><i>distributed</i></b> applications, ensuring your traces connect across all of your services (even those outside of Cloudflare), automatically linking spans together to create complete, end-to-end visibility is critical. For example, a trace from Workers could be nested from a parent service or vice versa. When fully implemented, our automatic trace context propagation will follow <a href="https://www.w3.org/TR/trace-context/"><u>W3C standards</u></a> to ensure compatibility across your existing tools and services. </p></li><li><p><b>Support for custom spans and attributes</b>: While automatic instrumentation gives you visibility into what’s happening within the Workers platform, we know you need visibility into your own application logic too. So, we’ll give you the ability to manually add your own spans as well.</p></li><li><p><b>Ability to export metrics: </b>Today, metrics, logs and traces are available for you to monitor and view within the Workers dashboard. But the final missing piece is giving you the ability to export both infrastructure metrics (like request volume, error rates, and execution duration) and custom application metrics to your preferred observability provider.</p></li></ul>
    <div>
      <h3>What you can expect from tracing pricing  </h3>
      <a href="#what-you-can-expect-from-tracing-pricing">
        
      </a>
    </div>
    <p>Today, at the start of beta, viewing traces in the Cloudflare dashboard and exporting traces to a 3rd party provider are both free. On <b>January 15, 2026</b>, tracing and log events will be charged the following pricing:</p><p><b>Viewing Workers traces in the Cloudflare dashboard</b></p><p>To view traces in the Cloudflare dashboard, you can do so on a <a href="https://www.cloudflare.com/plans/developer-platform/"><u>Workers Free and Paid plan</u></a> at the pricing shown below:</p>
<div><table><thead>
  <tr>
    <th></th>
    <th><span>Workers Free</span></th>
    <th><span>Workers Paid</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>Included Volume </span></td>
    <td><span>200K events per day</span></td>
    <td><span>20M events per month </span></td>
  </tr>
  <tr>
    <td><span>Additional Events </span></td>
    <td><span>N/A</span></td>
    <td><span>$0.60 per million logs </span></td>
  </tr>
  <tr>
    <td><span>Retention </span></td>
    <td><span>3 days </span></td>
    <td><span>7 days </span></td>
  </tr>
</tbody></table></div><p><b>Exporting traces and logs </b></p><p>To export traces to a 3rd-party OTLP-compatible destination, you will need a <b>Workers Paid </b>subscription. Pricing is based on total span or log events with the following inclusions:</p>
<div><table><thead>
  <tr>
    <th></th>
    <th><span>Workers Free</span></th>
    <th><span>Workers Paid</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>Events  </span></td>
    <td><br /><span>Not available</span></td>
    <td><span>10 million events per month </span></td>
  </tr>
  <tr>
    <td><span>Additional events </span></td>
    <td><span>$0.05 per million batched events</span></td>
  </tr>
</tbody></table></div>
    <div>
      <h3>Enable tracing today</h3>
      <a href="#enable-tracing-today">
        
      </a>
    </div>
    <p>Ready to get started with tracing on your Workers application? </p><ul><li><p><b>Check out our </b><a href="http://developers.cloudflare.com/workers/observability/traces/"><b><u>documentation</u></b></a><b>: </b>Learn how to get set up, read about current limitations and discover more about what’s coming up. </p></li><li><p><b>Join the chatter in our </b><a href="https://github.com/cloudflare/workers-sdk/discussions/11062"><b>GitHub discussion</b></a><b>:</b> Your feedback will be extremely valuable in our beta period on our automatic instrumentation, tracing dashboard, and OpenTelemetry export flow. Head to our GitHub discussion to raise issues, put in feature requests and get in touch with us!</p></li></ul><p></p> ]]></content:encoded>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Tracing]]></category>
            <category><![CDATA[OpenTelemetry ]]></category>
            <guid isPermaLink="false">2Np8UAH0AuW7KjeIwym0NY</guid>
            <dc:creator>Nevi Shah</dc:creator>
            <dc:creator>Boris Tane</dc:creator>
            <dc:creator>Jeremy Morrell</dc:creator>
        </item>
        <item>
            <title><![CDATA[Performance measurements… and the people who love them]]></title>
            <link>https://blog.cloudflare.com/loving-performance-measurements/</link>
            <pubDate>Tue, 20 May 2025 13:00:00 GMT</pubDate>
            <description><![CDATA[ Developers have a gut-felt understanding for performance, but that intuition breaks down when systems reach Cloudflare’s scale. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>⚠️ WARNING ⚠️ This blog post contains graphic depictions of probability. Reader discretion is advised.</p><p>Measuring performance is tricky. You have to think about accuracy and precision. Are your sampling rates high enough? Could they be too high?? How much metadata does each recording need??? Even after all that, all you have is raw data. Eventually for all this raw performance information to be useful, it has to be aggregated and communicated. Whether it's in the form of a dashboard, customer report, or a paged alert, performance measurements are only useful if someone can see and understand them.</p><p>This post is a collection of things I've learned working on customer performance escalations within Cloudflare and analyzing existing tools (both internal and commercial) that we use when evaluating our own performance.  A lot of this information also comes from Gil Tene's talk, <a href="https://youtu.be/lJ8ydIuPFeU"><u>How NOT to Measure Latency</u></a>. You should definitely watch that too (but maybe after reading this, so you don't spoil the ending). I was surprised by my own blind spots and which assumptions turned out to be wrong, even though they seemed "obviously true" at the start. I expect I am not alone in these regards. For that reason this journey starts by establishing fundamental definitions and ends with some new tools and techniques that we will be sharing as well as the surprising results that those tools uncovered.</p>
    <div>
      <h2>Check your verbiage</h2>
      <a href="#check-your-verbiage">
        
      </a>
    </div>
    <p>So ... what is performance? Alright, let's start with something easy: definitions. "Performance" is not a very precise term because it gets used in too many contexts. Most of us as nerds and engineers have a gut understanding of what it means, without a real definition. We can't <i>really</i> measure it because how "good" something is depends on what makes that thing good. "Latency" is better ... but not as much as you might think. Latency does at least have an implicit time unit, so we <i>can</i> measure it. But ... <a href="https://www.cloudflare.com/learning/performance/glossary/what-is-latency/">what is latency</a>? There are lots of good, specific examples of measurements of latency, but we are going to use a general definition. Someone starts something, and then it finishes — the elapsed time between is the latency.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1r4blwH5oeloUdXoizuLB4/f58014b1b4b3715f54400e6b03c60ea7/image7.png" />
          </figure><p>This seems a bit reductive, but it’s a surprisingly useful definition because it gives us a key insight. This fundamental definition of latency is based around the client's perspective. Indeed, when we look at our internal measurements of latency for health checks and monitoring, they all have this one-sided caller/callee relationship. There is the latency of the caching layer from the point of view of the ingress proxy. There’s the latency of the origin from the cache’s point of view. Each component can measure the latency of its upstream counterparts, but not the other way around. </p><p>This one-sided nature of latency observation is a real problem for us because Cloudflare <i>only</i> exists on the server side. This makes all of our internal measurements of latency purely estimations. Even if we did have full visibility into a client’s request timing, the start-to-finish latency of a request to Cloudflare isn’t a great measure of Cloudflare’s latency. The process of making an HTTP request has lots of steps, only a subset of which are affected by us. Time spent on things like DNS lookup, local computation for TLS, or resource contention <i>do</i> affect the client’s experience of latency, but only serve as sources of noise when we are considering our own performance.</p><p>There is a very useful and common metric that is used to measure web requests, and I’m sure lots of you have been screaming it in your brains from the second you read the title of this post. ✨Time to first byte✨. Clearly this is the answer, right?!  But ... what is “Time to first byte”?</p>
    <div>
      <h2>TTFB mine</h2>
      <a href="#ttfb-mine">
        
      </a>
    </div>
    <p>Time to first byte (TTFB) on its face is simple. The name implies that it's the time it takes (on the client's side) to receive the first byte of the response from the server, but unfortunately, that only describes when the timer should end. It doesn't say when the timer should start. This ambiguity is just one factor that leads to inconsistencies when trying to compare TTFB across different measurement platforms ... or even across a single platform because there is no <i>one</i> definition of TTFB. Similar to “performance”, it is used in too many places to have a single definition. That being said, TTFB is a very useful concept, so in order to measure it and report it in an unambiguous way, we need to pick a definition that’s already in use.</p><p>We have mentioned TTFB in other blog posts, but <a href="https://blog.cloudflare.com/ttfb-is-not-what-it-used-to-be/"><u>this one</u></a> sums up the problem best with “Time to first byte isn’t what it used to be.” You should read that article too, but the gist is that one popular TTFB definition used by browsers was changed in a confusing way with the introduction of <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/103"><u>early hints</u></a> in June 2022. That post and <a href="https://blog.cloudflare.com/tag/ttfb/"><u>others</u></a> make the point that while TTFB is useful, it isn’t the best direct measurement for web performance. Later on in this post we will derive why that’s the case.</p><p>One common place <i>we</i> see TTFB used is our customers’ analysis comparing Cloudflare's performance to our competitors through <a href="https://www.catchpoint.com/"><u>Catchpoint</u></a>. Customers, as you might imagine, have a vested interest in measuring our latency, as it affects theirs. Catchpoint provides several tools built on their global Internet probe network for measuring HTTP request latency (among other things) and visualizing it in their web interface. In an effort to align better with our customers, we decided to adopt Catchpoint’s terminology for talking about latency, both internally and externally.</p>
    <div>
      <h2>Catchpoint catch-up</h2>
      <a href="#catchpoint-catch-up">
        
      </a>
    </div>
    <p>While Catchpoint makes things like TTFB easy to plot over time, the visualization tool doesn't give a definition of what TTFB is, but after going through all of their technical blog posts and combing through thousands of lines of raw data, we were able to get functional definitions for TTFB and other composite metrics. This was an important step because these metrics are how our customers are viewing our performance, so we all need to be able to understand exactly what they signify! The final report for this is internal (and long and dry), so in this post, I'll give you the highlights in the form of colorful diagrams, starting with this one.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5bB3HmSrIIhQ2AzVpheJWa/8d2b73f3f2f0602217daaf7fea847e11/image6.png" />
          </figure><p>This diagram shows our customers' most commonly viewed client metrics on Catchpoint and how they fit together into the processing of a request from the server side. Notice that some are directly measured, and some are calculated based on the direct measurements. Right in the middle is TTFB, which Catchpoint calculates as the sum of the DNS, Connect, TLS, and Wait times. It’s worth noting again that this is not <i>the</i> definition of TTFB, this is just Catchpoint’s definition, and now ours.</p><p>This breakdown of HTTPS phases is not the only one commonly used. Browsers themselves have a standard for measuring the stages of a request. The diagram below shows how most browsers are reporting request metrics. Luckily (and maybe unsurprisingly) these phases match Catchpoint's very closely.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ZouyuBQV7XgER2kqhMy8r/04f750eef44ba12bb6915a06eac532ca/image1.png" />
          </figure><p>There are some differences beyond the inclusion of things like <a href="https://html.spec.whatwg.org/#applicationcache"><u>AppCache</u></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Redirections"><u>Redirects</u></a> (which are not directly impacted by Cloudflare's latency). Browser timing metrics are based on timestamps instead of durations. The diagram subtly calls this out with gaps between the different phases indicating that there is the potential for the computer running the browser to do things that are not part of any phase. We can line up these timestamps with Catchpoint's metrics like so:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4TvwOuTxWvMBxKGQQTUfZc/a8105d77725a9fa0d3e5bf6a115a13a5/Screenshot_2025-05-15_at_11.31.46.png" />
          </figure><p>Now that we, our customers, and our browsers (with data coming from <a href="https://en.wikipedia.org/wiki/Real_user_monitoring"><u>RUM</u></a>) have a common and well-defined language to talk about the phases of a request, we can start to measure, visualize, and compare the components that make up the network latency of a request. </p>
    <div>
      <h2>Visual basics</h2>
      <a href="#visual-basics">
        
      </a>
    </div>
    <p>Now that we have defined what our key values for latency are, we can record numbers and put them in a chart and watch them roll by ... except not directly. In most cases, the systems we use to record the data actively prevent us from seeing the recorded data in its raw form. Tools like <a href="https://prometheus.io/"><u>Prometheus</u></a> are designed to collect pre-aggregated data, not individual samples, and for a good reason. Storing every recorded metric (even compacted) would be an enormous amount of data. Even worse, the data loses its value exponentially over time, since the most recent data is the most actionable.</p><p>The unavoidable conclusion is that some aggregation has to be done before performance data can be visualized. In most cases, the aggregation means looking at a series of windowed percentiles over time. The most common are 50th percentile (median), 75th, 90th, and 99th if you're really lucky. Here is an example of a latency visualization from one of our own internal dashboards.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/lvjAR41mTJf2d5Vdg5SwT/19ff931587790b1fb7fbcc317ab83a5e/image8.png" />
          </figure><p>It clearly shows a spike in latency around 14:40 UTC. Was it an incident? The p99 jumped by 1300% (500ms to 6500</p><p>ms) for multiple minutes while the p50 jumped by more than 13600% (4.4ms to 600ms). It is a clear signal, so something must have happened, but what was it? Let me keep you in suspense for a second while we talk about statistics and probability.</p>
    <div>
      <h2>Uncooked math</h2>
      <a href="#uncooked-math">
        
      </a>
    </div>
    <p>Let me start with a quote from my dear, close, personal friend <a href="https://www.youtube.com/watch?v=xV4rLfpidIk"><u>@ThePrimeagen</u></a>:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/I8VbrcSjVSKY1i7fbVEMl/8108e25e78c1ee5356bbd080c467c056/Screenshot_2025-05-15_at_11.33.40.png" />
          </figure><p>It's a good reminder that while statistics is a great tool for providing a simplified and generalized representation of a complex system, it can also obscure important subtleties of that system. A good way to think of statistical modeling is like lossy compression. In the latency visualization above (which is a plot of TTFB over time), we are compressing the entire spectrum of latency metrics into 4 percentile bands, and because we are only considering up to the 99th percentile, there's an entire 1% of samples left over that we are ignoring! </p><p>"What?" I hear you asking. "P99 is already well into perfection territory. We're not trying to be perfectionists. Maybe we should get our p50s down first". Let's put things in perspective. This zone (<a href="http://www.cloudflare.com/"><u>www.cloudflare.com</u></a>) is getting about 30,000 req/s and the 99th percentile latency is 500 ms. (Here we are defining latency as “Edge TTFB”, a server-side approximation of our now official definition.) So there are 300 req/s that are taking longer than half a second to complete, and that's just the portion of the request that <i>we</i> can see. How much worse than 500 ms are those requests in the top 1%? If we look at the 100th percentile (the max), we get a much different vibe from our Edge TTFB plot.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/NDvJObDLjy5D8bKIEhsjS/10f1c40940ba41aae308100c7f374836/image12.png" />
          </figure><p>Viewed like this, the spike in latency no longer looks so remarkable. Without seeing more of the picture, we could easily believe something was wrong when in reality, even if something is wrong, it is not localized to that moment. In this case, it's like we are using our own statistics to lie to ourselves. </p>
    <div>
      <h2>The top 1% of requests have 99% of the latency</h2>
      <a href="#the-top-1-of-requests-have-99-of-the-latency">
        
      </a>
    </div>
    <p>Maybe you're still not convinced. It feels more intuitive to focus on the median because the latency experienced by 50 out of 100 people seems more important to focus on than that of 1 in 100. I would argue that is a totally true statement, but notice I said "people"<sup> </sup>and not "requests." A person visiting a website is not likely to be doing it one request at a time.</p><p>Taking <a href="http://www.cloudflare.com/"><u>www.cloudflare.com</u></a> as an example again, when a user opens that page, their browser makes more than <b>70</b> requests. It sounds big, but in the world of user-facing websites, it’s not that bad. In contrast, <a href="http://www.amazon.com/"><u>www.amazon.com</u></a> issues more than <b>400</b> requests! It's worth noting that not all those requests need to complete before a web page or application becomes usable. That's why more advanced and browser-focused metrics exist, but I will leave a discussion of those for later blog posts. I am more interested in how making that many requests changes the probability calculations for expected latency on a per-user basis. </p><p>Here's a brief primer on combining probabilities that covers everything you need to know to understand this section.</p><ul><li><p>The probability of two things happening is the probability of the first happening multiplied by the probability of the second thing happening. $$P(X\cap Y )=P(X) \times P (Y)$$</p></li><li><p>The probability of something in the $X^{th}$ percentile happening is $X\%$. $$P(pX) = X\%$$</p></li></ul><p>Let's define $P( pX_{N} )$ as the probability that someone on a website with $N$ requests experiences no latencies &gt;= the $X^{th}$ percentile. For example, $P(p50_{2})$ would be the probability of getting no latencies greater than the median on a page with 2 requests. This is equivalent to the probability of one request having a latency less than the $p50$ and the other request having a latency less than the $p50$. We can use the first identities above. </p><p>$$\begin{align}
P( p50_{2}) &amp;= P\left ( p50 \cap p50 \right ) \\
   &amp;= P( p50) \times P\left ( p50 \right ) \\
   &amp;= 50\%^{2} \\
   &amp;= 25\%
\end{align}$$</p><p>We can generalize this for any percentile and any number of requests. $$P( pX_{N}) = X\%^{N}$$</p><p>For <a href="http://www.cloudflare.com/"><u>www.cloudflare.com</u></a> and its 70ish requests, the percentage of visitors that won't experience a latency above the median is </p><p>$$\begin{align} 
P( p50_{70}) &amp;= 50\%^{70} \\
  &amp;\approx 0.000000000000000000001\%
\end{align}$$</p><p>This vanishingly small number should make you question why we would value the $p50$ latency so highly at all when effectively no one experiences it as their worst case latency.</p><p>So now the question is, what request latency percentile <i>should</i> we be looking at? Let's go back to the statement at the beginning of this section. What does the median person experience on <a href="http://www.cloudflare.com./"><u>www.cloudflare.com</u></a>? We can use a little algebra to solve for that.</p><p>$$\begin{align} 
P( pX_{70}) &amp;= 50\% \\
X^{70}  &amp;= 50\% \\
X &amp;= e^{ \frac{ln\left ( 50\% \right )}{70}} \\
X &amp;\approx  99\%
\end{align}$$</p><p>This seems a little too perfect, but I am not making this up. For <a href="http://www.cloudflare.com/"><u>www.cloudflare.com</u></a>, if you want to capture a value that's representative of what the median user can expect, you need to look at $p99$ request latency. Extending this even further, if you want a value that's representative of what 99% of users will experience, you need to look at the <b>99.99th</b> <b>percentile</b>!</p>
    <div>
      <h2>Spherical latency in a vacuum</h2>
      <a href="#spherical-latency-in-a-vacuum">
        
      </a>
    </div>
    <p>Okay, this is where we bring everything together, so stay with me. So far, we have only talked about measuring the performance of a single system. This gives us absolute numbers to look at internally for monitoring, but if you’ll recall, the goal of this post was to be able to clearly communicate about performance outside the company. Often this communication takes the form of comparing Cloudflare’s performance against other providers. How are these comparisons done? By plotting a percentile request "latency" over time and eyeballing the difference.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/x9j5kstMS1kXdsb1PaIbu/837398e0da4758743155595f4f570340/image2.png" />
          </figure><p>With everything we have discussed in this post, it seems like we can devise a better method for doing this comparison. We saw how exposing more of the percentile spectrum can provide a new perspective on existing data, and how impactful higher percentile statistics can be when looking at a more complete user experience. Let me close this post with an example of how putting those two concepts together yields some intriguing results.</p>
    <div>
      <h2>One last thing</h2>
      <a href="#one-last-thing">
        
      </a>
    </div>
    <p>Below is a comparison of the latency (defined here as the sum of the TLS, Connect, and Wait times or the equivalent of TTFB - DNS lookup time) for the customer when viewed through Cloudflare and a competing provider. This is the same data represented in the chart immediately above (containing 90,000 samples for each provider), just in a different form called a <a href="https://en.wikipedia.org/wiki/Cumulative_distribution_function"><u>CDF plot</u></a>, which is one of a few ways we are making it easier to visualize the entire percentile range. The chart shows the percentiles on the y-axis and latency measurements on the x-axis, so to see the latency value for a given percentile, you go up to the percentile you want and then over to the curve. Interpreting these charts is as easy as finding which curve is farther to the left for any given percentile. That curve will have the lower latency.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/53sRk6UCoflU2bGcXypgEQ/f435bbdf43e1646cf2afb56d2aca26be/image4.png" />
          </figure><p>It's pretty clear that for nearly the entire percentile range, the other provider has the lower latency by as much as 30ms. That is, until you get to the very top of the chart. There's a little bit of blue that's above (and therefore to the left) of the green. In order to see what's going on there more clearly, we can use a different kind of visualization. This one is called a <a href="https://en.wikipedia.org/wiki/Q%E2%80%93Q_plot"><u>QQ-Plot</u></a>, or quantile-quantile plot. This shows the same information as the CDF plot, but now each point on the curve represents a specific quantile, and the 2 axes are the latency values of the two providers at that percentile.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/jeYkDomZjnqhrCIIUJBqj/ebb4533c6982b0f8b9f5f491aa1549fb/image9.png" />
          </figure><p>This chart looks complicated, but interpreting it is similar to the CDF plot. The blue is a dividing marker that shows where the latency of both providers is equal. Points below the line indicate percentiles where the other provider has a lower latency than Cloudflare, and points above the line indicate percentiles where Cloudflare is faster. We see again that for most of the percentile range, the other provider is faster, but for percentiles above 99, Cloudflare is significantly faster. </p><p>This is not so compelling by itself, but what if we take into account the number of requests this page issues ... which is over 180. Using the same math from above, and only considering <i>half</i> the requests to be required for the page to be considered loaded, yields this new effective QQ plot.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/S0lLIZfVyVM7KjWUawcNg/967417729939f454bacd0d4c12b0c0e2/image3.png" />
          </figure><p>Taking multiple requests into account, we see that the median latency is close to even for both Cloudflare and the other provider, but the stories above and below that point are very different. A user has about an even chance of an experience where Cloudflare is significantly faster and one where Cloudflare is slightly slower than the other provider. We can show the impact of this shift in perspective more directly by calculating the <a href="https://en.wikipedia.org/wiki/Expected_value#Arbitrary_real-valued_random_variables"><u>expected value</u></a> for request and experienced latency.</p><table><tr><td><p><b>Latency Kind</b></p></td><td><p><b>Cloudflare </b>(ms)</p></td><td><p><b>Other CDN</b> (ms)</p></td><td><p><b>Difference</b> (ms)</p></td></tr><tr><td><p>Expected Request Latency</p></td><td><p>141.9</p></td><td><p>129.9</p></td><td><p><b>+12.0</b></p></td></tr><tr><td><p>Expected Experienced Latency </p><p>Based on 90 Requests </p></td><td><p>207.9</p></td><td><p>281.8</p></td><td><p><b>-71.9</b></p></td></tr></table><p>Shifting the focus from individual request latency to user latency we see that Cloudflare is 70 ms faster than the other provider. This is where our obsession with reliability and tail latency becomes a win for our customers, but without a large volume of raw data, knowledge, and tools, this win would be totally hidden. That is why in the near future we are going to be making this tool and others available to our customers so that we can all get a more accurate and clear picture of our users’ experiences with latency. Keep an eye out for more announcements to come later in 2025.</p> ]]></content:encoded>
            <category><![CDATA[Internet Performance]]></category>
            <category><![CDATA[Latency]]></category>
            <category><![CDATA[Open Source]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[TTFB]]></category>
            <guid isPermaLink="false">6R3IB3ISH3fXyycnjNPyZC</guid>
            <dc:creator>Kevin Guthrie</dc:creator>
        </item>
        <item>
            <title><![CDATA[Moving Baselime from AWS to Cloudflare: simpler architecture, improved performance, over 80% lower cloud costs]]></title>
            <link>https://blog.cloudflare.com/80-percent-lower-cloud-cost-how-baselime-moved-from-aws-to-cloudflare/</link>
            <pubDate>Thu, 31 Oct 2024 13:00:00 GMT</pubDate>
            <description><![CDATA[ Post-acquisition, we migrated Baselime from AWS to the Cloudflare Developer Platform and in the process, we improved query times, simplified data ingestion, and now handle far more events, all while cutting costs. Here’s how we built a modern, high-performing observability platform on Cloudflare’s network.  ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h2>Introduction</h2>
      <a href="#introduction">
        
      </a>
    </div>
    <p>When <a href="https://blog.cloudflare.com/cloudflare-acquires-baselime-expands-observability-capabilities/"><u>Baselime joined Cloudflare</u></a> in April 2024, our architecture had evolved to hundreds of AWS Lambda functions, dozens of databases, and just as many queues. We were drowning in complexity and our cloud costs were growing fast. We are now building <a href="https://baselime.io/"><u>Baselime</u></a> and <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/"><u>Workers Observability</u></a> on Cloudflare and will save over 80% on our cloud compute bill. The estimated potential Cloudflare costs are for Baselime, which remains a stand-alone offering, and the estimate is based on the <a href="https://developers.cloudflare.com/workers/platform/pricing/"><u>Workers Paid plan</u></a>. Not only did we achieve huge cost savings, we also simplified our architecture and improved overall latency, scalability, and reliability.</p><table><tr><td><p><b>Cost (daily)</b></p></td><td><p><b>Before (AWS)</b></p></td><td><p><b>After (Cloudflare)</b></p></td></tr><tr><td><p>Compute</p></td><td><p>$650 - AWS Lambda</p></td><td><p>$25 - Cloudflare Workers</p></td></tr><tr><td><p>CDN</p></td><td><p>$140 - Cloudfront</p></td><td><p>$0 - Free</p></td></tr><tr><td><p>Data Stream + Analytics database</p></td><td><p>$1,150 - Kinesis Data Stream + EC2</p></td><td><p>$300 - Workers Analytics Engine</p></td></tr><tr><td><p>Total (daily)</p></td><td><p>$1,940</p></td><td><p>$325</p></td></tr><tr><td><p><b>Total (annual)</b></p></td><td><p><b>$708,100</b></p></td><td><p><b>$118,625</b> (83% cost reduction)</p></td></tr></table><p><sub><i>Table 1: AWS vs. Workers Costs Comparison ($USD)</i></sub></p><p>When we joined Cloudflare, we immediately saw a surge in usage, and within the first week following the announcement, we were processing over a billion events daily and our weekly active users tripled.</p><p>As the platform grew, so did the challenges of managing real-time observability with new scalability, reliability, and cost considerations. This drove us to rebuild Baselime on the Cloudflare Developer Platform, where we could innovate quickly while reducing operational overhead.</p>
    <div>
      <h2>Initial architecture — all on AWS</h2>
      <a href="#initial-architecture-all-on-aws">
        
      </a>
    </div>
    <p>Our initial architecture was all on Amazon Web Services (AWS). We’ll focus here on the data pipeline, which covers ingestion, processing, and storage of tens of billions of events daily.</p><p>This pipeline was built on top of AWS Lambda, Cloudfront, Kinesis, EC2, DynamoDB, ECS, and ElastiCache.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ruQuVe7kcd31FOS91nstS/9d422e751e0d2d3a90d603e6adcd5c48/image1.png" />
          </figure><p><sup><i>Figure1: Initial data pipeline architecture</i></sup></p><p>The key elements are:</p><ul><li><p><b>Data receptors</b>: Responsible for receiving telemetry data from multiple sources, including OpenTelemetry, Cloudflare Logpush, CloudWatch, Vercel, etc. They cover validation, authentication, and transforming data from each source into a common internal format. The data receptors were deployed either on AWS Lambda (using function URLs and Cloudfront) or ECS Fargate depending on the data source.</p></li><li><p><b>Kinesis Data Stream</b>: Responsible for transporting the data from the receptors to the next step: data processing.</p></li><li><p><b>Processor</b>: A single AWS Lambda function responsible for enriching and transforming the data for storage. It also performed real-time error tracking and detecting patterns in logs.</p></li><li><p><b>ClickHouse cluster</b>: All the telemetry data was ultimately indexed and stored in a self-hosted ClickHouse cluster on EC2.</p></li></ul><p>In addition to these key elements, the existing stack also included orchestration with Firehose, S3 buckets, SQS, DynamoDB and RDS for error handling, retries, and storing metadata.</p><p>While this architecture served us well in the early days, it started to show major cracks as we scaled our solution to more and larger customers.</p><p>Handling retries at the interface between the data receptors and the Kinesis Data Stream was complex, requiring introducing and orchestrating Firehose, S3 buckets, SQS, and another Lambda function.</p><p>Self-hosting ClickHouse also introduced major challenges at scale, as we continuously had to plan our capacity and update our setup to keep pace with our growing user base whilst attempting to maintain control over costs.</p><p>Costs began scaling unpredictably with our growing workloads, especially in AWS Lambda, Kinesis, and EC2, but also in less obvious ways, such as in Cloudfront (required for a custom domain in front of Lambda function URLs) and DynamoDB. Specifically, the time spent on I/O operations in AWS Lambda was a particularly costly piece. At every step, from the data receptors to the ClickHouse cluster, moving data to the next stage required waiting for a network request to complete, accounting for over 70% of wall time in the Lambda function.</p><p>In a nutshell, we were continuously paged by our alerts, innovating at a slower pace, and our costs were out of control.</p><p>Additionally, the entire solution was deployed in a single AWS region: eu-west-1. As a result, all developers located outside continental Europe were experiencing high latency when emitting logs and traces to Baselime. </p>
    <div>
      <h2>Modern architecture — transitioning to Cloudflare</h2>
      <a href="#modern-architecture-transitioning-to-cloudflare">
        
      </a>
    </div>
    <p>The shift to the <a href="https://www.cloudflare.com/en-gb/developer-platform/products/"><u>Cloudflare Developer Platform</u></a> enabled us to rethink our architecture to be exceptionally fast, globally distributed, and highly scalable, without compromising on cost, complexity, or agility. This new architecture is built on top of Cloudflare primitives.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/16ndcTUS2vAg6TM4djTGUH/0e187d50ae466c275839c6aac91e5249/image5.png" />
          </figure><p><sup><i>Figure 2: Modern data pipeline architecture</i></sup></p>
    <div>
      <h3>Cloudflare Workers: the core of Baselime</h3>
      <a href="#cloudflare-workers-the-core-of-baselime">
        
      </a>
    </div>
    <p><a href="https://www.cloudflare.com/developer-platform/workers/"><u>Cloudflare Workers</u></a> are now at the core of everything we do. All the data receptors and the processor run in Workers. Workers minimize cold-start times and are deployed globally by default. As such, developers always experience lower latency when emitting events to Baselime.</p><p>Additionally, we heavily use <a href="https://blog.cloudflare.com/javascript-native-rpc/"><u>JavaScript-native RPC</u></a> for data transfer between steps of the pipeline. It’s low-latency, lightweight, and simplifies communication between components. This further simplifies our architecture, as separate components behave more as functions within the same process, rather than completely separate applications.</p>
            <pre><code>export default {
  async fetch(request: Request, env: Bindings, ctx: ExecutionContext): Promise&lt;Response&gt; {
      try {
        const { err, apiKey } = auth(request);
        if (err) return err;

        const data = {
          workspaceId: apiKey.workspaceId,
          environmentId: apiKey.environmentId,
          events: request.body
        };
        await env.PROCESSOR.ingest(data);

        return success({ message: "Request Accepted" }, 202);
      } catch (error) {
        return failure({ message: "Internal Error" });
      }
  },
};</code></pre>
            <p><sup><i>Code Block 1: Simplified data receptor using JavaScript-native RPC to execute the processor.</i></sup></p><p>Workers also expose a <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/"><u>Rate Limiting binding</u></a> that enables us to automatically add rate limiting to our services, which we previously had to build ourselves using a combination of DynamoDB and ElastiCache.</p><p>Moreover, we heavily use <code>ctx.waitUntil</code> within our Worker invocations, to offload data transformation outside the request / response path. This further reduces the latency of calls developers make to our data receptors.</p>
    <div>
      <h3>Durable Objects: stateful data processing</h3>
      <a href="#durable-objects-stateful-data-processing">
        
      </a>
    </div>
    <p><a href="https://www.cloudflare.com/en-gb/developer-platform/durable-objects/"><u>Durable Objects</u></a> is a unique service within the Cloudflare Developer Platform, as it enables building stateful applications in a serverless environment. We use Durable Objects in the data pipelines for both real-time error tracking and detecting log patterns.</p><p>For instance, to track errors in real-time, we create a durable object for each new type of error, and this durable object is responsible for keeping track of the frequency of the error, when to notify customers, and the notification channels for the error. <b>This implementation with a single building block removes the need for ElastiCache, Kinesis, and multiple Lambda functions to coordinate protecting the RDS database from being overwhelmed by a high frequency error.</b></p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/14SC0ackxCGiRxAr8DY1Vs/c5729735296552013765c298af802b38/image4.png" />
          </figure><p><sup><i>Figure 3: Real-time error detection architecture comparison</i></sup></p><p>Durable Objects gives us precise control over consistency and concurrency of managing state in the data pipeline.</p><p>In addition to the data pipeline, we use Durable Objects for alerting. Our previous architecture required orchestrating EventBridge Scheduler, SQS, DynamoDB and multiple AWS Lambda functions, whereas with Durable Objects, everything is handled within the <code>alarm</code> handler. </p>
    <div>
      <h3>Workers Analytics Engine: high-cardinality analytics at scale</h3>
      <a href="#workers-analytics-engine-high-cardinality-analytics-at-scale">
        
      </a>
    </div>
    <p>Though managing our own ClickHouse cluster was technically interesting and challenging, it took us away from building the best observability developer experience. With this migration, more of our time is spent enhancing our product and none is spent managing server instances.</p><p><a href="https://developers.cloudflare.com/analytics/analytics-engine/"><u>Workers Analytics Engine</u></a> lets us synchronously write events to a scalable high-cardinality analytics database. We built on top of the same technology that powers Workers Analytics Engine. We also made internal changes to Workers Analytics Engine to natively enable high dimensionality in addition to high cardinality.</p><p>Moreover, Workers Analytics Engine and our solution leverages <a href="https://blog.cloudflare.com/explaining-cloudflares-abr-analytics/"><u>Cloudflare’s ABR analytics</u></a>. ABR stands for Adaptive Bit Rate, and enables us to store telemetry data in multiple tables with varying resolutions, from 100% to 0.0001% of the data. Querying the table with 0.0001% of the data will be several orders of magnitudes faster than the table with all the data, with a corresponding trade-off in accuracy. As such, when a query is sent to our systems, Workers Analytics Engine dynamically selects the most appropriate table to run the query, optimizing both query time and accuracy. Users always get the most accurate result with optimal query time, regardless of the size of their dataset or the timeframe of the query. Compared to our previous system, which was always running queries on the full dataset, the new system now delivers faster queries across our entire user base and use cases<i>.</i></p><p>In addition to these core services (Workers, Durable Objects, Workers Analytics Engine), the new architecture leverages other building blocks from the Cloudflare Developer Platform. <a href="https://www.cloudflare.com/en-gb/developer-platform/products/cloudflare-queues/"><u>Queues</u></a> for asynchronous messaging, decoupling services and enabling an event-driven architecture; <a href="https://www.cloudflare.com/en-gb/developer-platform/d1/"><u>D1</u></a> as our main database for transactional data (queries, alerts, dashboards, configurations, etc.); <a href="https://www.cloudflare.com/en-gb/developer-platform/workers-kv/"><u>Workers KV</u></a> for fast distributed storage; <a href="https://hono.dev/"><u>Hono</u></a> for all our APIs, etc.</p>
    <div>
      <h2>How did we migrate?</h2>
      <a href="#how-did-we-migrate">
        
      </a>
    </div>
    <p>Baselime is built on an event-driven architecture, where every user action triggers an event. It operates on the principle that every user action is recorded as an event and emitted to the rest of the system — whether it’s creating a user, editing a dashboard, or performing any other action. Migrating to Cloudflare involved transitioning our event-driven architecture without compromising uptime and data consistency. Previously, this was powered by AWS EventBridge and SQS, and we moved entirely to Cloudflare Queues.</p><p>We followed the <a href="https://martinfowler.com/bliki/StranglerFigApplication.html"><u>strangler fig pattern</u></a> to incrementally migrate the solution from AWS to Cloudflare. It consists of gradually replacing specific parts of the system with newer services, with minimal disruption to the system. Early in the process, we created a central Cloudflare Queue which acted as the backbone for all transactional event processing during the migration. Every event, whether a new user signup or a dashboard edit, was funneled into this Queue. From there, events were dynamically routed, each event to the relevant part of the application. User actions were synced into D1 and KV, ensuring that all user actions were mirrored across both AWS and Cloudflare during the transition.</p><p>This syncing mechanism enabled us to maintain consistency and ensure that no data was lost as users continued to interact with Baselime.</p><p>Here's an example of how events are processed:</p>
            <pre><code>export default {
  async queue(batch, env) {
    for (const message of batch.messages) {
      try {
        const event = message.body;
        switch (event.type) {
          case "WORKSPACE_CREATED":
            await workspaceHandler.create(env, event.data);
            break;
          case "QUERY_CREATED":
            await queryHandler.create(env, event.data);
            break;
          case "QUERY_DELETED":
            await queryHandler.remove(env, event.data);
            break;
          case "DASHBOARD_CREATED":
            await dashboardHandler.create(env, event.data);
            break;
          //
          // Many more events...
          //
          default:
            logger.info("Matched no events", { type: event.type });
        }
        message.ack();
      } catch (e) {
        if (message.attempts &lt; 3) {
          message.retry({ delaySeconds: Math.ceil(30 ** message.attempts / 10), });
        } else {
          logger.error("Failed handling event - No more retrys", { event: message.body, attempts: message.attempts }, e);
        }
      }
    }
  },
} satisfies ExportedHandler&lt;Env, InternalEvent&gt;;</code></pre>
            <p><sup><i>Code Block 2: Simplified internal events processing during migration.</i></sup></p><p>We migrated the data pipeline from AWS to Cloudflare with an outside-in method: we started with the data receptors and incrementally moved the data processor and the ClickHouse cluster to the new architecture. We began writing telemetry data (logs, metrics, traces, wide-events, etc.) to both ClickHouse (in AWS) and to Workers Analytics Engine simultaneously for the duration of the retention period (30 days).</p><p>The final step was rewriting all of our endpoints, previously hosted on AWS Lambda and ECS containers, into Cloudflare Workers. Once those Workers were ready, we simply switched the DNS records to point to the Workers instead of the existing Lambda functions.</p><p>Despite the complexity, the entire migration process, from the data pipeline to all re-writing API endpoints, took our then team of 3 engineers less than three months.</p>
    <div>
      <h2>We ended up saving over 80% on our cloud bill</h2>
      <a href="#we-ended-up-saving-over-80-on-our-cloud-bill">
        
      </a>
    </div>
    
    <div>
      <h3>Savings on the data receptors</h3>
      <a href="#savings-on-the-data-receptors">
        
      </a>
    </div>
    <p>After switching the data receptors from AWS to Cloudflare in early June 2024, our AWS Lambda cost was reduced by over 85%. These costs were primarily driven by I/O time the receptors spent sending data to a Kinesis Data Stream in the same region.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6P34XkcxEhqR6cjWAGnZaL/54025f08f4642649b7ae53fbaa3775b4/image3.png" />
          </figure><p><sup><i>Figure 4: Baselime daily AWS Lambda cost [note: the gap in data is the result of AWS Cost Explorer losing data when the parent organization of the cloud accounts was changed.]</i></sup></p><p>Moreover, we used Cloudfront to enable custom domains pointing to the data receptors. When we migrated the data receptors to Cloudflare, there was no need for Cloudfront anymore. As such, our Cloudfront cost was reduced to $0.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1vj3JUrE580W749VX8JMBj/c82477d00fe33fba1ffd8658ef0a1229/image2.png" />
          </figure><p><sup><i>Figure 5: Baselime daily Cloudfront cost [note: the gap in data is the result of AWS Cost Explorer losing data when the parent organization of the cloud accounts was changed.]</i></sup></p><p>If we were a regular Cloudflare customer, we estimate that our Cloudflare Workers bill would be around \$25/day after the switch, against \$790/day on AWS: over 95% cost reduction. These savings are primarily driven by the Workers pricing model, since Workers charge for CPU time, and the receptors are primarily just moving data, and as such, are mostly I/O bound.</p>
    <div>
      <h3>Savings on the ClickHouse cluster</h3>
      <a href="#savings-on-the-clickhouse-cluster">
        
      </a>
    </div>
    <p>To evaluate the cost impact of switching from self-hosting ClickHouse to using Workers Analytics Engine, we need to take into account not only the EC2 instances, but also the disk space, networking, and the Kinesis Data Stream cost.</p><p>We completed this switch in late August, achieving over 95% cost reduction in both the Kinesis Data Stream and all EC2 related costs.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3hpbfmwC5vEjDXeSIC2XIr/d103e813848926f224efe6e22e0717ce/image9.png" />
          </figure><p><sup><i>Figure 6: Baselime daily Kinesis Data Stream cost [note: the gap in data is the result of AWS Cost Explorer losing data when the parent organization of the cloud accounts was changed.]</i></sup></p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7aBU0TVUoRTC5GAo3HkS0N/eebb0559ad8d32b56e20f1e5c6121c6f/image6.png" />
          </figure><p><sup><i>Figure 7: Baselime daily EC2 cost [note: the gap in data is the result of AWS Cost Explorer losing data when the parent organization of the cloud accounts was changed.]</i></sup></p><p>If we were a regular Cloudflare customer, we estimate that our Workers Analytics Engine cost would be around \$300/day after the switch, compared to \$1150/day on AWS, a cost reduction of over 70%.</p><p>Not only did we significantly reduce costs by migrating to Cloudflare, but we also improved performance across the board. Responses to users are now faster, with real-time event ingestion happening across Cloudflare’s network, closer to our users. Responses to users querying their data are also much faster, thanks to Cloudflare’s deep expertise in operating ClickHouse at scale.</p><p>Most importantly, we’re no longer bound by limitations in throughput or scale. We launched <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs"><u>Workers Logs</u></a> on September 26, 2024, and our system now handles a much higher volume of events than before, with no sacrifices in speed or reliability.</p><p>These cost savings are outstanding as is, and do not include the total cost of ownership of those systems. We significantly simplified our systems and our codebase, as the platform is taking care of more for us. We’re paged less, we spend less time monitoring infrastructure, and we can focus on delivering product improvements.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Migrating Baselime to Cloudflare has transformed how we build and scale our platform. With Workers, Durable Objects, Workers Analytics Engine, and other services, we now run a fully serverless, globally distributed system that’s more cost-efficient and agile. This shift has significantly reduced our operational overhead and enabled us to iterate faster, delivering better observability tooling to our users.</p><p>You can start observing your Cloudflare Workers today with <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/"><u>Workers Logs</u></a>. Looking ahead, we’re excited about the features we will deliver directly in the Cloudflare Dashboard, including real-time error tracking, alerting, and a query builder for high-cardinality and dimensionality events. All coming by early 2025.</p> ]]></content:encoded>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Performance]]></category>
            <guid isPermaLink="false">6heSTMT0I5jl0vpeR9TucD</guid>
            <dc:creator>Boris Tane</dc:creator>
        </item>
        <item>
            <title><![CDATA[Adopting OpenTelemetry for our logging pipeline]]></title>
            <link>https://blog.cloudflare.com/adopting-opentelemetry-for-our-logging-pipeline/</link>
            <pubDate>Mon, 03 Jun 2024 13:00:41 GMT</pubDate>
            <description><![CDATA[ Recently, Cloudflare's Observability team undertook an effort to migrate our existing syslog-ng backed logging infrastructure to instead being backed by OpenTelemetry Collectors. In this post, we detail the process that we undertook, and the difficulties we faced along the way ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Cloudflare’s logging pipeline is one of the largest data pipelines that Cloudflare has, serving millions of log events per second globally, from every server we run. Recently, we undertook a project to migrate the underlying systems of our logging pipeline from <a href="https://www.syslog-ng.com/">syslog-ng</a> to <a href="https://github.com/open-telemetry/opentelemetry-collector">OpenTelemetry Collector</a> and in this post we want to share how we managed to swap out such a significant piece of our infrastructure, why we did it, what went well, what went wrong, and how we plan to improve the pipeline even more going forward.</p>
    <div>
      <h2>Background</h2>
      <a href="#background">
        
      </a>
    </div>
    <p>A full breakdown of our existing infrastructure can be found in our previous post <a href="/an-overview-of-cloudflares-logging-pipeline">An overview of Cloudflare's logging pipeline</a>, but to quickly summarize here:</p><ul><li><p>We run a syslog-ng daemon on every server, reading from the local systemd-journald journal, and a set of named pipes.</p></li><li><p>We forward those logs to a set of centralized “log-x receivers”, in one of our core data centers.</p></li><li><p>We have a dead letter queue destination in another core data center, which receives messages that could not be sent to the primary receiver, and which get mirrored across to the primary receivers when possible.</p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5sjQKVdQN2fHiKpXDoz3KP/403e5d362685125b8c6fc7b8c7e60d7a/image1-16.png" />
            
            </figure><p>The goal of this project was to replace those syslog-ng instances as transparently as possible. That means we needed to implement all these behaviors as precisely as possible, so that we didn’t need to modify any downstream systems.</p><p>There were a few reasons for wanting to make this shift, and enduring the difficulties of overhauling such a large part of our infrastructure:</p><ul><li><p>syslog-ng is written in C, which is not a core competency of our team. While we have made upstream contributions to the project in the past, and the experience was great, having the OpenTelemetry collector in Go allows much more of our team to be able to contribute improvements to the system.</p></li><li><p>Building syslog-ng against our internal Post-Quantum cryptography libraries was difficult, due to having to maintain an often brittle C build chain, whereas our engineering teams have optimized the Go build model to make this as simple as possible.</p></li><li><p>OpenTelemetry Collectors have built in support for Prometheus metrics, which allows us to gather much deeper levels of telemetry data around what the collectors are doing, and surface these insights as “meta-observability” to our engineering teams.</p></li><li><p>We already use OpenTelemetry Collectors for some of our tracing infrastructure, so unifying onto one daemon rather than having separate collectors for all our different types of telemetry reduces the cognitive load on the team.</p></li></ul>
    <div>
      <h2>The Migration Process</h2>
      <a href="#the-migration-process">
        
      </a>
    </div>
    
    <div>
      <h3>What we needed to build</h3>
      <a href="#what-we-needed-to-build">
        
      </a>
    </div>
    <p>While the upstream <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib">contrib repository</a> contains a wealth of useful components, all packaged into its own distribution, it became clear early on that we would need our own internal components. Having our own internal components would require us to build our own distribution, so one of the first things we did was turn to <a href="https://pkg.go.dev/go.opentelemetry.io/collector/cmd/builder">OCB</a> (OpenTelemetry Collector Builder) to provide us a way to build an internal distribution of an OpenTelemetry Collector. We eventually ended up templating our OCB configuration file to automatically include all the internal components we have built, so that we didn’t have to add them manually.</p><p>In total, we built four internal components for our initial version of the collector.</p>
    <div>
      <h4>cfjs1exporter</h4>
      <a href="#cfjs1exporter">
        
      </a>
    </div>
    <p>Internally, our logging pipeline uses a line format we call “cfjs1”. This format describes a JSON encoded log, with two fields: a <code>format</code> field, that decides the type of the log, and a “wrapper” field which contains the log body (which is a structured JSON object in and of itself), with a field name that changes depending on the <code>format</code> field. These two fields decide which Kafka topic our receivers will end up placing the log message in.</p><p>Because we didn’t want to make changes to other parts of the pipeline, we needed to support this format in our collector. To do this, we took inspiration from the contrib repository’s <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/syslogexporter">syslogexporter</a>, building our cfjs1 format into it.</p><p>Ultimately, we would like to move towards using <a href="https://opentelemetry.io/docs/specs/otel/protocol/">OTLP</a> (OpenTelemetry Protocol) as our line format. This would allow us to remove our custom exporter, and utilize open standards, enabling easier migrations in the future.</p>
    <div>
      <h4>fileexporter</h4>
      <a href="#fileexporter">
        
      </a>
    </div>
    <p>While the upstream contrib repo does have a <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/fileexporter">file exporter</a> component, it only supports two formats: JSON and Protobuf. We needed to support two other formats, plain text and syslog, so we ended up forking the file exporter internally. Our plain text formatter simply outputs the body of the log message into a file, with newlines as a delimiter. Our syslog format outputs <a href="https://www.rfc-editor.org/rfc/rfc5424.html">RFC 5424</a> formatted syslog messages into a file.</p><p>The other feature we implemented on our internal fork was custom permissions. The upstream file exporter is a bit of a mess, in that it actually has two different modes of operation – a standard mode, not utilizing any of the compression or rotation features, and a more advanced mode which uses those features. Crucially, if you want to use any of the rotation features, you end up using <a href="https://github.com/natefinch/lumberjack">lumberjack</a>, whereas without those features you use a more native file handling. This leads to strange issues where some features of the exporter are supported in one mode, but not the other. In the case of permissions, the community <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/31459">seems open to the idea</a> in the native handling, but <a href="https://github.com/natefinch/lumberjack/issues/164">lumberjack seems against the idea</a>. This dichotomy is what led us to implement it ourselves internally.</p><p>Ultimately, we would love to upstream these improvements should the community be open to them. Having support for custom marshallers (<a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/30331">https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/30331</a>) would have made this a bit easier, however it’s not clear how that would work with OCB. Either that, or we could open source them in the <a href="https://github.com/cloudflare">Cloudflare organization</a>, but we would love to remove the need to maintain our own fork in the future.</p>
    <div>
      <h4>externaljsonprocessor</h4>
      <a href="#externaljsonprocessor">
        
      </a>
    </div>
    <p>We want to set the value of an attribute/field that comes from external sources: either from an HTTP endpoint or an output from running a specific command. In syslog-ng, we have a sidecar service that generates a syslog-ng configuration to achieve this. In replacing syslog-ng with our OpenTelemetry Collector, we thought it would be easier to implement this feature as a custom component of our collector instead.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/WG45quu9J9Mpm1SxOCQR8/0f2761748bb68af101ed2880cb5e2fbb/image2-14.png" />
            
            </figure><p>To that end, we implemented an “external JSON processor”, which is able to periodically query external data sources and add those fields to all the logs that flow through the processor. Cloudflare has many internal tools and APIs, and we use this processor to fetch data like the status of a data center, or the status of a systemd unit. This enables our engineers to have more filtering options, such as to exclude logs from data centers that are not supposed to receive customer traffic, or servers that are disabled for maintenance. Crucially, this allows us to update these values much faster than the standard three-hour cadence of other configuration updates through salt, allowing more rapid updates to these fields that may change quickly as we operate our network.</p>
    <div>
      <h4>ratelimit processor</h4>
      <a href="#ratelimit-processor">
        
      </a>
    </div>
    <p>The last component we needed to implement was a replacement for the syslog-ng <a href="https://www.syslog-ng.com/technical-documents/doc/syslog-ng-open-source-edition/3.36/administration-guide/69">ratelimit filter</a>, also contributed by us upstream. The ratelimit filter allows applying rate limits based on a specific field of a log message, dropping messages that exceed some limit (with an optional burst limit). In our case, we apply rate limits over the <code>service</code> field, ensuring that no individual service can degrade the log collection for any other.</p><p>While there has been some upstream discussion of <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/6908">similar components</a>, we couldn’t find anything that explicitly fit our needs. This was especially true when you consider that in our case the data loss during the rate limiting process is intentional, something that might be hard to sell when trying to build something more generally applicable.</p>
    <div>
      <h3>How we migrated</h3>
      <a href="#how-we-migrated">
        
      </a>
    </div>
    <p>Once we had an OpenTelemetry Collector binary, we had to deploy it. Our deployment process took two forks: Deploying to our core data centers, and deploying to our edge data centers. For those unfamiliar, Cloudflare’s core data centers contain a small number of servers with a very diverse set of workloads, from Postgresql, to ElasticSearch, to Kubernetes, and everything in between. Our edge data centers, on the other hand, are much more homogenous. They contain a much larger number of servers, each one running the same set of services.</p><p>Both edge and core use <a href="https://saltproject.io/index.html">salt</a> to configure the services running on their servers. This meant that the first step was to write salt states that would install the OpenTelemetry collector, and write the appropriate configurations to disk. Once we had those in place, we also needed to write some temporary migration pieces that would disable syslog-ng and start the OpenTelemetry collector, as well as the inverse in the case of a roll back.</p><p>For the edge data centers, once we had a set of configurations written, it mostly came down to rolling the changes out gradually across the edge servers. Because edge servers run the same set of services, once we had gained confidence in our set of configurations, it became a matter of rolling out the changes slowly and monitoring the logging pipelines along the way. We did have a few false starts here, and needed to instrument our cfjs1exporter a bit more to work around issues surrounding some of our more niche services and general Internet badness which we’ll detail below in our lessons learned.</p><p>The core data centers required a more hands-on approach. Many of our services in core have custom syslog-ng configurations. For example, our Postgresql servers have custom handling for their audit logs, and our Kubernetes servers have custom handling for <a href="https://projectcontour.io/">contour</a> ingress and error logs. This meant that each role with a custom config had to be manually onboarded, with extensive testing on the designated canary nodes of each role to validate the configurations.</p>
    <div>
      <h2>Lessons Learned</h2>
      <a href="#lessons-learned">
        
      </a>
    </div>
    
    <div>
      <h3>Failover</h3>
      <a href="#failover">
        
      </a>
    </div>
    <p>At Cloudflare, we regularly schedule chaos testing on our core data centers which contain our centralized log receivers. During one of these chaos tests, our cfjs1 exporter did not notice that it could not send to the primary central logging server. This caused our collector to not failover to the secondary central logging server and its log buffer to fill up, which resulted in the collector failing to consume logs from its receivers. This is not a problem with journal receivers since logs are buffered by journald before they get consumed by the collector, but it is a different case with named pipe receivers. Due to this bug, our collectors stopped consuming logs from named pipes, and services writing to these named pipes started blocking threads waiting to write to them. Our syslog-ng deployment solved this issue using a <a href="https://mmonit.com/">monit script</a> to periodically kill the connections between syslog-ng and the central receivers, however we opted to solve this more explicitly in our exporter by building in much tighter timeouts, and modifying the upstream <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/failoverconnector">failover receiver</a> to better respond to these partial failures.</p>
    <div>
      <h3>Cutover delays</h3>
      <a href="#cutover-delays">
        
      </a>
    </div>
    <p>As we’ve <a href="/how-we-use-hashicorp-nomad">previously blogged about</a>, at Cloudflare, we use <a href="https://www.nomadproject.io/">Nomad</a> for running dynamic tasks in our edge data centers. We use a custom driver to run containers and this custom driver handles the shipping of logs from the container to a named pipe.</p><p>We did the migration from syslog-ng to OpenTelemetry Collectors while servers were live and running production services. During the migration, there was a gap when syslog-ng was stopped by our configuration management and our OpenTelemetry collector was started on the server. This gap caused the logs in the named pipe to not get consumed and similar to the previous named pipe, the services writing to the named pipe receiver in blocking mode got affected. Similar to NGINX and Postgresql, Cloudflare’s driver for Nomad also writes logs to the named pipe driver in blocking mode. Because of this delay, the driver timed out sending logs and rescheduled the containers.</p><p>We ultimately caught this pretty early on in testing, and changed our approach to the rollout. Instead of using Salt to separately stop syslog-ng and start the collector, we instead used salt to schedule a systemd “one shot” service that simultaneously stopped syslog-ng and started the collector, minimizing the downtime between the two.</p>
    <div>
      <h2>What’s next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>Migrating such a critical part of our infrastructure is never easy, especially when it has remained largely untouched for nearly half a decade. Even with the issues we hit during our rollout, migrating to an OpenTelemetry Collector unlocks so many more improvements to our logging pipeline going forward. With the initial deployment complete, there are a number of changes we’re excited to work on next, including:</p><ul><li><p>Better handling for log sampling, including tail sampling</p></li><li><p>Better insights for our engineering teams on their telemetry production</p></li><li><p>Migration to OTLP as our line protocol</p></li><li><p>Upstreaming of some of our custom components</p></li></ul><p>If that sounds interesting to you, <a href="https://boards.greenhouse.io/cloudflare/jobs/5563753?gh_jid=5563753">we’re hiring engineers to come work on our logging pipeline</a>, so please reach out!</p> ]]></content:encoded>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Engineering]]></category>
            <guid isPermaLink="false">1Ykpgu09F0QGyb0bptVin4</guid>
            <dc:creator>Colin Douch</dc:creator>
            <dc:creator>Jayson Cena</dc:creator>
        </item>
        <item>
            <title><![CDATA[Reclaiming CPU for free with Go's Profile Guided Optimization]]></title>
            <link>https://blog.cloudflare.com/reclaiming-cpu-for-free-with-pgo/</link>
            <pubDate>Tue, 14 May 2024 13:00:32 GMT</pubDate>
            <description><![CDATA[ Golang 1.20 introduced support for Profile Guided Optimization (PGO) to the go compiler. This post covers the process we created for experimenting with PGO at Cloudflare, and measuring the CPU savings ]]></description>
            <content:encoded><![CDATA[ <p></p><p><a href="https://tip.golang.org/doc/go1.20">Golang 1.20</a> introduced support for Profile Guided Optimization (PGO) to the go compiler. This allows guiding the compiler to introduce optimizations based on the real world behaviour of your system. In the Observability Team at Cloudflare, we maintain a few Go-based services that use thousands of cores worldwide, so even the 2-7% savings advertised would drastically reduce our CPU footprint, effectively for free. This would reduce the CPU usage for our internal services, freeing up those resources to serve customer requests, providing measurable improvements to our customer experience. In this post, I will cover the process we created for experimenting with PGO – collecting representative profiles across our production infrastructure and then deploying new PGO binaries and measuring the CPU savings.</p>
    <div>
      <h3>How does PGO work?</h3>
      <a href="#how-does-pgo-work">
        
      </a>
    </div>
    <p>PGO itself is not a Go-specific tool, although it is relatively new. PGO allows you to take CPU profiles from a program running in production and use that to optimise the generated assembly for that program. This includes a bunch of different optimisations such as inlining heavily used functions more aggressively, reworking branch prediction to favour the more common branches, and rearranging the generated code to lump hot paths together to save on CPU cache swapping.</p><p>The general flow for using PGO is to compile a non-PGO binary and deploy it to production, collect CPU profiles from the binary in production, and then compile a <i>second</i> binary using that CPU profile. CPU Profiles contain samples of what the CPU was spending the most time on when executing a program, which provides valuable context to the compiler when it’s making decisions about optimising a program. For example, the compiler may choose to inline a function that is called many times to reduce the function call overhead, or it might choose to unroll a particularly jump-heavy loop. Crucially, using a profile from production can guide the compiler much more efficiently than any upfront heuristics.</p>
    <div>
      <h3>A practical example</h3>
      <a href="#a-practical-example">
        
      </a>
    </div>
    <p>In the Observability team, we operate a system we call “wshim”. Wshim is a service that runs on every one of our edge servers, providing a push gateway for telemetry sourced from our internal Cloudflare Workers. Because this service runs on every server, and is called every time an internal worker is called, wshim requires a lot of CPU time to run. In order to track exactly how much, we put wshim into its own <a href="https://en.wikipedia.org/wiki/Cgroups">cgroup</a>, and use <a href="https://github.com/google/cadvisor">cadvisor</a> to expose Prometheus metrics pertaining to the resources that it uses.</p><p>Before deploying PGO, wshim was using over 3000 cores globally:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1YefuIqASjynzrsnMuKMYk/9d5ef360c79393dac4771b21f7cdc1a3/image1-3.png" />
            
            </figure><p><code>container_cpu_time_seconds</code> is our internal metric that tracks the amount of time a CPU has spent running wshim across the world. Even a 2% saving would return 60 cores to our customers, making the Cloudflare network even more efficient.</p><p>The first step in deploying PGO was to collect representative profiles from our servers worldwide. The first problem we run into is that we run thousands of servers, each with different usage patterns at given points in time – a datacenter serving lots of requests during daytime hours will have a different usage pattern than a different data center that locally is in the middle of the night. As such, selecting exactly which servers to profile is paramount to collecting good profiles for PGO to use.</p><p>In the end, we decided that the best samples would be from those datacenters experiencing heavy load – those are the ones where the slowest parts of wshim would be most obvious. Even further, we will only collect profiles from our Tier 1 data centers. These are data centers that serve our most heavily populated regions, are generally our largest, and are generally under very heavy loads during peak hours.</p><p>Concretely, we can get a list of high CPU servers by querying our <a href="https://thanos.io/">Thanos</a> infrastructure:</p>
            <pre><code>num_profiles="1000"

# Fetch the top n CPU users for wshim across the edge using Thanos.
cloudflared access curl "https://thanos/api/v1/query?query=topk%28${num_profiles}%2Cinstance%3Acontainer_cpu_time_seconds_total%3Arate2m%7Bapp_name%3D%22wshim.service%22%7D%29&amp;dedup=true&amp;partial_response=true" --compressed | jq '.data.result[].metric.instance' -r &gt; "${instances_file}"</code></pre>
            <p>Go makes actually fetching CPU profiles trivial with <a href="https://pkg.go.dev/net/http/pprof">pprof</a>. In order for our engineers to debug their systems in production, we provide a method to easily retrieve production profiles that we can use here. Wshim provides a pprof interface that we can use to retrieve profiles, and we can collect these again with bash:</p>
            <pre><code># For every instance, attempt to pull a CPU profile. Note that due to the transient nature of some data centers
# a certain percentage of these will fail, which is fine, as long as we get enough nodes to form a representative sample.
while read instance; do fetch-pprof $instance –port 8976 –seconds 30' &gt; "${working_dir}/${instance}.pprof" &amp; done &lt; "${instances_file}"

wait $(jobs -p)</code></pre>
            <p>And then merge all the gathered profiles into one, with go tool:</p>
            <pre><code># Merge the fetched profiles into one.
go tool pprof -proto "${working_dir}/"*.pprof &gt; profile.pprof</code></pre>
            <p>It’s this merged profile that we will use to compile our pprof binary. As such, we commit it to our repo so that it lives alongside all the other deployment components of wshim:</p>
            <pre><code>~/cf-repos/wshim ± master
23/01/2024 10:49:08 AEDT❯ tree pgo
pgo
├── README.md
├── fetch-profiles.sh
└── profile.pprof</code></pre>
            <p>And update our Makefile to pass in the <code>-pgo</code> flag to the <code>go build</code> command:</p>
            <pre><code>build:
       go build -pgo ./pgo/profile.pprof -o /tmp/wshim ./cmd/wshim</code></pre>
            <p>After that, we can build and deploy our new PGO optimized version of wshim, like any other version.</p>
    <div>
      <h3>Results</h3>
      <a href="#results">
        
      </a>
    </div>
    <p>Once our new version is deployed, we can review our CPU metrics to see if we have any meaningful savings. Resource usages are notoriously hard to compare. Because wshim’s CPU usage scales with the amount of traffic that any given server is receiving, it has a lot of potentially confounding variables, including the time of day, day of the year, and whether there are any active attacks affecting the datacenter. That being said, we can take a couple of numbers that might give us a good indication of any potential savings.</p><p>Firstly, we can look at the CPU usage of wshim immediately before and after the deployment. This may be confounded by the time difference between the sets, but it shows a decent improvement. Because our release takes just under two hours to roll to every tier 1 datacenter, we can use PromQLs `offset` operator to measure the difference:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3enVHBJjLVGDCSBBRqrLAn/157a65a91619f9252ad8819600241921/image3-1.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/I7piaGicvlQbFmzpiHxUT/65553d182d296b4b1d663b69eb34cb41/image4-1.png" />
            
            </figure><p>This indicates that following the release, we’re using ~97 cores fewer than before the release, a ~3.5% reduction. This seems to be inline with the <a href="https://go.dev/doc/pgo#overview">upstream documentation</a> that gives numbers between 2% and 14%.</p><p>The second number we can look at is the usage at the same time of day on different days of the week. The average usage for the 7 days prior to the release was 3067.83 cores, whereas the 7 days <i>after</i> the release were 2996.78, a savings of 71 CPUs. Not quite as good as our 97 CPU savings, but still pretty substantial!</p><p>This seems to prove the benefits of PGO – without changing the code at all, we managed to save ourselves several servers worth of CPU time.</p>
    <div>
      <h3>Future work</h3>
      <a href="#future-work">
        
      </a>
    </div>
    <p>Looking at these initial results certainly seems to prove the case for PGO – saving multiple servers worth of CPU without any code changes is a big win for freeing up resources to better serve customer requests. However, there is definitely more work to be done here. In particular:</p><ul><li><p>Automating the collection of profiles, perhaps using <a href="https://www.cncf.io/blog/2022/05/31/what-is-continuous-profiling/">continuous profiling</a></p></li><li><p>Refining the deployment process to handle the new “two-step deployment”, deploying a non PGO binary, and then a PGO one</p></li><li><p>Refining our techniques to derive representative profiling samples</p></li><li><p>Implementing further improvements with <a href="https://github.com/facebookarchive/BOLT">BOLT</a>, or other Link Time Optimization (LTO) techniques</p></li></ul><p><i>If that sounds interesting to you, we’re hiring in both the </i><a href="https://boards.greenhouse.io/cloudflare/jobs/5563753?gh_jid=5563753"><i>USA</i></a><i> and </i><a href="https://boards.greenhouse.io/cloudflare/jobs/5443710?gh_jid=5443710"><i>EMEA</i></a><i>!</i></p> ]]></content:encoded>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Performance]]></category>
            <guid isPermaLink="false">7By5SCb5dDWXN5ymX5WfgD</guid>
            <dc:creator>Colin Douch</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare acquires Baselime to expand serverless application observability capabilities]]></title>
            <link>https://blog.cloudflare.com/cloudflare-acquires-baselime-expands-observability-capabilities/</link>
            <pubDate>Fri, 05 Apr 2024 15:50:00 GMT</pubDate>
            <description><![CDATA[ Today, we’re thrilled to announce that Cloudflare has acquired Baselime, a serverless observability company ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Today, we’re thrilled to announce that <a href="https://www.cloudflare.com/press-releases/2024/cloudflare-enters-observability-market-with-acquisition-baselime/">Cloudflare has acquired Baselime</a>.</p><p>The cloud is changing. Just a few years ago, serverless functions were revolutionary. Today, entire applications are built on serverless architectures, from compute to databases, storage, queues, etc. — with Cloudflare leading the way in making it easier than ever for developers to build, without having to think about their architecture. And while the adoption of serverless has made it simple for developers to run fast, it has also made one of the most difficult problems in software even harder: how the heck do you unravel the behavior of distributed systems?</p><p>When I started Baselime 2 years ago, our goal was simple: enable every developer to build, ship, and learn from their serverless applications such that they can resolve issues before they become problems.</p><p>Since then, we built an observability platform that enables developers to understand the behaviour of their cloud applications. It’s designed for high cardinality and dimensionality data, from logs to distributed tracing with <a href="https://opentelemetry.io/">OpenTelemetry</a>. With this data, we automatically surface insights from your applications, and enable you to quickly detect, troubleshoot, and resolve issues in production.</p><p>In parallel, Cloudflare has been busy the past few years building the next frontier of cloud computing: the connectivity cloud. The team is building primitives that enable developers to build applications with a completely new set of paradigms, from Workers to D1, <a href="https://www.cloudflare.com/developer-platform/r2/">R2</a>, Queues, KV, Durable Objects, AI, and all the other services available on the Cloudflare Developers Platform.</p><p>This synergy makes Cloudflare the perfect home for Baselime. Our core mission has always been to simplify and innovate around observability for the future of the cloud, and Cloudflare's ecosystem offers the ideal ground to further this cause. With Cloudflare, we're positioned to deeply integrate into a platform that tens of thousands of developers trust and use daily, enabling them to quickly build, ship, and troubleshoot applications. We believe that every Worker, Queue, KV, Durable Object, AI call, etc. should have built-in observability by default.</p><p>That’s why we’re incredibly excited about the potential of what we can build together and the impact it will have on developers around the world.</p><p>To give you a preview into what’s ahead, I wanted to dive deeper into the 3 core concepts we followed while building Baselime.</p>
    <div>
      <h3>High Cardinality and Dimensionality</h3>
      <a href="#high-cardinality-and-dimensionality">
        
      </a>
    </div>
    <p>Cardinality and dimensionality are best described using examples. Imagine you’re playing a board game with a deck of cards. High cardinality is like playing a game where every card is a unique character, making it hard to remember or match them. And high dimensionality is like each card has tons of details like strength, speed, magic, aura, etc., making the game's strategy complex because there's so much to consider.</p><p>This also applies to the data your application emits. For example, when you log an HTTP request that makes database calls.</p><ul><li><p>High cardinality means that your logs can have a unique <code>userId</code> or <code>requestId</code> (which can take millions of distinct values). Those are high cardinality fields.</p></li><li><p>High dimensionality means that your logs can have thousands of possible fields. You can record each HTTP header of your request and the details of each database call. Any log can be a key-value object with thousands of individual keys.</p></li></ul><p>The ability to query on high cardinality and dimensionality fields is key to modern observability. You can surface all errors or requests for a specific user, compute the duration of each of those requests, and group by location. You can answer all of those questions with a single tool.</p>
    <div>
      <h3>OpenTelemetry</h3>
      <a href="#opentelemetry">
        
      </a>
    </div>
    <p><a href="https://opentelemetry.io/">OpenTelemetry</a> provides a common set of tools, APIs, SDKs, and standards for instrumenting applications. It is a game-changer for debugging and understanding cloud applications. You get to see the big picture: how fast your HTTP APIs are, which routes are experiencing the most errors, or which database queries are slowest. You can also get into the details by following the path of a single request or user across your entire application.</p><p>Baselime is OpenTelemetry native, and it is built from the ground up to leverage OpenTelemetry data. To support this, we built a set of OpenTelemetry SDKs compatible with several serverless runtimes.</p><p>Cloudflare is building the cloud of tomorrow and has developed <a href="https://github.com/cloudflare/workerd">workerd</a>, a modern JavaScript runtime for Workers. With Cloudflare, we are considering embedding OpenTelemetry directly in the Workers’ runtime. That’s one more reason we’re excited to grow further at Cloudflare, enabling more developers to understand their applications, even in the most unconventional scenarios.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5srzwFELwKonKcv5tbyl7D/56db2543ab62c98e198b39164565308c/pasted-image-0--8-.png" />
            
            </figure>
    <div>
      <h3>Developer Experience</h3>
      <a href="#developer-experience">
        
      </a>
    </div>
    <p>Observability without action is just storage. I have seen too many developers pay for tools to store logs and metrics they never use, and the key reason is how opaque these tools are.</p><p>The crux of the issue in modern observability isn’t the technology itself, but rather the developer experience. Many tools are complex, with a significant learning curve. This friction reduces the speed at which developers can identify and resolve issues, ultimately affecting the reliability of their applications. Improving developer experience is key to unlocking the full potential of observability.</p><p>We built Baselime to be an exploratory solution that surfaces insights to you rather than requiring you to dig for them. For example, we notify you in real time when errors are discovered in your application, based on your logs and traces. You can quickly search through all of your data with full-text search, or using our powerful query engine, which makes it easy to correlate logs and traces for increased visibility, or ask our AI debugging assistant for insights on the issue you’re investigating.</p><p>It is always possible to go from one insight to another, asking questions about the state of your app iteratively until you get to the root cause of the issue you are troubleshooting.</p><p>Cloudflare has always prioritised the developer experience of its developer platform, especially with <a href="https://developers.cloudflare.com/workers/wrangler/">Wrangler</a>, and we are convinced it’s the right place to solve the developer experience problem of observability.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/57hiGcPaNP3WBnqhDbU1TC/48fd408810c38c23969a932b72636da0/pasted-image-0-2.png" />
            
            </figure>
    <div>
      <h3>What’s next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>Over the next few months, we’ll work to bring the core of Baselime into the Cloudflare ecosystem, starting with OpenTelemetry, real-time error tracking, and all the developer experience capabilities that make a great observability solution. We will keep building and improving observability for applications deployed outside Cloudflare because we understand that observability should work across providers.</p><p>But we don’t want to stop there. We want to push the boundaries of what modern observability looks like. For instance, directly connecting to your codebase and correlating insights from your logs and traces to functions and classes in your codebase. We also want to enable more AI capabilities beyond our debugging assistant. We want to deeply integrate with your repositories such that you can go from an error in your logs and traces to a Pull Request in your codebase within minutes.</p><p>We also want to enable everyone building on top of Large Language Models to do all your LLM observability directly within Cloudflare, such that you can optimise your prompts, improve latencies and reduce error rates directly within your cloud provider. These are just a handful of capabilities we can now build with the support of the Cloudflare platform.</p>
    <div>
      <h3>Thanks</h3>
      <a href="#thanks">
        
      </a>
    </div>
    <p>We are incredibly thankful to our community for its continued support, from day 0 to today. With your continuous feedback, you’ve helped us build something we’re incredibly proud of.</p><p>To all the developers currently using Baselime, you’ll be able to keep using the product and will receive ongoing support. Also, we are now making all the paid Baselime features completely free.</p><p>Baselime products remain available to sign up for while we work on integrating with the Cloudflare platform. We anticipate sunsetting the Baselime products towards the end of 2024 when you will be able to observe all of your applications within the Cloudflare dashboard. If you're interested in staying up-to-date on our work with Cloudflare, we will release a signup link in the coming weeks!</p><p>We are looking forward to continuing to innovate with you.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Acquisitions]]></category>
            <category><![CDATA[Connectivity Cloud]]></category>
            <guid isPermaLink="false">7MaPl0JR4okx51bFTrn69J</guid>
            <dc:creator>Boris Tane</dc:creator>
            <dc:creator>Rita Kozlov</dc:creator>
        </item>
        <item>
            <title><![CDATA[New tools for production safety — Gradual deployments, Source maps, Rate Limiting, and new SDKs]]></title>
            <link>https://blog.cloudflare.com/workers-production-safety/</link>
            <pubDate>Thu, 04 Apr 2024 13:05:00 GMT</pubDate>
            <description><![CDATA[ Today we are announcing five updates that put more power in your hands – Gradual Deployments, Source mapped stack traces in Tail Workers, a new Rate Limiting API, brand-new API SDKs, and updates to Durable Objects – each built with mission-critical production services in mind ]]></description>
            <content:encoded><![CDATA[ <p></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6HeyqozVOGygo2RCNnunIq/b429dc5b9f81c9fed6dfc0d200d296e5/image4-7.png" />
            
            </figure><p>2024’s Developer Week is all about production readiness. On Monday. April 1, we <a href="/making-full-stack-easier-d1-ga-hyperdrive-queues/">announced</a> that <a href="https://developers.cloudflare.com/d1/">D1</a>, <a href="https://developers.cloudflare.com/queues/">Queues</a>, <a href="https://developers.cloudflare.com/hyperdrive/">Hyperdrive</a>, and <a href="https://developers.cloudflare.com/analytics/analytics-engine/">Workers Analytics Engine</a> are ready for production scale and generally available. On Tuesday, April 2, we <a href="/workers-ai-ga-huggingface-loras-python-support">announced</a> the same about our inference platform, <a href="https://developers.cloudflare.com/workers-ai/">Workers AI</a>. And we’re not nearly done yet.</p><p>However, production readiness isn’t just about the scale and reliability of the services you build with. You also need tools to make changes safely and reliably. You depend not just on what Cloudflare provides, but on being able to precisely control and tailor how Cloudflare behaves to the needs of your application.</p><p>Today we are announcing five updates that put more power in your hands – Gradual Deployments, source mapped stack traces in Tail Workers, a new Rate Limiting API, brand-new API SDKs, and updates to Durable Objects – each built with mission-critical production services in mind. We build our own products using Workers, including <a href="https://developers.cloudflare.com/cloudflare-one/policies/access/">Access</a>, <a href="https://developers.cloudflare.com/r2/">R2</a>, <a href="https://developers.cloudflare.com/kv/">KV</a>, <a href="https://developers.cloudflare.com/waiting-room/">Waiting Room</a>, <a href="https://developers.cloudflare.com/vectorize/">Vectorize</a>, <a href="https://developers.cloudflare.com/queues/">Queues</a>, <a href="https://developers.cloudflare.com/stream/">Stream</a>, and more. We rely on each of these new features ourselves to ensure that we are production ready – and now we’re excited to bring them to everyone.</p>
    <div>
      <h3>Gradually deploy changes to Workers and Durable Objects</h3>
      <a href="#gradually-deploy-changes-to-workers-and-durable-objects">
        
      </a>
    </div>
    <p>Deploying a Worker is nearly instantaneous – a few seconds and your change is live <a href="https://www.cloudflare.com/network/">everywhere</a>.</p><p>When you reach production scale, each change you make carries greater risk, both in terms of volume and expectations. You need to meet your 99.99% availability SLA, or have an ambitious P90 latency SLO. A bad deployment that’s live for 100% of traffic for 45 seconds could mean millions of failed requests. A subtle code change could cause a thundering herd of retries to an overwhelmed backend, if rolled out all at once. These are the kinds of risks we consider and mitigate ourselves for our own services built on Workers.</p><p>The way to mitigate these risks is to deploy changes gradually – commonly called rolling deployments:</p><ol><li><p>The current version of your application runs in production.</p></li><li><p>You deploy the new version of your application to production, but only route a small percentage of traffic to this new version, and wait for it to “soak” in production, monitoring for regressions and bugs. If something bad happens, you’ve caught it early at a small percentage (e.g. 1%) of traffic and can revert quickly.</p></li><li><p>You gradually increment the percentage of traffic until the new version receives 100%, at which point it is fully rolled out.</p></li></ol><p>Today we’re opening up a first-class way to deploy code changes gradually to Workers and Durable Objects via the <a href="https://developers.cloudflare.com/api/operations/worker-deployments-list-deployments">Cloudflare API</a>, the <a href="https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#via-wrangler">Wrangler CLI</a>, or the <a href="https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#via-the-cloudflare-dashboard">Workers dashboard</a>. Gradual Deployments is entering open beta – you can use Gradual Deployments with any Cloudflare account that is on the <a href="https://developers.cloudflare.com/workers/platform/pricing/#workers">Workers Free plan</a>, and very soon you’ll be able to start using Gradual Deployments with Cloudflare accounts on the <a href="https://developers.cloudflare.com/workers/platform/pricing/#workers">Workers Paid</a> and Enterprise plans. You’ll see a banner on the Workers dashboard once your account has access.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5C2hc1EtfppDeWWDxJBh3K/3235bfa198e136bfac793f877415011d/pasted-image-0.png" />
            
            </figure><p>When you have two versions of your Worker or Durable Object running concurrently in production, you almost certainly want to be able to filter your metrics, exceptions, and logs by version. This can help you spot production issues early, when the new version is only rolled out to a small percentage of traffic, or compare performance metrics when splitting traffic 50/50. We’ve also added <a href="https://www.cloudflare.com/learning/performance/what-is-observability/">observability</a> at a version level across our platform:</p><ul><li><p>You can filter analytics in the Workers dashboard and via the <a href="https://developers.cloudflare.com/analytics/graphql-api/">GraphQL Analytics API</a> by version.</p></li><li><p><a href="https://developers.cloudflare.com/workers/observability/logging/logpush/">Workers Trace Events</a> and <a href="https://developers.cloudflare.com/workers/observability/logging/tail-workers/">Tail Worker</a> events include the version ID of your Worker, along with optional version message and version tag fields.</p></li><li><p>When using <a href="https://developers.cloudflare.com/workers/wrangler/commands/#tail">wrangler tail</a> to view live logs, you can view logs for a specific version.</p></li><li><p>You can access version ID, message, and tag from within your Worker’s code, by configuring the <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/">Version Metadata binding</a>.</p></li></ul><p>You may also want to make sure that each client or user only sees a consistent version of your Worker. We’ve added <a href="https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#version-keys-and-session-affinity">Version Affinity</a> so that requests associated with a particular identifier (such as user, session, or any unique ID) are always handled by a consistent version of your Worker. <a href="https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#version-keys-and-session-affinity">Session Affinity</a>, when used with <a href="https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#setting-cloudflare-workers-version-key-using-ruleset-engine">Ruleset Engine</a>, gives you full control over both the mechanism and identifier used to ensure “stickiness”.</p><p>Gradual Deployments is entering open beta. As we move towards GA, we’re working to support:</p><ul><li><p><b>Version Overrides.</b> Invoke a specific version of your Worker in order to test before it serves any production traffic. This will allow you to create Blue-Green Deployments.</p></li><li><p><b>Cloudflare Pages.</b> Let the <a href="https://www.cloudflare.com/learning/serverless/glossary/what-is-ci-cd/">CI/CD system</a> in Pages automatically progress the deployments on your behalf.</p></li><li><p><b>Automatic rollbacks.</b> Roll back deployments automatically when the error rate spikes for a new version of your Worker.</p></li></ul><p>We’re looking forward to hearing your feedback! Let us know what you think through <a href="https://www.cloudflare.com/lp/developer-week-deployments/">this</a> feedback form or reach out in our <a href="https://discord.gg/HJvPcPcN">Developer Discord</a> in the #workers-gradual-deployments-beta channel.</p>
    <div>
      <h3>Source mapped stack traces in Tail Workers</h3>
      <a href="#source-mapped-stack-traces-in-tail-workers">
        
      </a>
    </div>
    <p>Production readiness means tracking errors and exceptions, and trying to drive them down to zero. When an error occurs, the first thing you typically want to look at is the error’s <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack">stack trace</a> – the specific functions that were called, in what order, from which line and file, and with what arguments.</p><p>Most JavaScript code – not just on Workers, but across platforms – is first bundled, often transpiled, and then minified before being deployed to production. This is done behind the scenes to create smaller bundles to optimize performance and convert from Typescript to JavaScript if needed.</p><p>If you’ve ever seen an exception return a stack trace like: /src/index.js:1:342,it means the error occurred on the 342nd character of your function’s minified code. This is clearly not very helpful for debugging.</p><p><a href="https://web.dev/articles/source-maps">Source maps</a> solve this – they map compiled and minified code back to the original code that you wrote. Source maps are combined with the stack trace returned by the JavaScript runtime in order to present you with a human-readable stack trace. For example, the following stack trace shows that the Worker received an unexpected null value on line 30 of the down.ts file. This is a useful starting point for debugging, and you can move down the stack trace to understand the functions that were called that were set that resulted in the null value.</p>
            <pre><code>Unexpected input value: null
  at parseBytes (src/down.ts:30:8)
  at down_default (src/down.ts:10:19)
  at Object.fetch (src/index.ts:11:12)</code></pre>
            <p>Here’s how it works:</p><ol><li><p>When you set upload_source_maps = true in your <a href="https://developers.cloudflare.com/workers/wrangler/configuration/">wrangler.toml</a>, Wrangler will automatically generate and upload any source map files when you run <a href="https://developers.cloudflare.com/workers/wrangler/commands/#deploy">wrangler deploy</a> or <a href="https://developers.cloudflare.com/workers/wrangler/commands/#versions">wrangler versions upload</a>.</p></li><li><p>When your Worker throws an uncaught exception, we fetch the source map and use it to map the stack trace of the exception back to lines of your Worker’s original source code.</p></li><li><p>You can then view this deobfuscated stack trace in <a href="https://developers.cloudflare.com/workers/observability/logging/real-time-logs/">real-time logs</a> or in <a href="https://developers.cloudflare.com/workers/observability/logging/tail-workers/">Tail Workers</a>.</p></li></ol><p>Starting today, in open beta, you can upload source maps to Cloudflare when you deploy your Worker – <a href="https://developers.cloudflare.com/workers/observability/source-maps">get started by reading the docs</a>. And starting on April 15th , the Workers runtime will start using source maps to deobfuscate stack traces. We’ll post a notification in the Cloudflare dashboard and post on our <a href="https://twitter.com/CloudflareDev?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor">Cloudflare Developers X account</a> when source mapped stack traces are available.</p>
    <div>
      <h3>New Rate Limiting API in Workers</h3>
      <a href="#new-rate-limiting-api-in-workers">
        
      </a>
    </div>
    <p>An API is only production ready if it has a sensible <a href="https://www.cloudflare.com/learning/bots/what-is-rate-limiting/">rate limit</a>. And as you grow, so does the complexity and diversity of limits that you need to enforce in order to balance the needs of specific customers, protect the health of your service, or enforce and adjust limits in specific scenarios. Cloudflare’s own API has this challenge – each of our dozens of products, each with many API endpoints, may need to enforce different rate limits.</p><p>You’ve been able to configure <a href="https://developers.cloudflare.com/waf/rate-limiting-rules/">Rate Limiting rules</a> on Cloudflare since 2017. But until today, the only way to control this was in the Cloudflare dashboard or via the Cloudflare API. It hasn’t been possible to define behavior at <i>runtime</i>, or write code in a Worker that interacts directly with rate limits – you could only control whether a request is rate limited or not before it hits your Worker.</p><p>Today we’re introducing a new API, in open beta, that gives you direct access to rate limits from your Worker. It’s lightning fast, backed by memcached, and dead simple to add to your Worker. For example, the following configuration defines a rate limit of 100 requests within a 60-second period:</p>
            <pre><code>[[unsafe.bindings]]
name = "RATE_LIMITER"
type = "ratelimit"
namespace_id = "1001" # An identifier unique to your Cloudflare account

# Limit: the number of tokens allowed within a given period, in a single Cloudflare location
# Period: the duration of the period, in seconds. Must be either 60 or 10
simple = { limit = 100, period = 60 } </code></pre>
            <p>Then, in your Worker, you can call the limit method on the RATE_LIMITER binding, providing a key of your choosing. Given the configuration above, this code will return a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429">HTTP 429</a> response status code once more than 100 requests to a specific path are made within a 60-second period:</p>
            <pre><code>export default {
  async fetch(request, env) {
    const { pathname } = new URL(request.url)

    const { success } = await env.RATE_LIMITER.limit({ key: pathname })
    if (!success) {
      return new Response(`429 Failure – rate limit exceeded for ${pathname}`, { status: 429 })
    }

    return new Response(`Success!`)
  }
}</code></pre>
            <p>Now that Workers can connect directly to a data store like memcached, what else could we provide? Counters? Locks? An <a href="https://github.com/cloudflare/workerd/pull/1666">in-memory cache</a>? Rate limiting is the first of many primitives that we’re exploring providing in Workers that address questions we’ve gotten for years about where a temporary shared state that spans many Worker <a href="https://developers.cloudflare.com/workers/reference/how-workers-works/#isolates">isolates</a> should live. If you rely on putting state in the global scope of your Worker today, we’re working on better primitives that are purpose-built for specific use cases.</p><p>The Rate Limiting API in Workers is in open beta, and you can get started by <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit">reading the docs</a>.</p>
    <div>
      <h3>New auto-generated SDKs for Cloudflare’s API</h3>
      <a href="#new-auto-generated-sdks-for-cloudflares-api">
        
      </a>
    </div>
    <p>Production readiness means going from making changes by clicking buttons in a dashboard to making changes programmatically, using an infrastructure-as-code approach like <a href="https://github.com/cloudflare/terraform-provider-cloudflare">Terraform</a> or <a href="https://github.com/pulumi/pulumi-cloudflare">Pulumi</a>, or by making API requests directly, either on your own or via an SDK.</p><p>The <a href="https://developers.cloudflare.com/api/">Cloudflare API</a> is massive, and constantly adding new capabilities – on average we <a href="https://github.com/cloudflare/api-schemas/activity">update our API schemas between 20 and 30 times per day</a>. But to date, our API SDKs have been built and maintained manually, so we had a burning need to automate this.</p><p>We’ve done that, and today we’re announcing new client SDKs for the Cloudflare API in three languages – <a href="https://github.com/cloudflare/cloudflare-typescript">Typescript</a>, <a href="https://github.com/cloudflare/cloudflare-python">Python</a> and <a href="https://github.com/cloudflare/cloudflare-go">Go</a> – with more languages on the way.</p><p>Each SDK is generated automatically using <a href="https://www.stainlessapi.com/">Stainless API</a>, based on the <a href="https://github.com/cloudflare/api-schemas">OpenAPI schemas</a> that define the structure and capabilities of each of our API endpoints. This means that when we add any new functionality to the Cloudflare API, across any Cloudflare product, these API SDKs are automatically regenerated, and new versions are published, ensuring that they are correct and up-to-date.</p><p>You can install the SDKs by running one of the following commands:</p>
            <pre><code>// Typescript
npm install cloudflare

// Python
pip install cloudflare

// Go
go get -u github.com/cloudflare/cloudflare-go/v2</code></pre>
            <p>If you use Terraform or Pulumi, under the hood, Cloudflare’s Terraform Provider currently uses the existing, non-automated <a href="https://github.com/cloudflare/cloudflare-go">Go SDK</a>. When you run terraform apply, the Cloudflare Terraform Provider determines which API requests to make in what order, and executes these using the Go SDK.</p><p>The new, auto-generated Go SDK clears a path towards more comprehensive Terraform support for all Cloudflare products, providing a base set of tools that can be relied upon to be both correct and up-to-date with the latest API changes. We’re building towards a future where any time a product team at Cloudflare builds a new feature that is exposed via the Cloudflare API, it is automatically supported by the SDKs. Expect more updates on this throughout 2024.</p>
    <div>
      <h3>Durable Object namespace analytics and WebSocket Hibernation GA</h3>
      <a href="#durable-object-namespace-analytics-and-websocket-hibernation-ga">
        
      </a>
    </div>
    <p>Many of our own products, including <a href="https://developers.cloudflare.com/waiting-room/">Waiting Room</a>, <a href="https://developers.cloudflare.com/r2/">R2</a>, and <a href="https://developers.cloudflare.com/queues/">Queues</a>, as well as platforms like <a href="https://www.partykit.io/">PartyKit</a>, are built using <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects</a>. Deployed globally, including newly added support for Oceania, you can think of Durable Objects like singleton Workers that can provide a single point of coordination and <a href="https://developers.cloudflare.com/durable-objects/api/transactional-storage-api/">persist state</a>. They’re perfect for applications that need real-time user coordination, like interactive chat or collaborative editing. Take Atlassian’s word for it:</p><blockquote><p><i>One of our new capabilities is</i> <a href="https://www.atlassian.com/software/confluence/whiteboards"><i>Confluence whiteboards</i></a><i>, which provides a freeform way to capture unstructured work like brainstorming and early planning before teams document it more formally. The team considered many options for real-time collaboration and ultimately decided to use Cloudflare’s Durable Objects. Durable Objects have proven to be a fantastic fit for this problem space, with a unique combination of functionalities that has allowed us to greatly simplify our infrastructure and easily scale to a large number of users. -</i> <a href="https://www.atlassian.com/software/confluence/whiteboards"><i>Atlassian</i></a></p></blockquote><p>We haven’t previously exposed associated analytical trends in the dashboard, making it hard to understand the usage patterns and error rates within a <a href="https://developers.cloudflare.com/durable-objects/configuration/access-durable-object-from-a-worker/#generate-ids-randomly">Durable Objects namespace</a> unless you used the <a href="https://developers.cloudflare.com/analytics/graphql-api/">GraphQL Analytics API</a> directly. The <a href="https://dash.cloudflare.com/?to=/:account/workers/durable-objects">Durable Objects dashboard</a> has now been revamped, letting you drill down into metrics, and go as deep as you need.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2wFlllqLKz9G1J7ZU4cAfp/67b84ff52331c449bfbbb291fec01ffa/pasted-image-0--1-.png" />
            
            </figure><p>From <a href="/introducing-workers-durable-objects">day one</a>, Durable Objects have supported <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket">WebSockets</a>, allowing many clients to directly connect to a Durable Object to send and receive messages.</p><p>However, sometimes client applications open a WebSocket connection and then eventually stop doing...anything. Think about that tab you’ve had sitting open in your browser for the last 5 hours, but haven’t touched. If it uses WebSockets to send and receive messages, it effectively has a long-lived TCP connection that isn’t being used for anything. If this connection is to a Durable Object, the Durable Object must stay running, waiting for something to happen, consuming memory, and costing you money.</p><p>We first <a href="/workers-pricing-scale-to-zero">introduced WebSocket Hibernation</a> to solve this problem, and today we’re announcing that this feature is out of beta and is Generally Available. With WebSocket Hibernation, you set an automatic response to be used while hibernating and serialize state such that it survives hibernation. This gives Cloudflare the inputs we need in order to maintain open WebSocket connections from clients while “hibernating” the Durable Object such that it is not actively running, and you are not billed for idle time. The result is that your state is always available in-memory when you actually need it, but isn’t unnecessarily kept around when it’s not. As long as your Durable Object is hibernating, even if there are active clients still connected over a WebSocket, you won’t be billed for duration.</p><p>In addition, we’ve heard developer feedback on the costs of incoming WebSocket messages to Durable Objects, which favor smaller, more frequent messages for real-time communication. Starting today incoming WebSocket messages will be billed at the equivalent of 1/20th of a request (as opposed to 1 message being the equivalent of 1 request as it has been up until now). Following a <a href="https://developers.cloudflare.com/durable-objects/platform/pricing/#example-4">pricing example</a>:</p>
<table>
<thead>
  <tr>
    <th></th>
    <th><span>WebSocket Connection Requests</span></th>
    <th><span>Incoming WebSocket Messages</span></th>
    <th><span>Billed Requests</span></th>
    <th><span>Request Billing</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>Before</span></td>
    <td><span>10K</span></td>
    <td><span>432M</span></td>
    <td><span>432,010,000</span></td>
    <td><span>$64.65</span></td>
  </tr>
  <tr>
    <td><span>After</span></td>
    <td><span>10K</span></td>
    <td><span>432M</span></td>
    <td><span>21,610,000</span></td>
    <td><span>$3.09</span></td>
  </tr>
</tbody>
</table>
    <div>
      <h3>Production ready, without production complexity</h3>
      <a href="#production-ready-without-production-complexity">
        
      </a>
    </div>
    <p>Becoming production ready on the last generation of cloud platforms meant slowing down how fast you shipped. It meant stitching together many disconnected tools or standing up whole teams to work on internal platforms. You had to retrofit your own productivity layers onto platforms that put up roadblocks.</p><p>The Cloudflare Developer Platform is grown up and production ready, and committed to being an integrated platform where products intuitively work together and where there aren’t 10 ways to do the same thing, with no need for a compatibility matrix to help understand what works together. Each of these updates shows this in action, integrating new functionality across products and parts of Cloudflare’s platform.</p><p>To that end, we want to hear from you about not only what you want to see next, but where you think we could be even simpler, or where you think our products could work better together. Tell us where you think we could do more – the <a href="https://discord.cloudflare.com/">Cloudflare Developers Discord</a> is always open.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Rate Limiting]]></category>
            <category><![CDATA[SDK]]></category>
            <category><![CDATA[Observability]]></category>
            <guid isPermaLink="false">2IHoIDHRhxpNxOfd1ihQ0Y</guid>
            <dc:creator>Tanushree Sharma</dc:creator>
            <dc:creator>Jacob Bednarz</dc:creator>
        </item>
        <item>
            <title><![CDATA[Minimizing on-call burnout through alerts observability]]></title>
            <link>https://blog.cloudflare.com/alerts-observability/</link>
            <pubDate>Fri, 29 Mar 2024 13:00:36 GMT</pubDate>
            <description><![CDATA[ Learn how Cloudflare used open-source tools to enhance alert observability, leading to increased resilience and improved on-call team well-being ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h3>Introduction</h3>
      <a href="#introduction">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/eGxy8JaWamWJ4cahiswIx/859834037827feccb185badb8b35a37e/Screenshot-2024-01-18-at-10.49.47-PM.png" />
            
            </figure><p>Many people have probably come across the ‘<a href="https://en.wikipedia.org/wiki/Gunshow_(webcomic)#cite_note-Verge-8:~:text=A%202013%20Gunshow,internet%20meme.">this is fine</a>’ meme or the <a href="https://gunshowcomic.com/648">original comic</a>. This is what a typical day for a lot of on-call personnel looks like. On-calls get a lot of alerts, and dealing with too many alerts can result in alert fatigue – a feeling of exhaustion caused by responding to alerts that lack priority or clear actions. Ensuring the alerts are actionable and accurate, not false positives, is crucial because repeated false alarms can desensitize on-call personnel. To this end, within Cloudflare, numerous teams conduct periodic alert analysis, with each team developing its own dashboards for reporting. As members of the Observability team, we've encountered situations where teams reported inaccuracies in alerts or instances where alerts failed to trigger, as well as provided assistance in dealing with noisy/flapping alerts.</p><p>Observability aims to enhance insight into the technology stack by gathering and analyzing a broader spectrum of data. In this blog post, we delve into alert observability, discussing its importance and Cloudflare's approach to achieving it. We'll also explore how we overcome shortcomings in alert reporting within our architecture to simplify troubleshooting using open-source tools and best practices. Join us to understand how we use alerts effectively and use simple tools and practices to enhance our alerts observability, resilience, and on-call personnel health.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6qWK558VCZwC2jF5d2zaYJ/6046122acf14201640f4a8ae757d94b1/Screenshot-2024-01-18-at-10.52.33-PM.png" />
            
            </figure><p>Being on-call can disrupt sleep patterns, impact social life, and hinder leisure activities, potentially leading to burnout. While burnout can be caused by several factors, one contributing factor can be excessively noisy alerts or receiving alerts that are neither important nor actionable. Analyzing alerts can help mitigate the risk of such burnout by reducing unnecessary interruptions and improving the overall efficiency of the on-call process. It involves periodic review and feedback to the system for improving alert quality. Unfortunately, only some companies or teams do alert analysis, even though it is essential information that every on-call or manager should have access to.</p><p>Alert analysis is useful for on-call personnel, enabling them to easily see which alerts have fired during their shift to help draft handover notes and not miss anything important. In addition, managers can generate reports from these stats to see the improvements over time, as well as helping assess on-call vulnerability to burnout. Alert analysis also helps with writing incident reports, to see if alerts were fired, or to determine when an incident started.</p><p>Let’s first understand the alerting stack and how we used open-source tools to gain greater visibility into it, which allowed us to analyze and optimize its effectiveness.</p>
    <div>
      <h3>Prometheus architecture at Cloudflare</h3>
      <a href="#prometheus-architecture-at-cloudflare">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/B78dhbx9vkOS1axZyxHtJ/32b86612925a465cd4b3220830dbcd27/Untitled-2.png" />
            
            </figure><p>At Cloudflare, we <a href="/how-cloudflare-runs-prometheus-at-scale/">rely heavily on Prometheus</a> for monitoring. We have data centers in more than 310 cities, and each has several <a href="https://prometheus.io/docs/introduction/faq/#:~:text=Apache%202.0%20license.-,What%20is%20the%20plural%20of%20Prometheus%3F,Prometheus%27%20is%20%27Prometheis%27">Prometheis</a>. In total, we have over 1100 Prometheus servers. All alerts are sent to a central <a href="https://prometheus.io/docs/alerting/latest/alertmanager/">Alertmanager</a>, where we have various integrations to route them. Additionally, <a href="https://prometheus.io/docs/alerting/latest/configuration/#:~:text=The%20Alertmanager%20will%20send%20HTTP%20POST%20requests%20in%20the%20following%20JSON%20format%20to%20the%20configured%20endpoint%3A">using an alertmanager webhook</a>, we store all alerts in a datastore for analysis.</p>
    <div>
      <h3>Lifecycle of an alert</h3>
      <a href="#lifecycle-of-an-alert">
        
      </a>
    </div>
    <p>Prometheus collects metrics from configured targets at given intervals, evaluates rule expressions, displays the results, and can trigger alerts when the alerting conditions are met. Once an alert goes into firing state, it will be sent to the alertmanager.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4UkkuPB3o9pI0XIrrYueGn/21cde72388f6d8926ffb4f1fcf488a85/Screenshot-2024-01-18-at-10.53.57-PM.png" />
            
            </figure><p>Depending on the configuration, once Alertmanager receives an alert, it can inhibit, group, silence, or route the alerts to the correct receiver integration, such as chat, PagerDuty, or ticketing system. When configured properly, Alertmanager can mitigate a lot of alert noise. Unfortunately, that is not the case all the time, as not all alerts are optimally configured.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4Ne1iAiuXESU3YsK9AmO4r/00dcd310477cad614b8d1c88b4a9d6e3/Screenshot-2024-03-26-at-1.13.46-PM.png" />
            
            </figure><p><i>In Alertmanager, alerts initially enter the firing state, where they may be inhibited or silenced. They return to the firing state when the silence expires or the inhibiting alert resolves, and eventually transition to the resolved state.</i></p><p>Alertmanager sends notifications for <code>firing</code> and <code>resolved</code> alert events via webhook integration. We were using <a href="https://github.com/cloudflare/alertmanager2es">alertmanager2es</a>, which receives webhook alert notifications from Alertmanager and inserts them into an Elasticsearch index for searching and analysis. Alertmanager2es has been a reliable tool for us over the years, offering ways to monitor alerting volume, noisy alerts and do some kind of alert reporting. However, it had its limitations. The absence of <code>silenced</code> and <code>inhibited</code> alert states made troubleshooting issues challenging. We often found ourselves guessing why an alert didn't trigger - was it silenced by another alert or perhaps inhibited by one? Without concrete data, we lacked the means to confirm what was truly happening.</p><p>Since the Alertmanager doesn’t provide notifications for <code>silenced</code> or <code>inhibited</code> alert events via webhook integration, the alert reporting we were doing was somewhat lacking or incomplete. However, the <a href="https://raw.githubusercontent.com/prometheus/alertmanager/master/api/v2/openapi.yaml">Alertmanager API</a> provides querying capabilities and by querying the <code>/api/alerts</code> alertmanager endpoint, we can get the <code>silenced</code> and <code>inhibited</code> alert states. Having all four states in a datastore will enhance our ability to improve alert reporting and troubleshoot Alertmanager issues.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5QeyKMHibWHpSpAOJjHjSM/55e4385b5a8367feb29958062ebe2846/Screenshot-2024-03-26-at-1.14.02-PM.png" />
            
            </figure><p><i>Interfaces for providing information about alert states</i></p>
    <div>
      <h2>Solution</h2>
      <a href="#solution">
        
      </a>
    </div>
    <p>We opted to aggregate all states of the alerts (firing, silenced, inhibited, and resolved) into a datastore. Given that we're gathering data from two distinct sources (the webhook and API) each in varying formats and potentially representing different events, we correlate alerts from both sources using the fingerprint field. The <a href="https://github.com/prometheus/common/blob/main/model/alert.go#L48-L52">fingerprint</a> is a unique hash of the alert’s label set which enables us to match alerts across responses from the Alertmanager webhook and API.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5NKcBwPWjrCou8eQnGPs5u/3bb1d9631cd3d18fe8e356cc6685cd7c/Screenshot-2024-01-18-at-10.59.39-PM.png" />
            
            </figure><p><i>Alertmanager webhook and API response of same alert event</i></p><p>The Alertmanager API offers additional fields compared to the webhook (highlighted in pastel red on the right), such as <code>silencedBy</code> and <code>inhibitedBy</code> IDs, which aid in identifying silenced and inhibited alerts. We store both webhook and API responses in the datastore as separate rows. While querying, we match the alerts using the fingerprint field.</p><p>We decided to use a <a href="https://vector.dev/">vector.dev</a> instance to transform the data as necessary, and store it in a data store. Vector.dev (<a href="https://www.datadoghq.com/blog/datadog-acquires-timber-technologies-vector/">acquired by Datadog</a>) is an open-source, high-performance, <a href="https://www.cloudflare.com/learning/performance/what-is-observability/">observability data pipeline</a> that supports a vast range of sources to read data from and supports a lot of sinks for writing data to, as well as a variety of data transformation operations.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/13sHyssbezB3dZyEwKpWw6/bcb33d2dbadd33670dcbb8abf507af50/Screenshot-2024-01-18-at-10.50.14-PM.png" />
            
            </figure><p><i>Here, we use one http_server vector instance to receive Alertmanager webhook notifications, two http_client sources to query alerts and silence API endpoints, and two sinks for writing all of the state logs in ClickHouse into alerts and silences tables</i></p><p>Although we use ClickHouse to store this data, any other database can be used here. ClickHouse was chosen as a data store because it provides various data manipulation options. It allows aggregating data during insertion using Materialized Views, reduces duplicates with the replacingMergeTree table engine, and supports JOIN statements.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1b6fKIha9P9fUt8Rc2WQOl/38cec0a498874d33e8695104851e69ec/Untitled-1-1.png" />
            
            </figure><p>If we were to create individual columns for all the alert labels, the number of columns would grow exponentially with the addition of new alerts and unique labels. Instead, we decided to create individual columns for a few common labels like alert priority, instance, dashboard, alert-ref, alertname, etc., which helps us analyze the data in general and keep all other labels in a column of type <code>Map(String, String)</code>. This was done because we wanted to keep all the labels in the datastore with minimal resource usage and allow users to query specific labels or filter alerts based on particular labels. For example, we can select all Prometheus alerts using  <code>labelsmap[‘service’’] = ‘Prometheus’</code>.</p>
    <div>
      <h2>Dashboards</h2>
      <a href="#dashboards">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/14NoQxvbOYttnGrlFmIR3Z/6d0299ffa240ba562da42ccb8a6e2ddd/Screenshot-2024-01-18-at-11.02.31-PM.png" />
            
            </figure><p>We built multiple dashboards on top of this data:</p><ul><li><p><b>Alerts overview</b>: To get insights into all the alerts the Alertmanager receives.</p></li><li><p><b>Alertname overview</b>: To drill down on a specific alert.</p></li><li><p><b>Alerts overview by receiver:</b> This is similar to alerts overview but specific to a team or receiver.</p></li><li><p><b>Alerts state timeline</b>: This dashboard shows a snapshot of alert volume at a glance.</p></li><li><p><b>Jiralerts overview</b>: To get insights into the alerts the ticket system receives.</p></li><li><p><b>Silences overview</b>: To get insights into the Alertmanager silences.</p></li></ul>
    <div>
      <h3>Alerts overview</h3>
      <a href="#alerts-overview">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2pclX6PtvC0zJRhX4ibl9U/8125519c56fd4c7bd0f6286f7b374cda/Screenshot-2024-03-26-at-1.14.24-PM.png" />
            
            </figure><p>The image is a screenshot of the collapsed alerts overview dashboard by receiver. This dashboard comprises general stats, components, services, and alertname breakdown. The dashboard also highlights the number of P1 / P2 alerts in the last one day / seven days / thirty days, top alerts for the current quarter, and quarter-to-quarter comparison.</p>
    <div>
      <h3>Component breakdown</h3>
      <a href="#component-breakdown">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2RQfVZgBxphfO13f6AtBSA/b91ccaeb93740218f5699b4d003b4ea9/Screenshot-2024-01-19-at-3.47.38-PM.png" />
            
            </figure><p>We route alerts to teams and a team can have multiple services or components. This panel shows firing alerts component counts over time for a receiver. For example, the alerts are sent to the observability team, which owns multiple components like logging, metrics, traces, and errors. This panel gives an alerting component count over time, and provides a good idea about which component is noisy and at what time at a glance.</p>
    <div>
      <h3>Timeline of alerts</h3>
      <a href="#timeline-of-alerts">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/cqVYUsyX4kospY8JWdjGd/1aa9a0ad96bbb28f503983b34d4a5740/Screenshot-2024-01-19-at-3.22.12-PM.png" />
            
            </figure><p>We created this swimlane view using Grafana’s state timeline panel for the receivers. The panel shows how busy the on-call was and at what point. Red here means the alert started firing, orange represents the alert is active and green means it has resolved. It displays the start time, active duration, and resolution of an alert. This highlighted alert is changing state too frequently from firing to resolved - this looks like a flapping alert. Flapping occurs when an alert changes state too frequently. This can happen when alerts are not configured properly and need tweaking, such as adjusting the alert threshold or increasing the <code>for duration</code> period in the alerting rule. The <code>for duration</code> field in the alerting rules adds time tolerance before an alert starts firing. In other words, the alert won’t fire unless the condition is met for ‘X’ minutes.</p>
    <div>
      <h2>Findings</h2>
      <a href="#findings">
        
      </a>
    </div>
    <p>There were a few interesting findings within our analysis. We found a few alerts that were firing and did not have a notify label set, which means the alerts were firing but were not being sent or routed to any team, creating unnecessary load on the Alertmanager. We also found a few components generating a lot of alerts, and when we dug in, we found that they were for a cluster that was decommissioned where the alerts were not removed. These dashboards gave us excellent visibility and cleanup opportunities.</p>
    <div>
      <h3>Alertmanager inhibitions</h3>
      <a href="#alertmanager-inhibitions">
        
      </a>
    </div>
    <p>Alertmanager inhibition allows suppressing a set of alerts or notifications based on the presence of another set of alerts. We found that Alertmanager inhibitions were not working sometimes. Since there was no way to know about this, we only learned about it when a user reported getting alerted for inhibited alerts. Imagine a Venn diagram of firing and inhibited alerts to understand failed inhibitions. Ideally, there should be no overlap because the inhibited alerts shouldn’t be firing. But if there is an overlap, that means inhibited alerts are firing, and this overlap is considered a failed inhibition alert.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7icbS8sRQDJJrK21dGzD2u/8adba907e2fc6a7514468c829788c08b/Screenshot-2024-03-26-at-1.14.39-PM.png" />
            
            </figure><p><i>Failed inhibition venn diagram</i></p><p>After storing alert notifications in ClickHouse, we were able to come up with a query to find the fingerprint of the `alertnames` where the inhibitions were failing using the following query:</p>
            <pre><code>SELECT $rollup(timestamp) as t, count() as count
FROM
(
    SELECT
        fingerprint, timestamp
    FROM alerts
    WHERE
        $timeFilter
        AND status.state = 'firing'
    GROUP BY
        fingerprint, timestamp
) AS firing
ANY INNER JOIN
(
    SELECT
        fingerprint, timestamp
    FROM alerts
    WHERE
        $timeFilter
        AND status.state = 'suppressed' AND notEmpty(status.inhibitedBy)
    GROUP BY
        fingerprint, timestamp
) AS suppressed USING (fingerprint)
GROUP BY t</code></pre>
            <p>The first panel in the image below is the total number of firing alerts, the second panel is the number of failed inhibitions.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2mIaWG4xVdNgc6319DDTzn/a277241d0ff845578f627be148330de0/Screenshot-2024-03-15-at-10.40.59-PM.png" />
            
            </figure><p>We can also create breakdown for each failed inhibited alert</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2LSWZNB9WQ2d446vnL7V3B/f99463370e71486a8f12335ba14b5ea2/Screenshot-2024-01-24-at-5.03.08-PM.png" />
            
            </figure><p>By looking up the fingerprint from the database, we could map the alert inhibitions and found that the failed inhibited alerts have an inhibition loop. For example, alert <code>Service_XYZ_down</code> is inhibited by alert <code>server_OOR</code>, alert <code>server_OOR</code> is inhibited by alert <code>server_down</code>, and <code>server_down</code> is inhibited by alert <code>server_OOR</code>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2LELAVh6xxaSuVB2S43Emx/c337de542422b8b72c778bcc7c25ac04/Screenshot-2024-03-26-at-1.14.54-PM.png" />
            
            </figure><p>Failed inhibitions can be avoided if alert inhibitions are configured carefully.</p>
    <div>
      <h3>Silences</h3>
      <a href="#silences">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4l3wAB9Q2jLts6e6oN23Se/0fa489b3b08b1f132197731f390ea6e8/Screenshot-2024-03-26-at-1.15.09-PM.png" />
            
            </figure><p>Alertmanager provides a mechanism to silence an alert while it is being worked on or during maintenance. Silence can mute the alerts for a given time and it can be configured based on matchers, which can be an exact match, a regex, an alert name, or any other label. The silence matcher doesn’t necessarily translate to the alertname. By doing alert analysis, we could map the alerts and the silence ID by doing a JOIN query on the alerts and silences tables. We also discovered a lot of stale silences, where silence was created for a long duration and is not relevant anymore.</p>
    <div>
      <h2>DIY Alert analysis</h2>
      <a href="#diy-alert-analysis">
        
      </a>
    </div>
    <p><a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2024-03-alerts-observability">The directory</a> contains a basic demo for implementing alerts observability. Running `docker-compose up` spawns several containers, including Prometheus, Alertmanager, Vector, ClickHouse, and Grafana. The vector.dev container queries the Alertmanager alerts API and writes the data into ClickHouse after transforming it. The Grafana dashboard showcases a demo of Alerts and Silences overview.</p><p>Make sure you have docker installed and run <code>docker compose up</code> to get started.</p><p>Visit <a href="http://localhost:3000/dashboards">http://localhost:3000/dashboards</a> to explore the prebuilt demo dashboards.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>As part of the observability team, we manage the Alertmanager, which is a multi-tenant system. It's crucial for us to have visibility to detect and address system misuse, ensuring proper alerting. The use of alert analysis tools has significantly enhanced the experience for on-call personnel and our team, offering swift access to the alert system. Alerts observability has facilitated the troubleshooting of events such as why an alert did not fire, why an inhibited alert fired, or which alert silenced / inhibited another alert, providing valuable insights for improving alert management.</p><p>Moreover, alerts overview dashboards facilitate rapid review and adjustment, streamlining operations. Teams use these dashboards in the weekly alert reviews to provide tangible evidence of how an on-call shift went, identify which alerts fire most frequently, becoming candidates for cleanup or aggregation thus curbing system misuse and bolstering overall alert management. Additionally, we can pinpoint services that may require particular attention. Alerts observability has also empowered some teams to make informed decisions about on-call configurations, such as transitioning to longer but less frequent shifts or integrating on-call and unplanned work shifts.</p><p>In conclusion, alert observability plays a crucial role in averting burnout by minimizing interruptions and enhancing on-call duties' efficiency. Offering alerts observability as a service benefits all teams by obviating the need for individual dashboard development and fostering a proactive monitoring culture.If you found this blog post interesting and want to work on observability, please check out our job openings – we’re hiring for <a href="https://www.cloudflare.com/en-gb/careers/jobs/?department=Production+Engineering&amp;title=alerting">Alerting</a> and <a href="https://www.cloudflare.com/en-gb/careers/jobs/?department=Production+Engineering&amp;title=logging">Logging</a>!</p> ]]></content:encoded>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Prometheus]]></category>
            <category><![CDATA[Alertmanager]]></category>
            <guid isPermaLink="false">7vVEEoczGFUNpXkkopTmEU</guid>
            <dc:creator>Monika Singh</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing Foundations - our open source Rust service foundation library]]></title>
            <link>https://blog.cloudflare.com/introducing-foundations-our-open-source-rust-service-foundation-library/</link>
            <pubDate>Wed, 24 Jan 2024 14:00:17 GMT</pubDate>
            <description><![CDATA[ Foundations is a foundational Rust library, designed to help scale programs for distributed, production-grade systems ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2yQdeNHftkPvZAq7tcEYN9/eaf572b5329ea631b4df66b36e14e538/image1-4.png" />
            
            </figure><p>In this blog post, we're excited to present Foundations, our foundational library for Rust services, now released as <a href="https://github.com/cloudflare/foundations">open source on GitHub</a>. Foundations is a foundational Rust library, designed to help scale programs for distributed, production-grade systems. It enables engineers to concentrate on the core business logic of their services, rather than the intricacies of production operation setups.</p><p>Originally developed as part of our <a href="/introducing-oxy/">Oxy proxy framework</a>, Foundations has evolved to serve a wider range of applications. For those interested in exploring its technical capabilities, we recommend consulting the library’s <a href="https://docs.rs/foundations/latest/foundations/">API documentation</a>. Additionally, this post will cover the motivations behind Foundations' creation and provide a concise summary of its key features. Stay with us to learn more about how Foundations can support your Rust projects.</p>
    <div>
      <h2>What is Foundations?</h2>
      <a href="#what-is-foundations">
        
      </a>
    </div>
    <p>In software development, seemingly minor tasks can become complex when scaled up. This complexity is particularly evident when comparing the deployment of services on server hardware globally to running a program on a personal laptop.</p><p>The key question is: what fundamentally changes when transitioning from a simple laptop-based prototype to a full-fledged service in a production environment? Through our experience in developing numerous services, we've identified several critical differences:</p><ul><li><p><b>Observability</b>: locally, developers have access to various tools for monitoring and debugging. However, these tools are not as accessible or practical when dealing with thousands of software instances running on remote servers.</p></li><li><p><b>Configuration</b>: local prototypes often use basic, sometimes hardcoded, configurations. This approach is impractical in production, where changes require a more flexible and dynamic configuration system. Hardcoded settings are cumbersome, and command-line options, while common, don't always suit complex hierarchical configurations or align with the "Configuration as Code" paradigm.</p></li><li><p><b>Security</b>: services in production face a myriad of security challenges, exposed to diverse threats from external sources. Basic security hardening becomes a necessity.</p></li></ul><p>Addressing these distinctions, Foundations emerges as a comprehensive library, offering solutions to these challenges. Derived from our Oxy proxy framework, Foundations brings the tried-and-tested functionality of Oxy to a broader range of Rust-based applications at Cloudflare.</p><p>Foundations was developed with these guiding principles:</p><ul><li><p><b>High modularity</b>: recognizing that many services predate Foundations, we designed it to be modular. Teams can adopt individual components at their own pace, facilitating a smooth transition.</p></li><li><p><b>API ergonomics</b>: a top priority for us is user-friendly library interaction. Foundations leverages Rust's procedural macros to offer an intuitive, well-documented API, aiming for minimal friction in usage.</p></li><li><p><b>Simplified setup and configuration</b>: our goal is for engineers to spend minimal time on setup. Foundations is designed to be 'plug and play', with essential functions working immediately and adjustable settings for fine-tuning. We understand that this focus on ease of setup over extreme flexibility might be debatable, as it implies a trade-off. Unlike other libraries that cater to a wide range of environments with potentially verbose setup requirements, Foundations is tailored for specific, production-tested environments and workflows. This doesn't restrict Foundations’ adaptability to other settings, but we approach this with compile-time features to manage setup workflows, rather than a complex setup API.</p></li></ul><p>Next, let's delve into the components Foundations offers. To better illustrate the functionality that Foundations provides we will refer to the <a href="https://github.com/cloudflare/foundations/tree/main/examples/http_server">example web server</a> from Foundations’ source code repository.</p>
    <div>
      <h3>Telemetry</h3>
      <a href="#telemetry">
        
      </a>
    </div>
    <p>In any production system, <a href="https://www.cloudflare.com/learning/performance/what-is-observability/">observability</a>, which we refer to as telemetry, plays an essential role. Generally, three primary types of telemetry are adequate for most service needs:</p><ul><li><p><b>Logging</b>: this involves recording arbitrary textual information, which can be enhanced with tags or structured fields. It's particularly useful for documenting operational errors that aren't critical to the service.</p></li><li><p><b>Tracing</b>: this method offers a detailed timing breakdown of various service components. It's invaluable for identifying performance bottlenecks and investigating issues related to timing.</p></li><li><p><b>Metrics</b>: these are quantitative data points about the service, crucial for monitoring the overall health and performance of the system.</p></li></ul><p>Foundations integrates an API that encompasses all these telemetry aspects, consolidating them into a unified package for ease of use.</p>
    <div>
      <h3>Tracing</h3>
      <a href="#tracing">
        
      </a>
    </div>
    <p>Foundations’ tracing API shares similarities with <a href="https://github.com/tokio-rs/tracing">tokio/tracing</a>, employing a comparable approach with implicit context propagation, instrumentation macros, and futures wrapping:</p>
            <pre><code>#[tracing::span_fn("respond to request")]
async fn respond(
    endpoint_name: Arc&lt;String&gt;,
    req: Request&lt;Body&gt;,
    routes: Arc&lt;Map&lt;String, ResponseSettings&gt;&gt;,
) -&gt; Result&lt;Response&lt;Body&gt;, Infallible&gt; {
    …
}</code></pre>
            <p>Refer to the <a href="https://github.com/cloudflare/foundations/blob/347548000cab0ac549f8f23e2a0ce9e1147b7640/examples/http_server/main.rs#L154">example web server</a> and <a href="https://docs.rs/foundations/latest/foundations/telemetry/tracing/index.html">documentation</a> for more comprehensive examples.</p><p>However, Foundations distinguishes itself in a few key ways:</p><ul><li><p><b>Simplified API</b>: we've streamlined the setup process for tracing, aiming for a more minimalistic approach compared to tokio/tracing.</p></li><li><p><b>Enhanced trace sampling flexibility</b>: Foundations allows for selective override of the sampling ratio in specific code branches. This feature is particularly useful for detailed performance bug investigations, enabling a balance between global trace sampling for overall <a href="https://www.cloudflare.com/application-services/solutions/app-performance-monitoring/">performance monitoring</a> and targeted sampling for specific accounts, connections, or requests.</p></li><li><p><b>Distributed trace stitching</b>: our API supports the integration of trace data from multiple services, contributing to a comprehensive view of the entire pipeline. This functionality includes fine-tuned control over sampling ratios, allowing upstream services to dictate the sampling of specific traffic flows in downstream services.</p></li><li><p><b>Trace forking capability</b>: addressing the challenge of long-lasting connections with numerous multiplexed requests, Foundations introduces trace forking. This feature enables each request within a connection to have its own trace, linked to the parent connection trace. This method significantly simplifies the analysis and improves performance, particularly for connections handling thousands of requests.</p></li></ul><p>We regard telemetry as a vital component of our software, not merely an optional add-on. As such, we believe in rigorous testing of this feature, considering it our primary tool for monitoring software operations. Consequently, Foundations includes an API and user-friendly macros to facilitate the collection and analysis of tracing data within tests, presenting it in a format conducive to assertions.</p>
    <div>
      <h3>Logging</h3>
      <a href="#logging">
        
      </a>
    </div>
    <p>Foundations’ logging API shares its foundation with tokio/tracing and <a href="https://github.com/slog-rs/slog">slog</a>, but introduces several notable enhancements.</p><p>During our work on various services, we recognized the hierarchical nature of logging contextual information. For instance, in a scenario involving a connection, we might want to tag each log record with the connection ID and HTTP protocol version. Additionally, for requests served over this connection, it would be useful to attach the request URL to each log record, while still including connection-specific information.</p><p>Typically, achieving this would involve creating a new logger for each request, copying tags from the connection’s logger, and then manually passing this new logger throughout the relevant code. This method, however, is cumbersome, requiring explicit handling and storage of the logger object.</p><p>To streamline this process and prevent telemetry from obstructing business logic, we adopted a technique similar to tokio/tracing's approach for tracing, applying it to logging. This method relies on future instrumentation machinery (<a href="https://docs.rs/tracing/latest/tracing/struct.Span.html#in-asynchronous-code">tracing-rs documentation</a> has a good explanation of the concept), allowing for implicit passing of the current logger. This enables us to "fork" logs for each request and use this forked log seamlessly within the current code scope, automatically propagating it down the call stack, including through asynchronous function calls:</p>
            <pre><code> let conn_tele_ctx = TelemetryContext::current();

 let on_request = service_fn({
        let endpoint_name = Arc::clone(&amp;endpoint_name);

        move |req| {
            let routes = Arc::clone(&amp;routes);
            let endpoint_name = Arc::clone(&amp;endpoint_name);

            // Each request gets independent log inherited from the connection log and separate
            // trace linked to the connection trace.
            conn_tele_ctx
                .with_forked_log()
                .with_forked_trace("request")
                .apply(async move { respond(endpoint_name, req, routes).await })
        }
});</code></pre>
            <p>Refer to <a href="https://github.com/cloudflare/foundations/blob/347548000cab0ac549f8f23e2a0ce9e1147b7640/examples/http_server/main.rs#L155-L198">example web server</a> and <a href="https://docs.rs/foundations/latest/foundations/telemetry/log/index.html">documentation</a> for more comprehensive examples.</p><p>In an effort to simplify the user experience, we merged all APIs related to context management into a single, implicitly available in each code scope, TelemetryContext object. This integration not only simplifies the process but also lays the groundwork for future advanced features. These features could blend tracing and logging information into a cohesive narrative by cross-referencing each other.</p><p>Like tracing, Foundations also offers a user-friendly API for testing service’s logging.</p>
    <div>
      <h3>Metrics</h3>
      <a href="#metrics">
        
      </a>
    </div>
    <p>Foundations incorporates the official <a href="https://github.com/prometheus/client_rust">Prometheus Rust client library</a> for its metrics functionality, with a few enhancements for ease of use. One key addition is a procedural macro provided by Foundations, which simplifies the definition of new metrics with typed labels, reducing boilerplate code:</p>
            <pre><code>use foundations::telemetry::metrics::{metrics, Counter, Gauge};
use std::sync::Arc;

#[metrics]
pub(crate) mod http_server {
    /// Number of active client connections.
    pub fn active_connections(endpoint_name: &amp;Arc&lt;String&gt;) -&gt; Gauge;

    /// Number of failed client connections.
    pub fn failed_connections_total(endpoint_name: &amp;Arc&lt;String&gt;) -&gt; Counter;

    /// Number of HTTP requests.
    pub fn requests_total(endpoint_name: &amp;Arc&lt;String&gt;) -&gt; Counter;

    /// Number of failed requests.
    pub fn requests_failed_total(endpoint_name: &amp;Arc&lt;String&gt;, status_code: u16) -&gt; Counter;
}</code></pre>
            <p>Refer to the <a href="https://github.com/cloudflare/foundations/blob/347548000cab0ac549f8f23e2a0ce9e1147b7640/examples/http_server/metrics.rs">example web server</a> and <a href="https://docs.rs/foundations/latest/foundations/telemetry/metrics/index.html">documentation</a> for more information of how metrics can be defined and used.</p><p>In addition to this, we have refined the approach to metrics collection and structuring. Foundations offers a streamlined, user-friendly API for both these tasks, focusing on simplicity and minimalism.</p>
    <div>
      <h3>Memory profiling</h3>
      <a href="#memory-profiling">
        
      </a>
    </div>
    <p>Recognizing the <a href="https://mjeanroy.dev/2021/04/19/Java-in-K8s-how-weve-reduced-memory-usage-without-changing-any-code.html">efficiency</a> of <a href="https://jemalloc.net/">jemalloc</a> for long-lived services, Foundations includes a feature for enabling jemalloc memory allocation. A notable aspect of jemalloc is its memory profiling capability. Foundations packages this functionality into a straightforward and safe Rust API, making it accessible and easy to integrate.</p>
    <div>
      <h3>Telemetry server</h3>
      <a href="#telemetry-server">
        
      </a>
    </div>
    <p>Foundations comes equipped with a built-in, customizable telemetry server endpoint. This server automatically handles a range of functions including health checks, metric collection, and memory profiling requests.</p>
    <div>
      <h2>Security</h2>
      <a href="#security">
        
      </a>
    </div>
    <p>A vital component of Foundations is its robust and ergonomic API for <a href="https://en.wikipedia.org/wiki/Seccomp">seccomp</a>, a Linux kernel feature for syscall sandboxing. This feature enables the setting up of hooks for syscalls used by an application, allowing actions like blocking or logging. Seccomp acts as a formidable line of defense, offering an additional layer of security against threats like arbitrary code execution.</p><p>Foundations provides a simple way to define lists of all allowed syscalls, also allowing a composition of multiple lists (in addition, Foundations ships predefined lists for common use cases):</p>
            <pre><code>  use foundations::security::common_syscall_allow_lists::{ASYNC, NET_SOCKET_API, SERVICE_BASICS};
    use foundations::security::{allow_list, enable_syscall_sandboxing, ViolationAction};

    allow_list! {
        static ALLOWED = [
            ..SERVICE_BASICS,
            ..ASYNC,
            ..NET_SOCKET_API
        ]
    }

    enable_syscall_sandboxing(ViolationAction::KillProcess, &amp;ALLOWED)
 </code></pre>
            <p>Refer to the <a href="https://github.com/cloudflare/foundations/blob/347548000cab0ac549f8f23e2a0ce9e1147b7640/examples/http_server/main.rs#L239-L254">web server example</a> and <a href="https://docs.rs/foundations/latest/foundations/security/index.html">documentation</a> for more comprehensive examples of this functionality.</p>
    <div>
      <h2>Settings and CLI</h2>
      <a href="#settings-and-cli">
        
      </a>
    </div>
    <p>Foundations simplifies the management of service settings and command-line argument parsing. Services built on Foundations typically use YAML files for configuration. We advocate for a design where every service comes with a default configuration that's functional right off the bat. This philosophy is embedded in Foundations’ settings functionality.</p><p>In practice, applications define their settings and defaults using Rust structures and enums. Foundations then transforms Rust documentation comments into configuration annotations. This integration allows the CLI interface to generate a default, fully annotated YAML configuration files. As a result, service users can quickly and easily understand the service settings:</p>
            <pre><code>use foundations::settings::collections::Map;
use foundations::settings::net::SocketAddr;
use foundations::settings::settings;
use foundations::telemetry::settings::TelemetrySettings;

#[settings]
pub(crate) struct HttpServerSettings {
    /// Telemetry settings.
    pub(crate) telemetry: TelemetrySettings,
    /// HTTP endpoints configuration.
    #[serde(default = "HttpServerSettings::default_endpoints")]
    pub(crate) endpoints: Map&lt;String, EndpointSettings&gt;,
}

impl HttpServerSettings {
    fn default_endpoints() -&gt; Map&lt;String, EndpointSettings&gt; {
        let mut endpoint = EndpointSettings::default();

        endpoint.routes.insert(
            "/hello".into(),
            ResponseSettings {
                status_code: 200,
                response: "World".into(),
            },
        );

        endpoint.routes.insert(
            "/foo".into(),
            ResponseSettings {
                status_code: 403,
                response: "bar".into(),
            },
        );

        [("Example endpoint".into(), endpoint)]
            .into_iter()
            .collect()
    }
}

#[settings]
pub(crate) struct EndpointSettings {
    /// Address of the endpoint.
    pub(crate) addr: SocketAddr,
    /// Endoint's URL path routes.
    pub(crate) routes: Map&lt;String, ResponseSettings&gt;,
}

#[settings]
pub(crate) struct ResponseSettings {
    /// Status code of the route's response.
    pub(crate) status_code: u16,
    /// Content of the route's response.
    pub(crate) response: String,
}</code></pre>
            <p>The settings definition above automatically generates the following default configuration YAML file:</p>
            <pre><code>---
# Telemetry settings.
telemetry:
  # Distributed tracing settings
  tracing:
    # Enables tracing.
    enabled: true
    # The address of the Jaeger Thrift (UDP) agent.
    jaeger_tracing_server_addr: "127.0.0.1:6831"
    # Overrides the bind address for the reporter API.
    # By default, the reporter API is only exposed on the loopback
    # interface. This won't work in environments where the
    # Jaeger agent is on another host (for example, Docker).
    # Must have the same address family as `jaeger_tracing_server_addr`.
    jaeger_reporter_bind_addr: ~
    # Sampling ratio.
    #
    # This can be any fractional value between `0.0` and `1.0`.
    # Where `1.0` means "sample everything", and `0.0` means "don't sample anything".
    sampling_ratio: 1.0
  # Logging settings.
  logging:
    # Specifies log output.
    output: terminal
    # The format to use for log messages.
    format: text
    # Set the logging verbosity level.
    verbosity: INFO
    # A list of field keys to redact when emitting logs.
    #
    # This might be useful to hide certain fields in production logs as they may
    # contain sensitive information, but allow them in testing environment.
    redact_keys: []
  # Metrics settings.
  metrics:
    # How the metrics service identifier defined in `ServiceInfo` is used
    # for this service.
    service_name_format: metric_prefix
    # Whether to report optional metrics in the telemetry server.
    report_optional: false
  # Server settings.
  server:
    # Enables telemetry server
    enabled: true
    # Telemetry server address.
    addr: "127.0.0.1:0"
# HTTP endpoints configuration.
endpoints:
  Example endpoint:
    # Address of the endpoint.
    addr: "127.0.0.1:0"
    # Endoint's URL path routes.
    routes:
      /hello:
        # Status code of the route's response.
        status_code: 200
        # Content of the route's response.
        response: World
      /foo:
        # Status code of the route's response.
        status_code: 403
        # Content of the route's response.
        response: bar</code></pre>
            <p>Refer to the <a href="https://github.com/cloudflare/foundations/blob/347548000cab0ac549f8f23e2a0ce9e1147b7640/examples/http_server/settings.rs">example web server</a> and documentation for <a href="https://docs.rs/foundations/latest/foundations/settings/index.html">settings</a> and <a href="https://docs.rs/foundations/latest/foundations/cli/index.html">CLI API</a> for more comprehensive examples of how settings can be defined and used with Foundations-provided CLI API.</p>
    <div>
      <h2>Wrapping Up</h2>
      <a href="#wrapping-up">
        
      </a>
    </div>
    <p>At Cloudflare, we greatly value the contributions of the open source community and are eager to reciprocate by sharing our work. Foundations has been instrumental in reducing our development friction, and we hope it can do the same for others. We welcome external contributions to Foundations, aiming to integrate diverse experiences into the project for the benefit of all.</p><p>If you're interested in working on projects like Foundations, consider joining our team — <a href="https://www.cloudflare.com/en-gb/careers/">we're hiring</a>!</p> ]]></content:encoded>
            <category><![CDATA[Open Source]]></category>
            <category><![CDATA[Rust]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Oxy]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">5R4Wv17SBVwevN7lzcL2GW</guid>
            <dc:creator>Ivan Nikulin</dc:creator>
        </item>
        <item>
            <title><![CDATA[An overview of Cloudflare's logging pipeline]]></title>
            <link>https://blog.cloudflare.com/an-overview-of-cloudflares-logging-pipeline/</link>
            <pubDate>Mon, 08 Jan 2024 14:00:21 GMT</pubDate>
            <description><![CDATA[ In this post, we’re going to go over what that looks like, how we achieve high availability, and how we meet our Service Level Objectives (SLOs) while shipping close to a million log lines per second ]]></description>
            <content:encoded><![CDATA[ <p></p><p>One of the roles of Cloudflare's Observability Platform team is managing the operation, improvement, and maintenance of our internal logging pipelines. These pipelines are used to ship debugging logs from every service across Cloudflare’s infrastructure into a centralised location, allowing our engineers to operate and debug their services in near real time. In this post, we’re going to go over what that looks like, how we achieve high availability, and how we meet our Service Level Objectives (SLOs) while shipping close to a million log lines per second.</p><p>Logging itself is a simple concept. Virtually every programmer has written a <code>Hello, World!</code> program at some point. Printing something to the console like that is logging, whether intentional or not.</p><p>Logging <i>pipelines</i> have been around since the beginning of computing itself. Starting with putting string lines in a file, or simply in memory, our industry quickly outgrew the concept of each machine in the network having its own logs. To centralise logging, and to provide scaling beyond a single machine, we used protocols such as the <a href="https://www.ietf.org/rfc/rfc3164.txt">BSD Syslog Protocol</a> to provide a method for individual machines to send logs over the network to a collector, providing a single pane of glass for logs over an entire set of machines.</p><p>Our logging infrastructure at Cloudflare is a bit more complicated, but still builds on these foundational principles.</p>
    <div>
      <h3>The beginning</h3>
      <a href="#the-beginning">
        
      </a>
    </div>
    <p>Logs at Cloudflare start the same as any other, with a <code>println</code>. Generally systems don’t call println directly however, they outsource that logic to a logging library. Systems at Cloudflare use various logging libraries such as Go’s <a href="https://github.com/rs/zerolog">zerolog</a>, C++’s <a href="https://github.com/capnproto/capnproto/blob/7fc185568b4649d1fae97b0791a8f7aa110bfa5d/kjdoc/tour.md">KJ_LOG</a>, or Rusts <a href="https://docs.rs/log/latest/log/">log</a>, however anything that is able to print lines to a program's stdout/stderr streams is compatible with our pipeline. This offers our engineers the greatest flexibility in choosing tools that work for them and their teams.</p><p>Because we use systemd for most of our service management, these stdout/stderr streams are generally piped into systemd-journald which handles the local machine logs. With its <code>RateLimitBurst</code> and <code>RateLimitInterval</code> configurations, this gives us a simple knob to control the output of any given service on a machine. This has given our logging pipeline the colloquial name of the “journal pipeline”, however as we will see, our pipeline has expanded far beyond just journald logs.</p>
    <div>
      <h3>Syslog-NG</h3>
      <a href="#syslog-ng">
        
      </a>
    </div>
    <p>While journald provides us a method to collect logs on every machine, logging onto each machine individually is impractical for debugging large scale services. To this end, the next step of our pipeline is <a href="https://github.com/syslog-ng/syslog-ng">syslog-ng</a>. Syslog-ng is a daemon that implements the aforementioned BSD syslog protocol. In our case, it reads logs from journald, and applies another layer of rate limiting. It then applies rewriting rules to add common fields, such as the name of the machine that emitted the log, the name of the data center the machine is in, and the state of the data center that the machine is in. It then wraps the log in a JSON wrapper and forwards it to our Core data centers.</p><p>journald itself has an interesting feature that makes it difficult for some of our use cases - it guarantees a global ordering of every log on a machine. While this is convenient for the single node case, it imposes the limitation that journald is <a href="https://github.com/systemd/systemd/issues/15677">single-threaded</a>. This means that for our heavier workloads, where every millisecond of delay counts, we provide a more direct path into our pipeline. In particular, we offer a Unix Domain Socket that syslog-ng listens on. This socket operates as a separate source of logs into the same pipeline that the journald logs follow, but allows greater throughput by eschewing the need for a global ordering that journald enforces. Logging in this manner is a bit more involved than outputting logs to the stdout streams, as services have to have a pipe created for them and then manually open that socket to write to. As such, this is generally reserved for services that need it, and don’t mind the management overhead it requires.</p>
    <div>
      <h3>log-x</h3>
      <a href="#log-x">
        
      </a>
    </div>
    <p>Our logging pipeline is a critical service at Cloudflare. Any potential delays or missing data can cause downstream effects that may hinder or even prevent the resolving of customer facing incidents. Because of this strict requirement, we have to offer redundancy in our pipeline. This is where the operation we call “log-x” comes into play.</p><p>We operate two main core data centers. One in the United States, and one in Europe. From each machine, we ship logs to both of these data centers. We call these endpoints log-a, and log-b. The log-a and log-b receivers will insert the logs into a Kafka topic for later consumption. By duplicating the data to two different locations, we achieve a level of redundancy that can handle the failure of either data center.</p><p>The next problem we encounter is that we have many <a href="https://www.cloudflare.com/learning/cdn/glossary/data-center/">data centers</a> all around the world, which at any time due to changing Internet conditions may become disconnected from one, or both core data centers. If the data center is disconnected for long enough we may end up in a situation where we drop logs to either the log-a or log-b receivers. This would result in an incomplete view of logs from one data center and is unacceptable; Log-x was designed to alleviate this problem. In the event that syslog-ng fails to send logs to either log-a or log-b, it will actually send the log <i>twice</i> to the available receiver. This second copy will be marked as actually destined for the other log-x receiver. When a log-x receiver receives such a log, it will insert it into a <i>different</i> Kafka queue, known as the Dead Letter Queue (DLQ). We then use <a href="https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27846330">Kafka Mirror Maker</a> to sync this DLQ across to the data center that was inaccessible. With this logic log-x allows us to maintain a full copy of all the logs in each core data center, regardless of any transient failures from any of our data centers.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1PLoVSIH48LYsFxCwUK9vf/85598f497674f9509fced29decaf7e58/Blog-1945_Kafka.png" />
            
            </figure>
    <div>
      <h3>Kafka</h3>
      <a href="#kafka">
        
      </a>
    </div>
    <p>When logs arrive in the core data centers, we buffer them in a Kafka queue. This provides a few benefits. Firstly, it means that any consumers of the logs can be added without any changes - they only need to register with Kafka as a consumer group on the logs topic. Secondly, it allows us to tolerate transient failures of the consumers without losing any data. Because the Kafka clusters in the core data centers are much larger than any single machine, Kafka allows us to tolerate up to eight hours of total outage for our consumers without losing any data. This has proven to be enough to recover without data loss from all but the largest of incidents.</p><p>When it comes to partitioning our Kafka data, we have an interesting dilemma. Rather arbitrarily, the syslog protocol only supports <a href="https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.3.1">timestamps up to microseconds</a>. For our faster log emitters, this means that the syslog protocol cannot guarantee ordering with timestamps alone. To work around this limitation, we partition our logs using a key made up of both the host, and the service name. Because Kafka guarantees ordering within a partition, this means that any logs from a service on a machine are guaranteed to be ordered between themselves. Unfortunately, because logs from a service can have vastly different rates between different machines, this can result in unbalanced Kafka partitions. We have an ongoing project to move towards <a href="https://opentelemetry.io/docs/specs/otel/protocol/">Open Telemetry Logs</a> to combat this.</p>
    <div>
      <h3>Onward to storage</h3>
      <a href="#onward-to-storage">
        
      </a>
    </div>
    <p>With the logs in Kafka, we can proceed to insert them into a more long term storage. For storage, we operate two backends. An ElasticSearch/Logstash/Kibana (ELK) stack, and a Clickhouse cluster.</p><p>For ElasticSearch, we split our cluster of 90 nodes into a few types. The first being “master” nodes. These nodes act as the ElasticSearch masters, and coordinate insertions into the cluster. We then have “data” nodes that handle the actual insertion and storage. Finally, we have the “HTTP” nodes that handle HTTP queries. Traditionally in an ElasticSearch cluster, all the data nodes will also handle HTTP queries, however because of the size of our cluster and shards we have found that designating only a few nodes to handle <a href="https://www.cloudflare.com/learning/ddos/glossary/hypertext-transfer-protocol-http/">HTTP requests</a> greatly reduces our query times by allowing us to take advantage of aggressive caching.</p><p>On the Clickhouse side, we operate a ten node Clickhouse cluster that stores our service logs. We are in the process of migrating this to be our primary storage, but at the moment it provides an alternative interface into the same logs that ElasticSearch provides, allowing our Engineers to use either Lucene through the ELK stack, or SQL and Bash scripts through the Clickhouse interface.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7iTClO1Axdg7wI0UVLK0oL/13e1a46890d207290f8ee0a230af6e03/Blog-1945_What-s-next_.png" />
            
            </figure>
    <div>
      <h2>What’s next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>As Cloudflare continues to grow, our demands on our Observability systems, and our logging pipeline in particular continue to grow with it. This means that we’re always thinking ahead to what will allow us to scale and improve the experience for our engineers. On the horizon, we have a number of projects to further that goal including:</p><ul><li><p>Increasing our multi-tenancy capabilities with better resource insights for our engineers</p></li><li><p>Migrating our syslog-ng pipeline towards Open Telemetry</p></li><li><p>Tail sampling rather than our probabilistic head sampling we have at the moment</p></li><li><p>Better balancing for our Kafka clusters</p></li></ul><p>If you’re interested in working with logging at Cloudflare, then reach out - <a href=" https://www.cloudflare.com/en-gb/careers/jobs/?department=Production+Engineering">we’re hiring</a>!</p> ]]></content:encoded>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Logs]]></category>
            <guid isPermaLink="false">2oaeQB5iiazhLNCnVk7Cn6</guid>
            <dc:creator>Colin Douch</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare Integrations Marketplace introduces three new partners: Sentry, Momento and Turso]]></title>
            <link>https://blog.cloudflare.com/cloudflare-integrations-marketplace-new-partners-sentry-momento-turso/</link>
            <pubDate>Thu, 28 Sep 2023 13:00:36 GMT</pubDate>
            <description><![CDATA[ We introduced integrations with Supabase, PlanetScale, Neon and Upstash. Today, we are thrilled to introduce our newest additions to Cloudflare’s Integrations Marketplace – Sentry, Turso and Momento ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Building modern full-stack applications requires connecting to many hosted third party services, from observability platforms to databases and more. All too often, this means spending time doing busywork, managing credentials and writing glue code just to get started. This is why we’re building out the Cloudflare Integrations Marketplace to allow developers to easily discover, configure and deploy products to use with Workers.</p><p>Earlier this year, we introduced integrations with <a href="/announcing-database-integrations/">Supabase, PlanetScale, Neon</a> and <a href="/cloudflare-workers-database-integration-with-upstash/">Upstash</a>. Today, we are thrilled to introduce our newest additions to Cloudflare’s Integrations Marketplace – <a href="https://developers.cloudflare.com/workers/observability/sentry-integration/">Sentry</a>, <a href="https://developers.cloudflare.com/workers/databases/native-integrations/turso/">Turso</a> and <a href="https://developers.cloudflare.com/workers/databases/native-integrations/momento/">Momento</a>.</p><p>Let's take a closer look at some of the exciting integration providers that are now part of the Workers Integration Marketplace.</p>
    <div>
      <h3>Improve performance and reliability by connecting Workers to Sentry</h3>
      <a href="#improve-performance-and-reliability-by-connecting-workers-to-sentry">
        
      </a>
    </div>
    <p>When your Worker encounters an error you want to know what happened and exactly what line of code triggered it. <a href="https://sentry.io/welcome/">Sentry</a> is an <a href="https://www.cloudflare.com/application-services/solutions/app-performance-monitoring/">application monitoring</a> platform that helps developers identify and resolve issues in real-time.</p><p>The <a href="https://developers.cloudflare.com/workers/observability/sentry-integration/">Workers and Sentry integration</a> automatically sends errors, exceptions and <code>console.log()</code> messages from your Worker to Sentry with no code changes required. Here’s how it works:</p><ol><li><p>You enable the integration from the Cloudflare Dashboard.</p></li><li><p>The credentials from the Sentry project of your choice are automatically added to your Worker.</p></li><li><p>You can configure <a href="https://docs.sentry.io/platforms/javascript/configuration/sampling/#configuring-the-transaction-sample-rate">sampling</a> to control the volume of events you want sent to Sentry. This includes selecting the sample rate for different status codes and exceptions.</p></li><li><p>Cloudflare deploys a <a href="https://developers.cloudflare.com/workers/observability/tail-workers/">Tail Worker</a> behind the scenes that contains all the logic needed to capture and send data to Sentry.</p></li><li><p>Like magic, errors, exceptions, and log messages are automatically sent to your Sentry project.</p></li></ol><p>In the future, we’ll be improving this integration by adding support for uploading source maps and stack traces so that you can pinpoint exactly which line of your code caused the issue. We’ll also be tying in <a href="https://developers.cloudflare.com/workers/configuration/deployments/">Workers deployments</a> with <a href="https://docs.sentry.io/product/releases/">Sentry releases</a> to correlate new versions of your Worker with events in Sentry that help pinpoint problematic deployments. Check out our <a href="https://developers.cloudflare.com/workers/observability/sentry-integration/">developer documentation</a> for more information.</p>
    <div>
      <h3>Develop at the Data Edge with Turso + Workers</h3>
      <a href="#develop-at-the-data-edge-with-turso-workers">
        
      </a>
    </div>
    <p><a href="https://turso.tech/">Turso</a> is an edge-hosted, distributed database based on libSQL, an open-source fork of SQLite. Turso focuses on providing a global service that minimizes query latency (and thus, application latency!). It’s perfect for use with Cloudflare Workers – both compute and data are served close to users.</p><p>Turso follows the model of having one primary database with replicas that are located globally, close to users. Turso automatically routes requests to a replica closest to where the Worker was invoked. This model works very efficiently for read heavy applications since read requests can be served globally. If you’re running an application that has heavy write workloads, or want to cut down on replication costs, you can run Turso with just the <a href="https://docs.turso.tech/concepts#primary">primary</a> instance and use <a href="https://developers.cloudflare.com/workers/configuration/smart-placement/">Smart Placement</a> to speed up queries.</p><p>The Turso and Workers integration automatically pulls in Turso API credentials and adds them as secrets to your Worker, so that you can start using Turso by simply establishing a connection using the <a href="https://www.npmjs.com/package/@libsql/client">libsql SDK</a>. Get started with the Turso and Workers Integration today by heading to our <a href="https://developers.cloudflare.com/workers/databases/native-integrations/turso/">developer documentation</a>.</p>
    <div>
      <h3>Cache responses from data stores with Momento</h3>
      <a href="#cache-responses-from-data-stores-with-momento">
        
      </a>
    </div>
    <p><a href="https://www.gomomento.com/services/cache">Momento Cache</a> is a low latency serverless caching solution that can be used on top of relational databases, key-value databases or object stores to get faster load times and better performance. Momento abstracts details like scaling, warming and replication so that users can deploy cache in a matter of minutes.</p><p>The Momento and Workers integration automatically pulls in your Momento API key using an OAuth2 flow. The Momento API key is added as a secret in Workers and, from there, you can start using the <a href="https://www.npmjs.com/package/@gomomento/sdk">Momento SDK</a> in Workers. Head to our <a href="https://developers.cloudflare.com/workers/databases/native-integrations/momento/">developer documentation</a> to learn more and use the Momento and Workers integration!</p>
    <div>
      <h3>Try integrations out today</h3>
      <a href="#try-integrations-out-today">
        
      </a>
    </div>
    <p>We want to give you back time, so that you can focus less on configuring and connecting third party tools to Workers and spend more time building. We’re excited to see what you build with integrations. Share your projects with us on Twitter (<a href="https://twitter.com/cloudflaredev">@CloudflareDev</a>) and stay tuned for more exciting updates as we continue to grow our Integrations Marketplace!</p><p>If you would like to partner with Cloudflare and build an integration, please fill out the <a href="https://www.cloudflare.com/partners/technology-partners/">partner request form</a> and we'll be in touch.</p> ]]></content:encoded>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Edge Database]]></category>
            <category><![CDATA[Cache]]></category>
            <category><![CDATA[Partners]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">HkjVgVT5KZ05Cjk2u6S99</guid>
            <dc:creator>Tanushree Sharma</dc:creator>
        </item>
        <item>
            <title><![CDATA[How Cloudflare runs Prometheus at scale]]></title>
            <link>https://blog.cloudflare.com/how-cloudflare-runs-prometheus-at-scale/</link>
            <pubDate>Fri, 03 Mar 2023 14:00:00 GMT</pubDate>
            <description><![CDATA[ Here at Cloudflare we run over 900 instances of Prometheus with a total of around 4.9 billion time series.
Operating such a large Prometheus deployment doesn’t come without challenges .
In this blog post we’ll cover some of the issues we hit and how we solved them ]]></description>
            <content:encoded><![CDATA[ <p></p><p>We use <a href="https://prometheus.io/">Prometheus</a> to gain insight into all the different pieces of hardware and software that make up our global network. Prometheus allows us to measure health &amp; performance over time and, if there’s anything wrong with any service, let our team know before it becomes a problem.</p><p>At the moment of writing this post we run 916 Prometheus instances with a total of around 4.9 billion time series. Here’s a screenshot that shows exact numbers:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4eQSVoAaOO2Bi1xpCxbkMP/c8086dc6482c75d73cbd4e45cd52e4b8/pasted-image-0--7-.png" />
            
            </figure><p>That’s an average of around 5 million time series per instance, but in reality we have a mixture of very tiny and very large instances, with the biggest instances storing around 30 million time series each.</p><p>Operating such a large Prometheus deployment doesn’t come without challenges. In this blog post we’ll cover some of the issues one might encounter when trying to collect many millions of time series per Prometheus instance.</p>
    <div>
      <h2>Metrics cardinality</h2>
      <a href="#metrics-cardinality">
        
      </a>
    </div>
    <p>One of the first problems you’re likely to hear about when you start running your own Prometheus instances is <a href="https://en.wikipedia.org/wiki/Cardinality">cardinality</a>, with the most dramatic cases of this problem being referred to as “cardinality explosion”.</p><p>So let’s start by looking at what cardinality means from Prometheus' perspective, when it can be a problem and some of the ways to deal with it.</p><p>Let’s say we have an application which we want to <a href="https://prometheus.io/docs/instrumenting/clientlibs/">instrument</a>, which means add some observable properties in the form of <a href="https://prometheus.io/docs/concepts/metric_types/">metrics</a> that Prometheus can read from our application. A metric can be anything that you can express as a number, for example:</p><ul><li><p>The speed at which a vehicle is traveling.</p></li><li><p>Current temperature.</p></li><li><p>The number of times some specific event occurred.</p></li></ul><p>To create metrics inside our application we can use one of many Prometheus client libraries. Let’s pick <a href="https://github.com/prometheus/client_python">client_python</a> for simplicity, but the same concepts will apply regardless of the language you use.</p>
            <pre><code>from prometheus_client import Counter

# Declare our first metric.
# First argument is the name of the metric.
# Second argument is the description of it.
c = Counter(mugs_of_beverage_total, 'The total number of mugs drank.')

# Call inc() to increment our metric every time a mug was drank.
c.inc()
c.inc()</code></pre>
            <p>With this simple code Prometheus client library will create a single metric. For Prometheus to collect this metric we need our application to run an HTTP server and expose our metrics there. The simplest way of doing this is by using functionality provided with client_python itself - see documentation <a href="https://github.com/prometheus/client_python#http">here</a>.</p><p>When Prometheus sends an HTTP request to our application it will receive this response:</p>
            <pre><code># HELP mugs_of_beverage_total The total number of mugs drank.
# TYPE mugs_of_beverage_total counter
mugs_of_beverage_total 2</code></pre>
            <p>This format and underlying data model are both covered extensively in Prometheus' own documentation.</p><p>Please see <a href="https://prometheus.io/docs/concepts/data_model/">data model</a> and <a href="https://prometheus.io/docs/instrumenting/exposition_formats/">exposition format</a> pages for more details.</p><p>We can add more metrics if we like and they will all appear in the HTTP response to the metrics endpoint.</p><p>Prometheus metrics can have extra dimensions in form of labels. We can use these to add more information to our metrics so that we can better understand what’s going on.</p><p>With our example metric we know how many mugs were consumed, but what if we also want to know what kind of beverage it was? Or maybe we want to know if it was a cold drink or a hot one? Adding labels is very easy and all we need to do is specify their names. Once we do that we need to pass label values (in the same order as label names were specified) when incrementing our counter to pass this extra information.</p><p>Let’s adjust the example code to do this.</p>
            <pre><code>from prometheus_client import Counter

c = Counter(mugs_of_beverage_total, 'The total number of mugs drank.', ['content', 'temperature'])

c.labels('coffee', 'hot').inc()
c.labels('coffee', 'hot').inc()
c.labels('coffee', 'cold').inc()
c.labels('tea', 'hot').inc()</code></pre>
            <p>Our HTTP response will now show more entries:</p>
            <pre><code># HELP mugs_of_beverage_total The total number of mugs drank.
# TYPE mugs_of_beverage_total counter
mugs_of_beverage_total{content="coffee", temperature="hot"} 2
mugs_of_beverage_total{content="coffee", temperature="cold"} 1
mugs_of_beverage_total{content="tea", temperature="hot"} 1</code></pre>
            <p>As we can see we have an entry for each unique combination of labels.</p><p>And this brings us to the definition of cardinality in the context of metrics. Cardinality is the <b>number of unique combinations of all labels</b>. The more labels you have and the more values each label can take, the more unique combinations you can create and the higher the cardinality.</p>
    <div>
      <h3>Metrics vs samples vs time series</h3>
      <a href="#metrics-vs-samples-vs-time-series">
        
      </a>
    </div>
    <p>Now we should pause to make an important distinction between <i>metrics</i> and <i>time series</i>.</p><p>A metric is an observable property with some defined dimensions (labels). In our example case it’s a Counter class object.</p><p>A time series is an instance of that metric, with a unique combination of all the dimensions (labels), plus a series of timestamp &amp; value pairs - hence the name “time series”. Names and labels tell us what is being observed, while timestamp &amp; value pairs tell us how that observable property changed over time, allowing us to plot graphs using this data.</p><p>What this means is that a single metric will create <b>one or more</b> time series. The number of time series depends purely on the number of labels and the number of all possible values these labels can take.</p><p>Every time we add a new label to our metric we risk multiplying the number of time series that will be exported to Prometheus as the result.</p><p>In our example we have two labels, “content” and “temperature”, and both of them can have two different values. So the maximum number of time series we can end up creating is four (2*2). If we add another label that can also have two values then we can now export up to eight time series (2*2*2). The more labels we have or the more distinct values they can have the more time series as a result.</p><p>If all the label values are controlled by your application you will be able to count the number of all possible label combinations. But the real risk is when you create metrics with label values coming from the outside world.</p><p>If instead of beverages we tracked the number of HTTP requests to a web server, and we used the request path as one of the label values, then anyone making a huge number of random requests could force our application to create a huge number of time series. To avoid this it’s in general best to never accept label values from untrusted sources.</p><p>To make things more complicated you may also hear about “samples” when reading Prometheus documentation. A sample is something in between metric and time series - it’s a time series value for a specific timestamp. Timestamps here can be explicit or implicit. If a sample lacks any explicit timestamp then it means that the sample represents the most recent value - it’s the current value of a given time series, and the timestamp is simply the time you make your observation at.</p><p>If you look at the HTTP response of our example metric you’ll see that none of the returned entries have timestamps. There’s no timestamp anywhere actually. This is because the Prometheus server itself is responsible for timestamps. When Prometheus collects metrics it records the time it started each collection and then it will use it to write timestamp &amp; value pairs for each time series.</p><p>That’s why what our application exports isn’t really metrics or time series - it’s samples.</p><p>Confusing? Let’s recap:</p><ul><li><p>We start with a <b>metric</b> - that’s simply a definition of something that we can observe, like the number of mugs drunk.</p></li><li><p>Our metrics are exposed as a HTTP response. That response will have a list of <b>samples</b> - these are individual instances of our metric (represented by name &amp; labels), plus the current value.</p></li><li><p>When Prometheus collects all the samples from our HTTP response it adds the timestamp of that collection and with all this information together we have a <b>time series</b>.</p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5lTDb6LR4pGWmpAC2mrug2/4771271c4b28e30930be606bfd5b4213/blog-4.png" />
            
            </figure>
    <div>
      <h3>Cardinality related problems</h3>
      <a href="#cardinality-related-problems">
        
      </a>
    </div>
    <p>Each time series will cost us resources since it needs to be kept in memory, so the more time series we have, the more resources metrics will consume. This is true both for client libraries and Prometheus server, but it’s more of an issue for Prometheus itself, since a single Prometheus server usually collects metrics from many applications, while an application only keeps its own metrics.</p><p>Since we know that the more labels we have the more time series we end up with, you can see when this can become a problem. Simply adding a label with two distinct values to all our metrics might double the number of time series we have to deal with. Which in turn will double the memory usage of our Prometheus server. If we let Prometheus consume more memory than it can physically use then it will crash.</p><p>This scenario is often described as “cardinality explosion” - some metric suddenly adds a huge number of distinct label values, creates a huge number of time series, causes Prometheus to run out of memory and you lose all <a href="https://www.cloudflare.com/learning/performance/what-is-observability/">observability</a> as a result.</p>
    <div>
      <h2>How is Prometheus using memory?</h2>
      <a href="#how-is-prometheus-using-memory">
        
      </a>
    </div>
    <p>To better handle problems with cardinality it’s best if we first get a better understanding of how Prometheus works and how time series consume memory.</p><p>For that let’s follow all the steps in the life of a time series inside Prometheus.</p>
    <div>
      <h3>Step one - HTTP scrape</h3>
      <a href="#step-one-http-scrape">
        
      </a>
    </div>
    <p>The process of sending HTTP requests from Prometheus to our application is called “scraping”. Inside the Prometheus configuration file we define a <a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config">“scrape config”</a> that tells Prometheus where to send the HTTP request, how often and, optionally, to apply extra processing to both requests and responses.</p><p>It will record the time it sends HTTP requests and use that later as the timestamp for all collected time series.</p><p>After sending a request it will parse the response looking for all the samples exposed there.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2g84l6ioJCxTsF64iZoHr8/8ae8b1a7140517cd8a8eb330482c7377/blog-1.png" />
            
            </figure>
    <div>
      <h3>Step two - new time series or an update?</h3>
      <a href="#step-two-new-time-series-or-an-update">
        
      </a>
    </div>
    <p>Once Prometheus has a list of samples collected from our application it will save it into <a href="https://pkg.go.dev/github.com/prometheus/prometheus/tsdb">TSDB</a> - Time Series DataBase - the database in which Prometheus keeps all the time series.</p><p>But before doing that it needs to first check which of the samples belong to the time series that are already present inside TSDB and which are for completely new time series.</p><p>As we mentioned before a time series is generated from metrics. There is a single time series for each unique combination of metrics labels.</p><p>This means that Prometheus must check if there’s already a time series with identical name and exact same set of labels present. Internally time series names are just another label called __name__, so there is no practical distinction between name and labels. Both of the representations below are different ways of exporting the same time series:</p>
            <pre><code>mugs_of_beverage_total{content="tea", temperature="hot"} 1
{__name__="mugs_of_beverage_total", content="tea", temperature="hot"} 1</code></pre>
            <p>Since everything is a label Prometheus can simply hash all labels using sha256 or any other algorithm to come up with a single ID that is unique for each time series.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4cXuu38kxa2u51z8Me0aBP/9dcc5694aef45e5a65a19dd4d7a7b8a7/blog-2.png" />
            
            </figure><p>Knowing that it can quickly check if there are any time series already stored inside TSDB that have the same hashed value. Basically our labels hash is used as a primary key inside TSDB.</p>
    <div>
      <h3>Step three - appending to TSDB</h3>
      <a href="#step-three-appending-to-tsdb">
        
      </a>
    </div>
    <p>Once TSDB knows if it has to insert new time series or update existing ones it can start the real work.</p><p>Internally all time series are stored <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head.go#L1604-L1616">inside a map</a> on a structure called <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head.go#L65">Head</a>. That map uses labels hashes as keys and a structure called <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head.go#L1827">memSeries</a> as values. Those memSeries objects are storing all the time series information. The struct definition for memSeries is fairly big, but all we really need to know is that it has a copy of all the <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head.go#L1831">time series labels</a> and <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head.go#L1843-L1844">chunks</a> that hold all the samples (timestamp &amp; value pairs).</p><p>Labels are stored once per each memSeries instance.</p><p>Samples are stored inside chunks using <a href="https://prometheus.io/blog/2016/05/08/when-to-use-varbit-chunks/#what-is-varbit-encoding">"varbit" encoding</a> which is a lossless compression scheme optimized for time series data. Each chunk represents a series of samples for a <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head.go#L1969">specific time range</a>. This helps Prometheus query data faster since all it needs to do is first locate the memSeries instance with labels matching our query and then find the chunks responsible for time range of the query.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2hE16dmyvOEjiTJUGnDVTy/11b37f49775fac13165d75ff3d7b1c78/blog-5.png" />
            
            </figure><p><a href="https://github.com/prometheus/prometheus/blob/v2.42.0/cmd/prometheus/main.go#L300-L301">By default</a> Prometheus will create a chunk per each <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/db.go#L53">two hours</a> of <b>wall clock</b>. So there would be a chunk for: 00:00 - 01:59, 02:00 - 03:59, 04:00 - 05:59, …, 22:00 - 23:59.</p><p>There’s only one chunk that we can append to, it’s called the “Head Chunk”. It’s the chunk responsible for the most recent time range, including the time of our scrape. Any other chunk holds historical samples and therefore is read-only.</p><p>There is a maximum of <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head_append.go#L1337">120</a> samples each chunk can hold. This is because once we have more than 120 samples on a chunk efficiency of “varbit” encoding drops. TSDB <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head_append.go#L1371-L1386">will try to estimate</a> when a given chunk will reach 120 samples and it will set the maximum allowed time for current Head Chunk accordingly.</p><p>If we try to append a sample with a timestamp higher than the maximum allowed time for current Head Chunk, then TSDB will create a new Head Chunk and calculate a new maximum time for it based on the rate of appends.</p><p>All chunks must be aligned to those two hour slots of wall clock time, so if TSDB was building a chunk for 10:00-11:59 and it was already “full” at 11:30 then it would create an extra chunk for the 11:30-11:59 time range.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1U540qt4t8r4OjMdKpBEIf/93aa0eb5e741683f71ef0eff002d6b1f/blog-6.png" />
            
            </figure><p>Since the default Prometheus scrape interval is one minute it would take two hours to reach 120 samples.</p><p>What this means is that using Prometheus defaults each memSeries should have a single chunk with 120 samples on it for every two hours of data.</p><p>Going back to our time series - at this point Prometheus either creates a new memSeries instance or uses already existing memSeries. Once it has a memSeries instance to work with it will append our sample to the Head Chunk. This might require Prometheus to create a new chunk if needed.</p>
    <div>
      <h3>Step four - memory-mapping old chunks</h3>
      <a href="#step-four-memory-mapping-old-chunks">
        
      </a>
    </div>
    <p>After a few hours of Prometheus running and scraping metrics we will likely have more than one chunk on our time series:</p><ul><li><p>One “Head Chunk” - containing up to two hours of the last two hour wall clock slot.</p></li><li><p>One or more for historical ranges - these chunks are only for reading, Prometheus won’t try to append anything here.</p></li></ul><p>Since all these chunks are stored in memory Prometheus will try to reduce memory usage by writing them to disk and memory-mapping. The advantage of doing this is that memory-mapped chunks don’t use memory unless TSDB needs to read them.</p><p>The Head Chunk is never memory-mapped, it’s always stored in memory.</p>
    <div>
      <h3>Step five - writing blocks to disk</h3>
      <a href="#step-five-writing-blocks-to-disk">
        
      </a>
    </div>
    <p>Up until now all time series are stored entirely in memory and the more time series you have, the higher Prometheus memory usage you’ll see. The only exception are memory-mapped chunks which are offloaded to disk, but will be read into memory if needed by queries.</p><p>This allows Prometheus to scrape and store thousands of samples per second, our biggest instances are appending 550k samples per second, while also allowing us to query all the metrics simultaneously.</p><p>But you can’t keep everything in memory forever, even with memory-mapping parts of data.</p><p>Every two hours Prometheus will persist chunks from memory onto the disk. This process is also aligned with the wall clock but <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head.go#L1489-L1494">shifted by one hour</a>.</p><p>When using Prometheus defaults and assuming we have a single chunk for each two hours of wall clock we would see this:</p><ul><li><p>02:00 - create a new chunk for 02:00 - 03:59 time range</p></li><li><p>03:00 - write a block for 00:00 - 01:59</p></li><li><p>04:00 - create a new chunk for 04:00 - 05:59 time range</p></li><li><p>05:00 - write a block for 02:00 - 03:59</p></li><li><p>…</p></li><li><p>22:00 - create a new chunk for 22:00 - 23:59 time range</p></li><li><p>23:00 - write a block for 20:00 - 21:59</p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/Bt2X8rR89TBWekMduXrSZ/d1cfc97d7fe7a60c431b123027755714/blog-7.png" />
            
            </figure><p>Once a chunk is written into a block it is removed from memSeries and thus from memory. Prometheus will keep each block on disk for the configured retention period.</p><p>Blocks will eventually be “compacted”, which means that Prometheus will take multiple blocks and merge them together to form a single block that covers a bigger time range. This process helps to reduce disk usage since each block has an index taking a good chunk of disk space. By merging multiple blocks together, big portions of that index can be reused, allowing Prometheus to store more data using the same amount of storage space.</p>
    <div>
      <h3>Step six - garbage collection</h3>
      <a href="#step-six-garbage-collection">
        
      </a>
    </div>
    <p>After a chunk was written into a block and removed from memSeries we might end up with an instance of memSeries that has no chunks. This would happen if any time series was no longer being exposed by any application and therefore there was no scrape that would try to append more samples to it.</p><p>A common pattern is to export software versions as a build_info metric, Prometheus itself does this too:</p>
            <pre><code>prometheus_build_info{version="2.42.0"} 1</code></pre>
            <p>When Prometheus 2.43.0 is released this metric would be exported as:</p>
            <pre><code>prometheus_build_info{version="2.43.0"} 1</code></pre>
            <p>Which means that a time series with version=”2.42.0” label would no longer receive any new samples.</p><p>Once the last chunk for this time series is written into a block and removed from the memSeries instance we have no chunks left. This means that our memSeries still consumes some memory (mostly labels) but doesn’t really do anything.</p><p>To get rid of such time series Prometheus will run “head garbage collection” (remember that Head is the structure holding all memSeries) right after writing a block. This garbage collection, among other things, will look for any <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/tsdb/head.go#L1642-L1648">time series without a single chunk</a> and remove it from memory.</p><p>Since this happens after writing a block, and writing a block happens in the middle of the chunk window (two hour slices aligned to the wall clock) the only memSeries this would find are the ones that are “orphaned” - they received samples before, but not anymore.</p>
    <div>
      <h3>What does this all mean?</h3>
      <a href="#what-does-this-all-mean">
        
      </a>
    </div>
    <p>TSDB used in Prometheus is a special kind of database that was highly optimized for a very specific workload:</p><ul><li><p>Time series scraped from applications are kept in memory.</p></li><li><p>Samples are compressed using encoding that works best if there are continuous updates.</p></li><li><p>Chunks that are a few hours old are written to disk and removed from memory.</p></li><li><p>When time series disappear from applications and are no longer scraped they still stay in memory until all chunks are written to disk and garbage collection removes them.</p></li></ul><p>This means that Prometheus is most efficient when continuously scraping the same time series over and over again. It’s least efficient when it scrapes a time series just once and never again - doing so comes with a significant memory usage overhead when compared to the amount of information stored using that memory.</p><p>If we try to visualize how the perfect type of data Prometheus was designed for looks like we’ll end up with this:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/58Sdl0opvl2leashpMABHR/2fbe55566085307e632f284c9f487695/blog-13.png" />
            
            </figure><p>A few continuous lines describing some observed properties.</p><p>If, on the other hand, we want to visualize the type of data that Prometheus is the least efficient when dealing with, we’ll end up with this instead:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/peimfAo5FJ8Z7U4N9pYtm/0779cb39815baea5a3ee3018d746c398/blog-14.png" />
            
            </figure><p>Here we have single data points, each for a different property that we measure.</p><p>Although you can tweak some of Prometheus' behavior and tweak it more for use with short lived time series, by passing one of <a href="https://github.com/prometheus/prometheus/blob/v2.42.0/cmd/prometheus/main.go#L300-L305">the hidden flags</a>, it’s generally discouraged to do so. These flags are only exposed for testing and might have a negative impact on other parts of Prometheus server.</p><p>To get a better understanding of the impact of a short lived time series on memory usage let’s take a look at another example.</p><p>Let’s see what happens if we start our application at 00:25, allow Prometheus to scrape it once while it exports:</p>
            <pre><code>prometheus_build_info{version="2.42.0"} 1</code></pre>
            <p>And then immediately after the first scrape we upgrade our application to a new version:</p>
            <pre><code>prometheus_build_info{version="2.43.0"} 1</code></pre>
            <p>At 00:25 Prometheus will create our memSeries, but we will have to wait until Prometheus writes a block that contains data for 00:00-01:59 and runs garbage collection before that memSeries is removed from memory, which will happen at 03:00.</p><p>This single sample (data point) will create a time series instance that will stay in memory for over two and a half hours using resources, just so that we have a single timestamp &amp; value pair.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/42ihJDKAWyj7hT4DicZoxV/abff5645102c8c22da0c1ad8e811445a/blog-8.png" />
            
            </figure><p>If we were to continuously scrape a lot of time series that only exist for a very brief period then we would be slowly accumulating a lot of memSeries in memory until the next garbage collection.</p><p>Looking at memory usage of such Prometheus server we would see this pattern repeating over time:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/qccLTfcv0dyJ6bFnEkkd6/ad547942173e2f6333412f694fbb0faa/blog-15.png" />
            
            </figure><p>The important information here is that <b>short lived time series are expensive</b>. A time series that was only scraped once is <b>guaranteed to live in Prometheus for one to three hours</b>, depending on the exact time of that scrape.</p>
    <div>
      <h2>The cost of cardinality</h2>
      <a href="#the-cost-of-cardinality">
        
      </a>
    </div>
    <p>At this point we should know a few things about Prometheus:</p><ul><li><p>We know what a metric, a sample and a time series is.</p></li><li><p>We know that the more labels on a metric, the more time series it can create.</p></li><li><p>We know that each time series will be kept in memory.</p></li><li><p>We know that time series will stay in memory for a while, even if they were scraped only once.</p></li></ul><p>With all of that in mind we can now see the problem - a <b>metric with high cardinality</b>, especially one with label values that come from the outside world, can easily create a huge number of <b>time series</b> in a very short time, causing <b>cardinality explosion</b>. This would inflate Prometheus memory usage, which can cause Prometheus server to crash, if it uses all available physical memory.</p><p>To get a better idea of this problem let’s adjust our example metric to track HTTP requests.</p><p>Our metric will have a single label that stores the request path.</p>
            <pre><code>from prometheus_client import Counter

c = Counter(http_requests_total, 'The total number of HTTP requests.', ['path'])

# HTTP request handler our web server will call
def handle_request(path):
  c.labels(path).inc()
  ...</code></pre>
            <p>If we make a single request using the curl command:</p>
            <pre><code>&gt; curl https://app.example.com/index.html</code></pre>
            <p>We should see these time series in our application:</p>
            <pre><code># HELP http_requests_total The total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{path="/index.html"} 1</code></pre>
            <p>But what happens if an evil hacker decides to send a bunch of random requests to our application?</p>
            <pre><code>&gt; curl https://app.example.com/jdfhd5343
&gt; curl https://app.example.com/3434jf833
&gt; curl https://app.example.com/1333ds5
&gt; curl https://app.example.com/aaaa43321</code></pre>
            <p>Extra time series would be created:</p>
            <pre><code># HELP http_requests_total The total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{path="/index.html"} 1
http_requests_total{path="/jdfhd5343"} 1
http_requests_total{path="/3434jf833"} 1
http_requests_total{path="/1333ds5"} 1
http_requests_total{path="/aaaa43321"} 1</code></pre>
            <p>With 1,000 random requests we would end up with 1,000 time series in Prometheus. If our metric had more labels and all of them were set based on the request payload (HTTP method name, IPs, headers, etc) we could easily end up with millions of time series.</p><p>Often it doesn’t require any malicious actor to cause cardinality related problems. A common class of mistakes is to have an error label on your metrics and pass raw error objects as values.</p>
            <pre><code>from prometheus_client import Counter

c = Counter(errors_total, 'The total number of errors.', [error])

def my_func:
  try:
    ...
  except Exception as err:
    c.labels(err).inc()</code></pre>
            <p>This works well if errors that need to be handled are generic, for example “Permission Denied”:</p>
            <pre><code>errors_total{error="Permission Denied"} 1</code></pre>
            <p>But if the error string contains some task specific information, for example the name of the file that our application didn’t have access to, or a TCP connection error, then we might easily end up with high cardinality metrics this way:</p>
            <pre><code>errors_total{error="file not found: /myfile.txt"} 1
errors_total{error="file not found: /other/file.txt"} 1
errors_total{error="read udp 127.0.0.1:12421-&gt;127.0.0.2:443: i/o timeout"} 1
errors_total{error="read udp 127.0.0.1:14743-&gt;127.0.0.2:443: i/o timeout"} 1</code></pre>
            <p>Once scraped all those time series will stay in memory for a minimum of one hour. It’s very easy to keep accumulating time series in Prometheus until you run out of memory.</p><p>Even Prometheus' own <a href="https://github.com/prometheus/client_golang/security/advisories/GHSA-cg3q-j54f-5p7p">client libraries had bugs</a> that could expose you to problems like this.</p>
    <div>
      <h2>How much memory does a time series need?</h2>
      <a href="#how-much-memory-does-a-time-series-need">
        
      </a>
    </div>
    <p>Each time series stored inside Prometheus (as a memSeries instance) consists of:</p><ul><li><p>Copy of all labels.</p></li><li><p>Chunks containing samples.</p></li><li><p>Extra fields needed by Prometheus internals.</p></li></ul><p>The amount of memory needed for labels will depend on the number and length of these. The more labels you have, or the longer the names and values are, the more memory it will use.</p><p>The way labels are stored internally by Prometheus also matters, but that’s something the user has no control over. There is an open pull request which improves memory usage of labels by <a href="https://github.com/prometheus/prometheus/pull/10991">storing all labels as a single string</a>.</p><p>Chunks will consume more memory as they slowly fill with more samples, after each scrape, and so the memory usage here will follow a cycle - we start with low memory usage when the first sample is appended, then memory usage slowly goes up until a new chunk is created and we start again.</p><p>You can calculate how much memory is needed for your time series by running this query on your Prometheus server:</p>
            <pre><code>go_memstats_alloc_bytes / prometheus_tsdb_head_series</code></pre>
            <p>Note that your Prometheus server must be configured to scrape itself for this to work.</p><p>Secondly this calculation is based on all memory used by Prometheus, not only time series data, so it’s just an approximation. Use it to get a rough idea of how much memory is used per time series and don’t assume it’s that exact number.</p><p>Thirdly Prometheus is written in <a href="https://go.dev/">Golang</a> which is a language with garbage collection. The actual amount of physical memory needed by Prometheus will usually be higher as a result, since it will include unused (garbage) memory that needs to be freed by Go runtime.</p>
    <div>
      <h2>Protecting Prometheus from cardinality explosions</h2>
      <a href="#protecting-prometheus-from-cardinality-explosions">
        
      </a>
    </div>
    <p>Prometheus does offer some options for dealing with high cardinality problems. There are a <a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config">number of options</a> you can set in your scrape configuration block. Here is the extract of the relevant options from Prometheus documentation:</p>
            <pre><code># An uncompressed response body larger than this many bytes will cause the
# scrape to fail. 0 means no limit. Example: 100MB.
# This is an experimental feature, this behaviour could
# change or be removed in the future.
[ body_size_limit: &lt;size&gt; | default = 0 ]
# Per-scrape limit on number of scraped samples that will be accepted.
# If more than this number of samples are present after metric relabeling
# the entire scrape will be treated as failed. 0 means no limit.
[ sample_limit: &lt;int&gt; | default = 0 ]

# Per-scrape limit on number of labels that will be accepted for a sample. If
# more than this number of labels are present post metric-relabeling, the
# entire scrape will be treated as failed. 0 means no limit.
[ label_limit: &lt;int&gt; | default = 0 ]

# Per-scrape limit on length of labels name that will be accepted for a sample.
# If a label name is longer than this number post metric-relabeling, the entire
# scrape will be treated as failed. 0 means no limit.
[ label_name_length_limit: &lt;int&gt; | default = 0 ]

# Per-scrape limit on length of labels value that will be accepted for a sample.
# If a label value is longer than this number post metric-relabeling, the
# entire scrape will be treated as failed. 0 means no limit.
[ label_value_length_limit: &lt;int&gt; | default = 0 ]

# Per-scrape config limit on number of unique targets that will be
# accepted. If more than this number of targets are present after target
# relabeling, Prometheus will mark the targets as failed without scraping them.
# 0 means no limit. This is an experimental feature, this behaviour could
# change in the future.
[ target_limit: &lt;int&gt; | default = 0 ]</code></pre>
            <p>Setting all the label length related limits allows you to avoid a situation where extremely long label names or values end up taking too much memory.</p><p>Going back to our metric with error labels we could imagine a scenario where some operation returns a huge error message, or even stack trace with hundreds of lines. If such a stack trace ended up as a label value it would take a lot more memory than other time series, potentially even megabytes. Since labels are copied around when Prometheus is handling queries this could cause significant memory usage increase.</p><p>Setting label_limit provides some cardinality protection, but even with just one label name and huge number of values we can see high cardinality. Passing sample_limit is the ultimate protection from high cardinality. It enables us to enforce a hard limit on the number of time series we can scrape from each application instance.</p><p>The downside of all these limits is that <b>breaching any of them will cause an error for the entire scrape</b>.</p><p>If we configure a sample_limit of 100 and our metrics response contains 101 samples, then Prometheus <b>won’t scrape anything at all</b>. This is a deliberate design decision made by Prometheus developers.</p><p>The main motivation seems to be that dealing with partially scraped metrics is difficult and you’re better off treating failed scrapes as incidents.</p>
    <div>
      <h2>How does Cloudflare deal with high cardinality?</h2>
      <a href="#how-does-cloudflare-deal-with-high-cardinality">
        
      </a>
    </div>
    <p>We have hundreds of data centers spread across the world, each with dedicated Prometheus servers responsible for scraping all metrics.</p><p>Each Prometheus is scraping a few hundred different applications, each running on a few hundred servers.</p><p>Combined that’s a lot of different metrics. It’s not difficult to accidentally cause cardinality problems and in the past we’ve dealt with a fair number of issues relating to it.</p>
    <div>
      <h3>Basic limits</h3>
      <a href="#basic-limits">
        
      </a>
    </div>
    <p>The most basic layer of protection that we deploy are scrape limits, which we enforce on all configured scrapes. These are the sane defaults that 99% of application exporting metrics would never exceed.</p><p>By default we allow up to 64 labels on each time series, which is way more than most metrics would use.</p><p>We also limit the length of label names and values to 128 and 512 characters, which again is more than enough for the vast majority of scrapes.</p><p>Finally we do, by default, set sample_limit to 200 - so each application can export up to 200 time series without any action.</p><p>What happens when somebody wants to export more time series or use longer labels? All they have to do is set it explicitly in their scrape configuration.</p><p>Those limits are there to catch accidents and also to make sure that if any application is exporting a high number of time series (more than 200) the team responsible for it knows about it. This helps us avoid a situation where applications are exporting thousands of times series that aren’t really needed. Once you cross the 200 time series mark, you should start thinking about your metrics more.</p>
    <div>
      <h3>CI validation</h3>
      <a href="#ci-validation">
        
      </a>
    </div>
    <p>The next layer of protection is checks that run in CI (Continuous Integration) when someone makes a pull request to add new or modify existing scrape configuration for their application.</p><p>These checks are designed to ensure that we have enough capacity on all Prometheus servers to accommodate extra time series, if that change would result in extra time series being collected.</p><p>For example, if someone wants to modify sample_limit, let’s say by changing existing limit of 500 to 2,000, for a scrape with 10 targets, that’s an increase of 1,500 per target, with 10 targets that’s 10*1,500=15,000 extra time series that might be scraped. Our CI would check that all Prometheus servers have spare capacity for at least 15,000 time series before the pull request is allowed to be merged.</p><p>This gives us confidence that we won’t overload any Prometheus server after applying changes.</p>
    <div>
      <h3>Our custom patches</h3>
      <a href="#our-custom-patches">
        
      </a>
    </div>
    <p>One of the most important layers of protection is a set of patches we maintain on top of Prometheus. There is an <a href="https://github.com/prometheus/prometheus/pull/11124">open pull request</a> on the Prometheus repository. This patchset consists of two main elements.</p><p>First is the patch that allows us to enforce a limit on the total number of time series TSDB can store at any time. There is no equivalent functionality in a standard build of Prometheus, if any scrape produces some samples they will be appended to time series inside TSDB, creating new time series if needed.</p><p>This is the standard flow with a scrape that doesn’t set any sample_limit:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4oxj5PicoVqieXzyDzlq2C/cbbf6a05d61adad33da574b05a297bde/blog-10.png" />
            
            </figure><p>With our patch we tell TSDB that it’s allowed to store up to N time series in total, from all scrapes, at any time. So when TSDB is asked to append a new sample by any scrape, it will first check how many time series are already present.</p><p>If the total number of stored time series is below the configured limit then we append the sample as usual.</p><p>The difference with standard Prometheus starts when a new sample is about to be appended, but TSDB already stores the maximum number of time series it’s allowed to have. Our patched logic will then check if the sample we’re about to append belongs to a time series that’s already stored inside TSDB or is it a new time series that needs to be created.</p><p>If the time series already exists inside TSDB then we allow the append to continue. If the time series doesn’t exist yet and our append would create it (a new memSeries instance would be created) then we skip this sample. We will also signal back to the scrape logic that some samples were skipped.</p><p>This is the modified flow with our patch:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3luOmih7ZayqWukDhgoC5S/98cade46c7b5da6bb3a85ae6877532bb/blog-11.png" />
            
            </figure><p>By running <i>“go_memstats_alloc_bytes / prometheus_tsdb_head_series”</i> query we know how much memory we need per single time series (on average), we also know how much physical memory we have available for Prometheus on each server, which means that we can easily calculate the rough number of time series we can store inside Prometheus, taking into account the fact the there’s garbage collection overhead since Prometheus is written in Go:</p><p><i>memory available to Prometheus / bytes per time series = our capacity</i></p><p>This doesn’t capture all complexities of Prometheus but gives us a rough estimate of how many time series we can expect to have capacity for.</p><p>By setting this limit on all our Prometheus servers we know that it will never scrape more time series than we have memory for. This is the last line of defense for us that avoids the risk of the Prometheus server crashing due to lack of memory.</p><p>The second patch modifies how Prometheus handles sample_limit - with our patch instead of failing the entire scrape it simply ignores excess time series. If we have a scrape with sample_limit set to 200 and the application exposes 201 time series, then all except one final time series will be accepted.</p><p>This is the standard Prometheus flow for a scrape that has the sample_limit option set:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2d8iEKXhwkrME4gR7IYEvm/71faa4cdb0781465b35e2c303e4c4e3e/blog-9.png" />
            
            </figure><p>The entire scrape either succeeds or fails. Prometheus simply counts how many samples are there in a scrape and if that’s more than sample_limit allows it will fail the scrape.</p><p>With our custom patch we don’t care how many samples are in a scrape. Instead we count time series as we append them to TSDB. Once we appended sample_limit number of samples we start to be selective.</p><p>Any excess samples (after reaching sample_limit) will only be appended if they belong to time series that are already stored inside TSDB.</p><p>The reason why we still allow appends for some samples even after we’re above sample_limit is that <b>appending samples to existing time series is cheap</b>, it’s just adding an extra timestamp &amp; value pair.</p><p><b>Creating new time series on the other hand is a lot more expensive</b> - we need to allocate new memSeries instances with a copy of all labels and keep it in memory for at least an hour.</p><p>This is how our modified flow looks:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3FstYUSQFx7G4uY1jIdlME/dbba3406fe4fcfea15410b4431bb01ca/blog-12.png" />
            
            </figure><p>Both patches give us two levels of protection.</p><p>The TSDB limit patch protects the entire Prometheus from being overloaded by too many time series.</p><p>This is because the only way to stop time series from eating memory is to prevent them from being appended to TSDB. Once they’re in TSDB it’s already too late.</p><p>While the sample_limit patch stops individual scrapes from using too much Prometheus capacity, which could lead to creating too many time series in total and exhausting total Prometheus capacity (enforced by the first patch), which would in turn affect all other scrapes since some new time series would have to be ignored. At the same time our patch gives us graceful degradation by capping time series from each scrape to a certain level, rather than failing hard and dropping all time series from affected scrape, which would mean losing all observability of affected applications.</p><p>It’s also worth mentioning that without our TSDB total limit patch we could keep adding new scrapes to Prometheus and that alone could lead to exhausting all available capacity, even if each scrape had sample_limit set and scraped fewer time series than this limit allows.</p><p>Extra metrics exported by Prometheus itself tell us if any scrape is exceeding the limit and if that happens we alert the team responsible for it.</p><p>This also has the benefit of allowing us to self-serve capacity management - there’s no need for a team that signs off on your allocations, if CI checks are passing then we have the capacity you need for your applications.</p><p>The main reason why we prefer graceful degradation is that we want our engineers to be able to deploy applications and their metrics with confidence without being subject matter experts in Prometheus. That way even the most inexperienced engineers can start exporting metrics without constantly wondering <i>“Will this cause an incident?”</i>.</p><p>Another reason is that trying to stay on top of your usage can be a challenging task. It might seem simple on the surface, after all you just need to stop yourself from creating too many metrics, adding too many labels or setting label values from untrusted sources.</p><p>In reality though this is as simple as trying to ensure your application doesn’t use too many resources, like CPU or memory - you can achieve this by simply allocating less memory and doing fewer computations. It doesn’t get easier than that, until you actually try to do it. The more any application does for you, the more useful it is, the more resources it might need. Your needs or your customers' needs will evolve over time and so you can’t just draw a line on how many bytes or cpu cycles it can consume. If you do that, the line will eventually be redrawn, many times over.</p><p>In general, having more labels on your metrics allows you to gain more insight, and so the more complicated the application you're trying to <a href="https://www.cloudflare.com/application-services/solutions/app-performance-monitoring/">monitor</a>, the more need for extra labels.</p><p>In addition to that in most cases we don’t see all possible label values at the same time, it’s usually a small subset of all possible combinations. For example our errors_total metric, which we used in example before, might not be present at all until we start seeing some errors, and even then it might be just one or two errors that will be recorded. This holds true for a lot of labels that we see are being used by engineers.</p><p>This means that looking at how many time series an application could potentially export, and how many it actually exports, gives us two completely different numbers, which makes capacity planning a lot harder.</p><p>Especially when dealing with big applications maintained in part by multiple different teams, each exporting some metrics from their part of the stack.</p><p>For that reason we do tolerate some percentage of short lived time series even if they are not a perfect fit for Prometheus and cost us more memory.</p>
    <div>
      <h3>Documentation</h3>
      <a href="#documentation">
        
      </a>
    </div>
    <p>Finally we maintain a set of internal documentation pages that try to guide engineers through the process of scraping and working with metrics, with a lot of information that’s specific to our environment.</p><p>Prometheus and PromQL (Prometheus Query Language) are conceptually very simple, but this means that all the complexity is hidden in the interactions between different elements of the whole metrics pipeline.</p><p>Managing the entire lifecycle of a metric from an engineering perspective is a complex process.</p><p>You must define your metrics in your application, with names and labels that will allow you to work with resulting time series easily. Then you must configure Prometheus scrapes in the correct way and deploy that to the right Prometheus server. Next you will likely need to create recording and/or alerting rules to make use of your time series. Finally you will want to create a dashboard to visualize all your metrics and be able to spot trends.</p><p>There will be traps and room for mistakes at all stages of this process. We covered some of the most basic pitfalls in our previous blog post on Prometheus - <a href="/monitoring-our-monitoring/">Monitoring our monitoring</a>. In the same blog post we also mention one of the tools we use to help our engineers write valid Prometheus alerting rules.</p><p>Having good internal documentation that covers all of the basics specific for our environment and most common tasks is very important. Being able to answer <i>“How do I X?”</i> yourself without having to wait for a subject matter expert allows everyone to be more productive and move faster, while also avoiding Prometheus experts from answering the same questions over and over again.</p>
    <div>
      <h2>Closing thoughts</h2>
      <a href="#closing-thoughts">
        
      </a>
    </div>
    <p>Prometheus is a great and reliable tool, but dealing with high cardinality issues, especially in an environment where a lot of different applications are scraped by the same Prometheus server, can be challenging.</p><p>We had a fair share of problems with overloaded Prometheus instances in the past and developed a number of tools that help us deal with them, including custom patches.</p><p>But the key to tackling high cardinality was better understanding how Prometheus works and what kind of usage patterns will be problematic.</p><p>Having better insight into Prometheus internals allows us to maintain a fast and reliable observability platform without too much red tape, and the tooling we’ve developed around it, some of which is <a href="https://github.com/cloudflare/pint">open sourced</a>, helps our engineers avoid most common pitfalls and deploy with confidence.</p> ]]></content:encoded>
            <category><![CDATA[Prometheus]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Open Source]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">oXX2cdLdXFvg8ZtUNSaqk</guid>
            <dc:creator>Lukasz Mierzwa</dc:creator>
        </item>
        <item>
            <title><![CDATA[Intelligent, automatic restarts for unhealthy Kafka consumers]]></title>
            <link>https://blog.cloudflare.com/intelligent-automatic-restarts-for-unhealthy-kafka-consumers/</link>
            <pubDate>Tue, 24 Jan 2023 14:00:00 GMT</pubDate>
            <description><![CDATA[ At Cloudflare, we take steps to ensure we are resilient against failure at all levels of our infrastructure. This includes Kafka, which we use for critical workflows such as sending time-sensitive emails and alerts. ]]></description>
            <content:encoded><![CDATA[ <p></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7eWbGD5pEX9bKf2p58iqOw/b55ba4bfd305da7ed38cf66fe770585c/image3-8-2.png" />
            
            </figure><p>At Cloudflare, we take steps to ensure we are resilient against failure at all levels of our infrastructure. This includes Kafka, which we use for critical workflows such as sending time-sensitive emails and alerts.</p><p>We learned a lot about keeping our applications that leverage Kafka healthy, so they can always be operational. Application health checks are notoriously hard to implement: What determines an application as healthy? How can we keep services operational at all times?</p><p>These can be implemented in many ways. We’ll talk about an approach that allows us to considerably reduce incidents with unhealthy applications while requiring less manual intervention.</p>
    <div>
      <h3>Kafka at Cloudflare</h3>
      <a href="#kafka-at-cloudflare">
        
      </a>
    </div>
    <p><a href="/using-apache-kafka-to-process-1-trillion-messages/">Cloudflare is a big adopter of Kafka</a>. We use Kafka as a way to decouple services due to its asynchronous nature and reliability. It allows different teams to work effectively without creating dependencies on one another. You can also read more about how other teams at Cloudflare use Kafka in <a href="/http-analytics-for-6m-requests-per-second-using-clickhouse/">this</a> post.</p><p>Kafka is used to send and receive messages. Messages represent some kind of event like a credit card payment or details of a new user created in your platform. These messages can be represented in multiple ways: JSON, Protobuf, Avro and so on.</p><p>Kafka organises messages in topics. A topic is an ordered log of events in which each message is marked with a progressive offset. When an event is written by an external system, that is appended to the end of that topic. These events are not deleted from the topic by default (retention can be applied).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2KUYbqCCL74YZVU8NXOThl/4ec5024168993a2300add7221016af0d/1-4.png" />
            
            </figure><p>Topics are stored as log files on disk, which are finite in size. Partitions are a systematic way of breaking the one topic log file into many logs, each of which can be hosted on separate servers–enabling to scale topics.</p><p>Topics are managed by brokers–nodes in a Kafka cluster. These are responsible for writing new events to partitions, serving reads and replicating partitions among themselves.</p><p>Messages can be consumed by individual consumers or co-ordinated groups of consumers, known as consumer groups.</p><p>Consumers use a unique id (consumer id) that allows them to be identified by the broker as an application which is consuming from a specific topic.</p><p>Each topic can be read by an infinite number of different consumers, as long as they use a different id. Each consumer can replay the same messages as many times as they want.</p><p>When a consumer starts consuming from a topic, it will process all messages, starting from a selected offset, from each partition. With a consumer group, the partitions are divided amongst each consumer in the group. This division is determined by the consumer group leader. This leader will receive information about the other consumers in the group and will decide which consumers will receive messages from which partitions (partition strategy).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6Qe2Qe5nQ5gcHyhV0zpTWw/5182eea9de66164a36a28e92270fdb3f/2-3.png" />
            
            </figure><p>The offset of a consumer’s commit can demonstrate whether the consumer is working as expected. Committing a processed offset is the way a consumer and its consumer group report to the broker that they have processed a particular message.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/29Y9mQiHkvGKUzc3RGF1sk/09d2987f53eef026c164e6c49cacc95c/unnamed-6.png" />
            
            </figure><p>A standard measurement of whether a consumer is processing fast enough is lag. We use this to measure how far behind the newest message we are. This tracks time elapsed between messages being written to and read from a topic. When a service is lagging behind, it means that the consumption is at a slower rate than new messages being produced.</p><p>Due to Cloudflare’s scale, message rates typically end up being very large and a lot of requests are time-sensitive so monitoring this is vital.</p><p>At Cloudflare, our applications using Kafka are deployed as microservices on Kubernetes.</p>
    <div>
      <h3>Health checks for Kubernetes apps</h3>
      <a href="#health-checks-for-kubernetes-apps">
        
      </a>
    </div>
    <p>Kubernetes uses <a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/">probes</a> to understand if a service is healthy and is ready to receive traffic or to run. When a liveness probe fails and the bounds for retrying are exceeded, Kubernetes restarts the services.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4FagbTygES9L7dmEQ6ratD/0a6f0d4c5ac117b723ad726a12d3936a/4-3.png" />
            
            </figure><p>When a readiness probe fails and the bounds for retrying are exceeded, it stops sending HTTP traffic to the targeted pods. In the case of Kafka applications this is not relevant as they don’t run an http server. For this reason, we’ll cover only liveness checks.</p><p>A classic Kafka liveness check done on a consumer checks the status of the connection with the broker. It’s often best practice to keep these checks simple and perform some basic operations - in this case, something like listing topics. If, for any reason, this check fails consistently, for instance the broker returns a TLS error, Kubernetes terminates the service and starts a new pod of the same service, therefore forcing a new connection. Simple Kafka liveness checks do a good job of understanding when the connection with the broker is unhealthy.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6gNWb3Rit0MmTutsurm7sf/70355c422fab7ebce7d59d8c2c682d6d/5-2.png" />
            
            </figure>
    <div>
      <h3>Problems with Kafka health checks</h3>
      <a href="#problems-with-kafka-health-checks">
        
      </a>
    </div>
    <p>Due to Cloudflare’s scale, a lot of our Kafka topics are divided into multiple partitions (in some cases this can be hundreds!) and in many cases the replica count of our consuming service doesn’t necessarily match the number of partitions on the Kafka topic. This can mean that in a lot of scenarios this simple approach to health checking is not quite enough!</p><p>Microservices that consume from Kafka topics are healthy if they are consuming and committing offsets at regular intervals when messages are being published to a topic. When such services are not committing offsets as expected, it means that the consumer is in a bad state, and it will start accumulating lag. An approach we often take is to manually terminate and restart the service in Kubernetes, this will cause a reconnection and rebalance.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/N4YalYdgNRxYJK7PVAlzY/26b55fc38c53855a6c28c71b25cdac02/lag.png" />
            
            </figure><p>When a consumer joins or leaves a consumer group, a rebalance is triggered and the consumer group leader must re-assign which consumers will read from which partitions.</p><p>When a rebalance happens, each consumer is notified to stop consuming. Some consumers might get their assigned partitions taken away and re-assigned to another consumer. We noticed when this happened within our library implementation; if the consumer doesn’t acknowledge this command, it will wait indefinitely for new messages to be consumed from a partition that it’s no longer assigned to, ultimately leading to a deadlock. Usually a manual restart of the faulty client-side app is needed to resume processing.</p>
    <div>
      <h3>Intelligent health checks</h3>
      <a href="#intelligent-health-checks">
        
      </a>
    </div>
    <p>As we were seeing consumers reporting as “healthy” but sitting idle, it occurred to us that maybe we were focusing on the wrong thing in our health checks. Just because the service is connected to the Kafka broker and can read from the topic, it does not mean the consumer is actively processing messages.</p><p>Therefore, we realised we should be focused on message ingestion, using the offset values to ensure that forward progress was being made.</p>
    <div>
      <h4>The PagerDuty approach</h4>
      <a href="#the-pagerduty-approach">
        
      </a>
    </div>
    <p>PagerDuty wrote an excellent <a href="https://www.pagerduty.com/eng/kafka-health-checks/">blog</a> on this topic which we used as inspiration when coming up with our approach.</p><p>Their approach used the current (latest) offset and the committed offset values. The current offset signifies the last message that was sent to the topic, while the committed offset is the last message that was processed by the consumer.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2fwem7NtBnO6M1RMhrezr8/af4cbbd7a63d3145f5c7fe9f405bd04d/pasted-image-0-4.png" />
            
            </figure><p>Checking the consumer is moving forwards, by ensuring that the latest offset was changing (receiving new messages) and the committed offsets were changing as well (processing the new messages).</p><p>Therefore, the solution we came up with:</p><ul><li><p>If we cannot read the current offset, fail liveness probe.</p></li><li><p>If we cannot read the committed offset, fail liveness probe.</p></li><li><p>If the committed offset == the current offset, pass liveness probe.</p></li><li><p>If the value for the committed offset has not changed since the last run of the health check, fail liveness probe.</p></li></ul>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5r76n2Iew7pSqA8vYNZzIy/c9e0f6a113a34d0c36a216c054e4d840/pasted-image-0--1--3.png" />
            
            </figure><p>To measure if the committed offset is changing, we need to store the value of the previous run, we do this using an in-memory map where partition number is the key. This means each instance of our service only has a view of the partitions it is currently consuming from and will run the health check for each.</p>
    <div>
      <h4>Problems</h4>
      <a href="#problems">
        
      </a>
    </div>
    <p>When we first rolled out our smart health checks we started to notice cascading failures some time after release. After initial investigations we realised this was happening when a rebalance happens. It would initially affect one replica then quickly result in the others reporting as unhealthy.</p><p>What we observed was due to us storing the previous value of the committed offset in-memory, when a rebalance happens the service may get re-assigned a different partition. When this happened it meant our service was incorrectly assuming that the committed offset for that partition had not changed (as this specific replica was no longer updating the latest value), therefore it would start to report the service as unhealthy. The failing liveness probe would then cause it to restart which would in-turn trigger another rebalancing in Kafka causing other replicas to face the same issue.</p>
    <div>
      <h4>Solution</h4>
      <a href="#solution">
        
      </a>
    </div>
    <p>To fix this issue we needed to ensure that each replica only kept track of the offsets for the partitions it was consuming from at that moment. Luckily, the Shopify Sarama library, which we use internally, has functionality to observe when a rebalancing happens. This meant we could use it to rebuild the in-memory map of offsets so that it would only include the relevant partition values.</p><p>This is handled by receiving the signal from the session context channel:</p>
            <pre><code>for {
  select {
  case message, ok := &lt;-claim.Messages(): // &lt;-- Message received

     // Store latest received offset in-memory
     offsetMap[message.Partition] = message.Offset


     // Handle message
     handleMessage(ctx, message)


     // Commit message offset
     session.MarkMessage(message, "")


  case &lt;-session.Context().Done(): // &lt;-- Rebalance happened

     // Remove rebalanced partition from in-memory map
     delete(offsetMap, claim.Partition())
  }
}</code></pre>
            <p>Verifying this solution was straightforward, we just needed to trigger a rebalance. To test this worked in all possible scenarios we spun up a single replica of a service consuming from multiple partitions, then proceeded to scale up the number of replicas until it matched the partition count, then scaled back down to a single replica. By doing this we verified that the health checks could safely handle new partitions being assigned as well as partitions being taken away.</p>
    <div>
      <h3>Takeaways</h3>
      <a href="#takeaways">
        
      </a>
    </div>
    <p>Probes in Kubernetes are very easy to set up and can be a powerful tool to ensure your application is running as expected. Well implemented probes can often be the difference between engineers being called out to fix trivial issues (sometimes outside of working hours) and a service which is self-healing.</p><p>However, without proper thought, “dumb” health checks can also lead to a false sense of security that a service is running as expected even when it’s not. One thing we have learnt from this was to think more about the specific behaviour of the service and decide what being unhealthy means in each instance, instead of just ensuring that dependent services are connected.</p> ]]></content:encoded>
            <category><![CDATA[Kafka]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Go]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <guid isPermaLink="false">7s1ijlG7zMlxJPI6Hcs3zl</guid>
            <dc:creator>Chris Shepherd</dc:creator>
            <dc:creator>Andrea Medda</dc:creator>
        </item>
        <item>
            <title><![CDATA[Monitor your own network with free network flow analytics from Cloudflare]]></title>
            <link>https://blog.cloudflare.com/free-magic-network-monitoring/</link>
            <pubDate>Wed, 28 Sep 2022 13:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare is excited to announce that we are releasing a free version of Magic Networking Monitoring (previously called Flow Based Monitoring). Magic Network Monitoring receives network flow data from a customer’s router(s) and provides network traffic analytics via Cloudflare’s dashboard. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>As a network engineer or manager, answering questions about the traffic flowing across your infrastructure is a key part of your job. Cloudflare built Magic Network Monitoring (previously called <a href="/flow-based-monitoring-for-magic-transit/">Flow Based Monitoring</a>) to <a href="https://www.cloudflare.com/network-services/solutions/network-monitoring-tools/">give you better visibility into your network</a> and to answer questions like, “What is my network’s peak traffic volume? What are the sources of that traffic? When does my network see that traffic?” Today, Cloudflare is excited to announce early access to a free version of Magic Network Monitoring that will be available to everyone. You can request early access by <a href="https://forms.gle/58jPPjcZRA596DCN7">filling out this form</a>.</p><p>Magic Network Monitoring now features a powerful analytics dashboard, self-serve configuration, and a step-by-step onboarding wizard. You’ll have access to a tool that helps you visualize your traffic and filter by packet characteristics including protocols, source IPs, destination IPs, ports, TCP flags, and router IP. Magic Network Monitoring also includes network traffic volume alerts for specific IP addresses or IP prefixes on your network.</p>
    <div>
      <h3>Making Network Monitoring easy</h3>
      <a href="#making-network-monitoring-easy">
        
      </a>
    </div>
    <p>Magic Networking Monitoring allows customers to collect network analytics without installing a physical device like a network TAP (Test Access Point) or setting up overly complex remote monitoring systems. Our product works with any hardware that exports network flow data, and customers can quickly configure any router to send flow data to Cloudflare’s network. From there, our network flow analyzer will aggregate your traffic data and display it in Magic Network Monitoring analytics.</p>
    <div>
      <h3>Analytics dashboard</h3>
      <a href="#analytics-dashboard">
        
      </a>
    </div>
    <p>In Magic Network Monitoring analytics, customers can take a deep dive into their network traffic data. You can filter traffic data by protocol, source IP, destination IP, TCP flags, and router IP. Customers can combine these filters together to answer questions like, “How much ICMP data was requested from my speed test server over the past 24 hours?” Visibility into traffic analytics is a key part of understanding your network’s operations and proactively improving your security. Let’s walk through some cases where Magic Network Monitoring analytics can answer your <a href="https://www.cloudflare.com/learning/network-layer/network-security/">network</a> visibility and security questions.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7dp6erIjjdaC4MHbVVnH4j/2ff2e0293bfd1d749b1003b2523a2ad1/image3-40.png" />
            
            </figure>
    <div>
      <h3>Create network volume alert thresholds per IP address or IP prefix</h3>
      <a href="#create-network-volume-alert-thresholds-per-ip-address-or-ip-prefix">
        
      </a>
    </div>
    <p>Magic Network Monitoring is incredibly flexible, and it can be customized to meet the needs of any network hobbyist or business. You can monitor your traffic volume trends over time via the analytics dashboard and build an understanding of your network’s traffic profile. After gathering historical network data, you can set custom volumetric threshold alerts for one IP prefix or a group of IP prefixes. As your network traffic changes over time, or their network expands, they can easily update their Magic Network Monitoring configuration to receive data from new routers or destinations within their network.</p>
    <div>
      <h3>Monitoring a speed test server in a home lab</h3>
      <a href="#monitoring-a-speed-test-server-in-a-home-lab">
        
      </a>
    </div>
    <p>Let’s run through an example where you’re running a network home lab. You decide to use Magic Network Monitoring to track the volume of requests a speed test server you’re hosting receives and check for potential bad actors. Your goal is to identify when your speed test server experiences peak traffic, and the volume of that traffic. You set up Magic Network Monitoring and create a rule that analyzes all traffic destined for your speed test server’s IP address. After collecting data for seven days, the analytics dashboard shows that peak traffic occurs on weekdays in the morning, and that during this time, your traffic volume ranges from 450 - 550 Mbps.</p><p>As you’re checking over the analytics data, you also notice strange traffic spikes of 300 - 350 Mbps in the middle of the night that occur at the same time. As you investigate further, the analytics dashboard shows the source of this traffic spike is from the same IP prefix. You research some source IPs, and find they’re associated with malicious activity. As a result, you update your firewall to block traffic from this problematic source.</p>
    <div>
      <h3>Identifying a network layer DDoS attack</h3>
      <a href="#identifying-a-network-layer-ddos-attack">
        
      </a>
    </div>
    <p>Magic Network Monitoring can also be leveraged to identify a variety of L3, L4, and L7 DDoS attacks. Let’s run through an example of how ACME Corp, a small business using Magic Network Monitoring, can identify a Ping (ICMP) Flood attack on their network. Ping Flood attacks aim to overwhelm the targeted network’s ability to respond to a high number of requests or overload the network connection with bogus traffic.</p><p>At the start of a Ping Flood attack, your server’s traffic volume will begin to ramp up. Magic Network Monitoring will analyze traffic across your network, and send an email, webhook, or PagerDuty alert once an unusual volume of traffic is identified. Your network and security team can respond to the volumetric alert by checking the data in Magic Network Monitoring analytics and identifying the attack type. In this case, they’ll notice the following traffic characteristics:</p><ol><li><p>Network traffic volume above your historical traffic averages</p></li><li><p>An unusually large amount of ICMP traffic</p></li><li><p>ICMP traffic coming from a specific set of source IPs</p></li></ol><p>Now, your network security team has confirmed the traffic is malicious by identifying the attack type, and can begin taking steps to mitigate the attack.</p>
    <div>
      <h3>Magic Network Monitoring and Magic Transit</h3>
      <a href="#magic-network-monitoring-and-magic-transit">
        
      </a>
    </div>
    <p>If your business is impacted by DDoS attacks, Magic Network Monitoring will identify attacks, and Magic Transit can be used to mitigate those DDoS attacks. <a href="https://www.cloudflare.com/magic-transit">Magic Transit</a> protects customers’ entire network from DDoS attacks by placing our network in front of theirs. You can use Magic Transit Always On to reduce latency and mitigate attacks all the time, or Magic Transit On Demand to protect your network during active attacks. With Magic Transit, you get DDoS protection, traffic acceleration, and other network functions delivered as a service from every Cloudflare data center. Magic Transit works by allowing Cloudflare to <a href="/bringing-your-own-ips-to-cloudflare-byoip/">advertise customers’ IP prefixes</a> to the Internet with BGP to route the customer’s traffic through our network for DDoS protection. If you’re interested in protecting your network with Magic Transit, you can visit the <a href="https://www.cloudflare.com/magic-transit/">Magic Transit</a> product page and request a demo today.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4l1c4kr7eVfWgzvoKJuq8o/0a69d632ca6ed5420b76c28b9a123bff/image1-65.png" />
            
            </figure>
    <div>
      <h3>Sign up for early access and what’s next</h3>
      <a href="#sign-up-for-early-access-and-whats-next">
        
      </a>
    </div>
    <p>The free version of Magic Network Monitoring (MNM) will be released in the next few weeks. You can request early access by <a href="https://forms.gle/58jPPjcZRA596DCN7">filling out this form</a>.</p><p>This is just the beginning for Magic Network Monitoring. In the future, you can look forward to features like advanced DDoS attack identification, network incident history and trends, and volumetric alert threshold recommendations.</p> ]]></content:encoded>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[Free]]></category>
            <category><![CDATA[Magic Network Monitoring]]></category>
            <category><![CDATA[Network]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Product News]]></category>
            <guid isPermaLink="false">5Ffo0vjuubeJk4SVWNRskx</guid>
            <dc:creator>Chris Draper</dc:creator>
        </item>
        <item>
            <title><![CDATA[Monitoring our monitoring: how we validate our Prometheus alert rules]]></title>
            <link>https://blog.cloudflare.com/monitoring-our-monitoring/</link>
            <pubDate>Thu, 19 May 2022 15:39:19 GMT</pubDate>
            <description><![CDATA[ Pint is a tool we developed to validate our Prometheus alerting rules and ensure they are always working ]]></description>
            <content:encoded><![CDATA[ <p></p>
    <div>
      <h3>Background</h3>
      <a href="#background">
        
      </a>
    </div>
    <p>We use <a href="https://prometheus.io/">Prometheus</a> as our core monitoring system. We’ve been heavy Prometheus users since 2017 when we migrated off our previous monitoring system which used a customized <a href="https://www.nagios.org/">Nagios</a> setup. Despite growing our infrastructure a lot, adding tons of new products and learning some hard lessons about operating Prometheus at scale, our original architecture of Prometheus (see <a href="https://www.youtube.com/watch?v=ypWwvz5t_LE">Monitoring Cloudflare's Planet-Scale Edge Network with Prometheus</a> for an in depth walk through) remains virtually unchanged, proving that Prometheus is a solid foundation for building <a href="https://www.cloudflare.com/learning/performance/what-is-observability/">observability</a> into your services.</p><p>One of the key responsibilities of Prometheus is to alert us when something goes wrong and in this blog post we’ll talk about how we make those alerts more reliable - and we’ll introduce an open source tool we’ve developed to help us with that, and share how you can use it too. If you’re not familiar with Prometheus you might want to start by watching <a href="https://www.youtube.com/watch?v=PzFUwBflXYc">this video</a> to better understand the topic we’ll be covering here.</p><p>Prometheus works by collecting metrics from our services and storing those metrics inside its database, called TSDB. We can then <a href="https://prometheus.io/docs/prometheus/latest/querying/basics/">query these metrics</a> using Prometheus query language called PromQL using ad-hoc queries (for example to power <a href="https://grafana.com/grafana/">Grafana dashboards</a>) or via <a href="https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/">alerting</a> or <a href="https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/">recording</a> rules. A rule is basically a query that Prometheus will run for us in a loop, and when that query returns any results it will either be recorded as new metrics (with recording rules) or trigger alerts (with alerting rules).</p>
    <div>
      <h3>Prometheus alerts</h3>
      <a href="#prometheus-alerts">
        
      </a>
    </div>
    <p>Since we’re talking about improving our alerting we’ll be focusing on alerting rules.</p><p>To create alerts we first need to have some metrics collected. For the purposes of this blog post let’s assume we’re working with http_requests_total metric, which is used on the examples <a href="https://prometheus.io/docs/prometheus/latest/querying/examples/">page</a>. Here are some examples of how our metrics will look:</p>
            <pre><code>http_requests_total{job="myserver", handler="/", method=”get”, status=”200”}
http_requests_total{job="myserver", handler="/", method=”get”, status=”500”}
http_requests_total{job="myserver", handler="/posts", method=”get”, status=”200”}
http_requests_total{job="myserver", handler="/posts", method=”get”, status=”500”}
http_requests_total{job="myserver", handler="/posts/new", method=”post”, status=”201”}
http_requests_total{job="myserver", handler="/posts/new", method=”post”, status=”401”}</code></pre>
            <p>Let’s say we want to alert if our HTTP server is returning errors to customers.</p><p>Since, all we need to do is check our metric that tracks how many responses with HTTP status code 500 there were, a simple alerting rule could like this:</p>
            <pre><code>- alert: Serving HTTP 500 errors
  expr: http_requests_total{status=”500”} &gt; 0</code></pre>
            <p>This will alert us if we have any 500 errors served to our customers. Prometheus will run our query looking for a time series named http_requests_total that also has a <b>status</b> label with value <b>“500”</b>. Then it will filter all those matched time series and only return ones with value greater than zero.</p><p>If our alert rule returns any results a fire will be triggered, one for each returned result.</p><p>If our rule doesn’t return anything, meaning there are no matched time series, then alert will not trigger.</p><p>The whole flow from metric to alert is pretty simple here as we can see on the diagram below.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/57mqOPxVQ847pKAkMden9K/8ba472d0359bc3fcfd09535cc04e82b7/1-3.png" />
            
            </figure><p>If we want to provide more information in the alert we can by setting additional labels and annotations, but alert and expr fields are all we need to get a working rule.</p><p>But the problem with the above rule is that our alert starts when we have our first error, and then it will never go away.</p><p>After all, our http_requests_total is a counter, so it gets incremented every time there’s a new request, which means that it will keep growing as we receive more requests. What this means for us is that our alert is really telling us “was there ever a 500 error?” and even if we fix the problem causing 500 errors we’ll keep getting this alert.</p><p>A better alert would be one that tells us if we’re serving errors <b>right now</b>.</p><p>For that we can use the <a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#rate">rate()</a> function to calculate the per second rate of errors.</p><p>Our modified alert would be:</p>
            <pre><code>- alert: Serving HTTP 500 errors
  expr: rate(http_requests_total{status=”500”}[2m]) &gt; 0</code></pre>
            <p>The query above will calculate the rate of 500 errors in the last two minutes. If we start responding with errors to customers our alert will fire, but once errors stop so will this alert.</p><p>This is great because if the underlying issue is resolved the alert will resolve too.</p><p>We can improve our alert further by, for example, alerting on the percentage of errors, rather than absolute numbers, or even calculate error budget, but let’s stop here for now.</p><p>It’s all very simple, so what do we mean when we talk about improving the reliability of alerting? What could go wrong here?</p>
    <div>
      <h3>What could go wrong?</h3>
      <a href="#what-could-go-wrong">
        
      </a>
    </div>
    <p>We can craft a valid YAML file with a rule definition that has a perfectly valid query that will simply not work how we expect it to work. Which, when it comes to alerting rules, might mean that the alert we rely upon to tell us when something is not working correctly will fail to alert us when it should. To better understand why that might happen let’s first explain how querying works in Prometheus.</p>
    <div>
      <h3>Prometheus querying basics</h3>
      <a href="#prometheus-querying-basics">
        
      </a>
    </div>
    <p>There are two basic types of queries we can run against Prometheus. The first one is an <b>instant query</b>. It allows us to ask Prometheus for a point in time value of some time series. If we write our query as http_requests_total we’ll get all time series named http_requests_total along with the most recent value for each of them. We can further customize the query and filter results by adding label matchers, like http_requests_total{status=”500”}.</p><p>Let’s consider we have two instances of our server, green and red, each one is scraped (Prometheus collects metrics from it) every one minute (independently of each other).</p><p>This is what happens when we issue an instant query:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2KyKw0PTiNywwYY6euDbuH/7910f018372888b78a4609d16395f1d4/2-fixed.png" />
            
            </figure><p>There’s obviously more to it as we can use <a href="https://prometheus.io/docs/prometheus/latest/querying/functions/">functions</a> and build complex queries that utilize multiple metrics in one expression. But for the purposes of this blog post we’ll stop here.</p><p>The important thing to know about instant queries is that they return the most recent value of a matched time series, and they will look back for up to five minutes (by default) into the past to find it. If the last value is older than five minutes then it’s considered stale and Prometheus won’t return it anymore.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4V3s3dupV4RHtWcAwbPpYd/bd14209ea0ebb73807da506422ccc5f3/3-fixed.png" />
            
            </figure><p>The second type of query is a <b>range query</b> - it works similarly to instant queries, the difference is that instead of returning us the most recent value it gives us a list of values from the selected time range. That time range is always relative so instead of providing two timestamps we provide a range, like “20 minutes”. When we ask for a range query with a 20 minutes range it will return us all values collected for matching time series from 20 minutes ago until now.</p><p>An important distinction between those two types of queries is that range queries don’t have the same “look back for up to five minutes” behavior as instant queries. If Prometheus cannot find any values collected in the provided time range then it doesn’t return anything.</p><p>If we modify our example to request [3m] range query we should expect Prometheus to return three data points for each time series:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1dBwnYThdf3ioljbPdmSKV/54b428335adffe68f87cd40ff80e3ecc/4-fixed.png" />
            
            </figure>
    <div>
      <h3>When queries don’t return anything</h3>
      <a href="#when-queries-dont-return-anything">
        
      </a>
    </div>
    <p>Knowing a bit more about how queries work in Prometheus we can go back to our alerting rules and spot a potential problem: queries that don’t return anything.</p><p>If our query doesn’t match any time series or if they’re considered stale then Prometheus will return an empty result. This might be because we’ve made a typo in the metric name or label filter, the metric we ask for is no longer being exported, or it was never there in the first place, or we’ve added some condition that wasn’t satisfied, like value of being non-zero in our http_requests_total{status=”500”} &gt; 0 example.</p><p>Prometheus will not return any error in any of the scenarios above because none of them are really problems, it’s just how querying works. If you ask for something that doesn’t match your query then you get empty results. This means that there’s no distinction between “all systems are operational” and “you’ve made a typo in your query”. So if you’re not receiving any alerts from your service it’s either a sign that everything is working fine, or that you’ve made a typo, and you have no working monitoring at all, and it’s up to you to verify which one it is.</p><p>For example, we could be trying to query for http_requests_totals instead of http_requests_total (an extra “s” at the end) and although our query will look fine it won’t ever produce any alert.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2p2hNra24jqNgnfeaaLZ6S/9a07d508dcf2c6a1417fe5b3dc4ad46b/5-1.png" />
            
            </figure><p>Range queries can add another twist - they’re mostly used in Prometheus functions like rate(),  which we used in our example. This function will only work correctly if it receives a range query expression that returns at least two data points for each time series, after all it’s impossible to calculate rate from a single number.</p><p>Since the number of data points depends on the time range we passed to the range query, which we then pass to our rate() function, if we provide a time range that only contains a single value then rate won’t be able to calculate anything and once again we’ll return empty results.</p><p>The number of values collected in a given time range depends on the interval at which Prometheus collects all metrics, so to use rate() correctly you need to know how your Prometheus server is configured. You can read more about this <a href="https://www.robustperception.io/what-range-should-i-use-with-rate">here</a> and <a href="https://promlabs.com/blog/2021/01/29/how-exactly-does-promql-calculate-rates">here</a> if you want to better understand how rate() works in Prometheus.</p><p>For example if we collect our metrics every one minute then a range query http_requests_total[1m] will be able to find only one data point. Here’s a reminder of how this looks:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3ghp7yB5qlTe23SxtTKEm3/c66a1cbe0687303ea58e672783bfdef9/6-fixed.png" />
            
            </figure><p>Since, as we mentioned before, we can only calculate rate() if we have at least two data points, calling rate(http_requests_total[1m]) will never return anything and so our alerts will never work.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1xBQkUPr4OsGRpAbeXXtmc/093488f9d4c3c129897cb0dc62190031/7.png" />
            
            </figure><p>There are more potential problems we can run into when writing Prometheus queries, for example any operations between two metrics will only work if both have the same set of labels, you can read about this <a href="https://prometheus.io/docs/prometheus/latest/querying/operators/#vector-matching">here</a>. But for now we’ll stop here, listing all the gotchas could take a while. The point to remember is simple: if your alerting query doesn’t return anything then it might be that everything is ok and there’s no need to alert, but it might also be that you’ve mistyped your metrics name, your label filter cannot match anything, your metric disappeared from Prometheus, you are using too small time range for your range queries etc.</p>
    <div>
      <h3>Renaming metrics can be dangerous</h3>
      <a href="#renaming-metrics-can-be-dangerous">
        
      </a>
    </div>
    <p>We’ve been running Prometheus for a few years now and during that time we’ve grown our collection of alerting rules a lot. Plus we keep adding new products or modifying existing ones, which often includes adding and removing metrics, or modifying existing metrics, which may include renaming them or changing what labels are present on these metrics.</p><p>A lot of metrics come from metrics exporters maintained by the Prometheus community, like <a href="https://github.com/prometheus/node_exporter">node_exporter</a>, which we use to gather some operating system metrics from all of our servers. Those exporters also undergo changes which might mean that some metrics are deprecated and removed, or simply renamed.</p><p>A problem we’ve run into a few times is that sometimes our alerting rules wouldn’t be updated after such a change, for example when we upgraded node_exporter across our fleet. Or the addition of a new label on some metrics would suddenly cause Prometheus to no longer return anything for some of the alerting queries we have, making such an alerting rule no longer useful.</p><p>It’s worth noting that Prometheus does have a way of <a href="https://prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/">unit testing rules</a>, but since it works on mocked data it’s mostly useful to validate the logic of a query. Unit testing won’t tell us if, for example, a metric we rely on suddenly disappeared from Prometheus.</p>
    <div>
      <h3>Chaining rules</h3>
      <a href="#chaining-rules">
        
      </a>
    </div>
    <p>When writing alerting rules we try to limit <a href="https://en.wikipedia.org/wiki/Alarm_fatigue">alert fatigue</a> by ensuring that, among many things, alerts are only generated when there’s an action needed, they clearly describe the problem that needs addressing, they have a link to a runbook and a dashboard, and finally that we aggregate them as much as possible. This means that a lot of the alerts we have won’t trigger for each individual instance of a service that’s affected, but rather once per data center or even globally.</p><p>For example, we might alert if the rate of HTTP errors in a datacenter is above 1% of all requests. To do that we first need to calculate the overall rate of errors across all instances of our server. For that we would use a <a href="https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/">recording rule</a>:</p>
            <pre><code>- record: job:http_requests_total:rate2m
  expr: sum(rate(http_requests_total[2m])) without(method, status, instance)

- record: job:http_requests_status500:rate2m
  expr: sum(rate(http_requests_total{status=”500”}[2m])) without(method, status, instance)</code></pre>
            <p>First rule will tell Prometheus to calculate per second rate of all requests and sum it across all instances of our server. Second rule does the same but only sums time series with status labels equal to “500”. Both rules will produce new metrics named after the value of the <b>record</b> field.</p><p>Now we can modify our alert rule to use those new metrics we’re generating with our recording rules:</p>
            <pre><code>- alert: Serving HTTP 500 errors
  expr: job:http_requests_status500:rate2m / job:http_requests_total:rate2m &gt; 0.01</code></pre>
            <p>If we have a data center wide problem then we will raise just one alert, rather than one per instance of our server, which can be a great quality of life improvement for our on-call engineers.</p><p>But at the same time we’ve added two new rules that we need to maintain and ensure they produce results. To make things more complicated we could have recording rules producing metrics based on other recording rules, and then we have even more rules that we need to ensure are working correctly.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3j3cCrMguixvR5m9ZTnhPH/b38180795e05fd9d266f3e90d0e731d7/8.png" />
            
            </figure><p>What if all those rules in our chain are maintained by different teams? What if the rule in the middle of the chain suddenly gets renamed because that’s needed by one of the teams? Problems like that can easily crop up now and then if your environment is sufficiently complex, and when they do, they’re not always obvious, after all the only sign that something stopped working is, well, silence - your alerts no longer trigger. If you’re lucky you’re plotting your metrics on a dashboard somewhere and hopefully someone will notice if they become empty, but it’s risky to rely on this.</p><p>We definitely felt that we needed something better than hope.</p>
    <div>
      <h3>Introducing pint: a Prometheus rule linter</h3>
      <a href="#introducing-pint-a-prometheus-rule-linter">
        
      </a>
    </div>
    <p>To avoid running into such problems in the future we’ve decided to write a tool that would help us do a better job of testing our alerting rules against live Prometheus servers, so we can spot missing metrics or typos easier. We also wanted to allow new engineers, who might not necessarily have all the in-depth knowledge of how Prometheus works, to be able to write rules with confidence without having to get feedback from more experienced team members.</p><p>Since we believe that such a tool will have value for the entire Prometheus community we’ve open-sourced it, and it’s available for anyone to use - say hello to pint!</p><p>You can find sources on <a href="https://github.com/cloudflare/pint">github</a>, there’s also <a href="https://cloudflare.github.io/pint/">online documentation</a> that should help you get started.</p><p>Pint works in 3 different ways:</p><ul><li><p>You can run it against a file(s) with Prometheus rules</p></li><li><p>It can run as a part of your CI pipeline</p></li><li><p>Or you can deploy it as a side-car to all your Prometheus servers</p></li></ul><p>It doesn’t require any configuration to run, but in most cases it will provide the most value if you create a configuration file for it and define some Prometheus servers it should use to validate all rules against. Running without any configured Prometheus servers will limit it to static analysis of all the rules, which can identify a range of problems, but won’t tell you if your rules are trying to query non-existent metrics.</p><p>First mode is where pint reads a file (or a directory containing multiple files), parses it, does all the basic syntax checks and then runs a series of checks for all Prometheus rules in those files.</p><p>Second mode is optimized for validating git based pull requests. Instead of testing all rules from all files pint will only test rules that were modified and report only problems affecting modified lines.</p><p>Third mode is where pint runs as a daemon and tests all rules on a regular basis. If it detects any problem it will expose those problems as metrics. You can then collect those metrics using Prometheus and alert on them as you would for any other problems. This way you can basically use Prometheus to monitor itself.</p><p>What kind of checks can it run for us and what kind of problems can it detect?</p><p>All the checks are documented <a href="https://cloudflare.github.io/pint/checks/">here</a>, along with some tips on how to deal with any detected problems. Let’s cover the most important ones briefly.</p><p>As mentioned above the main motivation was to catch rules that try to query metrics that are missing or when the query was simply mistyped. To do that pint will run each query from every alerting and recording rule to see if it returns any result, if it doesn’t then it will break down this query to identify all individual metrics and check for the existence of each of them. If any of them is missing or if the query tries to filter using labels that aren’t present on any time series for a given metric then it will report that back to us.</p><p>So if someone tries to add a new alerting rule with http_requests_totals typo in it, pint will detect that when running CI checks on the pull request and stop it from being merged. Which takes care of validating rules as they are being added to our configuration management system.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6pVy8R1PRoDvIt8jgooQJu/0bcf4d8f15e4eec4a61a854603153ffc/9.png" />
            
            </figure><p>Another useful check will try to estimate the number of times a given alerting rule would trigger an alert. Which is useful when raising a pull request that’s adding new alerting rules - nobody wants to be flooded with alerts from a rule that’s too sensitive so having this information on a pull request allows us to spot rules that could lead to alert fatigue.</p><p>Similarly, another check will provide information on how many new time series a recording rule adds to Prometheus. In our setup a single unique time series uses, on average, 4KiB of memory. So if a recording rule generates 10 thousand new time series it will increase Prometheus server memory usage by 10000*4KiB=40MiB. 40 megabytes might not sound like but our peak time series usage in the last year was around 30 million time series in a single Prometheus server, so we pay attention to anything that’s might add a substantial amount of new time series, which pint helps us to notice before such rule gets added to Prometheus.</p><p>On top of all the Prometheus query checks, pint allows us also to ensure that all the alerting rules comply with some policies we’ve set for ourselves. For example, we require everyone to write a runbook for their alerts and link to it in the alerting rule using annotations.</p><p>We also require all alerts to have priority labels, so that high priority alerts are generating pages for responsible teams, while low priority ones are only routed to <a href="https://github.com/prymitive/karma">karma dashboard</a> or create tickets using <a href="https://github.com/prometheus-community/jiralert">jiralert</a>. It’s easy to forget about one of these required fields and that’s not something which can be enforced using unit testing, but pint allows us to do that with a few configuration lines.</p><p>With pint running on all stages of our Prometheus rule life cycle, from initial pull request to monitoring rules deployed in our many data centers, we can rely on our Prometheus alerting rules to always work and notify us of any incident, large or small.</p><p>GitHub: <a href="https://github.com/cloudflare/pint">https://github.com/cloudflare/pint</a></p>
    <div>
      <h3>Putting it all together</h3>
      <a href="#putting-it-all-together">
        
      </a>
    </div>
    <p>Let’s see how we can use pint to validate our rules as we work on them.</p><p>We can begin by creating a file called “rules.yml” and adding both recording rules there.</p><p>The goal is to write new rules that we want to add to Prometheus, but before we actually add those, we want pint to validate it all for us.</p>
            <pre><code>groups:
- name: Demo recording rules
  rules:
  - record: job:http_requests_total:rate2m
    expr: sum(rate(http_requests_total[2m])) without(method, status, instance)

  - record: job:http_requests_status500:rate2m
    expr: sum(rate(http_requests_total{status="500"}[2m]) without(method, status, instance)</code></pre>
            <p>Next we’ll download the latest version of pint from <a href="https://github.com/cloudflare/pint/releases">GitHub</a> and run check our rules.</p>
            <pre><code>$ pint lint rules.yml 
level=info msg="File parsed" path=rules.yml rules=2
rules.yml:8: syntax error: unclosed left parenthesis (promql/syntax)
    expr: sum(rate(http_requests_total{status="500"}[2m]) without(method, status, instance)

level=info msg="Problems found" Fatal=1
level=fatal msg="Execution completed with error(s)" error="problems found"</code></pre>
            <p>Whoops, we have “sum(rate(...)” and so we’re missing one of the closing brackets. Let’s fix that and try again.</p>
            <pre><code>groups:
- name: Demo recording rules
  rules:
  - record: job:http_requests_total:rate2m
    expr: sum(rate(http_requests_total[2m])) without(method, status, instance)

  - record: job:http_requests_status500:rate2m
    expr: sum(rate(http_requests_total{status="500"}[2m])) without(method, status, instance)</code></pre>
            
            <pre><code>$ pint lint rules.yml 
level=info msg="File parsed" path=rules.yml rules=2</code></pre>
            <p>Our rule now passes the most basic checks, so we know it’s valid. But to know if it works with a real Prometheus server we need to tell pint how to talk to Prometheus. For that we’ll need a config file that defines a Prometheus server we test our rule against, it should be the same server we’re planning to deploy our rule to. Here we’ll be using a test instance running on localhost. Let’s create a “pint.hcl” file and define our Prometheus server there:</p>
            <pre><code>prometheus "prom1" {
  uri     = "http://localhost:9090"
  timeout = "1m"
}</code></pre>
            <p>Now we can re-run our check using this configuration file:</p>
            <pre><code>$ pint -c pint.hcl lint rules.yml 
level=info msg="Loading configuration file" path=pint.hcl
level=info msg="File parsed" path=rules.yml rules=2
rules.yml:5: prometheus "prom1" at http://localhost:9090 didn't have any series for "http_requests_total" metric in the last 1w (promql/series)
    expr: sum(rate(http_requests_total[2m])) without(method, status, instance)

rules.yml:8: prometheus "prom1" at http://localhost:9090 didn't have any series for "http_requests_total" metric in the last 1w (promql/series)
    expr: sum(rate(http_requests_total{status="500"}[2m])) without(method, status, instance)

level=info msg="Problems found" Bug=2
level=fatal msg="Execution completed with error(s)" error="problems found"</code></pre>
            <p>Yikes! It’s a test Prometheus instance, and we forgot to collect any metrics from it.</p><p>Let’s fix that by starting our server locally on port 8080 and configuring Prometheus to collect metrics from it:</p>
            <pre><code>scrape_configs:
  - job_name: webserver
    static_configs:
      - targets: ['localhost:8080’]</code></pre>
            <p>Let’ re-run our checks once more:</p>
            <pre><code>$ pint -c pint.hcl lint rules.yml 
level=info msg="Loading configuration file" path=pint.hcl
level=info msg="File parsed" path=rules.yml rules=2</code></pre>
            <p>This time everything works!</p><p>Now let’s add our alerting rule to our file, so it now looks like this:</p>
            <pre><code>groups:
- name: Demo recording rules
  rules:
  - record: job:http_requests_total:rate2m
    expr: sum(rate(http_requests_total[2m])) without(method, status, instance)

  - record: job:http_requests_status500:rate2m
    expr: sum(rate(http_requests_total{status="500"}[2m])) without(method, status, instance)

- name: Demo alerting rules
  rules:
  - alert: Serving HTTP 500 errors
    expr: job:http_requests_status500:rate2m / job:http_requests_total:rate2m &gt; 0.01</code></pre>
            <p>And let’s re-run pint once again:</p>
            <pre><code>$ pint -c pint.hcl lint rules.yml 
level=info msg="Loading configuration file" path=pint.hcl
level=info msg="File parsed" path=rules.yml rules=3
rules.yml:13: prometheus "prom1" at http://localhost:9090 didn't have any series for "job:http_requests_status500:rate2m" metric in the last 1w but found recording rule that generates it, skipping further checks (promql/series)
    expr: job:http_requests_status500:rate2m / job:http_requests_total:rate2m &gt; 0.01

rules.yml:13: prometheus "prom1" at http://localhost:9090 didn't have any series for "job:http_requests_total:rate2m" metric in the last 1w but found recording rule that generates it, skipping further checks (promql/series)
    expr: job:http_requests_status500:rate2m / job:http_requests_total:rate2m &gt; 0.01

level=info msg="Problems found" Information=2</code></pre>
            <p>It all works according to pint, and so we now can safely deploy our new rules file to Prometheus.</p><p>Notice that pint recognised that both metrics used in our alert come from recording rules, which aren’t yet added to Prometheus, so there’s no point querying Prometheus to verify if they exist there.</p><p>Now what happens if we deploy a new version of our server that renames the “status” label to something else, like “code”?</p>
            <pre><code>$ pint -c pint.hcl lint rules.yml 
level=info msg="Loading configuration file" path=pint.hcl
level=info msg="File parsed" path=rules.yml rules=3
rules.yml:8: prometheus "prom1" at http://localhost:9090 has "http_requests_total" metric but there are no series with "status" label in the last 1w (promql/series)
    expr: sum(rate(http_requests_total{status="500"}[2m])) without(method, status, instance)

rules.yml:13: prometheus "prom1" at http://localhost:9090 didn't have any series for "job:http_requests_status500:rate2m" metric in the last 1w but found recording rule that generates it, skipping further checks (promql/series)
    expr: job:http_requests_status500:rate2m / job:http_requests_total:rate2m &gt; 0.01

level=info msg="Problems found" Bug=1 Information=1
level=fatal msg="Execution completed with error(s)" error="problems found"</code></pre>
            <p>Luckily pint will notice this and report it, so we can adopt our rule to match the new name.</p><p>But what if that happens after we deploy our rule? For that we can use the “pint watch” command that runs pint as a daemon periodically checking all rules.</p><p>Please note that validating all metrics used in a query will eventually produce some false positives. In our example metrics with status=”500” label might not be exported by our server until there’s at least one request ending in HTTP 500 error.</p><p>The <a href="https://cloudflare.github.io/pint/checks/promql/series.html">promql/series</a> check responsible for validating presence of all metrics has some documentation on how to deal with this problem. In most cases you’ll want to add a comment that instructs pint to ignore some missing metrics entirely or stop checking label values (only check if there’s “status” label present, without checking if there are time series with status=”500”).</p>
    <div>
      <h3>Summary</h3>
      <a href="#summary">
        
      </a>
    </div>
    <p>Prometheus metrics don’t follow any strict schema, whatever services expose will be collected. At the same time a lot of problems with queries hide behind empty results, which makes noticing these problems non-trivial.</p><p>We use pint to find such problems and report them to engineers, so that our global network is always monitored correctly, and we have confidence that lack of alerts proves how reliable our infrastructure is.</p> ]]></content:encoded>
            <category><![CDATA[Monitoring]]></category>
            <category><![CDATA[Prometheus]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <guid isPermaLink="false">YZO15EI2Aw87tTU31VtF0</guid>
            <dc:creator>Lukasz Mierzwa</dc:creator>
        </item>
        <item>
            <title><![CDATA[Expanding the Cloudflare Workers Observability Ecosystem]]></title>
            <link>https://blog.cloudflare.com/observability-ecosystem/</link>
            <pubDate>Tue, 13 Apr 2021 13:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare adds Data Dog, Honeycomb, New Relic, Sentry, Splunk, and Sumologic as observability partners to the Cloudflare Workers Ecosystem ]]></description>
            <content:encoded><![CDATA[ <p></p><p>One of the themes of Developer Week is “it takes a village”, and <a href="https://www.cloudflare.com/learning/performance/what-is-observability/">observability</a> is one area where that is especially true. Cloudflare Workers lets you quickly write code that is infinitely scalable — no availability regions, no scaling policies. Your code runs in every one of our data centers by default: <b>region Earth,</b> as we like to say. While fast time to market and effortless scale are amazing benefits, seasoned developers know that as soon as your code is in the wild… <i>stuff</i> happens, and you need the tools in place to investigate, diagnose, fix and monitor those issues.</p><p>Today we’re delighted to add to our existing analytics partners. We’re announcing new partnerships with six observability-focused companies that are deeply integrated into the Cloudflare Workers ecosystem. We’re confident these partnerships will provide immediate value in building the operational muscle to maintain and make your next generation of applications fast, secure and bullet-proof in production.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3okZhOLa6mCLgd9sVdimJV/fc15fb727c3958ec87d8ac778ffad8b1/Screenshot-2021-04-12-at-18.23.19.png" />
            
            </figure><p><code>console.log(`Got request. Extracted name=${name}. Saving…`);</code></p><p>Cloudflare <a href="https://developers.cloudflare.com/workers/get-started/guide#2-install-the-workers-cli">wrangler</a> gives you the ability to generate, configure, build, preview and publish your projects, from the comfort of your dev environment. Writing code in your favorite IDE with a fully-fledged CLI tool that also allows you to simulate how your code will run on the edge is a delightful developer experience and one I personally love.</p><p>If you’re like me, you’ll start out your app with console.log statements. <a href="https://developers.cloudflare.com/workers/cli-wrangler/commands#dev">wrangler dev</a> and <a href="https://developers.cloudflare.com/workers/cli-wrangler/commands#tail">wrangler tail</a> both make it incredibly easy to get visibility into your code during dev and test, but for robust applications, you need more — much more. Things like correlating client and server side event data, seeing context around issues, version awareness and data visualization are what allows DevOps teams to create truly robust applications and make customers happy. The great news is — it’s easy to go from <code>console.log</code> to a code or systems monitoring solution with our partners <a href="http://sentry.io/welcome"><b>Sentry</b></a> and <a href="https://newrelic.com/"><b>New Relic</b></a>.</p><p>Sentry enables monitoring application code health. From error tracking to performance monitoring, developers can see issues that really matter, solve them more quickly, and learn continuously about their applications — from frontend to backend.</p><p><a href="https://www.npmjs.com/package/toucan-js">Toucan-js</a>, courtesy of Cloudflare’s very own Robert Cepa, is a reliable Sentry client for Cloudflare Workers and it’s an open-source npm module. It makes it easy to convert basic logging into full application monitoring. A simple <code>npm install toucan-js</code> and a couple of lines of boilerplate setup allow you convert those console.logs into a streaming source of client-side events that will be rendered for analysis in Sentry. Additionally, the distributed nature of serverless means developers need to think about <a href="https://developers.cloudflare.com/workers/learning/how-workers-works#distributed-execution">where and how they can manage state</a>. Toucan-js abstracts that away and allows simple log statements like:</p>
            <pre><code>sentry.addBreadcrumb({
    message: "Got request. Extracted name=${name}. Saving…",
    category: "log"
});</code></pre>
            <p>to be visualized in <a href="http://sentry.io/for/serverless">Sentry</a> as a user journey with filters, times, versioning and more, allowing you to understand what events led to the errors.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1GPKEs7UewznHt3bGyccSj/a399411af7697d2ab3bf56e17cc0406c/image2-7.png" />
            
            </figure><p>New Relic is a popular observability platform, particularly with Enterprises offering a Telemetry Data Platform, Full-Stack observability and Applied Intelligence. While there isn't (yet!) a specific npm package for NewRelic and Cloudflare Workers, the combination of NewRelic’s <a href="https://docs.newrelic.com/docs/logs/log-management/log-api/introduction-log-api/">https log endpoint</a> and Cloudflare Workers <a href="https://developers.cloudflare.com/workers/learning/fetch-event-lifecycle#waituntil">event.waitUntil()</a> means you can very easily instrument your application with NewRelic, without blocking the request and thus not impacting performance.</p>
            <pre><code>let url = "https://log-api.newrelic.com/log/v1";
let init = {
	method: "POST", 
	headers: {"content-type":"application/json"}, 
	body: JSON.stringify(payload)
};
event.waitUntil(fetch(url, init));</code></pre>
            <p>Like Sentry, those logs and events are then available for analysis in the NewRelicOne platform. Cloudflare uses both Sentry and New Relic for exactly the reasons outlined above, and I’m delighted to welcome them to our Developer Ecosystem as Observability Partners.</p><p><a href="https://newrelic.com/platform?wvideo=itkaxutw1r target=_blank"><img src="https://embed-fastly.wistia.com/deliveries/036304a3ca118a002c1f9b34e2de8529.jpg?image_play_button_size=2x&amp;image_crop_resized=960x540&amp;image_play_button=1&amp;image_play_button_color=008c99e0" /></a>
<a href="https://newrelic.com/platform?wvideo=itkaxutw1r target=_blank">New Relic One | New Relic</a></p><blockquote><p>Monitoring your Cloudflare Workers serverless environment with New Relic lets you deliver serverless apps with confidence by rapidly identifying when something goes wrong and quickly pinpointing the problem—without wading through millions of invocation logs. Monitor, visualize, troubleshoot, and alert on all your Workers apps in a single experience.-- <b>Raj Ramanujam, Vice President, Alliances and Channels, New Relic.</b></p></blockquote><blockquote><p>With Cloudflare Workers and Sentry, software teams immediately have the tools and information to solve issues and learn continuously about their code health instead of worrying about systems and resources. We’re thrilled to partner with Cloudflare on building technologies that make it easy for developers to deploy with confidence.-- <b>Elain Szu, Vice President of Marketing, Sentry.</b></p></blockquote>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5nZbt0OxrYEfEyeMMarvTG/5ac46bcadbf1cb42401519d9df1ddbc7/Screenshot-2021-04-12-at-18.34.08.png" />
            
            </figure><p>Developers are not the only part of an organization that need to observe all aspects of their applications in production. As organizations grow and the sophistication of their infrastructure monitoring and security systems grow, they typically implement observability platforms, which provide overall visibility into the entire infrastructure and the ability to alert on anomalies — not just individual applications, appliances, hardware or network. To achieve that goal, observability platforms must ingest as much data as possible. Cloudflare already <a href="https://www.cloudflare.com/partners/analytics/">partners</a> with Datadog, Sumo Logic and Splunk — this allows security and operations teams to ingest <a href="https://developers.cloudflare.com/logs/analytics-integrations">HTTP logs</a> from the network edge along with origin logs and many other sources of data.</p><p>Since that announcement, specific <a href="https://developers.cloudflare.com/logs/log-fields">Cloudflare Workers</a> fields such as WorkerCPUTime, WorkerStatus, WorkerSubrequest, and WorkerSubrequestCount have been added to offer out-of-the-box visibility to Cloudflare Workers execution. Of course, since the value of observability platforms is about whole-of-infrastructure visibility, ideally we want not just execution logs, but the <i>application</i> logs from our systems, similar to the examples in the section above.</p><p>Fortunately, our partners all offer simple HTTP interfaces into their ingestion engines. Check out <b>Datadog</b>’s <a href="https://docs.datadoghq.com/getting_started/api/">HTTP API</a>, <b>Splunk</b>’s <a href="https://docs.splunk.com/Documentation/Splunk/8.1.3/RESTREF/RESTprolog">REST API</a> and <b>SumoLogic</b>’s <a href="https://help.sumologic.com/03Send-Data/Sources/02Sources-for-Hosted-Collectors/HTTP-Source">HTTP Logs and Metric Source</a> for step-by-step instructions on how to easily ingest your Cloudflare Workers logs. Besides getting on your CISO’s good side, if your organization has a Detection and Response team, they’ll be able to help you ensure your Cloudflare Workers application is integrated and <a href="https://www.cloudflare.com/application-services/solutions/app-performance-monitoring/">monitored</a> as a first-class citizen in your organization's security apparatus. For example, the screenshot below shows Datadog surfacing a security signal detecting malicious activity in Cloudflare HTTP logs based on threat intel feeds.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6ivjoT4S5JIVJDMGsfAJRX/6cab40955d6980af3ed37ef31f24f480/image10.png" />
            
            </figure><blockquote><p>Maintaining a strong security posture means ensuring every part of your toolchain is being monitored - from the datacenter/VPC, to your edge network, all the way to your end users. With Datadog’s partnership with Cloudflare, you get edge computing logs alongside the rest of your application stack’s telemetry - giving you an end to end view of your application’s health, performance and security.- <b>Michael Gerstenhaber, Sr. Director, Datadog.</b></p></blockquote><blockquote><p>Teams using Cloudflare Workers with Splunk Observability get full-stack visibility and contextualized insights from metrics, traces and logs across all of their infrastructure, applications and user transactions in real-time and at any scale. With Splunk Observability, IT and DevOps teams now have a seamless and analytics-powered workflow across monitoring, troubleshooting, incident response and optimization. We're excited to partner with Cloudflare to help developers and operations teams slice through the complexity of modern applications and ship code more quickly and reliably.- <b>Jeff Lo, Director of Product Marketing, Splunk</b></p></blockquote><blockquote><p>Reduce downtime and solve customer-impacting issues faster with an integrated observability platform for all of your Cloudflare data, including its Workers serverless platform. By using Cloudflare Workers with Sumo Logic, customers can seamlessly correlate system issues measured by performance monitoring, gain deep visibility provided by logging, and monitor user experience provided by tracing and transaction analytics.- <b>Abelardo Gonzalez, Director of Product Marketing at Sumo Logic</b></p></blockquote>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5pFJW7FkhI2LRQH1c2nAYr/d275d2707bd89aaa52d6b41ad7b482ab/Screenshot-2021-04-12-at-18.34.34.png" />
            
            </figure><p><a href="https://honeycomb.io">Honeycomb.io</a> is an observability platform that gives you high level data regarding how your services are performing, combined with the ability to drill down all the way to the individual user level to troubleshoot issues without having to hop across different data types to piece the data together. Traditionally, when debugging production incidents with dashboards and metrics, it is difficult to drill down beyond aggregate measures. For example, a graph with error rates can’t tell you which exact customers are experiencing the most errors. Logs show you the raw error data, but it's hard to see the big picture unless you know exactly where to look.</p><p>Honeycomb’s event-based model for application telemetry and powerful query engine make it possible to slice your data across billions of rows and thousands of fields to find hidden patterns. The <a href="https://docs.honeycomb.io/working-with-your-data/bubbleup/">BubbleUp</a> feature also helps you automatically detect the differences between “good” sets and “bad” sets of events. The ability to quickly get results means teams can resolve incidents faster and figure out where to make system optimizations.</p><p>The Honeycomb <a href="https://www.npmjs.com/package/@cloudflare/workers-honeycomb-logger">beta npm module</a> for Cloudflare Workers observability is unique, in that it has first-class knowledge of the concept of sub-requests that are a core part of many Worker applications, and this is surfaced directly in the platform. We can’t wait to see the GA version and more innovation around observability for Cloudflare Workers.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2WLS74Q36uCQ31dNtZx0BZ/26ebd003243d6b680578d067c796b988/image6-2.png" />
            
            </figure><blockquote><p>Honeycomb is excited to partner with Cloudflare as they build an ecosystem of tools that support the full lifecycle of delivering successful apps. Writing and deploying code is only part of the equation. Understanding how that code performs and behaves when it is in the hands of users also determines success. Cloudflare and Honeycomb together are shining the light of observability all the way to the edge, which helps technical teams build and operate better apps.- <b>Charity Majors, Honeycomb CTO &amp; cofounder</b>.</p></blockquote>
    <div>
      <h2>Summary</h2>
      <a href="#summary">
        
      </a>
    </div>
    <p>Developers love writing code on Cloudflare Workers. The speed, scale, and developer tooling all combine to make it a delightful experience. Our observability partner announcements today extend that experience from development to operations. Getting real-time, contextual insights into what your code is doing, how it’s performing and any errors it’s generating is at the core of shipping the next generation of transformative apps. Our serverless platform takes care of getting your code right next to your users, and our observability partners make sure that that code does exactly what you designed it to do.</p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Partners]]></category>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Product News]]></category>
            <guid isPermaLink="false">2eoGhYjqHIK0pL7FjrBP4s</guid>
            <dc:creator>Steven Pack</dc:creator>
            <dc:creator>Erwin van der Koogh</dc:creator>
        </item>
        <item>
            <title><![CDATA[Soar: Simulation for Observability, reliAbility, and secuRity]]></title>
            <link>https://blog.cloudflare.com/soar-simulation-for-observability-reliability-and-security/</link>
            <pubDate>Thu, 14 Jan 2021 12:00:00 GMT</pubDate>
            <description><![CDATA[ In this article, we will discuss one of the techniques we use to fight such software complexity: simulations. Simulations are basically system tests that run with synthesized customer traffic and applications. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Serving more than approximately 25 million Internet properties is not an easy thing, and neither is serving 20 million requests per second on average. At Cloudflare, we achieve this by running a homogeneous edge environment: almost every Cloudflare server runs all Cloudflare products.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4mK5DnfRzmZ6cLXBUCAxPm/b8d1c5b1a466b1efb0a1c821ebae51fb/image3-2.png" />
            
            </figure><p>Figure 1. Typical Cloudflare service model: when an end-user (a browser/mobile/etc) visits an origin (a Cloudflare customer), traffic is routed via the Internet to the Cloudflare edge network, and Cloudflare communicates with the origin servers from that point.</p><p>As we offer more and more products and enjoy the benefit of horizontal scalability, our edge stack continues to grow in complexity. Originally, we only operated at the application layer with our CDN service and DoS protection. Then we launched transport layer products, such as Spectrum and Argo. Now we have further expanded our footprint into the IP layer and physical link with Magic Transit. They all run on every machine we have. The work of our engineers enables our products to evolve at a fast pace, and to serve our customers better.</p><p>However, such software complexity presents a sheer challenge to operation: the more changes you make, the more likely it is that something is going to break. And we don’t tolerate any of our mistakes slipping into the production environment and affecting our customers.</p><p>In this article, we will discuss one of the techniques we use to fight such software complexity: simulations. Simulations are basically system tests that run with synthesized customer traffic and applications. We would like to introduce our simulation system, SOAR, i.e. Simulation for Observability, reliAbility, and secuRity.</p><p>What is SOAR? Simply put, it’s a data center built specifically for simulations. It runs the same software stack as our production data centers, but without any production traffic. Within SOAR, there are end-user servers, product servers, and origin servers (Figure 2). The product servers behave exactly the same as servers in our production edge network, and they are the targets that we want to test. End-user servers and origin servers run applications that try to simulate customer behaviors. The simplest case is to run network benchmarks through product servers, in order to evaluate how effective the corresponding products are. Instead of sending test traffic over the Internet, everything happens in the LAN environment that Cloudflare tightly controls. This gives us great flexibility in testing network features such as bring-your-own-IP (BYOIP) products.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3h4OZxnBtAiC8ZTN5YRbaI/e67049ce7ebdb68b3724bc0e2a3903d0/image5-1.png" />
            
            </figure><p>Figure 2. SOAR architectural view: by simulating the end users and origin on Cloudflare servers in the same VLAN, we can focus on examining the problems occurring in our edge network.</p><p>To demonstrate how this works, let’s go through a running example using Magic Transit.</p><p><a href="https://www.cloudflare.com/magic-transit/">Magic Transit</a> is a product that provides IP layer protection and acceleration. One of the main functions of Magic Transit is to shield customers from DDoS attacks.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7tEzWft9k3pSOjMFuI19Pu/812c03938859f751957ceb4a077ed4e9/image1-4.png" />
            
            </figure><p>Figure 3. Magic Transit workflow in a nutshell</p><p>Customers bring their IP ranges to advertise from Cloudflare edge. When attackers initiate a DoS attack, Cloudflare absorbs all the customer’s traffic, drops the attack traffic, and encapsulates clean traffic to customers. For this product, operational concerns are multifold, and here are some examples:</p><ul><li><p>Have we properly configured our data plane so that traffic can reach customers? Is BGP ready? Are ECMP routes programmed correctly? Are health probes working correctly?</p></li><li><p>Do we have any untested corner cases that only manifest with a large amount of traffic?</p></li><li><p>Is our DoS system dropping malicious traffic as intended? How effective</p></li><li><p>Will any other team’s changes break Magic Transit as our edge keeps growing?</p></li></ul><p>To ease these concerns, we run <b>simulated customers</b> with SOAR. Yes, simulated, not real. For example, assume a customer Alice onboarded an IP range 192.0.2.0/24 to Magic Transit. To simulate this customer, in SOAR we can configure a test application (e.g. iperf server) on one origin server to represent Alice’s service. We also bring up a product server to run Magic Transit. This product server will filter traffic toward <code>a.b.c.0/24</code>, and GRE encapsulated cleansed traffic to Alice’s specified GRE endpoint. To make it work, we also add routing rules to forward packets destined to <code>192.0.2.0/24</code> to go through the product server above. Similarly, we add routing rules to deliver GRE packets from the product server to the origin servers. Lastly, we start running test clients as eyeballs to evaluate the functional correctness, performance metrics, and resource usage.</p><p>For the rest of this article, we will talk about the design and implementation of this simulation system, as well as several real cases in which it helped us catch problems early or avoid problems altogether.</p>
    <div>
      <h2>System Design</h2>
      <a href="#system-design">
        
      </a>
    </div>
    
    <div>
      <h3>From performance simulation to config simulation</h3>
      <a href="#from-performance-simulation-to-config-simulation">
        
      </a>
    </div>
    <p>Before we created SOAR, we had already built a “performance simulation” for our layer 7 services. It is based on <a href="https://www.saltstack.com/">SaltStack</a>, our configuration management software. All the simulation cases are system test cases against Cloudflare-owned HTTP sites. These cases are statically configured and run non-stop. Each simulation case produces multiple Prometheus metrics such as requests per second and latency. We monitor these metrics daily on our Grafana dashboard.</p><p>While this simulation system is very useful, it becomes less efficient as we have more and more simulation cases and products to run and analyze.</p>
    <div>
      <h3>Isolation and Coordination</h3>
      <a href="#isolation-and-coordination">
        
      </a>
    </div>
    <p>As more types of simulations are onboarded, it is critical to ensure each simulation runs in a clean environment, and all tasks of a simulation run together. This challenge is specific to providers like Cloudflare, whose products are not virtualized because we want to maximize our edge performance. As a result, we have to isolate simulations and clean up by ourselves; otherwise, different simulations may cross-affect each other.</p><p>For example, for Magic Transit simulations, we need to create a GRE tunnel on an origin server and set up several routes on all three servers, to make sure simulated traffic can flow as real Magic Transit customers would. We cannot leave these routes after the simulation finishes, or there might be a conflict. We once ran into a situation where different simulations required different source IP addresses to the same destination. In our original performance simulation environment, we will have to modify simulation applications to avoid these conflicts. This approach is less desirable as different engineering teams have to pay attention to other teams' code.</p><p>Moreover, the performance simulation addresses only the most basic system test cases: a client sends traffic to a server and measures the performance characteristics like request per second and latency quantile. But the situation we want to simulate and validate in our production environment can be far more complex.</p><p>In our previous example of Magic Transit, customers can configure complicated network topology. Figure 4 is one simplified case. Let’s say Alice establishes four GRE tunnels with Cloudflare; two connect to her data center 1, and traffic will be ECMP hashed between these two tunnels. Similarly, she also establishes another two tunnels to her data center 2 with similar ECMP settings. She would like to see traffic hit data center 1 normally and fail over to data center 2 when tunnels 1 and 2 are both down.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2AMPDDJYHhwhKDMopAN1Ek/a9da8d83fdec73c7e00f8afd9b9c9dc8/image7.png" />
            
            </figure><p>Figure 4. The customer configured Magic Transit to establish four tunnels to her two data centers. Traffic to data center 1 is hashed between tunnel 1 and 2 using ECMP, and traffic to data center 2 is hashed between tunnel 3 and 4. Data center 1 is the primary one, and traffic should failover to data center 2 if tunnels 1 and 2 are both down. Note the number “2” is purely symbolic, as real customers can have more than just 2 data centers, or 2 paths per ECMP route.</p><p>In order to examine the effectiveness of route failover, we would need to inject errors on the product servers only after the traffic on the eyeball server has started. But this type of coordination is not achievable with statically defined simulations.</p>
    <div>
      <h3>Engineer friendliness and Interactiveness</h3>
      <a href="#engineer-friendliness-and-interactiveness">
        
      </a>
    </div>
    <p>Our performance simulation is not engineer-friendly. Not just because it is all statically configured in SaltStack (most engineering teams do not possess Salt expertise), but it is also not integrated with an engineer's daily routine. Can engineers trigger a simulation on every branch build? Can simulation results get back in time to inform that a performance problem occurs? The answer is no, it is not possible with our static configuration. An engineer can submit a Salt PR to config a new simulation, but this simulation may have to wait for several hours because all other unfinished simulations need to complete first (recall it is just a static loop). Nor can an engineer add a test to the team's repository to run on every build, as it needs to reside in the SRE-managed Salt repository, making it unmanageable as the number of simulations grows.</p>
    <div>
      <h2>The Architecture</h2>
      <a href="#the-architecture">
        
      </a>
    </div>
    <p>To address the above limitations, we designed SOAR.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2fbCpF5WSWm0quf4paQUv6/582ce75160c4d4f9bc31fed0a5a02ad7/image8.png" />
            
            </figure><p>Figure 5. The Architecture of SOAR</p><p>The architecture is a performance simulation structure, extended. We created an internal coordinator service to:</p><ol><li><p>Interface with engineers, so they could now submit one-time simulations from their laptop or within the building pipeline, or view previous execution results.</p></li><li><p>Dispatch and coordinate simulation tasks to each simulation server, where a simulation agent executes these tasks. The coordinator will isolate simulations properly so none of them contends on system resources. For example, the simplest policy we implemented is to never run two simulations on the same server at the same time.</p></li></ol><p>The coordinator is secured by <a href="https://www.cloudflare.com/teams-access/">Cloudflare Access</a>, so that only employees can visit this service. The coordinator will serve two types of simulations: one-time simulation to be run in an ad-hoc way and mainly on a per pull request manner, to ease development testing. It’s also callable from our CI system. Another type is repetitive simulations that are stored in the coordinator’s persistent storage. These simulations serve daily monitoring purposes and will be executed periodically.</p><p>Each simulation server runs a simulation agent. This agent will execute two types of tasks received from the coordinator: system tasks and user tasks. System tasks change the system-wide configurations and will be reverted after each simulation terminates. These will include but are not limited to route change, link change, address change, ipset change and iptables change.</p><p>User tasks, on the other hand, run benchmarks that we are interested in evaluating, and will be terminated if it exceeds an allocated execution budget. Each user task is isolated in a cgroup, and the agent will ensure all user tasks are executed with dedicated resources. The generic runtime metrics of user tasks is monitored by <a href="https://github.com/google/cadvisor">Cadvisor</a> and sent to <a href="https://prometheus.io/">Prometheus</a> and Alert Manager. A user task can export its own metrics to Prometheus as well.</p><p>For SOAR to run reliably, we provisioned a dedicated environment that enforces the same settings for the production environment and operates it as a production system: hardened security, standard alerts on watch, no engineer access except approved tools. This to a large extent allows us to run simulations as a stable source of anomaly detection.</p>
    <div>
      <h2>Simulating with customer-specific configuration</h2>
      <a href="#simulating-with-customer-specific-configuration">
        
      </a>
    </div>
    <p>An important ability of SOAR is to simulate for a specific customer. This will provide the customer with more guarantees that both their configurations and our services are battle-tested with traffic before they go live. It can also be used to bisect problems during a customer escalation, helping customer support to rule out unrelated factors more easily.</p><p>All of our edge servers know how to dispatch an incoming customer packet. This factor greatly reduces difficulties in simulating a specific customer. What we need to do in simulation is to mock routing and domain translation on simulated eyeballs and origins, so that they will correctly send traffic to designated product servers. And the problem is solved—magic!</p><p>The actual implementation is also straightforward: as simulations run in a LAN environment, we have tight control over how to route packets (servers are on the same broadcast domain). Any eyeball, origin, or product server can just use a direct routing rule and a static DNS entry in <code>/etc/hosts</code> to redirect packets to go to the correct destination.</p><p>Running a simulation this way allows us to separate customer configuration management from the simulation service: our products will manage it, so any time a customer configuration is changed, they will already reflect in simulations without special care.</p>
    <div>
      <h2>Implementation and Integration</h2>
      <a href="#implementation-and-integration">
        
      </a>
    </div>
    <p>All SOAR components are built with Golang from scratch on Linux servers. It took three engineer-months to build the prototype and onboard the first engineering use case. While there are other mature platforms for job scheduling, task isolation, and monitoring, building our own allows us to better absorb new requirements from engineering teams, which is much easier and quicker than an external dependency.</p><p>In addition, we are currently integrating the simulation service into our release pipeline. Cloudflare built a release manager internally to schedule product version changes in controlled steps: a new product is first deployed into dogfooding data centers. After the product has been trialed by Cloudflare employees, it moves to several canary data centers with limited customer traffic. If nothing bad happens, in an hour or so, it starts to land in larger data centers spread across three tiers. Tier-3 will receive the changes an hour earlier than tier-2, and the same applies to tier-2 and tier-1. This ensures a product would be battle-tested enough before it can serve the majority of Cloudflare customers.</p><p>Now we move this further by adding a simulation step even before dogfooding. In this step, all changes are deployed into the simulation environment, and engineering teams will configure which simulations to run. Dogfooding starts only when there is no performance regression or functional breakage. Here performance regression is based on Prometheus metrics, where each engineering team can define their own Prometheus query to interpret the performance results. All configured simulations will run periodically to detect problems in releases that do not tie to a specific product, e.g. a Linux kernel upgrade. SREs receive notifications asynchronously if any issue is discovered.</p>
    <div>
      <h2>Simulations at Cloudflare: Case Studies</h2>
      <a href="#simulations-at-cloudflare-case-studies">
        
      </a>
    </div>
    <p>Simulations are very useful inside Cloudflare. Let’s see some real experiences we had in the past.</p>
    <div>
      <h3>Detecting an anomaly on data center specific releases</h3>
      <a href="#detecting-an-anomaly-on-data-center-specific-releases">
        
      </a>
    </div>
    <p>In our Magic Transit example, the engineering team was about to release physical network interconnect (PNI) support. With PNI, customer data centers physically peer with Cloudflare routers.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3CGGm79waYwSswyZgBOI8q/98be9fdd333b02e35feb211b8c72bfb2/service---3-.png" />
            
            </figure><p>Figure 6. The Magic Transit service flow for a customer without PNI support. Any Cloudflare data center can receive eyeball traffic. After mitigating a DoS attack traffic, valid traffic is encapsulated to the customer data center from any of the handling Cloudflare data centers.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2cOrt8HDF3v551i4digjDS/2530583007d9932e6f5aef9d83388e3c/image6.png" />
            
            </figure><p>Figure 7. Magic Transit with PNI support. Traffic received from any data center will be moved to the PNI data center that the customer connects to. The PNI data center becomes a choke point.‌‌</p><p>However, this PNI functionality introduces a problem in our normal release process. However, PNI data centers are typically different from our dogfooding and canary data centers. If we still release with the normal process, then two critical phases are skipped. And what’s worse, the PNI data center could be a choke point in front of that customer’s traffic. If the PNI data center is taken down, no other data center can replace its role.</p><p>SOAR in this case is an important utility to help. The basic idea is to configure a server with PNI information. This server will act as if it runs in a PNI data center. Then we run simulated eyeball and origin to examine if there is any functional breakage:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1lzWSFrQPQMkzRODGuqHtV/e3fadbafd4bbf78a953192bb78567e2d/image4-3.png" />
            
            </figure><p>Figure 8. SOAR configures a server with PNI information and runs simulated eyeball and origin on this server. If a PNI related code release has a problem, then with proper simulation traffic it will be caught before rolling into production.</p><p>With such simulation capability, we were able to detect several problems early on and before releasing. For example, we caught a problem that impacts checksum offloading, which could encapsulate TCP packets with the wrong inner checksum and cause the packets to be dropped at the origin side. This problem does not exist in our virtualized testing environment and integration tests; it only happens when production hardware comes into play. We then use this simulation as a success indicator to test various fixes until we get the packet flow running normally again.</p>
    <div>
      <h3>Continuously monitor performance on the edge stack</h3>
      <a href="#continuously-monitor-performance-on-the-edge-stack">
        
      </a>
    </div>
    <p>When a team configures a simulation, it runs on the same stack where all other teams run their products as well. This means when a simulation starts to show unhealthy results, it may or may not directly relate to the product associated with that simulation.</p><p>But with continuous simulations, we will have more chances to detect issues before things go south, or at least it will serve as a hint to quickly react to emerging problems. In an example early this year, we noticed one of our performance simulation dashboards showed that some HTTP request throughput was dropping by 20%. After digging into the case, we found our bot detection system had made a change that affected related requests. Luckily enough we moved fast thanks to the hint from the simulation (and some other useful tools like Opentracing).</p><p>Our recent enhancement from just HTTP performance simulation to SOAR makes it even more useful for customers. This is because we are now able to simulate with customer-specific configurations, so we might expose customer-specific problems. We are still dogfooding this, and hopefully, we can deploy it to our customers soon.</p>
    <div>
      <h3>DoS Attacks as Simulations</h3>
      <a href="#dos-attacks-as-simulations">
        
      </a>
    </div>
    <p>When we started to develop Magic Transit, a question worth monitoring was how effective our mitigation pipeline is, and how to apply thresholds for different customers. For our new ACK flood mitigation system, flowtrackd, we onboarded its performance simulation cases together with tunable ACK flood. Combined with customer-specific configuration, this allows us to compare the throughput result under different volumes of attacks, and systematically tune our mitigation threshold.</p><p>Another important factor that we will be able to achieve with our “attack simulation” system is to mount attacks we have seen in the past, making sure the development of our mitigation pipelines won't ever pass on these known attacks to our customers.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>In this article, we introduced Cloudflare’s simulation system, SOAR. While simulation is not a new tool, we can use it to improve reliability, observability, and security. Our adoption of SOAR is still in its early stages, but we are pretty confident that, by fully leveraging simulations, we will push our quality of service to a new level.</p>
    <div>
      <h2>Watch it on Cloudflare TV</h2>
      <a href="#watch-it-on-cloudflare-tv">
        
      </a>
    </div>
    <div></div>
<p></p> ]]></content:encoded>
            <category><![CDATA[Observability]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <guid isPermaLink="false">2lZUB7Y0Xmrqgl7uqdcyYC</guid>
            <dc:creator>Yan Zhai</dc:creator>
        </item>
    </channel>
</rss>