
<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>Wed, 08 Apr 2026 01:37:01 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Code Mode: give agents an entire API in 1,000 tokens]]></title>
            <link>https://blog.cloudflare.com/code-mode-mcp/</link>
            <pubDate>Fri, 20 Feb 2026 14:00:00 GMT</pubDate>
            <description><![CDATA[ The Cloudflare API has over 2,500 endpoints. Exposing each one as an MCP tool would consume over 2 million tokens. With Code Mode, we collapsed all of it into two tools and roughly 1,000 tokens of context. ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://www.cloudflare.com/learning/ai/what-is-model-context-protocol-mcp/"><u>Model Context Protocol (MCP)</u></a> has become the standard way for AI agents to use external tools. But there is a tension at its core: agents need many tools to do useful work, yet every tool added fills the model's context window, leaving less room for the actual task. </p><p><a href="https://blog.cloudflare.com/code-mode/"><u>Code Mode</u></a> is a technique we first introduced for reducing context window usage during agent tool use. Instead of describing every operation as a separate tool, let the model write code against a typed SDK and execute the code safely in a <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/worker-loader/"><u>Dynamic Worker Loader</u></a>. The code acts as a compact plan. The model can explore tool operations, compose multiple calls, and return just the data it needs. Anthropic independently explored the same pattern in their <a href="https://www.anthropic.com/engineering/code-execution-with-mcp"><u>Code Execution with MCP</u></a> post.</p><p>Today we are introducing <a href="https://github.com/cloudflare/mcp"><u>a new MCP server</u></a> for the <a href="https://developers.cloudflare.com/api/"><u>entire Cloudflare API</u></a> — from <a href="https://developers.cloudflare.com/dns/"><u>DNS</u></a> and <a href="https://developers.cloudflare.com/cloudflare-one/"><u>Zero Trust</u></a> to <a href="https://workers.cloudflare.com/product/workers/"><u>Workers</u></a> and <a href="https://workers.cloudflare.com/product/r2/"><u>R2</u></a> — that uses Code Mode. With just two tools, search() and execute(), the server is able to provide access to the entire Cloudflare API over MCP, while consuming only around 1,000 tokens. The footprint stays fixed, no matter how many API endpoints exist.</p><p>For a large API like the Cloudflare API, Code Mode reduces the number of input tokens used by 99.9%. An equivalent MCP server without Code Mode would consume 1.17 million tokens — more than the entire context window of the most advanced foundation models.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7KqjQiI09KubtUSe9Dgf0N/6f37896084c7f34abca7dc36ab18d8e0/image2.png" />
          </figure><p><sup><i>Code mode savings vs native MCP, measured with </i></sup><a href="https://github.com/openai/tiktoken"><sup><i><u>tiktoken</u></i></sup></a><sup></sup></p><p>You can start using this new Cloudflare MCP server today. And we are also open-sourcing a new <a href="https://github.com/cloudflare/agents/tree/main/packages/codemode"><u>Code Mode SDK</u></a> in the <a href="https://github.com/cloudflare/agents"><u>Cloudflare Agents SDK</u></a>, so you can use the same approach in your own MCP servers and AI Agents.</p>
    <div>
      <h3>Server‑side Code Mode</h3>
      <a href="#server-side-code-mode">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/ir1KOZHIjVNyqdC9FSuZs/334456a711fb2b5fa612b3fc0b4adc48/images_BLOG-3184_2.png" />
          </figure><p>This new MCP server applies Code Mode server-side. Instead of thousands of tools, the server exports just two: <code>search()</code> and <code>execute()</code>. Both are powered by Code Mode. Here is the full tool surface area that gets loaded into the model context:</p>
            <pre><code>[
  {
    "name": "search",
    "description": "Search the Cloudflare OpenAPI spec. All $refs are pre-resolved inline.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "description": "JavaScript async arrow function to search the OpenAPI spec"
        }
      },
      "required": ["code"]
    }
  },
  {
    "name": "execute",
    "description": "Execute JavaScript code against the Cloudflare API.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "description": "JavaScript async arrow function to execute"
        }
      },
      "required": ["code"]
    }
  }
]
</code></pre>
            <p>To discover what it can do, the agent calls <code>search()</code>. It writes JavaScript against a typed representation of the OpenAPI spec. The agent can filter endpoints by product, path, tags, or any other metadata and narrow thousands of endpoints to the handful it needs. The full OpenAPI spec never enters the model context. The agent only interacts with it through code.</p><p>When the agent is ready to act, it calls <code>execute()</code>. The agent writes code that can make Cloudflare API requests, handle pagination, check responses, and chain operations together in a single execution. </p><p>Both tools run the generated code inside a <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/worker-loader/"><u>Dynamic Worker</u></a> isolate — a lightweight V8 sandbox with no file system, no environment variables to leak through prompt injection and external fetches disabled by default. Outbound requests can be explicitly controlled with outbound fetch handlers when needed.</p>
    <div>
      <h4>Example: Protecting an origin from DDoS attacks</h4>
      <a href="#example-protecting-an-origin-from-ddos-attacks">
        
      </a>
    </div>
    <p>Suppose a user tells their agent: "protect my origin from DDoS attacks." The agent's first step is to consult documentation. It might call the <a href="https://developers.cloudflare.com/agents/model-context-protocol/mcp-servers-for-cloudflare/"><u>Cloudflare Docs MCP Server</u></a>, use a <a href="https://github.com/cloudflare/skills"><u>Cloudflare Skill</u></a>, or search the web directly. From the docs it learns: put <a href="https://www.cloudflare.com/application-services/products/waf/"><u>Cloudflare WAF</u></a> and <a href="https://www.cloudflare.com/ddos/"><u>DDoS protection</u></a> rules in front of the origin.</p><p><b>Step 1: Search for the right endpoints
</b>The <code>search</code> tool gives the model a <code>spec</code> object: the full Cloudflare OpenAPI spec with all <code>$refs</code> pre-resolved. The model writes JavaScript against it. Here the agent looks for WAF and ruleset endpoints on a zone:</p>
            <pre><code>async () =&gt; {
  const results = [];
  for (const [path, methods] of Object.entries(spec.paths)) {
    if (path.includes('/zones/') &amp;&amp;
        (path.includes('firewall/waf') || path.includes('rulesets'))) {
      for (const [method, op] of Object.entries(methods)) {
        results.push({ method: method.toUpperCase(), path, summary: op.summary });
      }
    }
  }
  return results;
}
</code></pre>
            <p>The server runs this code in a Workers isolate and returns:</p>
            <pre><code>[
  { "method": "GET",    "path": "/zones/{zone_id}/firewall/waf/packages",              "summary": "List WAF packages" },
  { "method": "PATCH",  "path": "/zones/{zone_id}/firewall/waf/packages/{package_id}", "summary": "Update a WAF package" },
  { "method": "GET",    "path": "/zones/{zone_id}/firewall/waf/packages/{package_id}/rules", "summary": "List WAF rules" },
  { "method": "PATCH",  "path": "/zones/{zone_id}/firewall/waf/packages/{package_id}/rules/{rule_id}", "summary": "Update a WAF rule" },
  { "method": "GET",    "path": "/zones/{zone_id}/rulesets",                           "summary": "List zone rulesets" },
  { "method": "POST",   "path": "/zones/{zone_id}/rulesets",                           "summary": "Create a zone ruleset" },
  { "method": "GET",    "path": "/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint", "summary": "Get a zone entry point ruleset" },
  { "method": "PUT",    "path": "/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint", "summary": "Update a zone entry point ruleset" },
  { "method": "POST",   "path": "/zones/{zone_id}/rulesets/{ruleset_id}/rules",        "summary": "Create a zone ruleset rule" },
  { "method": "PATCH",  "path": "/zones/{zone_id}/rulesets/{ruleset_id}/rules/{rule_id}", "summary": "Update a zone ruleset rule" }
]
</code></pre>
            <p>The full Cloudflare API spec has over 2,500 endpoints. The model narrowed that to the WAF and ruleset endpoints it needs, without any of the spec entering the context window. </p><p>The model can also drill into a specific endpoint's schema before calling it. Here it inspects what phases are available on zone rulesets:</p>
            <pre><code>async () =&gt; {
  const op = spec.paths['/zones/{zone_id}/rulesets']?.get;
  const items = op?.responses?.['200']?.content?.['application/json']?.schema;
  // Walk the schema to find the phase enum
  const props = items?.allOf?.[1]?.properties?.result?.items?.allOf?.[1]?.properties;
  return { phases: props?.phase?.enum };
}

{
  "phases": [
    "ddos_l4", "ddos_l7",
    "http_request_firewall_custom", "http_request_firewall_managed",
    "http_response_firewall_managed", "http_ratelimit",
    "http_request_redirect", "http_request_transform",
    "magic_transit", "magic_transit_managed"
  ]
}
</code></pre>
            <p>The agent now knows the exact phases it needs: <code>ddos_l7 </code>for DDoS protection and <code>http_request_firewall_managed</code> for WAF.</p><p><b>Step 2: Act on the API
</b>The agent switches to using <code>execute</code>. The sandbox gets a <code>cloudflare.request()</code> client that can make authenticated calls to the Cloudflare API. First the agent checks what rulesets already exist on the zone:</p>
            <pre><code>async () =&gt; {
  const response = await cloudflare.request({
    method: "GET",
    path: `/zones/${zoneId}/rulesets`
  });
  return response.result.map(rs =&gt; ({
    name: rs.name, phase: rs.phase, kind: rs.kind
  }));
}

[
  { "name": "DDoS L7",          "phase": "ddos_l7",                        "kind": "managed" },
  { "name": "Cloudflare Managed","phase": "http_request_firewall_managed", "kind": "managed" },
  { "name": "Custom rules",     "phase": "http_request_firewall_custom",   "kind": "zone" }
]
</code></pre>
            <p>The agent sees that managed DDoS and WAF rulesets already exist. It can now chain calls to inspect their rules and update sensitivity levels in a single execution:</p>
            <pre><code>async () =&gt; {
  // Get the current DDoS L7 entrypoint ruleset
  const ddos = await cloudflare.request({
    method: "GET",
    path: `/zones/${zoneId}/rulesets/phases/ddos_l7/entrypoint`
  });

  // Get the WAF managed ruleset
  const waf = await cloudflare.request({
    method: "GET",
    path: `/zones/${zoneId}/rulesets/phases/http_request_firewall_managed/entrypoint`
  });
}
</code></pre>
            <p>This entire operation, from searching the spec and inspecting a schema to listing rulesets and fetching DDoS and WAF configurations, took four tool calls.</p>
    <div>
      <h3>The Cloudflare MCP server</h3>
      <a href="#the-cloudflare-mcp-server">
        
      </a>
    </div>
    <p>We started with MCP servers for individual products. Want an agent that manages DNS? Add the <a href="https://github.com/cloudflare/mcp-server-cloudflare/tree/main/apps/dns-analytics"><u>DNS MCP server</u></a>. Want Workers logs? Add the <a href="https://developers.cloudflare.com/agents/model-context-protocol/mcp-servers-for-cloudflare/"><u>Workers Observability MCP server</u></a>. Each server exported a fixed set of tools that mapped to API operations. This worked when the tool set was small, but the Cloudflare API has over 2,500 endpoints. No collection of hand-maintained servers could keep up.</p><p>The Cloudflare MCP server simplifies this. Two tools, roughly 1,000 tokens, and coverage of every endpoint in the API. When we add new products, the same <code>search()</code> and <code>execute()</code> code paths discover and call them — no new tool definitions, no new MCP servers. It even has support for the <a href="https://developers.cloudflare.com/analytics/graphql-api/"><u>GraphQL Analytics API</u></a>.</p><p>Our MCP server is built on the latest MCP specifications. It is OAuth 2.1 compliant, using <a href="https://github.com/cloudflare/workers-oauth-provider"><u>Workers OAuth Provider</u></a> to downscope the token to selected permissions approved by the user when connecting. The agent  only gets the capabilities the user explicitly granted. </p><p>For developers, this means you can use a simple agent loop and still give your agent access to the full Cloudflare API with built-in progressive capability discovery.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/60ZoSFdK6t6hR6DpAn6Bub/93b86239cedb06d7fb265859be7590e8/images_BLOG-3184_4.png" />
          </figure>
    <div>
      <h3>Comparing approaches to context reduction</h3>
      <a href="#comparing-approaches-to-context-reduction">
        
      </a>
    </div>
    <p>Several approaches have emerged to reduce how many tokens MCP tools consume:</p><p><b>Client-side Code Mode</b> was our first experiment. The model writes TypeScript against typed SDKs and runs it in a Dynamic Worker Loader on the client. The tradeoff is that it requires the agent to ship with secure sandbox access. Code Mode is implemented in <a href="https://block.github.io/goose/blog/2025/12/15/code-mode-mcp/"><u>Goose</u></a> and Anthropics Claude SDK as <a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling"><u>Programmatic Tool Calling</u></a>.</p><p><b>Command-line interfaces </b>are another path. CLIs are self-documenting and reveal capabilities as the agent explores. Tools like <a href="https://openclaw.ai/"><u>OpenClaw</u></a> and <a href="https://blog.cloudflare.com/moltworker-self-hosted-ai-agent/"><u>Moltworker</u></a> convert MCP servers into CLIs using <a href="https://github.com/steipete/mcporter"><u>MCPorter</u></a> to give agents progressive disclosure. The limitation is obvious: the agent needs a shell, which not every environment provides and which introduces a much broader attack surface than a sandboxed isolate.</p><p><b>Dynamic tool search</b>, as used by <a href="https://x.com/trq212/status/2011523109871108570"><u>Anthropic in Claude Code</u></a>, surfaces a smaller set of tools hopefully relevant to the current task. It shrinks context use but now requires a search function that must be maintained and evaluated, and each matched tool still uses tokens.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5FPxVAuJggv7A08DbPsksb/aacb9087a79d08a1430ea87bb6960ad3/images_BLOG-3184_5.png" />
          </figure><p>Each approach solves a real problem. But for MCP servers specifically, server-side Code Mode combines their strengths: fixed token cost regardless of API size, no modifications needed on the agent side, progressive discovery built in, and safe execution inside a sandboxed isolate. The agent just calls two tools with code. Everything else happens on the server.</p>
    <div>
      <h3>Get started today</h3>
      <a href="#get-started-today">
        
      </a>
    </div>
    <p>The Cloudflare MCP server is available now. Point your MCP client at the server URL and you'll be redirected to Cloudflare to authorize and select the permissions to grant to your agent. Add this config to your MCP client: </p>
            <pre><code>{
  "mcpServers": {
    "cloudflare-api": {
      "url": "https://mcp.cloudflare.com/mcp"
    }
  }
}
</code></pre>
            <p>For CI/CD, automation, or if you prefer managing tokens yourself, create a Cloudflare API token with the permissions you need. Both user tokens and account tokens are supported and can be passed as bearer tokens in the <code>Authorization</code> header.</p><p>More information on different MCP setup configurations can be found at the <a href="https://github.com/cloudflare/mcp"><u>Cloudflare MCP repository</u></a>.</p>
    <div>
      <h3>Looking forward</h3>
      <a href="#looking-forward">
        
      </a>
    </div>
    <p>Code Mode solves context costs for a single API. But agents rarely talk to one service. A developer's agent might need the Cloudflare API alongside GitHub, a database, and an internal docs server. Each additional MCP server brings the same context window pressure we started with.</p><p><a href="https://blog.cloudflare.com/zero-trust-mcp-server-portals/"><u>Cloudflare MCP Server Portals</u></a> let you compose multiple MCP servers behind a single gateway with unified auth and access control. We are building a first-class Code Mode integration for all your MCP servers, and exposing them to agents with built-in progressive discovery and the same fixed-token footprint, regardless of how many services sit behind the gateway.</p> ]]></content:encoded>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[AI]]></category>
            <category><![CDATA[Workers AI]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Open Source]]></category>
            <guid isPermaLink="false">2lWwgP33VT0NJjZ3pWShsw</guid>
            <dc:creator>Matt Carey</dc:creator>
        </item>
        <item>
            <title><![CDATA[A good day to trie-hard: saving compute 1% at a time]]></title>
            <link>https://blog.cloudflare.com/pingora-saving-compute-1-percent-at-a-time/</link>
            <pubDate>Tue, 10 Sep 2024 14:00:00 GMT</pubDate>
            <description><![CDATA[ Pingora handles 35M+ requests per second, so saving a few microseconds per request can translate to thousands of dollars saved on computing costs. In this post, we share how we freed up over 500 CPU  ]]></description>
            <content:encoded><![CDATA[ 
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5uwVNobeSBws457ad5SoNY/080de413142fc98caffc3c0108912fe4/2442-1-hero.png" />
          </figure><p>Cloudflare’s global network handles <i>a lot</i> of HTTP requests – over 60 million per second on average. That in and of itself is not news, but it is the starting point to an adventure that started a few months ago and ends with the announcement of a new <a href="https://github.com/cloudflare/trie-hard"><u>open-source Rust crate</u></a> that we are using to reduce our CPU utilization, enabling our CDN to handle even more of the world’s ever-increasing Web traffic. </p>
    <div>
      <h2>Motivation</h2>
      <a href="#motivation">
        
      </a>
    </div>
    <p>Let’s start at the beginning. You may recall a few months ago we released <a href="https://blog.cloudflare.com/pingora-open-source/"><u>Pingora</u></a> (the heart of our Rust-based proxy services) as an <a href="https://github.com/cloudflare/pingora"><u>open-source project on GitHub</u></a>. I work on the team that maintains the Pingora framework, as well as Cloudflare’s production services built upon it. One of those services is responsible for the final step in transmitting users’ (non-cached) requests to their true destination. Internally, we call the request’s destination server its “origin”, so our service has the (unimaginative) name of “pingora-origin”.</p><p>One of the many responsibilities of pingora-origin is to ensure that when a request leaves our infrastructure, it has been cleaned to remove the internal information we use to route, measure, and optimize traffic for our customers. This has to be done for every request that leaves Cloudflare, and as I mentioned above, it’s <i>a lot</i> of requests. At the time of writing, the rate of requests leaving pingora-origin (globally) is 35 million requests per second. Any code that has to be run per-request is in the hottest of hot paths, and it’s in this path that we find this code and comment:</p>
            <pre><code>// PERF: heavy function: 1.7% CPU time
pub fn clear_internal_headers(request_header: &amp;mut RequestHeader) {
    INTERNAL_HEADERS.iter().for_each(|h| {
        request_header.remove_header(h);
    });
}</code></pre>
            <p></p><p>This small and pleasantly-readable function consumes more than 1.7% of pingora-origin’s total cpu time. To put that in perspective, the total cpu time consumed by pingora-origin is 40,000 compute-seconds per second. You can think of this as 40,000 saturated CPU cores fully dedicated to running pingora-origin. Of those 40,000, 1.7% (680) are only dedicated to evaluating <code>clear_internal_headers</code>. The function’s heavy usage and simplicity make it seem like a great place to start optimizing.</p>
    <div>
      <h2>Benchmarking</h2>
      <a href="#benchmarking">
        
      </a>
    </div>
    <p>Benchmarking the function shown above is straightforward because we can use the wonderful <a href="https://crates.io/crates/criterion"><u>criterion</u></a> Rust crate. Criterion provides an api for timing rust code down to the nanosecond by aggregating multiple isolated executions. It also provides feedback on how the performance improves or regresses over time. The input for the benchmark is a large set of synthesized requests with a random number of headers with a uniform distribution of internal vs. non-internal headers. With our tooling and test data we find that our original <code>clear_internal_headers</code> function runs in an average of <b>3.65µs</b>. Now for each new method of clearing headers, we can measure against the same set of requests and get a relative performance difference. </p>
    <div>
      <h2>Reducing Reads</h2>
      <a href="#reducing-reads">
        
      </a>
    </div>
    <p>One potentially quick win is to invert how we find the headers that need to be removed from requests. If you look at the original code, you can see that we are evaluating <code>request_header.remove_header(h)</code> for each header in our list of internal headers, so 100+ times. Diagrammatically, it looks like this:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7y2qHbNfBQeoGRc8PqjBcp/9e8fccb6951a475a26def66695e47635/2442-2.png" />
          </figure><p></p><p>Since an average request has significantly fewer than 100 headers (10-30), flipping the lookup direction should reduce the number of reads while yielding the same intersection. Because we are working in Rust (and because <code>retain</code> does not exist for <code>http::HeaderMap</code> <a href="https://github.com/hyperium/http/issues/541"><u>yet</u></a>), we have to collect the identified internal headers in a separate step before removing them from the request. Conceptually, it looks like this:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6hgLavu1hZbwkw91Tee8e1/4d43b538274ae2c680236ca66791d73b/2442-3.png" />
          </figure><p></p><p>Using our benchmarking tool, we can measure the impact of this small change, and surprisingly this is already a substantial improvement. The runtime improves from <b>3.65µs</b> to <b>1.53µs</b>. That’s a 2.39x speed improvement for our function. We can calculate the theoretical CPU percentage by multiplying the starting utilization by the ratio of the new and old times: 1.71% * 1.53 / 3.65 = 0.717%. Unfortunately, if we subtract that from the original 1.71% that only equates to saving 1.71% - 0.717% = <i>0.993%</i> of the total CPU time. We should be able to do better. </p>
    <div>
      <h2>Searching Data Structures</h2>
      <a href="#searching-data-structures">
        
      </a>
    </div>
    <p>Now that we have reorganized our function to search a static set of internal headers instead of the actual request, we have the freedom to choose what data structure we store our header name in simply by changing the type of <code>INTERNAL_HEADER_SET</code>.</p>
            <pre><code>pub fn clear_internal_headers(request_header: &amp;mut RequestHeader) {
   let to_remove = request_header
       .headers
       .keys()
       .filter_map(|name| INTERNAL_HEADER_SET.get(name))
       .collect::&lt;Vec&lt;_&gt;&gt;();


   to_remove.into_iter().for_each(|k| {
       request_header.remove_header(k);
   });</code></pre>
            <p></p><p>Our first attempt used <code>std::HashMap</code>, but there may be other data structures that better suit our needs. All computer science students were taught at some point that hash tables are great because they have constant-time asymptotic behavior, or O(1), for reading. (If you are not familiar with <a href="https://www.khanacademy.org/computing/computer-science/algorithms/asymptotic-notation/a/big-o-notation"><u>big O notation</u></a>, it is a way to express how an algorithm consumes a resource, in this case time, as the input size changes.) This means no matter how large the map gets, reads always take the same amount of time. Too bad this is only partially true. In order to read from a hash table, you have to compute the hash. Computing a hash for strings requires reading every byte, so while read time for a hashmap is constant over the table’s size, it’s linear over key length. So, our goal is to find a data structure that is better than O(L) where L is the length of the key.</p><p>There are a few common data structures that provide for reads that have read behavior that meets our criteria. Sorted sets like <code>BTreeSet</code> use comparisons for searching, and that makes them logarithmic over key length <b>O(log(L))</b>, but they are also logarithmic in size too. The net effect is that even very fast sorted sets like <a href="https://crates.io/crates/fst"><u>FST</u></a> work out to be a little (50 ns) slower in our benchmarks than the standard hashmap.</p><p>State machines like parsers and regex are another common tool for searching for strings, though it’s hard to consider them data structures. These systems work by accepting input one unit at a time and determining on each step whether or not to keep evaluating. Being able to make these determinations at every step means state machines are very fast to identify negative cases (i.e. when a string is not valid or not a match). This is perfect for us because only one or two headers per request on average will be internal. In fact, benchmarking an implementation of <code>clear_internal_headers</code> using regular expressions clocks in as taking about twice as long as the hashmap-based solution. This is impressively fast given that regexes, while powerful, aren't known for their raw speed. This approach feels promising – we just need something in between a data structure and a state machine. </p><p>That’s where the trie comes in.</p>
    <div>
      <h2>Don’t Just Trie</h2>
      <a href="#dont-just-trie">
        
      </a>
    </div>
    <p>A <a href="https://en.wikipedia.org/wiki/Trie"><u>trie</u></a> (pronounced like “try” or “tree”) is a type of <a href="https://en.wikipedia.org/wiki/Tree_(data_structure)"><u>tree data structure</u></a> normally used for prefix searches or auto-complete systems over a known set of strings. The structure of the trie lends itself to this because each node in the trie represents a substring of characters found in the initial set. The connections between the nodes represent the characters that can follow a prefix. Here is a small example of a trie built from the words: “and”, “ant”, “dad”, “do”, &amp; “dot”. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5wy48j3XNs9awxRNvjLljC/4e2a05b4e1802eba26f9e10e95bd843f/2442-4.png" />
          </figure><p>The root node represents an empty string prefix, so the two lettered edges directed out of it are the only letters that can appear as the first letter in the list of strings, “a” and “d”. Subsequent nodes have increasingly longer prefixes until the final valid words are reached. This layout should make it easy to see how a trie could be useful for quickly identifying strings that are not contained. Even at the root node, we can eliminate any strings that are presented that do not start with “a” or “d”. This paring down of the search space on every step gives reading from a trie the <b>O(log(L))</b> we were looking for … but only for misses. Hits within a trie are still <b>O(L)</b>, but that’s okay, because we are getting misses over 90% of the time.</p><p>Benchmarking a few trie implementations from <a href="https://crates.io/search?q=trie"><u>crates.io</u></a> was disheartening. Remember, most tries are used in response to keyboard events, so optimizing them to run in the hot path of tens of millions of requests per second is not a priority. The fastest existing implementation we found was <a href="https://crates.io/crates/radix_trie"><u>radix_trie</u></a>, but it still clocked in at a full microsecond slower than hashmap. The only thing left to do was write our own implementation of a trie that was optimized for our use case.</p>
    <div>
      <h2>Trie Hard</h2>
      <a href="#trie-hard">
        
      </a>
    </div>
    <p>And we did! Today we are announcing <a href="https://github.com/cloudflare/trie-hard"><u>trie-hard</u></a>. The repository gives a full description of how it works, but the big takeaway is that it gets its speed from storing node relationships in the bits of unsigned integers and keeping the entire tree in a contiguous chunk of memory. In our benchmarks, we found that trie-hard reduced the average runtime for <code>clear_internal_headers</code> to under a microsecond (0.93µs). We can reuse the same formula from above to calculate the expected CPU utilization for trie-hard to be 1.71% * 3.65 / 0.93 = 0.43% That means we have finally achieved and surpassed our goal by reducing the compute utilization of pingora-origin by 1.71% - 0.43% =  <b>1.28%</b>! </p><p>Up until now we have been working only in theory and local benchmarking. What really matters is whether our benchmarking reflects real-life behavior. Trie-hard has been running in production since July 2024, and over the course of this project we have been collecting performance metrics from the running production of pingora-origin using a statistical sampling of its stack trace over time. Using this technique, the CPU utilization percentage of a function is estimated by the percent of samples in which the function appears. If we compare the sampled performance of the different versions of <code>clear_internal_headers</code>, we can see that the results from the performance sampling closely match what our benchmarks predicted.</p><table><tr><th><p>Implementation</p></th><th><p>Stack trace samples containing <code>clear_internal_headers</code></p></th><th><p>Actual CPU Usage (%)</p></th><th><p>Predicted CPU Usage (%)</p></th></tr><tr><td><p>Original </p></td><td><p>19 / 1111</p></td><td><p>1.71</p></td><td><p>n/a</p></td></tr><tr><td><p>Hashmap</p></td><td><p>9 / 1103</p></td><td><p>0.82</p></td><td><p>0.72</p></td></tr><tr><td><p>trie-hard</p></td><td><p>4 / 1171</p></td><td><p>0.34</p></td><td><p>0.43</p></td></tr></table>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Optimizing functions and writing new data structures is cool, but the real conclusion for this post is that knowing where your code is slow and by how much is more important than how you go about optimizing it. Take a moment to thank your observability team (if you're lucky enough to have one), and make use of flame graphs or any other profiling and benchmarking tool you can. Optimizing operations that are already measured in microseconds may seem a little silly, but these small improvements add up.</p> ]]></content:encoded>
            <category><![CDATA[Internet Performance]]></category>
            <category><![CDATA[Rust]]></category>
            <category><![CDATA[Open Source]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Pingora]]></category>
            <guid isPermaLink="false">2CqKLNS1jaf5H2j99sDONe</guid>
            <dc:creator>Kevin Guthrie</dc:creator>
        </item>
        <item>
            <title><![CDATA[Making WAF ML models go brrr: saving decades of processing time]]></title>
            <link>https://blog.cloudflare.com/making-waf-ai-models-go-brr/</link>
            <pubDate>Thu, 25 Jul 2024 13:00:46 GMT</pubDate>
            <description><![CDATA[ In this post, we discuss performance optimizations we've implemented for our WAF ML product. We'll guide you through code examples, benchmarks, and we'll share the impressive latency reduction numbers ]]></description>
            <content:encoded><![CDATA[ <p>We made our WAF Machine Learning models <b>5.5x</b> faster, reducing execution time by approximately <b>82%</b>, from <b>1519</b> to <b>275</b> microseconds! Read on to find out how we achieved this remarkable improvement.</p><p><a href="https://developers.cloudflare.com/waf/about/waf-attack-score/">WAF Attack Score</a> is Cloudflare's machine learning (ML)-powered layer built on top of our <a href="https://developers.cloudflare.com/waf/">Web Application Firewall (WAF)</a>. Its goal is to complement the WAF and detect attack bypasses that we haven't encountered before. This has proven invaluable in <a href="/detecting-zero-days-before-zero-day">catching zero-day vulnerabilities</a>, like the one detected in <a href="/how-cloudflares-ai-waf-proactively-detected-ivanti-connect-secure-critical-zero-day-vulnerability">Ivanti Connect Secure</a>, before they are publicly disclosed and enhancing our customers' protection against emerging and unknown threats.</p><p>Since its <a href="/waf-ml">launch in 2022</a>, WAF attack score adoption has grown exponentially, now protecting millions of Internet properties and running real-time inference on tens of millions of requests per second. The feature's popularity has driven us to seek performance improvements, enabling even broader customer use and enhancing Internet security.</p><p>In this post, we will discuss the performance optimizations we've implemented for our WAF ML product. We'll guide you through specific code examples and benchmark numbers, demonstrating how these enhancements have significantly improved our system's efficiency. Additionally, we'll share the impressive latency reduction numbers observed after the rollout.</p><p>Before diving into the optimizations, let's take a moment to review the inner workings of the WAF Attack Score, which powers our WAF ML product.</p>
    <div>
      <h2>WAF Attack Score system design</h2>
      <a href="#waf-attack-score-system-design">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3Bis9LE38A3aK4k7HEn7k9/44ae7b31096471a5256961715f8c7991/unnamed--4--6.png" />
            
            </figure><p>Cloudflare's WAF attack score identifies various traffic types and attack vectors (<a href="https://www.cloudflare.com/learning/security/threats/how-to-prevent-sql-injection/">SQLi</a>, <a href="https://www.cloudflare.com/learning/security/how-to-prevent-xss-attacks/">XSS</a>, Command Injection, etc.) based on structural or statistical content properties. Here's how it works during inference:</p><ol><li><p><b>HTTP Request Content</b>: Start with raw HTTP input.</p></li><li><p><b>Normalization &amp; Transformation</b>: Standardize and clean the data, applying normalization, content substitutions, and de-duplication.</p></li><li><p><b>Feature Extraction</b>: Tokenize the transformed content to generate statistical and structural data.</p></li><li><p><b>Machine Learning Model Inference</b>: Analyze the extracted features with pre-trained models, mapping content representations to classes (e.g., XSS, SQLi or <a href="https://www.cloudflare.com/learning/security/what-is-remote-code-execution/">RCE</a>) or scores.</p></li><li><p><b>Classification Output in WAF</b>: Assign a score to the input, ranging from 1 (likely malicious) to 99 (likely clean), guiding security actions.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/ZzHRYXU27VYB5F3F3QjXf/9e5248610a1e89ac8c73a446925abb69/cfce15fb-ce84-4489-a05a-6872b9e502b8.png" />
            
            </figure><p>Next, we will explore feature extraction and inference optimizations.</p>
    <div>
      <h2>Feature extraction optimizations</h2>
      <a href="#feature-extraction-optimizations">
        
      </a>
    </div>
    <p>In the context of the WAF Attack Score ML model, feature extraction or pre-processing is essentially a process of tokenizing the given input and producing a float tensor of 1 x m size:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7DIxHJ5zLkdeknndiNGbk0/e802888a2212ddfcae688f1c4201587f/8cc41311-3a09-4c39-b47c-9dc449760ee2.png" />
            
            </figure><p>In our initial pre-processing implementation, this is achieved via a sliding window of 3 bytes over the input with the help of Rust’s <a href="https://doc.rust-lang.org/std/collections/struct.HashMap.html">std::collections::HashMap</a> to look up the tensor index for a given ngram.</p>
    <div>
      <h3>Initial benchmarks</h3>
      <a href="#initial-benchmarks">
        
      </a>
    </div>
    <p>To establish performance baselines, we've set up four benchmark cases representing example inputs of various lengths, ranging from 44 to 9482 bytes. Each case exemplifies typical input sizes, including those for a request body, user agent, and URI. We run benchmarks using the <a href="https://bheisler.github.io/criterion.rs/book/getting_started.html">Criterion.rs</a> statistics-driven micro-benchmarking tool:</p>
            <pre><code>RUSTFLAGS="-C opt-level=3 -C target-cpu=native" cargo criterion</code></pre>
            <p>Here are initial numbers for these benchmarks executed on a Linux laptop with a 13th Gen Intel® Core™ i7-13800H processor:</p>
<table><thead>
  <tr>
    <th><span>Benchmark case</span></th>
    <th><span>Pre-processing time, μs</span></th>
    <th><span>Throughput, MiB/s</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>preprocessing/long-body-9482</span></td>
    <td><span>248.46</span></td>
    <td><span>36.40</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-body-1000</span></td>
    <td><span>28.19</span></td>
    <td><span>33.83</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-url-44</span></td>
    <td><span>1.45</span></td>
    <td><span>28.94</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-ua-91</span></td>
    <td><span>2.87</span></td>
    <td><span>30.24</span></td>
  </tr>
</tbody></table><p>An important observation from these results is that pre-processing time correlates with the length of the input string, with throughput ranging from 28 MiB/s to 36 MiB/s. This suggests that considerable time is spent iterating over longer input strings. Optimizing this part of the process could significantly enhance performance. The dependency of processing time on input size highlights a key area for performance optimization. To validate this, we should examine where the processing time is spent by analyzing flamegraphs created from a 100-second profiling session visualized using <a href="https://www.honeycomb.io/blog/golang-observability-using-the-new-pprof-web-ui-to-debug-memory-usage">pprof</a>:</p>
            <pre><code>RUSTFLAGS="-C opt-level=3 -C target-cpu=native" cargo criterion -- --profile-time 100
 
go tool pprof -http=: target/criterion/profile/preprocessing/avg-body-1000/profile.pb</code></pre>
            
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/WGhFT3j6vn4QGFOmdyNGO/8dd1c6e4d171cd2c407af7bf4d9a9ac7/unnamed--5--6.png" />
            
            </figure><p>Looking at the pre-processing flamegraph above, it's clear that most of the time was spent on the following two operations:</p>
<table><thead>
  <tr>
    <th><span>Function name</span></th>
    <th><span>% Time spent</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>std::collections::hash::map::HashMap&lt;K,V,S&gt;::get</span></td>
    <td><span>61.8%</span></td>
  </tr>
  <tr>
    <td><span>regex::regex::bytes::Regex::replace_all</span></td>
    <td><span>18.5%</span></td>
  </tr>
</tbody></table><p>Let's tackle the HashMap lookups first. Lookups are happening inside the <i>tensor_populate_ngrams</i> function, where input is split into windows of 3 bytes representing ngram and then lookup inside two hash maps:</p>
            <pre><code>fn tensor_populate_ngrams(tensor: &amp;mut [f32], input: &amp;[u8]) {   
   // Populate the NORM ngrams
   let mut unknown_norm_ngrams = 0;
   let norm_offset = 1;
 
   for s in input.windows(3) {
       match NORM_VOCAB.get(s) {
           Some(pos) =&gt; {
               tensor[*pos as usize + norm_offset] += 1.0f32;
           }
           None =&gt; {
               unknown_norm_ngrams += 1;
           }
       };
   }
 
   // Populate the SIG ngrams
   let mut unknown_sig_ngrams = 0;
   let sig_offset = norm_offset + NORM_VOCAB.len();
 
   let res = SIG_REGEX.replace_all(&amp;input, b"#");
 
   for s in res.windows(3) {
       match SIG_VOCAB.get(s) {
           Some(pos) =&gt; {
               // adding +1 here as the first position will be the unknown_sig_ngrams
               tensor[*pos as usize + sig_offset + 1] += 1.0f32;
           }
           None =&gt; {
               unknown_sig_ngrams += 1;
           }
       }
   }
}</code></pre>
            <p>So essentially the pre-processing function performs a ton of hash map lookups, the volume of which depends on the size of the input string, e.g. 1469 lookups for the given benchmark case <i>avg-body-1000</i>.</p>
    <div>
      <h3>Optimization attempt #1: HashMap → Aho-Corasick</h3>
      <a href="#optimization-attempt-1-hashmap-aho-corasick">
        
      </a>
    </div>
    <p>Rust hash maps are generally quite fast. However, when that many lookups are being performed, it's not very cache friendly.</p><p>So can we do better than hash maps, and what should we try first? The answer is the <a href="https://docs.rs/aho-corasick/latest/aho_corasick/">Aho-Corasick library</a>.</p><p>This library provides multiple pattern search principally through an implementation of the <a href="https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm">Aho-Corasick algorithm</a>, which builds a fast finite state machine for executing searches in linear time.</p><p>We can also tune Aho-Corasick settings based on this recommendation:</p><blockquote><p><i>“You might want to use</i> <a href="https://docs.rs/aho-corasick/1.1.3/aho_corasick/struct.AhoCorasickBuilder.html#method.kind"><i>AhoCorasickBuilder::kind</i></a> <i>to set your searcher to always use</i> <a href="https://docs.rs/aho-corasick/1.1.3/aho_corasick/enum.AhoCorasickKind.html#variant.DFA"><i>AhoCorasickKind::DFA</i></a> <i>if search speed is critical and memory usage isn’t a concern.”</i></p></blockquote>
            <pre><code>static ref NORM_VOCAB_AC: AhoCorasick = AhoCorasick::builder().kind(Some(AhoCorasickKind::DFA)).build(&amp;[    
    "abc",
    "def",
    "wuq",
    "ijf",
    "iru",
    "piw",
    "mjw",
    "isn",
    "od ",
    "pro",
    ...
]).unwrap();</code></pre>
            <p>Then we use the constructed AhoCorasick dictionary to lookup ngrams using its <a href="https://docs.rs/aho-corasick/latest/aho_corasick/struct.AhoCorasick.html#method.find_overlapping_iter">find_overlapping_iter</a> method:</p>
            <pre><code>for mat in NORM_VOCAB_AC.find_overlapping_iter(&amp;input) {
    tensor_input_data[mat.pattern().as_usize() + 1] += 1.0;
}</code></pre>
            <p>We ran benchmarks and compared them against the baseline times shown above:</p>
<table><thead>
  <tr>
    <th><span>Benchmark case</span></th>
    <th><span>Baseline time, μs</span></th>
    <th><span>Aho-Corasick time, μs</span></th>
    <th><span>Optimization</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>preprocessing/long-body-9482</span></td>
    <td><span>248.46</span></td>
    <td><span>129.59</span></td>
    <td><span>-47.84% or 1.64x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-body-1000</span></td>
    <td><span>28.19</span></td>
    <td>	<span>16.47</span></td>
    <td><span>-41.56% or 1.71x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-url-44</span></td>
    <td><span>1.45</span></td>
    <td><span>1.01</span></td>
    <td><span>-30.38% or 1.44x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-ua-91</span></td>
    <td><span>2.87</span></td>
    <td><span>1.90</span></td>
    <td><span>-33.60% or 1.51x</span></td>
  </tr>
</tbody></table><p>That's substantially better – Aho-Corasick DFA does wonders.</p>
    <div>
      <h3>Optimization attempt #2: Aho-Corasick → match</h3>
      <a href="#optimization-attempt-2-aho-corasick-match">
        
      </a>
    </div>
    <p>One would think optimization with Aho-Corasick DFA is enough and that it seems unlikely that anything else can beat it. Yet, we can throw Aho-Corasick away and simply use the Rust match statement and let the compiler do the optimization for us!</p>
            <pre><code>#[inline]
const fn norm_vocab_lookup(ngram: &amp;[u8; 3]) -&gt; usize {     
    match ngram {
        b"abc" =&gt; 1,
        b"def" =&gt; 2,
        b"wuq" =&gt; 3,
        b"ijf" =&gt; 4,
        b"iru" =&gt; 5,
        b"piw" =&gt; 6,
        b"mjw" =&gt; 7,
        b"isn" =&gt; 8,
        b"od " =&gt; 9,
        b"pro" =&gt; 10,
        ...
        _ =&gt; 0,
    }
}```</code></pre>
            <p>Here's how it performs in practice, based on the assembly generated by the <a href="https://godbolt.org/z/dqTq5n5Y3">Godbolt compiler explorer</a>. The corresponding assembly code efficiently implements this lookup by employing a jump table and byte-wise comparisons to determine the return value based on input sequences, optimizing for quick decisions and minimal branching. Although the example only includes ten ngrams, it's important to note that in applications like our WAF Attack Score ML models, we deal with thousands of ngrams. This simple match-based approach outshines both HashMap lookups and the Aho-Corasick method.</p>
<table><thead>
  <tr>
    <th><span>Benchmark case</span></th>
    <th><span>Baseline time, μs</span></th>
    <th><span>Match time, μs</span></th>
    <th><span>Optimization</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>preprocessing/long-body-9482</span></td>
    <td><span>248.46</span></td>
    <td>	<span>112.96</span></td>
    <td><span>-54.54% or 2.20x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-body-1000</span></td>
    <td><span>28.19</span></td>
    <td>	<span>13.12</span></td>
    <td><span>-53.45% or 2.15x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-url-44</span></td>
    <td><span>1.45</span></td>
    <td><span>0.75</span></td>
    <td><span>-48.37% or 1.94x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-ua-91</span></td>
    <td><span>2.87</span></td>
    <td><span>1.4076</span></td>
    <td><span>-50.91% or 2.04x</span></td>
  </tr>
</tbody></table><p>Switching to match gave us another 7-18% drop in latency, depending on the case.</p>
    <div>
      <h3>Optimization attempt #3: Regex → WindowedReplacer</h3>
      <a href="#optimization-attempt-3-regex-windowedreplacer">
        
      </a>
    </div>
    <p>So, what exactly is the purpose of <i>Regex::replace_all</i> in pre-processing? Regex is defined and used like this:</p>
            <pre><code>pub static SIG_REGEX: Lazy&lt;Regex&gt; =
    Lazy::new(|| RegexBuilder::new("[a-z]+").unicode(false).build().unwrap());
    ... 
    let res = SIG_REGEX.replace_all(&amp;input, b"#");
    for s in res.windows(3) {
        tensor[sig_vocab_lookup(s.try_into().unwrap())] += 1.0;
    }</code></pre>
            <p>Essentially, all we need is to:</p><ol><li><p>Replace every sequence of lowercase letters in the input with a single byte "#".</p></li><li><p>Iterate over replaced bytes in a windowed fashion with a step of 3 bytes representing an ngram.</p></li><li><p>Look up the ngram index and increment it in the tensor.</p></li></ol><p>This logic seems simple enough that we could implement it more efficiently with a single pass over the input and without any allocations:</p>
            <pre><code>type Window = [u8; 3];
type Iter&lt;'a&gt; = Peekable&lt;std::slice::Iter&lt;'a, u8&gt;&gt;;

pub struct WindowedReplacer&lt;'a&gt; {
    window: Window,
    input_iter: Iter&lt;'a&gt;,
}

#[inline]
fn is_replaceable(byte: u8) -&gt; bool {
    matches!(byte, b'a'..=b'z')
}

#[inline]
fn next_byte(iter: &amp;mut Iter) -&gt; Option&lt;u8&gt; {
    let byte = iter.next().copied()?;
    if is_replaceable(byte) {
        while iter.next_if(|b| is_replaceable(**b)).is_some() {}
        Some(b'#')
    } else {
        Some(byte)
    }
}

impl&lt;'a&gt; WindowedReplacer&lt;'a&gt; {
    pub fn new(input: &amp;'a [u8]) -&gt; Option&lt;Self&gt; {
        let mut window: Window = Default::default();
        let mut iter = input.iter().peekable();
        for byte in window.iter_mut().skip(1) {
            *byte = next_byte(&amp;mut iter)?;
        }
        Some(WindowedReplacer {
            window,
            input_iter: iter,
        })
    }
}

impl&lt;'a&gt; Iterator for WindowedReplacer&lt;'a&gt; {
    type Item = Window;

    #[inline]
    fn next(&amp;mut self) -&gt; Option&lt;Self::Item&gt; {
        for i in 0..2 {
            self.window[i] = self.window[i + 1];
        }
        let byte = next_byte(&amp;mut self.input_iter)?;
        self.window[2] = byte;
        Some(self.window)
    }
}</code></pre>
            <p>By utilizing the <i>WindowedReplacer</i>, we simplify the replacement logic:</p>
            <pre><code>if let Some(replacer) = WindowedReplacer::new(&amp;input) {                
    for ngram in replacer.windows(3) {
        tensor[sig_vocab_lookup(ngram.try_into().unwrap())] += 1.0;
    }
}</code></pre>
            <p>This new approach not only eliminates the need for allocating additional buffers to store replaced content, but also leverages Rust's iterator optimizations, which the compiler can more effectively optimize. You can view an example of the assembly output for this new iterator at the provided <a href="https://godbolt.org/z/fjaoP7z6Y">Godbolt link</a>.</p><p>Now let's benchmark this and compare against the original implementation:</p>
<table><thead>
  <tr>
    <th><span>Benchmark case</span></th>
    <th><span>Baseline time, μs</span></th>
    <th><span>Match time, μs</span></th>
    <th><span>Optimization</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>preprocessing/long-body-9482</span></td>
    <td><span>248.46</span></td>
    <td>	<span>51.00</span></td>
    <td><span>-79.47% or 4.87x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-body-1000</span></td>
    <td><span>28.19</span></td>
    <td>	<span>5.53</span></td>
    <td><span>-80.36% or 5.09x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-url-44</span></td>
    <td><span>1.45</span></td>
    <td><span>0.40</span></td>
    <td><span>-72.11% or 3.59x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-ua-91</span></td>
    <td><span>2.87</span></td>
    <td><span>0.69</span></td>
    <td><span>-76.07% or 4.18x</span></td>
  </tr>
</tbody></table><p>The new letters replacement implementation has doubled the preprocessing speed compared to the previously optimized version using match statements, and it is four to five times faster than the original version!</p>
    <div>
      <h3>Optimization attempt #4: Going nuclear with branchless ngram lookups</h3>
      <a href="#optimization-attempt-4-going-nuclear-with-branchless-ngram-lookups">
        
      </a>
    </div>
    <p>At this point, 4-5x improvement might seem like a lot and there is no point pursuing any further optimizations. After all, using an ngram lookup with a match statement has beaten the following methods, with benchmarks omitted for brevity:</p>
<table><thead>
  <tr>
    <th><span>Lookup method</span></th>
    <th><span>Description</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><a href="https://doc.rust-lang.org/std/collections/struct.HashMap.html"><span>std::collections::HashMap</span></a></td>
    <td><span>Uses </span><a href="https://github.com/rust-lang/hashbrown"><span>Google’s SwissTable</span></a><span> design with SIMD lookups to scan multiple hash entries in parallel. </span></td>
  </tr>
  <tr>
    <td><a href="https://docs.rs/aho-corasick/latest/aho_corasick/#"><span>Aho-Corasick</span></a><span> matcher with and without </span><a href="https://docs.rs/aho-corasick/latest/aho_corasick/dfa/struct.DFA.html"><span>DFA</span></a></td>
    <td><span>Also utilizes SIMD instructions in some cases.</span></td>
  </tr>
  <tr>
    <td><a href="https://crates.io/crates/phf"><span>phf crate</span></a><span> </span></td>
    <td><span>A library to generate efficient lookup tables at compile time using </span><a href="https://en.wikipedia.org/wiki/Perfect_hash_function"><span>perfect hash functions</span></a><span>.</span></td>
  </tr>
  <tr>
    <td><a href="https://crates.io/crates/ph"><span>ph crate</span></a></td>
    <td><span>Another Rust library of data structures based on perfect hashing. </span></td>
  </tr>
  <tr>
    <td><a href="https://crates.io/crates/quickphf"><span>quickphf crate</span></a></td>
    <td><span>A Rust crate that allows you to use static compile-time generated hash maps and hash sets using </span><a href="https://arxiv.org/abs/2104.10402"><span>PTHash perfect hash functions</span></a><span>.</span></td>
  </tr>
</tbody></table><p>However, if we look again at <a href="https://godbolt.org/z/dqTq5n5Y3">the assembly of the norm_vocab_lookup function</a>, it is clear that the execution flow has to perform a bunch of comparisons using <i>cmp</i> instructions. This creates many branches for the CPU to handle, which can lead to branch mispredictions. Branch mispredictions occur when the CPU incorrectly guesses the path of execution, causing delays as it discards partially completed instructions and fetches the correct ones. By reducing or eliminating these branches, we can avoid these mispredictions and improve the efficiency of the lookup process. How can we get rid of those branches when there is a need to look up thousands of unique ngrams?</p><p>Since there are only 3 bytes in each ngram, we can build two lookup tables of 256 x 256 x 256 size, storing the ngram tensor index. With this naive approach, our memory requirements will be: 256 x 256 x 256 x 2 x 2 = 64 MB, which seems like a lot.</p><p>However, given that we only care about ASCII bytes 0..127, then memory requirements can be lower: 128 x 128 x 128 x 2 x 2 = 8 MB, which is better. However, we will need to check for bytes &gt;= 128, which will introduce a branch again.</p><p>So can we do better? Considering that the actual number of distinct byte values used in the ngrams is significantly less than the total possible 256 values, we can reduce memory requirements further by employing the following technique:</p><p>1. To avoid the branching caused by comparisons, we use precomputed offset lookup tables. This means instead of comparing each byte of the ngram during each lookup, we precompute the positions of each possible byte in a lookup table. This way, we replace the comparison operations with direct memory accesses, which are much faster and do not involve branching. We build an ngram bytes offsets lookup const array, storing each unique ngram byte offset position multiplied by the number of unique ngram bytes:</p>
            <pre><code>const NGRAM_OFFSETS: [[u32; 256]; 3] = [
    [
        // offsets of first byte in ngram
    ],
    [
        // offsets of second byte in ngram
    ],
    [
        // offsets of third byte in ngram
    ],
];</code></pre>
            <p>2. Then to obtain the ngram index, we can use this simple const function:</p>
            <pre><code>#[inline]
const fn ngram_index(ngram: [u8; 3]) -&gt; usize {
    (NGRAM_OFFSETS[0][ngram[0] as usize]
        + NGRAM_OFFSETS[1][ngram[1] as usize]
        + NGRAM_OFFSETS[2][ngram[2] as usize]) as usize
}</code></pre>
            <p>3. To look up the tensor index based on the ngram index, we construct another const array at compile time using a list of all ngrams, where N is the number of unique ngram bytes:</p>
            <pre><code>const NGRAM_TENSOR_IDX: [u16; N * N * N] = {
    let mut arr = [0; N * N * N];
    arr[ngram_index(*b"abc")] = 1;
    arr[ngram_index(*b"def")] = 2;
    arr[ngram_index(*b"wuq")] = 3;
    arr[ngram_index(*b"ijf")] = 4;
    arr[ngram_index(*b"iru")] = 5;
    arr[ngram_index(*b"piw")] = 6;
    arr[ngram_index(*b"mjw")] = 7;
    arr[ngram_index(*b"isn")] = 8;
    arr[ngram_index(*b"od ")] = 9;
    ...
    arr
};</code></pre>
            <p>4. Finally, to update the tensor based on given ngram, we lookup the ngram index, then the tensor index, and then increment it with help of <a href="https://doc.rust-lang.org/std/primitive.slice.html#method.get_unchecked_mut">get_unchecked_mut</a>, which avoids unnecessary (in this case) boundary checks and eliminates another source of branching:</p>
            <pre><code>#[inline]
fn update_tensor_with_ngram(tensor: &amp;mut [f32], ngram: [u8; 3]) {
    let ngram_idx = ngram_index(ngram);
    debug_assert!(ngram_idx &lt; NGRAM_TENSOR_IDX.len());
    unsafe {
        let tensor_idx = *NGRAM_TENSOR_IDX.get_unchecked(ngram_idx) as usize;
        debug_assert!(tensor_idx &lt; tensor.len());
        *tensor.get_unchecked_mut(tensor_idx) += 1.0;
    }
}</code></pre>
            <p>This logic works effectively, passes correctness tests, and most importantly, it's completely branchless! Moreover, the memory footprint of used lookup arrays is tiny – just ~500 KiB of memory – which easily fits into modern CPU L2/L3 caches, ensuring that expensive cache misses are rare and performance is optimal.</p><p>The last trick we will employ is loop unrolling for ngrams processing. By taking 6 ngrams (corresponding to 8 bytes of the input array) at a time, the compiler can unroll the second loop and auto-vectorize it, leveraging parallel execution to improve performance:</p>
            <pre><code>const CHUNK_SIZE: usize = 6;

let chunks_max_offset =
    ((input.len().saturating_sub(2)) / CHUNK_SIZE) * CHUNK_SIZE;
for i in (0..chunks_max_offset).step_by(CHUNK_SIZE) {
    for ngram in input[i..i + CHUNK_SIZE + 2].windows(3) {
        update_tensor_with_ngram(tensor, ngram.try_into().unwrap());
    }
}</code></pre>
            <p>Tying up everything together, our final pre-processing benchmarks show the following:</p>
<table><thead>
  <tr>
    <th><span>Benchmark case</span></th>
    <th><span>Baseline time, μs</span></th>
    <th><span>Branchless time, μs</span></th>
    <th><span>Optimization</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>preprocessing/long-body-9482</span></td>
    <td><span>248.46</span></td>
    <td><span>21.53</span></td>
    <td><span>-91.33% or 11.54x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-body-1000</span></td>
    <td><span>28.19</span></td>
    <td><span>2.33</span></td>
    <td><span>-91.73% or 12.09x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-url-44</span></td>
    <td><span>1.45</span></td>
    <td>	<span>0.26</span></td>
    <td><span>-82.34% or 5.66x</span></td>
  </tr>
  <tr>
    <td><span>preprocessing/avg-ua-91</span></td>
    <td><span>2.87</span></td>
    <td>	<span>0.43</span></td>
    <td><span>-84.92% or 6.63x</span></td>
  </tr>
</tbody></table><p>The longer input is, the higher the latency drop will be due to branchless ngram lookups and loop unrolling, ranging from <b>six to twelve times faster</b> than baseline implementation.</p><p>After trying various optimizations, the final version of pre-processing retains optimization attempts 3 and 4, using branchless ngram lookup with offset tables and a single-pass non-allocating replacement iterator.</p><p>There are potentially more CPU cycles left on the table, and techniques like memory pre-fetching and manual SIMD intrinsics could speed this up a bit further. However, let's now switch gears into looking at inference latency a bit closer.</p>
    <div>
      <h2>Model inference optimizations</h2>
      <a href="#model-inference-optimizations">
        
      </a>
    </div>
    
    <div>
      <h3>Initial benchmarks</h3>
      <a href="#initial-benchmarks">
        
      </a>
    </div>
    <p>Let’s have a look at original performance numbers of the WAF Attack Score ML model, which uses <a href="https://github.com/tensorflow/tensorflow/releases/tag/v2.6.0">TensorFlow Lite 2.6.0</a>:</p>
<table><thead>
  <tr>
    <th><span>Benchmark case</span></th>
    <th><span>Inference time, μs</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>inference/long-body-9482</span></td>
    <td><span>247.31</span></td>
  </tr>
  <tr>
    <td><span>inference/avg-body-1000</span></td>
    <td><span>246.31</span></td>
  </tr>
  <tr>
    <td><span>inference/avg-url-44</span></td>
    <td><span>246.40</span></td>
  </tr>
  <tr>
    <td><span>inference/avg-ua-91</span></td>
    <td><span>246.88</span></td>
  </tr>
</tbody></table><p>Model inference is actually independent of the original input length, as inputs are transformed into tensors of predetermined size during the pre-processing phase, which we optimized above. From now on, we will refer to a singular inference time when benchmarking our optimizations.</p><p>Digging deeper with profiler, we observed that most of the time is spent on the following operations:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3uy64gatRk8PfdnpRz5Xm5/0d3da469c30e5941524289c1b13574c5/unnamed--6--6.png" />
            
            </figure>
<table><thead>
  <tr>
    <th><span>Function name</span></th>
    <th><span>% Time spent</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>tflite::tensor_utils::PortableMatrixBatchVectorMultiplyAccumulate</span></td>
    <td><span>42.46%</span></td>
  </tr>
  <tr>
    <td><span>tflite::tensor_utils::PortableAsymmetricQuantizeFloats</span></td>
    <td><span>30.59%</span></td>
  </tr>
  <tr>
    <td><span>tflite::optimized_ops::SoftmaxImpl</span></td>
    <td><span>12.02%</span></td>
  </tr>
  <tr>
    <td><span>tflite::reference_ops::MaximumMinimumBroadcastSlow</span></td>
    <td><span>5.35%</span></td>
  </tr>
  <tr>
    <td><span>tflite::ops::builtin::elementwise::LogEval</span></td>
    <td><span>4.13%</span></td>
  </tr>
</tbody></table><p>The most expensive operation is matrix multiplication, which boils down to iteration within <a href="https://github.com/tensorflow/tensorflow/blob/v2.6.0/tensorflow/lite/kernels/internal/reference/portable_tensor_utils.cc#L119-L136">three nested loops</a>:</p>
            <pre><code>void PortableMatrixBatchVectorMultiplyAccumulate(const float* matrix,
                                                 int m_rows, int m_cols,
                                                 const float* vector,
                                                 int n_batch, float* result) {
  float* result_in_batch = result;
  for (int b = 0; b &lt; n_batch; b++) {
    const float* matrix_ptr = matrix;
    for (int r = 0; r &lt; m_rows; r++) {
      float dot_prod = 0.0f;
      const float* vector_in_batch = vector + b * m_cols;
      for (int c = 0; c &lt; m_cols; c++) {
        dot_prod += *matrix_ptr++ * *vector_in_batch++;
      }
      *result_in_batch += dot_prod;
     ++result_in_batch;
    }
  }
}</code></pre>
            <p>This doesn’t look very efficient and many <a href="https://en.algorithmica.org/hpc/algorithms/matmul/">blogs</a> and <a href="https://www.cs.utexas.edu/~flame/pubs/GotoTOMS_revision.pdf">research papers</a> have been written on how matrix multiplication can be optimized, which basically boils down to:</p><ul><li><p><b>Blocking</b>: Divide matrices into smaller blocks that fit into the cache, improving cache reuse and reducing memory access latency.</p></li><li><p><b>Vectorization</b>: Use SIMD instructions to process multiple data points in parallel, enhancing efficiency with vector registers.</p></li><li><p><b>Loop Unrolling</b>: Reduce loop control overhead and increase parallelism by executing multiple loop iterations simultaneously.</p></li></ul><p>To gain a better understanding of how these techniques work, we recommend watching this video, which brilliantly depicts the process of matrix multiplication:</p>
<p></p>
    <div>
      <h3>Tensorflow Lite with AVX2</h3>
      <a href="#tensorflow-lite-with-avx2">
        
      </a>
    </div>
    <p>TensorFlow Lite does, in fact, support SIMD matrix multiplication – we just need to enable it and re-compile the TensorFlow Lite library:</p>
            <pre><code>if [[ "$(uname -m)" == x86_64* ]]; then
    # On x86_64 target x86-64-v3 CPU to enable AVX2 and FMA.
    arguments+=("--copt=-march=x86-64-v3")
fi</code></pre>
            <p>After running profiler again using the SIMD-optimized TensorFlow Lite library:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6NmJhJoYG42ZZhU41m0Uj5/1d9fce45d44f98b41375d6a56f1a7cac/unnamed--7--5.png" />
            
            </figure><p>Top operations as per profiler output:</p>
<table><thead>
  <tr>
    <th><span>Function name</span></th>
    <th><span>% Time spent</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>tflite::tensor_utils::SseMatrixBatchVectorMultiplyAccumulateImpl</span></td>
    <td><span>43.01%</span></td>
  </tr>
  <tr>
    <td><span>tflite::tensor_utils::NeonAsymmetricQuantizeFloats</span></td>
    <td><span>22.46%</span></td>
  </tr>
  <tr>
    <td><span>tflite::reference_ops::MaximumMinimumBroadcastSlow</span></td>
    <td><span>7.82%</span></td>
  </tr>
  <tr>
    <td><span>tflite::optimized_ops::SoftmaxImpl</span></td>
    <td><span>6.61%</span></td>
  </tr>
  <tr>
    <td><span>tflite::ops::builtin::elementwise::LogEval</span></td>
    <td><span>4.63%</span></td>
  </tr>
</tbody></table><p>Matrix multiplication now uses <a href="https://github.com/tensorflow/tensorflow/blob/15ec568b5505727c940b651aeb2a9643b504086c/tensorflow/lite/kernels/internal/optimized/sse_tensor_utils.cc#L161-L199">AVX2 instructions</a>, which uses blocks of 8x8 to multiply and accumulate the multiplication result.</p><p>Proportionally, matrix multiplication and <a href="https://www.cloudflare.com/learning/ai/what-is-quantization/">quantization</a> operations take a similar time share when compared to non-SIMD version, however in absolute numbers, it’s almost twice as fast when SIMD optimizations are enabled:</p>
<table><thead>
  <tr>
    <th><span>Benchmark case</span></th>
    <th><span>Baseline time, μs</span></th>
    <th><span>SIMD time, μs</span></th>
    <th><span>Optimization</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>inference/avg-body-1000</span></td>
    <td><span>246.31</span></td>
    <td><span>130.07</span></td>
    <td><span>-47.19% or 1.89x</span></td>
  </tr>
</tbody></table><p>Quite a nice performance boost just from a few lines of build config change!</p>
    <div>
      <h3>Tensorflow Lite with XNNPACK</h3>
      <a href="#tensorflow-lite-with-xnnpack">
        
      </a>
    </div>
    <p>Tensorflow Lite comes with a useful benchmarking tool called <a href="https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/benchmark">benchmark_model</a>, which also has a built-in profiler.</p><p>The tool can be built locally using the command:</p>
            <pre><code>bazel build -j 4 --copt=-march=native -c opt tensorflow/lite/tools/benchmark:benchmark_model</code></pre>
            <p>After building, benchmarks were run with different settings:</p>
<table><thead>
  <tr>
    <th><span>Benchmark run</span></th>
    <th><span>Inference time, μs</span></th>
  </tr></thead>
<tbody>
  <tr>
    <td><span>benchmark_model --graph=model.tflite --num_runs=100000 --use_xnnpack=false</span></td>
    <td><span>105.61</span></td>
  </tr>
  <tr>
    <td><span>benchmark_model --graph=model.tflite --num_runs=100000 --use_xnnpack=true --xnnpack_force_fp16=true</span></td>
    <td><span>111.95</span></td>
  </tr>
  <tr>
    <td><span>benchmark_model --graph=model.tflite --num_runs=100000 --use_xnnpack=true</span></td>
    <td><span>49.05</span></td>
  </tr>
</tbody></table><p>Tensorflow Lite with XNNPACK enabled emerges as a leader, achieving ~50% latency reduction, when compared to the original Tensorflow Lite implementation.</p><p>More technical details about XNNPACK can be found in these blog posts:</p><ul><li><p><a href="https://blog.tensorflow.org/2022/06/Profiling-XNNPACK-with-TFLite.html">Profiling XNNPACK with TFLite</a></p></li><li><p><a href="https://blog.tensorflow.org/2024/04/faster-dynamically-quantized-inference-with-xnnpack.html">Faster Dynamically Quantized Inference with XNNPack</a></p></li></ul><p>Re-running benchmarks with XNNPack enabled, we get the following results:</p>
<table><thead>
  <tr>
    <th><span>Benchmark case</span></th>
    <th><span>Baseline time, μs</span><br /><span>TFLite 2.6.0</span></th>
    <th><span>SIMD time, μs</span><br /><span>TFLite 2.6.0</span></th>
    <th><span>SIMD time, μs</span><br /><span>TFLite 2.16.1</span></th>
    <th><span>SIMD + XNNPack time, μs</span><br /><span>TFLite 2.16.1</span></th>
    <th><span>Optimization</span></th>
  </tr>
</thead>
<tbody>
  <tr>
    <td><span>inference/avg-body-1000</span></td>
    <td><span>246.31</span></td>
    <td><span>130.07</span></td>
    <td><span>115.17</span></td>
    <td><span>56.22</span></td>
    <td><span>-77.17% or 4.38x</span></td>
  </tr>
</tbody></table><p>By upgrading TensorFlow Lite from 2.6.0 to 2.16.1 and enabling SIMD optimizations along with the XNNPack, we were able to decrease WAF ML model inference time more than <b>four-fold</b>, achieving a <b>77.17%</b> reduction.</p>
    <div>
      <h2>Caching inference result</h2>
      <a href="#caching-inference-result">
        
      </a>
    </div>
    <p>While making code faster through pre-processing and inference optimizations is great, it's even better when code doesn't need to run at all. This is where caching comes in. <a href="https://en.wikipedia.org/wiki/Amdahl%27s_law">Amdahl's Law</a> suggests that optimizing only parts of a program has diminishing returns. By avoiding redundant executions with caching, we can achieve significant performance gains beyond the limitations of traditional code optimization.</p><p>A simple key-value cache would quickly occupy all available memory on the server due to the high cardinality of URLs, HTTP headers, and HTTP bodies. However, because "everything on the Internet has an L-shape" or more specifically, follows a <a href="https://en.wikipedia.org/wiki/Zipf%27s_law">Zipf's law</a> distribution, we can optimize our caching strategy.</p><p><a href="https://en.wikipedia.org/wiki/Zipf%27s_law">Zipf</a>'<a href="https://en.wikipedia.org/wiki/Zipf%27s_law">s law</a> states that in many natural datasets, the frequency of any item is inversely proportional to its rank in the frequency table. In other words, a few items are extremely common, while the majority are rare. By analyzing our request data, we found that URLs, HTTP headers, and even HTTP bodies follow this distribution. For example, here is the user agent header frequency distribution against its rank:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/50OWcB7Buza1Jp77ePY75X/e25e66e7665fccc454df026e5ca37729/unnamed--8--3.png" />
            
            </figure><p>By caching the top-N most frequently occurring inputs and their corresponding inference results, we can ensure that both pre-processing and inference are skipped for the majority of requests. This is where the <a href="https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU">Least Recently Used (LRU)</a> cache comes in – frequently used items stay hot in the cache, while the least recently used ones are evicted.</p><p>We use <a href="https://github.com/openresty/lua-resty-lrucache">lua-resty-mlcache</a> as our caching solution, allowing us to share cached inference results between different Nginx workers via a shared memory dictionary. The LRU cache effectively exploits the <a href="https://en.wikipedia.org/wiki/Space%E2%80%93time_tradeoff">space-time trade-off</a>, where we trade a small amount of memory for significant CPU time savings.</p><p>This approach enables us to achieve a <b>~70%</b> cache hit ratio, significantly reducing latency further, as we will analyze in the final section below.</p>
    <div>
      <h2>Optimization results</h2>
      <a href="#optimization-results">
        
      </a>
    </div>
    <p>The optimizations discussed in this post were rolled out in several phases to ensure system correctness and stability.</p><p>First, we enabled SIMD optimizations for TensorFlow Lite, which reduced WAF ML total execution time by approximately <b>41.80%,</b> decreasing from <b>1519</b> ➔ <b>884 μs</b> on average.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/15SMdamloYpjyUy5ZwH9o0/ab4ec787f870d27a45ff513db1f696c8/unnamed--9--3.png" />
            
            </figure><p>Next, we upgraded TensorFlow Lite from version 2.6.0 to 2.16.1, enabled XNNPack, and implemented pre-processing optimizations. This further reduced WAF ML total execution time by <b>~40.77%</b>, bringing it down from <b>932</b> ➔ <b>552 μs</b> on average. The initial average time of 932 μs was slightly higher than the previous 884 μs due to the increased number of customers using this feature and the months that passed between changes.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/01EuBB1eVopVjUjWsVvwrK/0d908285bd4296d75f5c98918cf1a561/unnamed--10--3.png" />
            
            </figure><p>Lastly, we introduced LRU caching, which led to an additional reduction in WAF ML total execution time by <b>~50.18%</b>, from <b>552</b> ➔ <b>275 μs</b> on average.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6epSwp5jz4ZMaVfdwahZnN/8c23c1b6a90bf301f9e7f6566d4f3295/unnamed--11--3.png" />
            
            </figure><p>Overall, we cut WAF ML execution time by <b>~81.90%</b>, decreasing from <b>1519</b> ➔ <b>275 μs</b>, or <b>5.5x</b> faster!</p><p>To illustrate the significance of this: with Cloudflare’s average rate of 9.5 million requests per second passing through WAF ML, saving <b>1244 microseconds</b> per request equates to saving ~<b>32 years</b> of processing time every single day! That’s in addition to the savings of <b>523 microseconds</b> per request or <b>65 years</b> of processing time per day demonstrated last year in our <a href="/scalable-machine-learning-at-cloudflare">Every request, every microsecond: scalable machine learning at Cloudflare</a> post about our Bot Management product.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>We hope you enjoyed reading about how we made our WAF ML models go brrr, just as much as we enjoyed implementing these optimizations to bring scalable WAF ML to more customers on a truly global scale.</p><p>Looking ahead, we are developing even more sophisticated ML security models. These advancements aim to bring our <a href="https://www.cloudflare.com/application-services/products/waf/">WAF</a> and <a href="https://www.cloudflare.com/application-services/products/bot-management/">Bot Management</a> products to the next level, making them even more useful and effective for our customers.</p> ]]></content:encoded>
            <category><![CDATA[Machine Learning]]></category>
            <category><![CDATA[WAF]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Rust]]></category>
            <category><![CDATA[AI WAF]]></category>
            <category><![CDATA[WAF Attack Score]]></category>
            <guid isPermaLink="false">6y0Im81Uj2lKntznfYHfUY</guid>
            <dc:creator>Alex Bocharov</dc:creator>
        </item>
        <item>
            <title><![CDATA[Optimizing TCP for high WAN throughput while preserving low latency]]></title>
            <link>https://blog.cloudflare.com/optimizing-tcp-for-high-throughput-and-low-latency/</link>
            <pubDate>Fri, 01 Jul 2022 13:00:01 GMT</pubDate>
            <description><![CDATA[ In this post, we describe how we modified the Linux kernel to optimize for both low latency and high throughput concurrently ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Here at Cloudflare we're constantly working on improving our service. Our engineers are looking at hundreds of parameters of our traffic, making sure that we get better all the time.</p><p>One of the core numbers we keep a close eye on is HTTP request latency, which is important for many of our products. We regard latency spikes as bugs to be fixed. One example is the 2017 story of <a href="/the-sad-state-of-linux-socket-balancing/">"Why does one NGINX worker take all the load?"</a>, where we optimized our TCP Accept queues to improve overall latency of TCP sockets waiting for accept().</p><p>Performance tuning is a holistic endeavor, and we monitor and continuously improve a range of other performance metrics as well, including throughput. Sometimes, tradeoffs have to be made. Such a case occurred in 2015, when a latency spike was discovered in our processing of HTTP requests. The solution at the time was to set tcp_rmem to 4 MiB, which minimizes the amount of time the kernel spends on TCP collapse processing. It was this collapse processing that was causing the latency spikes. Later in this post we discuss TCP collapse processing in more detail.</p><p>The tradeoff is that using a low value for tcp_rmem limits TCP throughput over high latency links. The following graph shows the maximum throughput as a function of network latency for a window size of 2 MiB. Note that the 2 MiB corresponds to a tcp_rmem value of 4 MiB due to the tcp_adv_win_scale setting in effect at the time.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1mRCX3pD7U0Yzn1QeHDDhL/cef49d60e7e32c23d364cbbc65dd50e7/image10-5.png" />
            
            </figure><p>For the Cloudflare products then in existence, this was not a major problem, as connections terminate and content is served from nearby servers due to our BGP anycast routing.</p><p>Since then, we have added new products, such as Magic WAN, WARP, Spectrum, Gateway, and others. These represent new types of use cases and traffic flows.</p><p>For example, imagine you're a typical Magic WAN customer. You have connected all of your worldwide offices together using the Cloudflare global network. While Time to First Byte still matters, Magic WAN office-to-office traffic also needs good throughput. For example, a lot of traffic over these corporate connections will be file sharing using protocols such as SMB. These are <a href="https://en.wikipedia.org/wiki/Elephant_flow">elephant flows</a> over <a href="https://datatracker.ietf.org/doc/html/rfc1072">long fat networks</a>. Throughput is the metric every eyeball watches as they are downloading files.</p><p>We need to continue to provide world-class low latency while simultaneously providing high throughput over high-latency connections.</p><p>Before we begin, let’s introduce the players in our game.</p><p><b>TCP receive window</b> is the maximum number of unacknowledged user payload bytes the sender should transmit (bytes-in-flight) at any point in time. The size of the receive window can and does go up and down during the course of a TCP session. It is a mechanism whereby the receiver can tell the sender to stop sending if the sent packets cannot be successfully received because the receive buffers are full. It is this receive window that often limits throughput over high-latency networks.</p><p><b>net.ipv4.tcp_adv_win_scale</b> is a (non-intuitive) number used to account for the overhead needed by Linux to process packets. The receive window is specified in terms of user payload bytes. Linux needs additional memory beyond that to track other data associated with packets it is processing.</p><p>The value of the receive window changes during the lifetime of a TCP session, depending on a number of factors. The maximum value that the receive window can be is limited by the amount of free memory available in the receive buffer, according to this table:</p><table><tr><td><p>tcp_adv_win_scale</p></td><td><p>TCP window size</p></td></tr><tr><td><p>4</p></td><td><p>15/16 * available memory in receive buffer</p></td></tr><tr><td><p>3</p></td><td><p>⅞ * available memory in receive buffer</p></td></tr><tr><td><p>2</p></td><td><p>¾ * available memory in receive buffer</p></td></tr><tr><td><p>1</p></td><td><p>½ * available memory in receive buffer</p></td></tr><tr><td><p>0</p></td><td><p>available memory in receive buffer</p></td></tr><tr><td><p>-1</p></td><td><p>½ * available memory in receive buffer</p></td></tr><tr><td><p>-2</p></td><td><p>¼ * available memory in receive buffer</p></td></tr><tr><td><p>-3</p></td><td><p>⅛ * available memory in receive buffer</p></td></tr></table><p>We can intuitively (and correctly) understand that the amount of available memory in the receive buffer is the difference between the used memory and the maximum limit. But what is the maximum size a receive buffer can be? The answer is sk_rcvbuf.</p><p><b>sk_rcvbuf</b> is a per-socket field that specifies the maximum amount of memory that a receive buffer can allocate. This can be set programmatically with the socket option SO_RCVBUF. This can sometimes be useful to do, for localhost TCP sessions, for example, but in general the use of SO_RCVBUF is not recommended.</p><p>So how is sk_rcvbuf set? The most appropriate value for that depends on the latency of the TCP session and other factors. This makes it difficult for L7 applications to know how to set these values correctly, as they will be different for every TCP session. The solution to this problem is Linux autotuning.</p>
    <div>
      <h2>Linux autotuning</h2>
      <a href="#linux-autotuning">
        
      </a>
    </div>
    <p>Linux autotuning is logic in the Linux kernel that adjusts the buffer size limits and the receive window based on actual packet processing. It takes into consideration a number of things including TCP session <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">RTT</a>, L7 read rates, and the amount of available host memory.</p><p>Autotuning can sometimes seem mysterious, but it is actually fairly straightforward.</p><p>The central idea is that Linux can track the rate at which the local application is reading data off of the receive queue. It also knows the session RTT. Because Linux knows these things, it can automatically increase the buffers and receive window until it reaches the point at which the application layer or network bottleneck links are the constraint on throughput (and not host buffer settings). At the same time, autotuning prevents slow local readers from having excessively large receive queues. The way autotuning does that is by limiting the receive window and its corresponding receive buffer to an appropriate size for each socket.</p><p>The values set by autotuning can be seen via the Linux “<code>ss</code>” command from the <code>iproute</code> package (e.g. “<code>ss -tmi</code>”).  The relevant output fields from that command are:</p><p><b>Recv-Q</b> is the number of user payload bytes not yet read by the local application.</p><p><b>rcv_ssthresh</b> is the window clamp, a.k.a. the maximum receive window size. This value is not known to the sender. The sender receives only the current window size, via the TCP header field. A closely-related field in the kernel, tp-&gt;window_clamp, is the maximum window size allowable based on the amount of available memory. rcv_ssthresh is the receiver-side slow-start threshold value.</p><p><b>skmem_r</b> is the actual amount of memory that is allocated, which includes not only user payload (Recv-Q) but also additional memory needed by Linux to process the packet (packet metadata). This is known within the kernel as sk_rmem_alloc.</p><p>Note that there are other buffers associated with a socket, so skmem_r does not represent the total memory that a socket might have allocated. Those other buffers are not involved in the issues presented in this post.</p><p><b>skmem_rb</b> is the maximum amount of memory that could be allocated by the socket for the receive buffer. This is higher than rcv_ssthresh to account for memory needed for packet processing that is not packet data. Autotuning can increase this value (up to tcp_rmem max) based on how fast the L7 application is able to read data from the socket and the RTT of the session. This is known within the kernel as sk_rcvbuf.</p><p><b>rcv_space</b> is the high water mark of the rate of the local application reading from the receive buffer during any RTT. This is used internally within the kernel to adjust sk_rcvbuf.</p><p>Earlier we mentioned a setting called tcp_rmem. <b>net.ipv4.tcp_rmem</b> consists of three values, but in this document we are always referring to the third value (except where noted). It is a global setting that specifies the maximum amount of memory that any TCP receive buffer can allocate, i.e. the maximum permissible value that autotuning can use for sk_rcvbuf. This is essentially just a failsafe for autotuning, and under normal circumstances should play only a minor role in TCP memory management.</p><p>It’s worth mentioning that receive buffer memory is not preallocated. Memory is allocated based on actual packets arriving and sitting in the receive queue. It’s also important to realize that filling up a receive queue is not one of the criteria that autotuning uses to increase sk_rcvbuf. Indeed, preventing this type of excessive buffering (<a href="https://en.wikipedia.org/wiki/Bufferbloat">bufferbloat</a>) is one of the benefits of autotuning.</p>
    <div>
      <h2>What’s the problem?</h2>
      <a href="#whats-the-problem">
        
      </a>
    </div>
    <p>The problem is that we must have a large TCP receive window for high <a href="https://en.wikipedia.org/wiki/Bandwidth-delay_product">BDP</a> sessions. This is directly at odds with the latency spike problem mentioned above.</p><p>Something has to give. The laws of physics (speed of light in glass, etc.) dictate that we must use large window sizes. There is no way to get around that. So we are forced to solve the latency spikes differently.</p>
    <div>
      <h2>A brief recap of the latency spike problem</h2>
      <a href="#a-brief-recap-of-the-latency-spike-problem">
        
      </a>
    </div>
    <p>Sometimes a TCP session will fill up its receive buffers. When that happens, the Linux kernel will attempt to reduce the amount of memory the receive queue is using by performing what amounts to a “defragmentation” of memory. This is called collapsing the queue. Collapsing the queue takes time, which is what drives up HTTP request latency.</p><p>We do not want to spend time collapsing TCP queues.</p><p>Why do receive queues fill up to the point where they hit the maximum memory limit? The usual situation is when the local application starts out reading data from the receive queue at one rate (triggering autotuning to raise the max receive window), followed by the local application slowing down its reading from the receive queue. This is valid behavior, and we need to handle it correctly.</p>
    <div>
      <h2>Selecting sysctl values</h2>
      <a href="#selecting-sysctl-values">
        
      </a>
    </div>
    <p>Before exploring solutions, let’s first decide what we need as the maximum TCP window size.</p><p>As we have seen above in the discussion about BDP, the window size is determined based upon the RTT and desired throughput of the connection.</p><p>Because Linux autotuning will adjust correctly for sessions with lower RTTs and bottleneck links with lower throughput, all we need to be concerned about are the maximums.</p><p>For latency, we have chosen 300 ms as the maximum expected latency, as that is the measured latency between our Zurich and Sydney facilities. It seems reasonable enough as a worst-case latency under normal circumstances.</p><p>For throughput, although we have very fast and modern hardware on the Cloudflare global network, we don’t expect a single TCP session to saturate the hardware. We have arbitrarily chosen 3500 mbps as the highest supported throughput for our highest latency TCP sessions.</p><p>The calculation for those numbers results in a BDP of 131MB, which we round to the more aesthetic value of 128 MiB.</p><p>Recall that allocation of TCP memory includes metadata overhead in addition to packet data. The ratio of actual amount of memory allocated to user payload size varies, depending on NIC driver settings, packet size, and other factors. For full-sized packets on some of our hardware, we have measured average allocations up to 3 times the packet data size. In order to reduce the frequency of TCP collapse on our servers, we set tcp_adv_win_scale to -2. From the table above, we know that the max window size will be ¼ of the max buffer space.</p><p>We end up with the following sysctl values:</p>
            <pre><code>net.ipv4.tcp_rmem = 8192 262144 536870912
net.ipv4.tcp_wmem = 4096 16384 536870912
net.ipv4.tcp_adv_win_scale = -2</code></pre>
            <p>A tcp_rmem of 512MiB and tcp_adv_win_scale of -2 results in a maximum window size that autotuning can set of 128 MiB, our desired value.</p>
    <div>
      <h2>Disabling TCP collapse</h2>
      <a href="#disabling-tcp-collapse">
        
      </a>
    </div>
    <p>Patient: Doctor, it hurts when we collapse the TCP receive queue.</p><p>Doctor: Then don’t do that!</p><p>Generally speaking, when a packet arrives at a buffer when the buffer is full, the packet gets dropped. In the case of these receive buffers, Linux tries to “save the packet” when the buffer is full by collapsing the receive queue. Frequently this is successful, but it is not guaranteed to be, and it takes time.</p><p>There are no problems created by immediately just dropping the packet instead of trying to save it. The receive queue is full anyway, so the local receiver application still has data to read. The sender’s congestion control will notice the drop and/or ZeroWindow and will respond appropriately. Everything will continue working as designed.</p><p>At present, there is no setting provided by Linux to disable the TCP collapse. We developed an in-house patch to the kernel to disable the TCP collapse logic.</p>
    <div>
      <h2>Kernel patch – Attempt #1</h2>
      <a href="#kernel-patch-attempt-1">
        
      </a>
    </div>
    <p>The kernel patch for our first attempt was straightforward. At the top of tcp_try_rmem_schedule(), if the memory allocation fails, we simply return (after pred_flag = 0 and tcp_sack_reset()), thus completely skipping the tcp_collapse and related logic.</p><p>It didn’t work.</p><p>Although we eliminated the latency spikes while using large buffer limits, we did not observe the throughput we expected.</p><p>One of the realizations we made as we investigated the situation was that standard network benchmarking tools such as iperf3 and similar do not expose the problem we are trying to solve. iperf3 does not fill the receive queue. Linux autotuning does not open the TCP window large enough. Autotuning is working perfectly for our well-behaved benchmarking program.</p><p>We need application-layer software that is slightly less well-behaved, one that exercises the autotuning logic under test. So we wrote one.</p>
    <div>
      <h2>A new benchmarking tool</h2>
      <a href="#a-new-benchmarking-tool">
        
      </a>
    </div>
    <p>Anomalies were seen during our “Attempt #1” that negatively impacted throughput. The anomalies were seen only under certain specific conditions, and we realized we needed a better benchmarking tool to detect and measure the performance impact of those anomalies.</p><p>This tool has turned into an invaluable resource during the development of this patch and raised confidence in our solution.</p><p>It consists of two Python programs. The reader opens a TCP session to the daemon, at which point the daemon starts sending user payload as fast as it can, and never stops sending.</p><p>The reader, on the other hand, starts and stops reading in a way to open up the TCP receive window wide open and then repeatedly causes the buffers to fill up completely. More specifically, the reader implemented this logic:</p><ol><li><p>reads as fast as it can, for five seconds</p><ul><li><p>this is called fast mode</p></li><li><p>opens up the window</p></li></ul></li><li><p>calculates 5% of the high watermark of the bytes reader during any previous one second</p></li><li><p>for each second of the next 15 seconds:</p><ul><li><p>this is called slow mode</p></li><li><p>reads that 5% number of bytes, then stops reading</p></li><li><p>sleeps for the remainder of that particular second</p></li><li><p>most of the second consists of no reading at all</p></li></ul></li><li><p>steps 1-3 are repeated in a loop three times, so the entire run is 60 seconds</p></li></ol><p>This has the effect of highlighting any issues in the handling of packets when the buffers repeatedly hit the limit.</p>
    <div>
      <h2>Revisiting default Linux behavior</h2>
      <a href="#revisiting-default-linux-behavior">
        
      </a>
    </div>
    <p>Taking a step back, let’s look at the default Linux behavior. The following is kernel v5.15.16.</p><table><tr><td><p>NIC speed (mbps)</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse</p></td><td><p>TCP window (MiB)</p></td><td><p>buffer metadata to user payload ratio</p></td><td><p>Prune Called</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>Test Result</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>0</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>256</p></td><td><p>1</p></td><td><p>0</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>170</p></td><td><p>2</p></td><td><p>0</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>24</p></td><td><p>490K</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>146</p></td><td><p>3</p></td><td><p>0</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>57</p></td><td><p>616K</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr><tr><td><p>1000</p></td><td><p>300</p></td><td><p>137</p></td><td><p>4</p></td><td><p>0</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>74</p></td><td><p>803K</p></td><td><p>0</p></td><td><p>0</p></td><td><p>GOOD</p></td></tr></table><p>The Linux kernel is effective at freeing up space in order to make room for incoming packets when the receive buffer memory limit is hit. As documented previously, the cost for saving these packets (i.e. not dropping them) is latency.</p><p>However, the latency spikes, in <i>milliseconds</i>, for tcp_try_rmem_schedule(), are:</p><p>tcp_rmem 170 MiB, tcp_adv_win_scale +2 (170p2):</p>
            <pre><code>@ms:
[0]       27093 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[1]           0 |
[2, 4)        0 |
[4, 8)        0 |
[8, 16)       0 |
[16, 32)      0 |
[32, 64)     16 |</code></pre>
            <p>tcp_rmem 146 MiB, tcp_adv_win_scale +3 (146p3):</p>
            <pre><code>@ms:
(..., 16)  25984 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[16, 20)       0 |
[20, 24)       0 |
[24, 28)       0 |
[28, 32)       0 |
[32, 36)       0 |
[36, 40)       0 |
[40, 44)       1 |
[44, 48)       6 |
[48, 52)       6 |
[52, 56)       3 |</code></pre>
            <p>tcp_rmem 137 MiB, tcp_adv_win_scale +4 (137p4):</p>
            <pre><code>@ms:
(..., 16)  37222 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[16, 20)       0 |
[20, 24)       0 |
[24, 28)       0 |
[28, 32)       0 |
[32, 36)       0 |
[36, 40)       1 |
[40, 44)       8 |
[44, 48)       2 |</code></pre>
            <p>These are the latency spikes we cannot have on the Cloudflare global network.</p>
    <div>
      <h2>Kernel patch – Attempt #2</h2>
      <a href="#kernel-patch-attempt-2">
        
      </a>
    </div>
    <p>So the “something” that was not working in Attempt #1 was that the receive queue memory limit was hit early on as the flow was just ramping up (when the values for sk_rmem_alloc and sk_rcvbuf were small, ~800KB). This occurred at about the two second mark for 137p4 test (about 2.25 seconds for 170p2).</p><p>In hindsight, we should have noticed that tcp_prune_queue() actually raises sk_rcvbuf when it can. So we modified the patch in response to that, added a guard to allow the collapse to execute when sk_rmem_alloc is less than the threshold value.</p><p><code>net.ipv4.tcp_collapse_max_bytes = 6291456</code></p><p>The next section discusses how we arrived at this value for tcp_collapse_max_bytes.</p><p>The patch is available <a href="https://github.com/cloudflare/linux/blob/master/patches/0014-add-a-sysctl-to-enable-disable-tcp_collapse-logic.patch">here</a>.</p><p>The results with the new patch are as follows:</p><p>oscil – 300ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>12</p></td><td><p>1-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>11</p></td><td><p>1-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>9</p></td><td><p>86</p></td><td><p>11</p></td><td><p>1-941</p><p>36-605</p><p>1-298</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>7</p></td><td><p>1550</p></td><td><p>16</p></td><td><p>1-940</p><p>2-82</p><p>292-395</p></td></tr><tr><td><p>oscil reader</p></td><td><p>300</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>10</p></td><td><p>3020</p></td><td><p>9</p></td><td><p>1-940</p><p>2-13</p><p>13-33</p></td></tr></table><p>oscil – 20ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>13</p></td><td><p>795-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>13</p></td><td><p>795-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>8</p></td><td><p>795-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>795-941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>20</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>4</p></td><td><p>196</p></td><td><p>12</p></td><td><p>795-941</p><p>13-941</p><p>941</p></td></tr></table><p>oscil – 0ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>9</p></td><td><p>941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>22</p></td><td><p>941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>8</p></td><td><p>941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>10</p></td><td><p>941</p><p>941</p><p>941</p></td></tr><tr><td><p>oscil reader</p></td><td><p>0.3</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>10</p></td><td><p>941</p><p>941</p><p>941</p></td></tr></table><p>iperf3 – 300 ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>9</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>11</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>300</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr></table><p>iperf3 – 20 ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>15</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>20</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr></table><p>iperf3 – 0ms tests</p><table><tr><td><p>Test</p></td><td><p>RTT (ms)</p></td><td><p>tcp_rmem (MiB)</p></td><td><p>tcp_adv_win_scale</p></td><td><p>tcp_disable_collapse (MiB)</p></td><td><p>NIC speed (mbps)</p></td><td><p>TCP window (MiB)</p></td><td><p>real buffer metadata to user payload ratio</p></td><td><p>RcvCollapsed</p></td><td><p>RcvQDrop</p></td><td><p>OFODrop</p></td><td><p>max latency (us)</p><p>
</p></td><td><p>Test Result</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>512</p></td><td><p>-2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>4</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>256</p></td><td><p>1</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>2</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>14</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>170</p></td><td><p>2</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.33</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>146</p></td><td><p>3</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.14</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>7</p></td><td><p>941</p></td></tr><tr><td><p>iperf3</p></td><td><p>0.3</p></td><td><p>137</p></td><td><p>4</p></td><td><p>6</p></td><td><p>1000</p></td><td><p>128</p></td><td><p>1.07</p></td><td><p>0</p></td><td><p>0</p></td><td><p>0</p></td><td><p>6</p></td><td><p>941</p></td></tr></table><p>All tests are successful.</p>
    <div>
      <h2>Setting tcp_collapse_max_bytes</h2>
      <a href="#setting-tcp_collapse_max_bytes">
        
      </a>
    </div>
    <p>In order to determine this setting, we need to understand what the biggest queue we <i>can</i> collapse without incurring unacceptable latency.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6PfP9U02g39dedYXSqz0H2/8b61121e3ffe7f102fea0d682d902c56/image8-12.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/14e9NHQT0aciu7bJvpLyIM/5df55a86e2a24c7c7501c2c80a38e847/image7-13.png" />
            
            </figure><p>Using 6 MiB should result in a maximum latency of no more than 2 ms.</p>
    <div>
      <h2>Cloudflare production network results</h2>
      <a href="#cloudflare-production-network-results">
        
      </a>
    </div>
    
    <div>
      <h3>Current production settings (“Old”)</h3>
      <a href="#current-production-settings-old">
        
      </a>
    </div>
    
            <pre><code>net.ipv4.tcp_rmem = 8192 2097152 16777216
net.ipv4.tcp_wmem = 4096 16384 33554432
net.ipv4.tcp_adv_win_scale = -2
net.ipv4.tcp_collapse_max_bytes = 0
net.ipv4.tcp_notsent_lowat = 4294967295</code></pre>
            <p>tcp_collapse_max_bytes of 0 means that the custom feature is disabled and that the vanilla kernel logic is used for TCP collapse processing.</p>
    <div>
      <h3>New settings under test (“New”)</h3>
      <a href="#new-settings-under-test-new">
        
      </a>
    </div>
    
            <pre><code>net.ipv4.tcp_rmem = 8192 262144 536870912
net.ipv4.tcp_wmem = 4096 16384 536870912
net.ipv4.tcp_adv_win_scale = -2
net.ipv4.tcp_collapse_max_bytes = 6291456
net.ipv4.tcp_notsent_lowat = 131072</code></pre>
            <p>The tcp_notsent_lowat setting is discussed in the last section of this post.</p><p>The middle value of tcp_rmem was changed as a result of separate work that found that Linux autotuning was setting receive buffers too high for localhost sessions. This updated setting reduces TCP memory usage for those sessions, but does not change anything about the type of TCP sessions that is the focus of this post.</p><p>For the following benchmarks, we used non-Cloudflare host machines in Iowa, US, and Melbourne, Australia performing data transfers to the Cloudflare data center in Marseille, France. In Marseille, we have some hosts configured with the existing production settings, and others with the system settings described in this post. Software used is perf3 version 3.9, kernel 5.15.32.</p>
    <div>
      <h3>Throughput results</h3>
      <a href="#throughput-results">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3GkgLi4X0vuXMl7rOEWvZK/e64146a2a197174ab5e563b34b8ba9e8/image3-36.png" />
            
            </figure><table><tr><td><p>
</p></td><td><p>RTT</p><p>(ms)</p></td><td><p>Throughput with Current Settings</p><p>(mbps)</p></td><td><p>Throughput with</p><p>New Settings</p><p>(mbps)</p></td><td><p>Increase</p><p>Factor</p></td></tr><tr><td><p>Iowa to</p><p>Marseille</p></td><td><p>121 </p></td><td><p>276</p></td><td><p>6600</p></td><td><p>24x</p></td></tr><tr><td><p>Melbourne to Marseille</p></td><td><p>282</p></td><td><p>120</p></td><td><p>3800</p></td><td><p>32x</p></td></tr></table><p><b>Iowa-Marseille throughput</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2WvQmKxKD8buJU17GU15Kw/798330bea25ad3dedfe5d1beb8578363/image6-16.png" />
            
            </figure><p><b>Iowa-Marseille receive window and bytes-in-flight</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2V2iFmp0s1h8vH4SyrzFQS/5b1118af5ff1ee2116aacfe7c9d60927/image2-51.png" />
            
            </figure><p><b>Melbourne-Marseille throughput</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/PnqeHPMJbmCuyGvt9UjtA/037328ab56686611fce568409e0c8336/image9-10.png" />
            
            </figure><p><b>Melbourne-Marseille receive window and bytes-in-flight</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4H5cZs3oyG7KiTKL9m4b7R/ff9563e9d1a69da0adb653587dd1f336/image5-21.png" />
            
            </figure><p>Even with the new settings in place, the Melbourne to Marseille performance is limited by the receive window on the Cloudflare host. This means that further adjustments to these settings yield even higher throughput.</p>
    <div>
      <h3>Latency results</h3>
      <a href="#latency-results">
        
      </a>
    </div>
    <p>The Y-axis on these charts are the 99th percentile time for TCP collapse in seconds.</p><p><b>Cloudflare hosts in Marseille running the current production settings</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1jxkFikBYH12eNxfHcHwXR/c8c3e183a28b84a4f93bc725309d5eff/image11-4.png" />
            
            </figure><p><b>Cloudflare hosts in Marseille running the new settings</b></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6dqk8mtvEpghINGK0bfKUz/cec115db36d4a927ca4764bfe022cad8/image1-59.png" />
            
            </figure><p>The takeaway in looking at these graphs is that maximum TCP collapse time for the new settings is no worse than with the current production settings. This is the desired result.</p>
    <div>
      <h3>Send Buffers</h3>
      <a href="#send-buffers">
        
      </a>
    </div>
    <p>What we have shown so far is that the receiver side seems to be working well, but what about the sender side?</p><p>As part of this work, we are setting tcp_wmem max to 512 MiB. For oscillating reader flows, this can cause the send buffer to become quite large. This represents bufferbloat and wasted kernel memory, both things that nobody likes or wants.</p><p>Fortunately, there is already a solution: <b>tcp_notsent_lowat</b>. This setting limits the size of unsent bytes in the write queue. More details can be found at <a href="https://lwn.net/Articles/560082/">https://lwn.net/Articles/560082</a>.</p><p>The results are significant:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3yXWQ4mWrhKWV8Tzv0bUCb/75339710d08b943a8cb884a68377d4ec/image4-29.png" />
            
            </figure><p>The RTT for these tests was 466ms. Throughput is not negatively affected. Throughput is at full wire speed in all cases (1 Gbps). Memory usage is as reported by /proc/net/sockstat, TCP mem.</p><p>Our web servers already set tcp_notsent_lowat to 131072 for its sockets. All other senders are using 4 GiB, the default value. We are changing the sysctl so that 131072 is in effect for all senders running on the server.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>The goal of this work is to open the throughput floodgates for high BDP connections while simultaneously ensuring very low HTTP request latency.</p><p>We have accomplished that goal.</p><p>...<i>We protect </i><a href="https://www.cloudflare.com/network-services/"><i>entire corporate networks</i></a><i>, help customers build </i><a href="https://workers.cloudflare.com/"><i>Internet-scale applications efficiently</i></a><i>, accelerate any </i><a href="https://www.cloudflare.com/performance/accelerate-internet-applications/"><i>website or Internet application</i></a><i>, ward off </i><a href="https://www.cloudflare.com/ddos/"><i>DDoS attacks</i></a><i>, keep </i><a href="https://www.cloudflare.com/application-security/"><i>hackers at bay</i></a><i>, and can help you on </i><a href="https://www.cloudflare.com/products/zero-trust/"><i>your journey to Zero Trust</i></a><i>.</i></p><p><i>Visit </i><a href="https://1.1.1.1/"><i>1.1.1.1</i></a><i> from any device to get started with our free app that makes your Internet faster and safer.To learn more about our mission to help build a better Internet, start </i><a href="https://www.cloudflare.com/learning/what-is-cloudflare/"><i>here</i></a><i>. If you’re looking for a new career direction, check out </i><a href="http://cloudflare.com/careers"><i>our open positions</i></a><i>.</i></p> ]]></content:encoded>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[TCP]]></category>
            <category><![CDATA[Latency]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">BcOgofewzZGwenQrFsVMq</guid>
            <dc:creator>Mike Freemon</dc:creator>
        </item>
        <item>
            <title><![CDATA[Computing Euclidean distance on 144 dimensions]]></title>
            <link>https://blog.cloudflare.com/computing-euclidean-distance-on-144-dimensions/</link>
            <pubDate>Fri, 18 Dec 2020 12:00:00 GMT</pubDate>
            <description><![CDATA[ Last year we deployed a CSAM image scanning tool. This is so cool! Image processing is always hard, and deploying a real image identification system at a Cloudflare scale is no small achievement! But we hit a problem - the matching algorithm was too slow for our needs. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4rNxjRfxGzhJI3MAVokLgA/8b8be5359634c3c75ecd5cfc696b6072/image1-62.png" />
            
            </figure><p>Late last year I read a blog post about <a href="/the-csam-scanning-tool/">our CSAM image scanning tool</a>. I remember thinking: this is so cool! Image processing is always hard, and deploying a real image identification system at Cloudflare is no small achievement!</p><p>Some time later, I was chatting with Kornel: "We have all the pieces in the image processing pipeline, but we are struggling with the performance of one component." Scaling to Cloudflare needs ain't easy!</p><p>The problem was in the speed of the matching algorithm itself. Let me elaborate. As John explained in his blog post <a href="/the-csam-scanning-tool">on the CSAM Scanning Tool</a>, the image matching algorithm creates a fuzzy hash from a processed image. The hash is exactly 144 bytes long. For example, it might look like this:</p>
            <pre><code>00e308346a494a188e1043333147267a 653a16b94c33417c12b433095c318012
5612442030d14a4ce82c623f4e224733 1dd84436734e4a5d6e25332e507a8218
6e3b89174e30372d</code></pre>
            <p>The hash is designed to be used in a fuzzy matching algorithm that can find "nearby", related images. The specific algorithm is well defined, but making it fast is left to the programmer — and at Cloudflare we need the matching to be done super fast. We want to match thousands of hashes per second, of images passing through our network, against a database of millions of known images. To make this work, we need to seriously optimize the matching algorithm.</p>
    <div>
      <h3>Naive quadratic algorithm</h3>
      <a href="#naive-quadratic-algorithm">
        
      </a>
    </div>
    <p>The first algorithm that comes to mind has <code>O(K*N)</code> complexity: for each query, go through every hash in the database. In naive implementation, this creates a lot of work. But how much work exactly?</p><p>First, we need to explain how fuzzy matching works.</p><p>Given a query hash, the fuzzy match is the "closest" hash in a database. This requires us to define a distance. We treat each hash as a vector containing 144 numbers, identifying a point in a 144-dimensional space. Given two such points, we can calculate the distance using the standard Euclidean formula.</p><p>For our particular problem, though, we are interested in the "closest" match in a database only if the distance is lower than some predefined threshold. Otherwise, when the distance is large,  we can assume the images aren't similar. This is the expected result — most of our queries will not have a related image in the database.</p><p>The <a href="https://en.wikipedia.org/wiki/Euclidean_distance">Euclidean distance</a> equation used by the algorithm is standard:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5p3VcvTiFKgdwx5xLkRRWv/33fa46b931b1764cf6e51df3198e0a23/image3-41.png" />
            
            </figure><p>To calculate the distance between two 144-byte hashes, we take each byte, calculate the delta, square it, sum it to an accumulator, do a square root, and ta-dah! We have the distance!</p><p><a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/mmdist-naive.c#L11-L20">Here's how to count the squared distance in C</a>:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4lwPTgH8PmEYB8mMyOhbsH/f660b81e92c9ff46a422bdbf1af18321/image4-24.png" />
            
            </figure><p>This function returns the squared distance. We avoid computing the actual distance to save us from running the square root function - it's slow. Inside the code, for performance and simplicity, we'll mostly operate on the squared value. We don't need the actual distance value, we just need to find the vector with the smallest one. In our case it doesn't matter if we'll compare distances or squared distances!</p><p>As you can see, fuzzy matching is basically a standard problem of finding the closest point in a multi-dimensional space. Surely this has been solved in the past — but let's not jump ahead.</p><p>While this code might be simple, we expect it to be rather slow. Finding the smallest hash distance in a database of, say, 1M entries, would require going over all records, and would need at least:</p><ol><li><p>144 * 1M subtractions</p></li><li><p>144 * 1M multiplications</p></li><li><p>144 * 1M additions</p></li></ol><p>And more. This alone adds up to 432 million operations! How does it look in practice? To illustrate this blog post <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2020-12-mmdist">we prepared a full test suite</a>. The large database of known hashes can be <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/generate.c">well emulated by random data</a>. The query hashes can't be random and must be slightly more sophisticated, otherwise the exercise wouldn't be that interesting. We generated the test smartly by byte-swaps of the actual data from the database — this allows us to precisely control the distance between test hashes and database hashes. <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/gentest.py">Take a look at the scripts for details</a>. Here's our first run of <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/mmdist-naive.c">the first, naive, algorithm</a>:</p>
            <pre><code>$ make naive
&lt; test-vector.txt ./mmdist-naive &gt; test-vector.tmp
Total: 85261.833ms, 1536 items, avg 55.509ms per query, 18.015 qps</code></pre>
            <p>We matched 1,536 test hashes against a database of 1 million random vectors in 85 seconds. It took 55ms of CPU time on average to find the closest neighbour. This is rather slow for our needs.</p>
    <div>
      <h3>SIMD for help</h3>
      <a href="#simd-for-help">
        
      </a>
    </div>
    <p>An obvious improvement is to use more complex SIMD instructions. SIMD is a way to instruct the CPU to process multiple data points using one instruction. This is a perfect strategy when dealing with vector problems — as is the case for our task.</p><p>We settled on using AVX2, with 256 bit vectors. We did this for a simple reason — newer AVX versions are not supported by our AMD CPUs. Additionally, in the past, we were <a href="/on-the-dangers-of-intels-frequency-scaling/">not thrilled by the AVX-512 frequency scaling</a>.</p><p>Using AVX2 is easier said than done. There is no single instruction to count Euclidean distance between two uint8 vectors! The fastest way of counting the full distance of two 144-byte vectors <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/mmdist-naive-avx2.c#L13-L36">with AVX2 we could find is authored</a> by <a href="https://twitter.com/thecomp1ler">Vlad</a>:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7yFtHcZSRvyub5vHtjDXKE/54596189ea43193a5fd51653ec73daea/image2-39.png" />
            
            </figure><p>It’s actually simpler than it looks: load 16 bytes, convert vector from uint8 to int16, subtract the vector, store intermediate sums as int32, repeat. At the end, we need to do complex 4 instructions to extract the partial sums into the final sum. <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/mmdist-naive-avx2.c">This AVX2 code</a> improves the performance around 3x:</p>
            <pre><code>$ make naive-avx2 
Total: 25911.126ms, 1536 items, avg 16.869ms per query, 59.280 qps</code></pre>
            <p>We measured 17ms per item, which is still below our expectations. Unfortunately, we can't push it much further without major changes. The problem is that this code is limited by memory bandwidth. The measurements come from my Intel i7-5557U CPU, which has the max theoretical memory bandwidth of just 25GB/s. The database of 1 million entries takes 137MiB, so it takes at least 5ms to feed the database to my CPU. With this naive algorithm we won't be able to go below that.</p>
    <div>
      <h3>Vantage Point Tree algorithm</h3>
      <a href="#vantage-point-tree-algorithm">
        
      </a>
    </div>
    <p>Since the naive brute force approach failed, we tried using more sophisticated algorithms. My colleague <a href="https://github.com/kornelski/vpsearch">Kornel Lesiński implemented</a> a super cool <a href="https://en.wikipedia.org/wiki/Vantage-point_tree">Vantage Point algorithm</a>. After a few ups and downs, optimizations and rewrites, we gave up. Our problem turned out to be unusually hard for this kind of algorithm.</p><p>We observed <a href="https://en.wikipedia.org/wiki/Curse_of_dimensionality">"the curse of dimensionality"</a>. Space partitioning algorithms don't work well in problems with large dimensionality — and in our case, we have an enormous number of 144 dimensions. K-D trees are doomed. Locality-sensitive hashing is also doomed. It's a bizarre situation in which the space is unimaginably vast, but everything is close together. The volume of the space is a 347-digit-long number, but the maximum distance between points is just 3060 - sqrt(255*255*144).</p><p>Space partitioning algorithms are fast, because they gradually narrow the search space as they get closer to finding the closest point. But in our case, the common query is never close to any point in the set, so the search space can’t be narrowed to a meaningful degree.</p><p>A VP-tree was a promising candidate, because it operates only on distances, subdividing space into near and far partitions, like a binary tree. When it has a close match, it can be very fast, and doesn't need to visit more than <code>O(log(N))</code> nodes. For non-matches, its speed drops dramatically. The algorithm ends up visiting nearly half of the nodes in the tree. Everything is close together in 144 dimensions! Even though the algorithm avoided visiting more than half of the nodes in the tree, the cost of visiting remaining nodes was higher, so the search ended up being slower overall.</p>
    <div>
      <h3>Smarter brute force?</h3>
      <a href="#smarter-brute-force">
        
      </a>
    </div>
    <p>This experience got us thinking. Since space partitioning algorithms can't narrow down the search, and still need to go over a very large number of items, maybe we should focus on going over all the hashes, extremely quickly. We must be smarter about memory bandwidth though — it was the limiting factor in the naive brute force approach before.</p><p>Perhaps we don't need to fetch all the data from memory.</p>
    <div>
      <h3>Short distance</h3>
      <a href="#short-distance">
        
      </a>
    </div>
    <p>The breakthrough came from the realization that we don't need to count the full distance between hashes. Instead, we can compute only a subset of dimensions, say 32 out of the total of 144. If this distance is already large, then there is no need to compute the full one! Computing more points is not going to reduce the Euclidean distance.</p><p>The proposed algorithm works as follows:</p><p>1. Take the query hash and extract a 32-byte short hash from it</p><p>2. Go over all the 1 million 32-byte short hashes from the database. They must be densely packed in the memory to allow the CPU to perform good prefetching and avoid reading data we won't need.</p><p>3. If the distance of the 32-byte short hash is greater or equal a best score so far, move on</p><p>4. Otherwise, investigate the hash thoroughly and compute the full distance.</p><p>Even though this algorithm needs to do less arithmetic and memory work, it's not faster than the previous naive one. See <code>make short-avx2</code>. The problem is: we still need to compute a full distance for hashes that are promising, and there are quite a lot of them. Computing the full distance for promising hashes adds enough work, both in ALU and memory latency, to offset the gains of this algorithm.</p><p>There is one detail of our particular application of the image matching problem that will help us a lot moving forward. As we described earlier, the problem is less about finding the closest neighbour and more about proving that the neighbour with a reasonable distance doesn't exist. Remember — in practice, we don't expect to find many matches! We expect almost every image we feed into the algorithm to be unrelated to image hashes stored in the database.</p><p>It's sufficient for our algorithm to prove that no neighbour exists within a predefined distance threshold. Let's assume we are not interested in hashes more distant than, say, 220, which squared is 48,400. <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/mmdist-short-avx2.c">This makes our short-distance algorithm variation</a> work much better:</p>
            <pre><code>$ make short-avx2-threshold
Total: 4994.435ms, 1536 items, avg 3.252ms per query, 307.542 qps</code></pre>
            
    <div>
      <h3>Origin distance variation</h3>
      <a href="#origin-distance-variation">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/rp4oarmXrL2JtieXZ4VWB/56dd69e834fa27e7c0c5cb0eb7544a58/image6-11.png" />
            
            </figure><p>At some point, John noted that the threshold allows additional optimization. We can order the hashes by their distance from some origin point. Given a query hash which has origin distance of A, we can inspect only hashes which are distant between |A-threshold| and |A+threshold| from the origin. This is pretty much how each level of Vantage Point Tree works, just simplified. This optimization — ordering items in the database by their distance from origin point — is relatively simple and can help save us a bit of work.</p><p>While great on paper, this method doesn't introduce much gain in practice, as the vectors are not grouped in clusters — they are pretty much random! For the threshold values we are interested in, the origin distance algorithm variation gives us ~20% speed boost, which is okay but not breathtaking. This change might bring more benefits if we ever decide to reduce the threshold value, so it might be worth doing for production implementation. However, it doesn't work well with query batching.</p>
    <div>
      <h3>Transposing data for better AVX</h3>
      <a href="#transposing-data-for-better-avx">
        
      </a>
    </div>
    <p>But we're not done with AVX optimizations! The usual problem with AVX is that the instructions don't normally fit a specific problem. Some serious mind twisting is required to adapt the right instruction to the problem, or to reverse the problem so that a specific instruction can be used. AVX2 doesn't have useful "horizontal" uint16 subtract, multiply and add operations. For example, <code>_mm_hadd_epi16</code> exists, but it's slow and cumbersome.</p><p>Instead, we can twist the problem to make use of fast available uint16 operands. For example we can use:</p><ol><li><p>_mm256_sub_epi16</p></li><li><p>_mm256_mullo_epi16</p></li><li><p>and _mm256_add_epu16.</p></li></ol><p>The <code>add</code> would overflow in our case, but fortunately there is add-saturate _mm256_adds_epu16.</p><p>The saturated <code>add</code> is great and saves us conversion to uint32. It just adds a small limitation: the threshold passed to the program (i.e., the max squared distance) must fit into uint16. However, this is fine for us.</p><p>To effectively use these instructions we need to transpose the data in the database. Instead of storing hashes in rows, we can store them in columns:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/29kHAIq6T1Mfif5MVP3Jol/7369ea0b4a38a416a4367bb9865996ca/image1-63.png" />
            
            </figure><p>So instead of:</p><ol><li><p>[a1, a2, a3],</p></li><li><p>[b1, b2, b3],</p></li><li><p>[c1, c2, c3],</p></li></ol><p>...</p><p>We can lay it out in memory transposed:</p><ol><li><p>[a1, b1, c1],</p></li><li><p>[a2, b2, c2],</p></li><li><p>[a3, b3, c3],</p></li></ol><p>...</p><p>Now we can load 16 first bytes of hashes using one memory operation. In the next step, we can subtract the first byte of the querying hash using a single instruction, and so on. The algorithm stays exactly the same as defined above; we just make the data easier to load and easier to process for AVX.</p><p><a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/mmdist-short-inv-avx2.c#L138-L147">The hot loop code</a> even looks relatively pretty:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2Xb2h3rI8mXT1yeiQcVYfn/4a7eb556d7aea0014c3944b301b52b0d/image5-26.png" />
            
            </figure><p>With the well-tuned batch size and short distance size parameters we can <a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-12-mmdist/mmdist-short-inv-avx2.c">see the performance of this algorithm</a>:</p>
            <pre><code>$ make short-inv-avx2
Total: 1118.669ms, 1536 items, avg 0.728ms per query, 1373.062 qps</code></pre>
            <p>Whoa! This is pretty awesome. We started from 55ms per query, and we finished with just 0.73ms. There are further micro-optimizations possible, like memory prefetching or using huge pages to reduce page faults, but they have diminishing returns at this point.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4dBsIg3yHloEmyLqUyfETK/3119d166040ef5d6b96d03acc9873120/image7-8.png" />
            
            </figure><p>Roofline model from Denis Bakhvalov's book‌‌</p><p>If you are interested in architectural tuning such as this, take a look at <a href="https://book.easyperf.net/perf_book">the new performance book by Denis Bakhvalov</a>. It discusses roofline model analysis, which is pretty much what we did here.</p><p><a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2020-12-mmdist">Do take a look at our code</a> and tell us if we missed some optimization!</p>
    <div>
      <h3>Summary</h3>
      <a href="#summary">
        
      </a>
    </div>
    <p>What an optimization journey! We jumped between memory and ALU bottlenecked code. We discussed more sophisticated algorithms, but in the end, a brute force algorithm — although tuned — gave us the best results.</p><p>To get even better numbers, I experimented with Nvidia GPU using CUDA. The CUDA intrinsics like <code>vabsdiff4</code> and <code>dp4a</code> fit the problem perfectly. The V100 gave us some amazing numbers, but I wasn't fully satisfied with it. Considering how many AMD Ryzen cores with AVX2 we can get for the cost of a single server-grade GPU, we leaned towards general purpose computing for this particular problem.</p><p>This is a great example of the type of complexities we deal with every day. Making even the best technologies work “at Cloudflare scale” requires thinking outside the box. Sometimes we rewrite the solution dozens of times before we find the optimal one. And sometimes we settle on a brute-force algorithm, just very very optimized.</p><p>The computation of hashes and image matching are challenging problems that require running very CPU intensive operations.. The CPU we have available on the edge is scarce and workloads like this are incredibly expensive. Even with the optimization work talked about in this blog post, running the CSAM scanner at scale is a challenge and has required a huge engineering effort. And we’re not done! We need to solve more hard problems before we're satisfied. If you want to help, consider <a href="https://www.cloudflare.com/careers/">applying</a>!</p> ]]></content:encoded>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Speed]]></category>
            <guid isPermaLink="false">3xsrCKMSJ12rBqyQDNejcu</guid>
            <dc:creator>Marek Majkowski</dc:creator>
        </item>
        <item>
            <title><![CDATA[My internship: Brotli compression using a reduced dictionary]]></title>
            <link>https://blog.cloudflare.com/brotli-compression-using-a-reduced-dictionary/</link>
            <pubDate>Wed, 11 Nov 2020 16:32:39 GMT</pubDate>
            <description><![CDATA[ Using the brotli dictionary to improve compression of web content without sacrificing performance. ]]></description>
            <content:encoded><![CDATA[ <p><a href="https://github.com/google/brotli">Brotli</a> is a state of the art lossless compression format, supported by all major browsers. It is capable of achieving considerably better compression ratios than the ubiquitous gzip, and is rapidly gaining in popularity. Cloudflare uses the Google brotli library to dynamically compress web content whenever possible. In 2015, we took <a href="/results-experimenting-brotli/">an in-depth look at how brotli works</a> and its compression advantages.</p><p>One of the more interesting features of the <a href="https://tools.ietf.org/html/rfc7932">brotli file format</a>, in the context of textual web content compression, is the inclusion of a built-in static dictionary. The dictionary is quite large, and in addition to containing various strings in multiple languages, it also supports the option to apply multiple transformations to those words, increasing its versatility.</p><p>The <a href="https://github.com/google/brotli">open sourced brotli library</a>, that implements an encoder and decoder for brotli, has 11 predefined quality levels for the encoder, with higher quality level demanding more CPU in exchange for a better compression ratio. The static dictionary feature is used to a limited extent starting with level 5, and to the full extent only at levels 10 and 11, due to the high CPU cost of this feature.</p><p>We improve on the limited dictionary use approach and add optimizations to improve the compression at levels 5 through 9 at a negligible performance impact when compressing web content.</p>
    <div>
      <h3>Brotli Static Dictionary</h3>
      <a href="#brotli-static-dictionary">
        
      </a>
    </div>
    <p>Brotli primarily uses the LZ77 algorithm to compress its data. Our previous blog post about <a href="/results-experimenting-brotli/">brotli compression</a> provides an introduction.</p><p>To improve compression on text files and web content, brotli also includes a static, predefined dictionary. If a byte sequence cannot be matched with an earlier sequence using LZ77 the encoder will try to match the sequence with a reference to the static dictionary, possibly using one of the multiple transforms. For example, every HTML file contains the opening  tag that cannot be compressed with LZ77, as it is unique, but it is contained in the brotli static dictionary and will be replaced by a reference to it. The reference generally takes less space than the sequence itself, which decreases the compressed file size.</p><p>The dictionary contains 13,504 words in six languages, with lengths from 4 to 24 characters. To improve the compression of real-world text and web data, some dictionary words are common phrases ("The current") or strings common in web content (‘type=”text/javascript”’). Unlike usual LZ77 compression, a word from the dictionary can only be matched as a whole. Starting a match in the middle of a dictionary word, ending it before the end of a word or even extending into the next word is not supported by the brotli format.</p><p>Instead, the dictionary supports 120 transforms of dictionary words to support a larger number of matches and find longer matches. The transforms include adding suffixes (“work” becomes “working”) adding prefixes (“book” =&gt; “ the book”) making the first character uppercase ("process" =&gt; "Process") or converting the whole word to uppercase (“html” =&gt; “HTML”). In addition to transforms that make words longer or capitalize them, the cut transform allows a shortened match (“consistently” =&gt; “consistent”), which makes it possible to find even more matches.</p>
    <div>
      <h3>Methods</h3>
      <a href="#methods">
        
      </a>
    </div>
    <p>With the transforms included, the static dictionary contains 1,633,984 different words – too many for exhaustive search, except when used with the slow brotli compression levels 10 and 11. When used at a lower compression level, brotli either disables the dictionary or only searches through a subset of roughly 5,500 words to find matches in an acceptable time frame. It also only considers matches at positions where no LZ77 match can be found and only uses the cut transform.</p><p>Our approach to the brotli dictionary uses a larger, but more specialized subset of the dictionary than the default, using more aggressive heuristics to improve the compression ratio with negligible cost to performance. In order to provide a more specialized dictionary, we provide the compressor with a content type hint from our servers, relying on the Content-Type header to tell the compressor if it should use a dictionary for HTML, JavaScript or CSS. The dictionaries can be furthermore refined by colocation language in the future.</p>
    <div>
      <h3>Fast dictionary lookup</h3>
      <a href="#fast-dictionary-lookup">
        
      </a>
    </div>
    <p>To improve compression without sacrificing performance, we needed a fast way to find matches if we want to search the dictionary more thoroughly than brotli does by default. Our approach uses three data structures to find a matching word directly. The radix trie is responsible for finding the word while the hash table and bloom filter are used to speed up the radix trie and quickly eliminate many words that can’t be matched using the dictionary.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3JdGRN9pPZ0GPTRvecnh0P/a3a4bb1289e25385b098c719ad865a1e/image1.png" />
            
            </figure><p>Lookup for a position starting with “type”</p><p>The <b>radix trie</b> easily finds the longest matching word without having to try matching several words. To find the match, we traverse the graph based on the text at the current position and remember the last node with a matching word. The radix trie supports compressed nodes (having more than one character as an edge label), which greatly reduces the number of nodes that need to be traversed for typical dictionary words.</p><p>The radix trie is slowed down by the large number of positions where we can’t find a match. An important finding is that most mismatching strings have a mismatching character in the first four bytes. Even for positions where a match exists, a lot of time is spent traversing nodes for the first four bytes since the nodes close to the tree root usually have many children.</p><p>Luckily, we can use a <b>hash table</b> to look up the node equivalent to four bytes, matching if it exists or reject the possibility of a match. We thus look up the first four bytes of the string, if there is a matching node we traverse the trie from there, which will be fast as each four-byte prefix usually only has a few corresponding dict words. If there is no matching node, there will not be a matching word at this position and we do not need to further consider it.</p><p>While the hash table is designed to reject mismatches quickly and avoid cache misses and high search costs in the trie, it still suffers from similar problems: We might search through several 4-byte prefixes with the hash value of the given position, only to learn that no match can be found. Additionally, hash lookups can be expensive due to cache misses.</p><p>To quickly reject words that do not match the dictionary, but might still cause cache misses, we use a <b>k=1 bloom filter</b> to quickly rule out most non-matching positions. In the k=1 case, the filter is simply a lookup table with one bit indicating whether any matching 4-byte prefixes exist for a given hash value. If the hash value for the given bit is 0, there won’t be a match. Since the bloom filter uses at most one bit for each four-byte prefix while the hash table requires 16 bytes, cache misses are much less likely. (The actual size of the structures is a bit different since there are many empty spaces in both structures and the bloom filter has twice as many elements to reject more non-matching positions.)</p><p>This is very useful for performance as a bloom filter lookup requires a single memory access. The bloom filter is designed to be fast and simple, but still rejects more than half of all non-matching positions and thus allows us to save a full hash lookup, which would often mean a cache miss.</p>
    <div>
      <h2>Heuristics</h2>
      <a href="#heuristics">
        
      </a>
    </div>
    <p>To improve the compression ratio without sacrificing performance, we employed a number of heuristics:</p><p><b>Only search the dictionary at some positions</b>This is also done using the stock dictionary, but we search more aggressively. While the stock dictionary only considers positions where the LZ77 match finder did not find a match, we also consider positions that have a bad match according to the brotli cost model: LZ77 matches that are short or have a long distance between the current position and the reference usually only offer a small compression improvement, so it is worth trying to find a better match in the static dictionary.</p><p><b>Only consider the longest match and then transform it</b>Instead of finding and transforming all matches at a position, the radix trie only gives us the longest match which we then transform. This approach results in a vast performance improvement. In most cases, this results in finding the best match.</p><p><b>Only include some transforms</b>While all transformations can improve the compression ratio, we only included those that work well with the data structures. The suffix transforms can easily be applied after finding a non-transformed match. For the upper case transforms, we include both the non-transformed and the upper case version of a word in the radix trie. The prefix and cut transforms do not play well with the radix trie, therefore a cut of more than 1 byte and prefix transforms are not supported.</p>
    <div>
      <h2>Generating the reduced dictionary</h2>
      <a href="#generating-the-reduced-dictionary">
        
      </a>
    </div>
    <p>At low compression levels, brotli searches a subset of ~5,500 out of 13,504 words of the dictionary, negatively impacting compression. To store the entire dictionary, we would need to store ~31,700 words in the trie considering the upper case transformed output of ASCII sequences and ~11,000 four-byte prefixes in the hash. This would slow down hash table and radix trie, so we needed to find a different subset of the dictionary that works well for web content.</p><p>For this purpose, we used a large data set containing representative content. We made sure to use web content from several world regions to reflect language diversity and optimize compression. Based on this data set, we identified which words are most common and result in the largest compression improvement according to the brotli cost model. We only include the most useful words based on this calculation. Additionally, we remove some words if they slow down hash table lookups of other, more common words based on their hash value.</p><p>We have generated separate dictionaries for HTML, CSS and JavaScript content and use the MIME type to identify the right dictionary to use. The dictionaries we currently use include about 15-35% of the entire dictionary including uppercase transforms. Depending on the type of data and the desired compression/speed tradeoff, different options for the size of the dictionary can be useful. We have also developed code that automatically gathers statistics about matches and generates a reduced dictionary based on this, which makes it easy to extend this to other textual formats, perhaps data that is majority non-English or XML data and achieve better results for this type of data.</p>
    <div>
      <h2>Results</h2>
      <a href="#results">
        
      </a>
    </div>
    <p>We tested the reduced dictionary on a large data set of HTML, CSS and JavaScript files.</p><p>The improvement is especially big for small files as the LZ77 compression is less effective on them. Since the improvement on large files is a lot smaller, we only tested files up to 256KB. We used compression level 5, the same compression level we currently use for dynamic compression on our edge, and tested on a Intel Core i7-7820HQ CPU.</p><p>Compression improvement is defined as 1 - (compressed size using the reduced dictionary / compressed size without dictionary). This ratio is then averaged for each input size range. We also provide an average value weighted by file size. Our data set mirrors typical web traffic, covering a wide range of file sizes with small files being more common, which explains the large difference between the weighted and unweighted average.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6fjLER7c308iIMA3kQx9Qs/cc51eec61ad9c4b9b3637e7cfec397cb/image-2.png" />
            
            </figure><p>With the improved dictionary approach, we are now able to compress HTML, JavaScript and CSS files as well, or sometimes even better than using a higher compression level would allow us, all while using only 1% to 3% more CPU. For reference using compression level 6 over 5 would increase CPU usage by up to 12%.</p> ]]></content:encoded>
            <category><![CDATA[Compression]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Careers]]></category>
            <guid isPermaLink="false">34MUGp0LHm4s1IhKECGkCR</guid>
            <dc:creator>Felix Hanau</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing support for the AVIF image format]]></title>
            <link>https://blog.cloudflare.com/generate-avif-images-with-image-resizing/</link>
            <pubDate>Sat, 03 Oct 2020 13:00:00 GMT</pubDate>
            <description><![CDATA[ We're adding support for the new AVIF image format. It compresses images significantly better than older-generation formats such as WebP and JPEG. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7pur7e2GCh3MalSZMrQS7q/10a180152a825d811ae832347dad8b66/BDES-1080_Cloudflare_Resize_Illustration_AV1-AVIF_Blog_Header.png" />
            
            </figure><p>We've added support for the new AVIF image format in <a href="https://developers.cloudflare.com/images/">Image Resizing</a>. It compresses images significantly better than older-generation formats such as WebP and JPEG. It's supported in Chrome desktop today, and support is coming to other Chromium-based browsers, as well as Firefox.</p>
    <div>
      <h3>What’s the benefit?</h3>
      <a href="#whats-the-benefit">
        
      </a>
    </div>
    <p>More than a half of an average website's bandwidth is spent on images. Improved <a href="https://www.cloudflare.com/learning/performance/glossary/what-is-image-compression/">image compression</a> can save bandwidth and improve overall performance of the web. The compression in AVIF is so good that images can reduce to half the size of JPEG and WebP</p>
    <div>
      <h3>What is AVIF?</h3>
      <a href="#what-is-avif">
        
      </a>
    </div>
    <p>AVIF is a combination of the HEIF ISO standard, and a royalty-free AV1 codec by <a href="https://aomedia.org/">Mozilla, Xiph, Google, Cisco, and many others</a>.</p><p>Currently, JPEG is the most popular image format on the Web. It's doing remarkably well for its age, and it will likely remain popular for years to come thanks to its excellent compatibility. There have been many previous attempts at replacing JPEG, such as JPEG 2000, JPEG XR and WebP. However, these formats offered only modest compression improvements, and didn't always beat JPEG on image quality. Compression and image quality in <a href="https://netflixtechblog.com/avif-for-next-generation-image-coding-b1d75675fe4">AVIF is better than in all of them, and by a wide margin</a>.</p><table>
<tr>
<td>
    <img src="http://staging.blog.mrk.cfdata.org/content/images/2021/11/avif-test-timothy-meinberg.jpeg" />
</td>
<td>
    
    
    <img src="https://images.ctfassets.net/slt3lc6tev37/4JGMoM6h1eV1YAv8N3QnRC/7e61ac8dfe6836ce46d347311993c50b/avif-test-timothy-meinberg.webp" />
    
</td>
<td>
    
    
    <img src="http://staging.blog.mrk.cfdata.org/content/images/2021/11/avif-test-timothy-meinberg.avif.png" />
    
</td>
</tr>
<tr>
    <td>JPEG (17KB)</td>
    <td>WebP (17KB)</td>
    <td>AVIF (17KB)</td>
</tr>
</table>
    <div>
      <h3>Why a new image format?</h3>
      <a href="#why-a-new-image-format">
        
      </a>
    </div>
    <p>One of the big things AVIF does is it fixes WebP's biggest flaws. WebP is over 10 years old, and AVIF is a major upgrade over the WebP format. These two formats are technically related: they're both based on a video codec from the VPx family. WebP uses the old VP8 version, while AVIF is based on AV1, which is the next generation after <a href="https://en.wikipedia.org/wiki/VP10">VP10</a>.</p><p>WebP is limited to 8-bit color depth, and in its best-compressing mode of operation, it can only store color at half of the image's resolution (known as chroma subsampling). This causes edges of saturated colors to be smudged or pixelated in WebP. AVIF supports 10- and 12-bit color at full resolution, and high dynamic range (HDR).</p><table>
<tr><td><img src="http://staging.blog.mrk.cfdata.org/content/images/2020/10/avif-test-jpeg1.png" /></td><td>JPEG</td></tr>
<tr><td><img src="http://staging.blog.mrk.cfdata.org/content/images/2020/10/avif-test-webp1.png" /></td><td>WebP</td></tr>
<tr><td><img src="http://staging.blog.mrk.cfdata.org/content/images/2020/10/avif-test-webp2.png" /></td><td>WebP (sharp YUV option)</td></tr>
<tr><td><img src="http://staging.blog.mrk.cfdata.org/content/images/2020/10/avif-test-avif1.png" /></td><td>AVIF</td></tr>
</table><p>AV1 also uses a new compression technique: chroma-from-luma. Most image formats store brightness separately from color hue. AVIF uses the brightness channel to guess what the color channel may look like. They are usually correlated, so a good guess gives smaller file sizes and sharper edges.</p>
    <div>
      <h3>Adoption of AV1 and AVIF</h3>
      <a href="#adoption-of-av1-and-avif">
        
      </a>
    </div>
    <p>VP8 and WebP suffered from reluctant industry adoption. Safari only added WebP support very recently, 10 years after Chrome.</p><p>Chrome 85 supports AVIF already. It’s a work in progress in other Chromium-based browsers, and Firefox. Apple hasn't announced whether Safari will support AVIF. However, this time Apple is one of the <a href="https://aomedia.org/membership/members/">companies in the Alliance for Open Media</a>, creators of AVIF. The AV1 codec is already seeing faster adoption than the previous royalty-free codecs. Latest GPUs from Nvidia, AMD, and Intel already have hardware decoding support for AV1.</p><p>AVIF uses the same HEIF container as the HEIC format used in iOS’s camera. AVIF and HEIC offer similar compression performance. The difference is that HEIC is based on a commercial, patent-encumbered H.265 format. In countries that allow software patents, H.265 is illegal to use without obtaining patent licenses. It's covered by thousands of patents, owned by hundreds of companies, which have fragmented into two competing licensing organizations. Costs and complexity of patent licensing used to be acceptable when videos were published by big studios, and the cost could be passed on to the customer in the price of physical discs and hardware players. Nowadays, when video is mostly consumed via free browsers and apps, <a href="https://blog.chiariglione.org/a-future-without-mpeg/">the old licensing model has become unsustainable</a>. This has created a huge incentive for Web giants like Google, Netflix, and Amazon to get behind the royalty-free alternative. AV1 is free to use by anyone, and the alliance of tech giants behind it will <a href="http://aomedia.org/press%20releases/the-alliance-for-open-media-statement/">defend it</a> from patent troll's lawsuits.</p><p>Non-standard image formats usually can only be created using their vendor's own implementation. AVIF and AV1 are already ahead with multiple independent implementations: libaom, Intel SVT-AV1, libgav1, dav1d, and rav1e. This is a sign of a healthy adoption and a prerequisite to be a Web standard. Our Image Resizing is implemented in <a href="https://www.rust-lang.org/">Rust</a>, so we've chosen the <a href="https://github.com/xiph/rav1e">rav1e</a> encoder to create a pure-Rust implementation of AVIF.</p>
    <div>
      <h3>Caveats</h3>
      <a href="#caveats">
        
      </a>
    </div>
    <p>AVIF is a feature-rich format. Most of its features are for smartphone cameras, such as "live" photos, depth maps, bursts, HDR, and lossless editing. Browsers will support only a fraction of these features. In our implementation in Image Resizing we’re supporting only still standard-range images.</p><p>Two biggest problems in AVIF are encoding speed and lack of progressive rendering.</p><p>AVIF images don't show anything on screen until they're fully downloaded. In contrast, a progressive JPEG can display a lower-quality approximation of the image very quickly, while it's still loading. <a href="/parallel-streaming-of-progressive-images/">When progressive JPEGs are delivered well</a>, they make websites appear to load much faster. Progressive JPEG can look loaded at half of its file size. AVIF can fully load at half of JPEG's size, so it somewhat overcomes the lack of progressive rendering with the sheer compression advantage. In this case only WebP is left behind, which has neither progressive rendering nor strong compression.</p><p>Decoding AVIF images for display takes relatively more CPU power than other codecs, but it should be fast enough in practice. Even low-end Android devices can play AV1 videos in full HD without help of hardware acceleration, and AVIF images are just a single frame of an AV1 video. However, encoding of AVIF images is much slower. It may take even a few seconds to create a single image. AVIF supports tiling, which accelerates encoding on multi-core CPUs. We have <a href="/cloudflares-gen-x-servers-for-an-accelerated-future/">lots of CPU cores</a>, so we take advantage of this to make encoding faster. Image Resizing doesn’t use the maximum compression level possible in AVIF to further increase compression speed. Resized images are cached, so the encoding speed is noticeable only on a cache miss.</p><p>We will be adding AVIF support to <a href="https://support.cloudflare.com/hc/en-us/articles/360000607372-Using-Cloudflare-Polish-to-compress-images">Polish</a> as well. Polish converts images asynchronously in the background, which completely hides the encoding latency, and it will be able to compress AVIF images better than Image Resizing.</p>
    <div>
      <h3>Note about benchmarking</h3>
      <a href="#note-about-benchmarking">
        
      </a>
    </div>
    <p>It's surprisingly difficult to fairly and accurately judge which lossy codec is better. Lossy codecs are specifically designed to mainly <a href="https://www.cloudflare.com/learning/performance/glossary/what-is-image-compression/">compress image details</a> that are too subtle for the naked eye to see, so "looks almost the same, but the file size is smaller!" is a common feature of all lossy codecs, and not specific enough to draw conclusions about their performance.</p><p>Lossy codecs can't be compared by comparing just file sizes. Lossy codecs can easily make files of any size. Their effectiveness is in the ratio between file size and visual quality they can achieve.</p><p>The best way to compare codecs is to make each compress an image to the exact same file size, and then to compare the actual visual quality they've achieved. If the file sizes differ, any judgement may be unfair, because the codec that generated the larger file may have done so only because it was asked to preserve more details, not because it can't compress better.</p>
    <div>
      <h3>How and when to enable AVIF today?</h3>
      <a href="#how-and-when-to-enable-avif-today">
        
      </a>
    </div>
    <p>We recommend AVIF for websites that need to deliver high-quality images with as little bandwidth as possible. This is important for users of slow networks and in <a href="https://whatdoesmysitecost.com/">countries where the bandwidth is expensive</a>.</p><p>Browsers that support the AVIF format announce it by adding <code>image/avif</code> to their <code>Accept</code> request header. To request the AVIF format from <a href="https://developers.cloudflare.com/images/">Image Resizing</a>, set the <code>format</code> option to <code>avif</code>.</p><p>The best method to detect and enable support for AVIF is to use <a href="https://developers.cloudflare.com/images/worker">image resizing in Workers</a>:</p>
            <pre><code>addEventListener('fetch', event =&gt; {
  const imageURL = "https://jpeg.speedcf.com/cat/4.jpg";

  const resizingOptions = {
    // You can set any options here, see:
    // https://developers.cloudflare.com/images/worker
    width: 800,
    sharpen: 1.0,
  };

  const accept = event.request.headers.get("accept");
  const isAVIFSupported = /image\/avif/.test(accept);
  if (isAVIFSupported) {
    resizingOptions.format = "avif";
  }
  event.respondWith(fetch(imageURL), {cf:{image: resizingOptions}})
})</code></pre>
            <p>The above script will auto-detect the supported format, and serve AVIF automatically. Alternatively, you can use URL-based resizing together with the <code>&lt;picture&gt;</code> element:</p>
            <pre><code>&lt;picture&gt;
    &lt;source type="image/avif" 
            srcset="/cdn-cgi/image/format=avif/image.jpg"&gt;
    &lt;img src="/image.jpg"&gt;
&lt;/picture&gt;</code></pre>
            <p>This uses our <a href="https://developers.cloudflare.com/images/about"><code>/cdn-cgi/image/…</code></a> endpoint to perform the conversion, and the alternative source will be picked only by browsers that support the AVIF format.</p><p>We have the <code>format=auto</code> option, but it won't choose AVIF yet. We're cautious, and we'd like to test the new format more before enabling it by default.</p> ]]></content:encoded>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[WebP]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Product News]]></category>
            <guid isPermaLink="false">49mNQJofaty5LTpZMvcjPb</guid>
            <dc:creator>Kornel Lesiński</dc:creator>
        </item>
        <item>
            <title><![CDATA[When Bloom filters don't bloom]]></title>
            <link>https://blog.cloudflare.com/when-bloom-filters-dont-bloom/</link>
            <pubDate>Mon, 02 Mar 2020 13:00:00 GMT</pubDate>
            <description><![CDATA[ Last month finally I had an opportunity to use Bloom filters. I became fascinated with the promise of this data structure, but I quickly realized it had some drawbacks. This blog post is the tale of my brief love affair with Bloom filters. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4bQ9cbvVLCJTUntwCHSGSp/570583980831b19e4da88411fdff5eda/bloom-filter_2x.png" />
            
            </figure><p>I've known about <a href="https://en.wikipedia.org/wiki/Bloom_filter">Bloom filters</a> (named after Burton Bloom) since university, but I haven't had an opportunity to use them in anger. Last month this changed - I became fascinated with the promise of this data structure, but I quickly realized it had some drawbacks. This blog post is the tale of my brief love affair with Bloom filters.</p><p>While doing research about <a href="/the-root-cause-of-large-ddos-ip-spoofing/">IP spoofing</a>, I needed to examine whether the source IP addresses extracted from packets reaching our servers were legitimate, depending on the geographical location of our data centers. For example, source IPs belonging to a legitimate Italian ISP should not arrive in a Brazilian datacenter. This problem might sound simple, but in the ever-evolving landscape of the internet this is far from easy. Suffice it to say I ended up with many large text files with data like this:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4EyhhPLD0IymvVMr3R8pMz/2c30f18b77fdee9184c438f80e94781b/Screenshot-from-2020-03-01-23-57-10.png" />
            
            </figure><p>This reads as: the IP 192.0.2.1 was recorded reaching Cloudflare data center number 107 with a legitimate request. This data came from many sources, including our active and passive probes, logs of certain domains we own (like cloudflare.com), public sources (like BGP table), etc. The same line would usually be repeated across multiple files.</p><p>I ended up with a gigantic collection of data of this kind. At some point I counted 1 billion lines across all the harvested sources. I usually write bash scripts to pre-process the inputs, but at this scale this approach wasn't working. For example, removing duplicates from this tiny file of a meager 600MiB and 40M lines, took... about an eternity:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7lzqpR8gHJRr6XC4fbcepJ/91185ef0b6036ecd484bd791f5a72651/Screenshot-from-2020-03-01-23-25-19a.png" />
            
            </figure><p>Enough to say that deduplicating lines using the usual bash commands like 'sort' in various configurations (see '--parallel', '--buffer-size' and '--unique') was not optimal for such a large data set.</p>
    <div>
      <h2>Bloom filters to the rescue</h2>
      <a href="#bloom-filters-to-the-rescue">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/vR5EB9TdKmVJDjStuvpwL/fe51959f24694cc5c072f3263aa42ab0/Bloom_filter.png" />
            
            </figure><p><a href="https://en.wikipedia.org/wiki/Bloom_filter#/media/File:Bloom_filter.svg">Image</a> by <a href="https://commons.wikimedia.org/wiki/User:David_Eppstein">David Eppstein</a> Public Domain</p><p>Then I had a brainwave - it's not necessary to sort the lines! I just need to remove duplicated lines - using some kind of "set" data structure should be much faster. Furthermore, I roughly know the cardinality of the input file (number of unique lines), and I can live with some data points being lost - using a probabilistic data structure is fine!</p><p>Bloom-filters are a perfect fit!</p><p>While you should go and read <a href="https://en.wikipedia.org/wiki/Bloom_filter#Algorithm_description">Wikipedia on Bloom Filters</a>, here is how I look at this data structure.</p><p>How would you implement a "<a href="https://en.wikipedia.org/wiki/Set_(abstract_data_type)">set</a>"? Given a perfect hash function, and infinite memory, we could just create an infinite bit array and set a bit number 'hash(item)' for each item we encounter. This would give us a perfect "set" data structure. Right? Trivial. Sadly, hash functions have collisions and infinite memory doesn't exist, so we have to compromise in our reality. But we can calculate and manage the probability of collisions. For example, imagine we have a good hash function, and 128GiB of memory. We can calculate the probability of the second item added to the bit array colliding would be 1 in 1099511627776. The probability of collision when adding more items worsens as we fill up the bit array.</p><p>Furthermore, we could use more than one hash function, and end up with a denser bit array. This is exactly what Bloom filters optimize for. A Bloom filter is a bunch of math on top of the four variables:</p><ul><li><p>'n' - The number of input elements (cardinality)</p></li><li><p>'m' - Memory used by the bit-array</p></li><li><p>'k' - Number of hash functions counted for each input</p></li><li><p>'p' - Probability of a false positive match</p></li></ul><p>Given the 'n' input cardinality and the 'p' desired probability of false positive, the Bloom filter math returns the 'm' memory required and 'k' number of hash functions needed.</p><p>Check out this excellent visualization by Thomas Hurst showing how parameters influence each other:</p><ul><li><p><a href="https://hur.st/bloomfilter/">https://hur.st/bloomfilter/</a></p></li></ul>
    <div>
      <h2>mmuniq-bloom</h2>
      <a href="#mmuniq-bloom">
        
      </a>
    </div>
    <p>Guided by this intuition, I set out on a journey to add a new tool to my toolbox - 'mmuniq-bloom', a probabilistic tool that, given input on STDIN, returns only unique lines on STDOUT, hopefully much faster than 'sort' + 'uniq' combo!</p><p>Here it is:</p><ul><li><p><a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-02-mmuniq/mmuniq-bloom.c">'mmuniq-bloom.c'</a></p></li></ul><p>For simplicity and speed I designed 'mmuniq-bloom' with a couple of assumptions. First, unless otherwise instructed, it uses 8 hash functions k=8. This seems to be a close to optimal number for the data sizes I'm working with, and the hash function can quickly output 8 decent hashes. Then we align 'm', number of bits in the bit array, to be a power of two. This is to avoid the pricey % modulo operation, which compiles down to slow assembly 'div'. With power-of-two sizes we can just do bitwise AND. (For a fun read, see <a href="https://stackoverflow.com/questions/41183935/why-does-gcc-use-multiplication-by-a-strange-number-in-implementing-integer-divi">how compilers can optimize some divisions by using multiplication by a magic constant</a>.)</p><p>We can now run it against the same data file we used before:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7eWA2sCxdTwbHW0JVE4dLm/ffffd6f6823305f41a15d0091d1acaef/image11.png" />
            
            </figure><p>Oh, this is so much better! 12 seconds is much more manageable than 2 minutes before. But hold on... The program is using an optimized data structure, relatively limited memory footprint, optimized line-parsing and good output buffering... 12 seconds is still eternity compared to 'wc -l' tool:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/10yS38GPKuBTqMMd4ZJVSe/199b9ceea61eb91700012362ef92e0ab/image5.png" />
            
            </figure><p>What is going on? I understand that counting lines by 'wc' is <i>easier</i> than figuring out unique lines, but is it really worth the 26x difference? Where does all the CPU in 'mmuniq-bloom' go?</p><p>It must be my hash function. 'wc' doesn't need to spend all this CPU performing all this strange math for each of the 40M lines on input. I'm using a pretty non-trivial 'siphash24' hash function, so it surely burns the CPU, right? Let's check by running the code computing hash function but <i>not</i> doing any Bloom filter operations:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4cmgeZbT0nJNh9bwRC4gEH/b5a71c8c573d506f20f6b6bc01173028/image2.png" />
            
            </figure><p>This is strange. Counting the hash function indeed costs about 2s, but the program took 12s in the previous run. The Bloom filter alone takes 10 seconds? How is that possible? It's such a simple data structure...</p>
    <div>
      <h2>A secret weapon - a profiler</h2>
      <a href="#a-secret-weapon-a-profiler">
        
      </a>
    </div>
    <p>It was time to use a proper tool for the task - let's fire up a profiler and see where the CPU goes. First, let's fire an 'strace' to confirm we are not running any unexpected syscalls:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/29N25pGnOTso9WBxn4SckO/4a8bbb0dccf4dfb5fd2a248625d29bd9/image14.png" />
            
            </figure><p>Everything looks good. The 10 calls to 'mmap' each taking 4ms (3971 us) is intriguing, but it's fine. We pre-populate memory up front with 'MAP_POPULATE' to save on page faults later.</p><p>What is the next step? Of course Linux's 'perf'!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/632ygHAtmgUaNH18WsUPAG/b9f3990786d96bf84b2bc28d5a55f0e3/image10.png" />
            
            </figure><p>Then we can see the results:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/61GXvk0ujwFsP0vEDyrD6c/ab4ec96b0e748f5dd8e6e496d1b499b4/image6.png" />
            
            </figure><p>Right, so we indeed burn 87.2% of cycles in our hot code. Let's see where exactly. Doing 'perf annotate process_line --source' quickly shows something I didn't expect.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3i10E5RjlO3vvJTuA751wv/da7db1a7614e3936218206689767e752/image3.png" />
            
            </figure><p>You can see 26.90% of CPU burned in the 'mov', but that's not all of it! The compiler correctly inlined the function, and unrolled the loop 8-fold. Summed up that 'mov' or 'uint64_t v = *p' line adds up to a great majority of cycles!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1sTjVPwEz6BILccJz8pdDa/2e3435805a1e092460dcb916f4d6ecb2/image4.png" />
            
            </figure><p>Clearly 'perf' must be mistaken, how can such a simple line cost so much? We can repeat the benchmark with any other profiler and it will show us the same problem. For example, I like using 'google-perftools' with kcachegrind since they emit eye-candy charts:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7fWWh8EZelfOMfwA5XMT16/4ded280402c936f2a6f67e2930115f5e/Screenshot-from-2020-03-02-00-08-23.png" />
            
            </figure><p>The rendered result looks like this:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2p5hkIX3zVAO7IFFWEVZuF/14f1f9505b91e15c904dab7309a11907/image13.png" />
            
            </figure><p>Allow me to summarise what we found so far.</p><p>The generic 'wc' tool takes 0.45s CPU time to process 600MiB file. Our optimized 'mmuniq-bloom' tool takes 12 seconds. CPU is burned on one 'mov' instruction, dereferencing memory....</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1eRwMPBq6BEAyWB0g10EDo/3802664ab1b47d94b96845f487338a08/6784957048_4661ea7dfc_c.jpg" />
            
            </figure><p><a href="https://flickr.com/photos/jonicdao/6784957048">Image</a> by <a href="https://flickr.com/photos/jonicdao/">Jose Nicdao</a> CC BY/2.0</p><p>Oh! I how could I have forgotten. Random memory access <i>is</i> slow! It's very, very, very slow!</p><p>According to the general rule <a href="http://highscalability.com/blog/2011/1/26/google-pro-tip-use-back-of-the-envelope-calculations-to-choo.html">"latency numbers every programmer should know about"</a>, one RAM fetch is about 100ns. Let's do the math: 40 million lines, 8 hashes counted for each line. Since our Bloom filter is 128MiB, on <a href="/gen-x-performance-tuning/">our older hardware</a> it doesn't fit into L3 cache! The hashes are uniformly distributed across the large memory range - each hash generates a memory miss. Adding it together that's...</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/ORPcRAqG2H2xqeEEdGbmh/ef6968c82bfe0aa44706a4f36e59bb1c/Screenshot-from-2020-03-02-00-34-29.png" />
            
            </figure><p>That suggests 32 seconds burned just on memory fetches. The real program is faster, taking only 12s. This is because, although the Bloom filter data does not completely fit into L3 cache, it still gets some benefit from caching. It's easy to see with 'perf stat -d':</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7pgVS7zKpVhueO7rAhQDRI/5145db41e7b06f5875d1da4adfa92142/image9.png" />
            
            </figure><p>Right, so we should have had at least 320M LLC-load-misses, but we had only 280M. This still doesn't explain why the program was running only 12 seconds. But it doesn't really matter. What matters is that the number of cache misses is a real problem and we can only fix it by reducing the number of memory accesses. Let's try tuning Bloom filter to use only one hash function:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/30BFuINOqYvVgCCdYXCzUB/0893742e7bc41b923af6e67f53629049/image12.png" />
            
            </figure><p>Ouch! That really hurt! The Bloom filter required 64 GiB of memory to get our desired false positive probability ratio of 1-error-per-10k-lines. This is terrible!</p><p>Also, it doesn't seem like we improved much. It took the OS 22 seconds to prepare memory for us, but we still burned 11 seconds in userspace. I guess this time any benefits from hitting memory less often were offset by lower cache-hit probability due to drastically increased memory size. In previous runs we required only 128MiB for the Bloom filter!</p>
    <div>
      <h2>Dumping Bloom filters altogether</h2>
      <a href="#dumping-bloom-filters-altogether">
        
      </a>
    </div>
    <p>This is getting ridiculous. To get the same false positive guarantees we either must use many hashes in Bloom filter (like 8) and therefore many memory operations, or we can have 1 hash function, but enormous memory requirements.</p><p>We aren't really constrained by available memory, instead we want to optimize for reduced memory accesses. All we need is a data structure that requires at most 1 memory miss per item, and use less than 64 Gigs of RAM...</p><p>While we could think of more sophisticated data structures like <a href="https://en.wikipedia.org/wiki/Cuckoo_filter">Cuckoo filter</a>, maybe we can be simpler. How about a good old simple hash table with linear probing?</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6PVz5hd2DqyiraxgMJ7KIp/37706f6ffcc1cd52f544d626381deaeb/linear-probing.png" />
            
            </figure><p><a href="https://www.sysadmins.lv/blog-en/array-search-hash-tables-behind-the-scenes.aspx">Image</a> by <a href="https://www.sysadmins.lv/about.aspx">Vadims Podāns</a></p>
    <div>
      <h2>Welcome mmuniq-hash</h2>
      <a href="#welcome-mmuniq-hash">
        
      </a>
    </div>
    <p>Here you can find a tweaked version of mmuniq-bloom, but using hash table:</p><ul><li><p><a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-02-mmuniq/mmuniq-hash.c">'mmuniq-hash.c'</a></p></li></ul><p>Instead of storing bits as for the Bloom-filter, we are now storing 64-bit hashes from the <a href="https://idea.popcount.org/2013-01-24-siphash/">'siphash24' function</a>. This gives us much stronger probability guarantees, with probability of false positives much better than one error in 10k lines.</p><p>Let's do the math. Adding a new item to a hash table containing, say 40M, entries has '40M/2^64' chances of hitting a hash collision. This is about one in 461 billion - a reasonably low probability. But we are not adding one item to a pre-filled set! Instead we are adding 40M lines to the initially empty set. As per <a href="https://en.wikipedia.org/wiki/Birthday_problem">birthday paradox</a> this has much higher chances of hitting a collision at some point. A decent approximation is 'n^2/2m', which in our case is '(40M<sup>2)/(2*(2</sup>64))'. This is a chance of one in 23000. In other words, assuming we are using good hash function, every one in 23 thousand random sets of 40M items, will have a hash collision. This practical chance of hitting a collision is non-negligible, but it's still better than a Bloom filter and totally acceptable for my use case.</p><p>The hash table code runs faster, has better memory access patterns and better false positive probability than the Bloom filter approach.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7i3tnYhlVqJ7NEfutgywVD/dafe27be80727cd61533f548457bc4a3/image7.png" />
            
            </figure><p>Don't be scared about the "hash conflicts" line, it just indicates how full the hash table was. We are using linear probing, so when a bucket is already used, we just pick up the next empty bucket. In our case we had to skip over 0.7 buckets on average to find an empty slot in the table. This is fine and, since we iterate over the buckets in linear order, we can expect the memory to be nicely prefetched.</p><p>From the previous exercise we know our hash function takes about 2 seconds of this. Therefore, it's fair to say 40M memory hits take around 4 seconds.</p>
    <div>
      <h2>Lessons learned</h2>
      <a href="#lessons-learned">
        
      </a>
    </div>
    <p>Modern CPUs are really good at sequential memory access when it's possible to predict memory fetch patterns (see <a href="https://en.wikipedia.org/wiki/Cache_prefetching#Methods_of_hardware_prefetching">Cache prefetching</a>). Random memory access on the other hand is very costly.</p><p>Advanced data structures are very interesting, but beware. Modern computers require cache-optimized algorithms. When working with large datasets, not fitting L3, prefer optimizing for reduced number loads, over optimizing the amount of memory used.</p><p>I guess it's fair to say that Bloom filters are great, as long as they fit into the L3 cache. The moment this assumption is broken, they are terrible. This is not news, Bloom filters optimize for memory usage, not for memory access. For example, see <a href="https://www.cs.cmu.edu/~dga/papers/cuckoo-conext2014.pdf">the Cuckoo Filters paper</a>.</p><p>Another thing is the ever-lasting discussion about hash functions. Frankly - in most cases it doesn't matter. The cost of counting even complex hash functions like 'siphash24' is small compared to the cost of random memory access. In our case simplifying the hash function will bring only small benefits. The CPU time is simply spent somewhere else - waiting for memory!</p><p>One colleague often says: "You can assume modern CPUs are infinitely fast. They run at infinite speed until they <a href="http://www.di-srv.unisa.it/~vitsca/SC-2011/DesignPrinciplesMulticoreProcessors/Wulf1995.pdf">hit the memory wall</a>".</p><p>Finally, don't follow my mistakes - everyone should start profiling with 'perf stat -d' and look at the "Instructions per cycle" (IPC) counter. If it's below 1, it generally means the program is stuck on waiting for memory. Values above 2 would be great, it would mean the workload is mostly CPU-bound. Sadly, I'm yet to see high values in the workloads I'm dealing with...</p>
    <div>
      <h2>Improved mmuniq</h2>
      <a href="#improved-mmuniq">
        
      </a>
    </div>
    <p>With the help of my colleagues I've prepared a further improved version of the 'mmuniq' hash table based tool. See the code:</p><ul><li><p><a href="https://github.com/cloudflare/cloudflare-blog/blob/master/2020-02-mmuniq/mmuniq.c">'mmuniq.c'</a></p></li></ul><p>It is able to dynamically resize the hash table, to support inputs of unknown cardinality. Then, by using batching, it can effectively use the 'prefetch' CPU hint, speeding up the program by 35-40%. Beware, sprinkling the code with 'prefetch' rarely works. Instead, I specifically changed the flow of algorithms to take advantage of this instruction. With all the improvements I got the run time down to 2.1 seconds:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6cnwoob2q1SVG27I7zqals/87621808c31bd4707e08e816a4a58480/Screenshot-from-2020-03-01-23-52-18.png" />
            
            </figure>
    <div>
      <h2>The end</h2>
      <a href="#the-end">
        
      </a>
    </div>
    <p>Writing this basic tool which tries to be faster than 'sort | uniq' combo revealed some hidden gems of modern computing. With a bit of work we were able to speed it up from more than two minutes to 2 seconds. During this journey we learned about random memory access latency, and the power of cache friendly data structures. Fancy data structures are exciting, but in practice reducing random memory loads often brings better results.</p> ]]></content:encoded>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[Hardware]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Programming]]></category>
            <category><![CDATA[Tools]]></category>
            <guid isPermaLink="false">3CPWTXjZJXbtWVNIawBWsd</guid>
            <dc:creator>Marek Majkowski</dc:creator>
        </item>
        <item>
            <title><![CDATA[Announcing Cloudflare Image Resizing: Simplifying Optimal Image Delivery]]></title>
            <link>https://blog.cloudflare.com/announcing-cloudflare-image-resizing-simplifying-optimal-image-delivery/</link>
            <pubDate>Wed, 15 May 2019 13:00:00 GMT</pubDate>
            <description><![CDATA[ In the past three years, the amount of image data on the median mobile webpage has doubled. Growing images translate directly to users hitting data transfer caps, experiencing slower websites, and even leaving if a website doesn’t load in a reasonable amount of time.  ]]></description>
            <content:encoded><![CDATA[ <p></p><p>In the past three years, the amount of image data on the median mobile webpage has doubled. Growing images translate directly to users hitting data transfer caps, experiencing slower websites, and even leaving if a website doesn’t load in a reasonable amount of time. The crime is many of these images are so slow because they are larger than they need to be, sending data over the wire which has absolutely no (positive) impact on the user’s experience.</p><p>To provide a concrete example, let’s consider this photo of Cloudflare’s Lava Lamp Wall:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4dS3q03MGE49VWNx9YFuCw/396ef448bd35a3fbbf1727630f6a182f/lava_300.jpeg.jpeg" />
            
            </figure><p> </p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7ddyepBCTb0JoVBb9R1ow6/d76f2b2c743db1ea607a2f2091f936f2/lava_full.jpg" />
            
            </figure><p></p><p>On the left you see the photo, scaled to 300 pixels wide. On the right you see the same image delivered in its original high resolution, scaled in a desktop web browser. On a regular-DPI screen, they both look the same, yet the image on the right takes more than <b>twenty times more data to load.</b> Even for the best and most conscientious developers resizing every image to handle every possible device geometry consumes valuable time, and it’s exceptionally easy to forget to do this resizing altogether.</p><p>Today we are launching a new product, <a href="https://www.cloudflare.com/developer-platform/cloudflare-images/">Image Resizing</a>, to fix this problem once and for all.</p>
    <div>
      <h3>Announcing Image Resizing</h3>
      <a href="#announcing-image-resizing">
        
      </a>
    </div>
    <p>With Image Resizing, Cloudflare adds another important product to its suite of available image optimizations.  This product allows customers to perform a rich set of the key actions on images.</p><ul><li><p><b>Resize</b> - The source image will be resized to the specified height and width.  This action allows multiple different sized variants to be created for each specific use.</p></li><li><p><b>Crop</b> - The source image will be resized to a new size that does not maintain the original aspect ratio and a portion of the image will be removed.  This can be especially helpful for headshots and product images where different formats must be achieved by keeping only a portion of the image.</p></li><li><p><b>Compress</b> - The source image will have its file size reduced by applying lossy compression.  This should be used when slight quality reduction is an acceptable trade for file size reduction.</p></li><li><p><b>Convert to</b> <a href="https://en.wikipedia.org/wiki/WebP"><b>WebP</b></a> - When the users browser supports it, the source image will be converted to WebP.  Delivering a WebP image takes advantage of the modern, highly optimized image format.</p></li></ul><p>By using a combination of these actions, customers store a single high quality image on their server, and Image Resizing can be leveraged to create specialized variants for each specific use case.  Without any additional effort, each variant will also automatically benefit from Cloudflare’s global caching.</p>
    <div>
      <h3>Examples</h3>
      <a href="#examples">
        
      </a>
    </div>
    
    <div>
      <h4>Ecommerce Thumbnails</h4>
      <a href="#ecommerce-thumbnails">
        
      </a>
    </div>
    <p>Ecommerce sites typically store a high-quality image of each product.  From that image, they need to <a href="https://www.cloudflare.com/solutions/ecommerce/optimization/">create different variants</a> depending on how that product will be displayed.  One example is creating thumbnails for a catalog view.  Using Image Resizing, if the high quality image is located here:</p><p><code>https://example.com/images/shoe123.jpg</code></p><p>This is how to display a 75x75 pixel thumbnail using Image Resizing:</p><p><code>&lt;img src="/cdn-cgi/image/width=75,height=75/images/shoe123.jpg"&gt;</code></p>
    <div>
      <h4>Responsive Images</h4>
      <a href="#responsive-images">
        
      </a>
    </div>
    <p>When tailoring a site to work on various device types and sizes, it’s important to always use correctly sized images.  This can be difficult when images are intended to fill a particular percentage of the screen.  To solve this problem,  can be used.</p><p>Without Image Resizing, multiple versions of the same image would need to be created and stored.  In this example, a single high quality copy of hero.jpg is stored, and Image Resizing is used to resize for each particular size as needed.</p>
            <pre><code>&lt;img width="100%" srcset=" /cdn-cgi/image/fit=contain,width=320/assets/hero.jpg 320w, /cdn-cgi/image/fit=contain,width=640/assets/hero.jpg 640w, /cdn-cgi/image/fit=contain,width=960/assets/hero.jpg 960w, /cdn-cgi/image/fit=contain,width=1280/assets/hero.jpg 1280w, /cdn-cgi/image/fit=contain,width=2560/assets/hero.jpg 2560w, " src="/cdn-cgi/image/width=960/assets/hero.jpg"&gt;</code></pre>
            
    <div>
      <h4>Enforce Maximum Size Without Changing URLs</h4>
      <a href="#enforce-maximum-size-without-changing-urls">
        
      </a>
    </div>
    <p>Image Resizing is also <a href="https://developers.cloudflare.com/images/worker/">available</a> from within a Cloudflare Worker. <a href="https://www.cloudflare.com/developer-platform/workers/">Workers</a> allow you to write code which runs close to your users all around the world. For example, you might wish to add Image Resizing to your images <b>while keeping the same URLs</b>. Your users and client would be able to use the same image URLs as always, but the images will be transparently modified in whatever way you need.</p><p>You can install a Worker on a route which matches your image URLs, and resize any images larger than a limit:</p>
            <pre><code>addEventListener('fetch', event =&gt; {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  return fetch(request, {
    cf: {
      image: {
        width: 800,
        height: 800,
        fit: 'scale-down'
      }
  });
}</code></pre>
            <p>As a Worker is just code, it is also easy to run this worker only on URLs with image extensions, or even to only resize images being delivered to mobile clients.</p>
    <div>
      <h3>Cloudflare and Images</h3>
      <a href="#cloudflare-and-images">
        
      </a>
    </div>
    <p>Cloudflare has a long history building tools to accelerate images. Our caching has always helped reduce latency by storing a copy of images closer to the user.  <a href="/introducing-polish-automatic-image-optimizati/">Polish</a> automates options for both <a href="https://www.cloudflare.com/learning/performance/glossary/what-is-image-compression/">lossless and lossy image compression</a> to remove unnecessary bytes from images.  <a href="https://www.cloudflare.com/website-optimization/mirage/">Mirage</a> accelerates image delivery based on device type. We are continuing to invest in all of these tools, as they all serve a unique role in improving the image experience on the web.</p><p>Image Resizing is different because it is the first image product at Cloudflare to give developers full control over how their images would be served. You should choose Image Resizing if you are comfortable defining the sizes you wish your images to be served at in advance or within a Cloudflare Worker.</p>
    <div>
      <h3>Next Steps and Simple Pricing</h3>
      <a href="#next-steps-and-simple-pricing">
        
      </a>
    </div>
    <p>Image Resizing is available today for Business and Enterprise Customers.  To enable it, login to the Cloudflare Dashboard and navigate to the Speed Tab.  There you’ll find the section for Image Resizing which you can enable with one click.</p><p>This product is included in the <a href="https://www.cloudflare.com/plans/business/">Business</a> and <a href="https://www.cloudflare.com/plans/enterprise/">Enterprise</a> plans at no additional cost with generous usage limits.  Business Customers have 100k requests per month limit and will be charged $10 for each additional 100k requests per month.  Enterprise Customers have a 10M request per month limit with discounted tiers for higher usage.  Requests are defined as a hit on a URI that contains Image Resizing or a call to Image Resizing from a Worker.</p><p>Now that you’ve enabled Image Resizing, it’s time to resize your first image.</p><ol><li><p>Using your existing site, store an image here: <code>https://yoursite.com/images/yourimage.jpg</code></p></li><li><p>Use this URL to resize that image:<code>https://yoursite.com/cdn-cgi/image/width=100,height=100,quality=75/images/yourimage.jpg</code></p></li><li><p>Experiment with changing <code>width=</code>, <code>height=</code>, and <code>quality=</code>.</p></li></ol><p>The instructions above use the Default URL Format for Image Resizing.  For details on options, uses cases, and compatibility, refer to our <a href="https://developers.cloudflare.com/images/">Developer Documentation</a>.</p> ]]></content:encoded>
            <category><![CDATA[Speed Week]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">K8YkfIc07iYdmFpZjLOAy</guid>
            <dc:creator>Isaac Specter</dc:creator>
        </item>
        <item>
            <title><![CDATA[Parallel streaming of progressive images]]></title>
            <link>https://blog.cloudflare.com/parallel-streaming-of-progressive-images/</link>
            <pubDate>Tue, 14 May 2019 16:00:00 GMT</pubDate>
            <description><![CDATA[ Image-optimized HTTP/2 multiplexing makes all progressive images across the page appear visually complete in half of the time. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/277BtPJSXREVafvPqyX3ZH/60d4273f08458c42a0190203a4bf9b7b/880BAE29-39B3-4733-96DA-735FE76443D5.png" />
            
            </figure><p>Progressive image rendering and HTTP/2 multiplexing technologies have existed for a while, but now we've combined them in a new way that makes them much more powerful. With Cloudflare progressive streaming <b>images appear to load in half of the time, and browsers can start rendering pages sooner</b>.</p>
<p>In HTTP/1.1 connections, servers didn't have any choice about the order in which resources were sent to the client; they had to send responses, as a whole, in the exact order they were requested by the web browser. HTTP/2 improved this by adding multiplexing and prioritization, which allows servers to decide exactly what data is sent and when. We’ve taken advantage of these new HTTP/2 capabilities to improve perceived speed of loading of progressive images by sending the most important fragments of image data sooner.</p>
    <div>
      <h3>What is progressive image rendering?</h3>
      <a href="#what-is-progressive-image-rendering">
        
      </a>
    </div>
    <p>Basic images load strictly from top to bottom. If a browser has received only half of an image file, it can show only the top half of the image. Progressive images have their content arranged not from top to bottom, but from a low level of detail to a high level of detail. Receiving a fraction of image data allows browsers to show the entire image, only with a lower fidelity. As more data arrives, the image becomes clearer and sharper.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/Mlb3C96uo4KLWgNe835l2/31ccbe118c20ce15f4c35c5ceb0cd377/image6.jpg" />
            
            </figure><p>This works great in the JPEG format, where only about 10-15% of the data is needed to display a preview of the image, and at 50% of the data the image looks almost as good as when the whole file is delivered. Progressive JPEG images contain exactly the same data as baseline images, merely reshuffled in a more useful order, so progressive rendering doesn’t add any cost to the file size. This is possible, because JPEG doesn't store the image as pixels. Instead, it represents the image as frequency coefficients, which are like a set of predefined patterns that can be blended together, in any order, to reconstruct the original image. The inner workings of JPEG are really fascinating, and you can learn more about them from my recent <a href="https://www.youtube.com/watch?v=jTXhYj2aCDU">performance.now() conference talk</a>.</p><p>The end result is that the images can look almost fully loaded in half of the time, for free! The page appears to be visually complete and can be used much sooner. The rest of the image data arrives shortly after, upgrading images to their full quality, before visitors have time to notice anything is missing.</p>
    <div>
      <h3>HTTP/2 progressive streaming</h3>
      <a href="#http-2-progressive-streaming">
        
      </a>
    </div>
    <p>But there's a catch. Websites have more than one image (sometimes even hundreds of images). When the server sends image files naïvely, one after another, the progressive rendering doesn’t help that much, because overall the images still load sequentially:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7KLYwVqXQSyp0QjnTTbCX4/b70c3a915dd7afaf7b0beec2aedf9f1d/image5.gif" />
            
            </figure><p>Having complete data for half of the images (and no data for the other half) doesn't look as good as having half of the data for all images.</p><p>And there's another problem: when the browser doesn't know image sizes yet, it lays the page out with placeholders instead, and relays out the page when each image loads. This can make pages jump during loading, which is inelegant, distracting and annoying for the user.</p><p>Our new progressive streaming feature greatly improves the situation: we can send all of the images at once, in parallel. This way the browser gets size information for all of the images as soon as possible, can paint a preview of all images without having to wait for a lot of data, and large images don’t delay loading of styles, scripts and other more important resources.</p><p>This idea of streaming of progressive images in parallel is as old as HTTP/2 itself, but it needs special handling in low-level parts of web servers, and so far this hasn't been implemented at a large scale.</p><p>When we were improving <a href="/better-http-2-prioritization-for-a-faster-web">our HTTP/2 prioritization</a>, we realized it can be also used to implement this feature. Image files as a whole are neither high nor low priority. The priority changes within each file, and dynamic re-prioritization gives us the behavior we want:</p><ul><li><p>The image header that contains the image size is very high priority, because the browser needs to know the size as soon as possible to do page layout. The image header is small, so it doesn't hurt to send it ahead of other data.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7qRxoj4KuoVnSD7nEpWbMe/6bd553099bbb167660c6a65fb782ea7c/image7.jpg" />
            
            </figure></li><li><p>The minimum amount of data in the image required to show a preview of the image has a medium priority (we'd like to plug "holes" left for unloaded images as soon as possible, but also leave some bandwidth available for scripts, fonts and other resources)</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6Cm6w0yIFtMJctt42maKMF/f585afb934ef9e1f98ecd4d3750e7b09/secondhalf.png" />
            
            </figure></li><li><p>The remainder of the image data is low priority. Browsers can stream it last to refine image quality once there's no rush, since the page is already fully usable.</p></li></ul><p>Knowing the exact amount of data to send in each phase requires understanding the structure of image files, but it seemed weird to us to make our web server parse image responses and have a format-specific behavior hardcoded at a protocol level. By framing the problem as a dynamic change of priorities, were able to elegantly separate low-level networking code from knowledge of image formats. We can use Workers or offline image processing tools to analyze the images, and instruct our server to change HTTP/2 priorities accordingly.</p><p>The great thing about parallel streaming of images is that it doesn’t add any overhead. We’re still sending the same data, the same amount of data, we’re just sending it in a smarter order. This technique takes advantage of existing web standards, so it’s compatible with all browsers.</p>
    <div>
      <h3>The waterfall</h3>
      <a href="#the-waterfall">
        
      </a>
    </div>
    <p>Here are waterfall charts from <a href="https://webpagetest.org">WebPageTest</a> showing comparison of regular HTTP/2 responses and progressive streaming. In both cases the files were exactly the same, the amount of data transferred was the same, and the overall page loading time was the same (within measurement noise). In the charts, blue segments show when data was transferred, and green shows when each request was idle.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4gWR5w8eEc3VASAU81KwBS/986a63df2f51f9f88237bc1bc74d5273/image8.png" />
            
            </figure><p>The first chart shows a typical server behavior that makes images load mostly sequentially. The chart itself looks neat, but the actual experience of loading that page was not great — the last image didn't start loading until almost the end.</p><p>The second chart shows images loaded in parallel. The blue vertical streaks throughout the chart are image headers sent early followed by a couple of stages of progressive rendering. You can see that useful data arrived sooner for all of the images. You may notice that one of the images has been sent in one chunk, rather than split like all the others. That’s because at the very beginning of a TCP/IP connection we don't know the true speed of the connection yet, and we have to sacrifice some opportunity to do prioritization in order to maximize the connection speed.</p>
    <div>
      <h3>The metrics compared to other solutions</h3>
      <a href="#the-metrics-compared-to-other-solutions">
        
      </a>
    </div>
    <p>There are other techniques intended to provide image previews quickly, such as low-quality image placeholder (LQIP), but they have several drawbacks. They add unnecessary data for the placeholders, and usually interfere with browsers' preload scanner, and delay loading of full-quality images due to dependence on JavaScript needed to upgrade the previews to full images.</p><ul><li><p>Our solution doesn't cause any additional requests, and doesn't add any extra data. Overall page load time is not delayed.</p></li><li><p>Our solution doesn't require any JavaScript. It takes advantage of functionality supported natively in the browsers.</p></li><li><p>Our solution doesn't require any changes to page's markup, so it's very safe and easy to deploy site-wide.</p></li></ul><p>The improvement in user experience is reflected in performance metrics such as <b>SpeedIndex</b> metric and and time to visually complete. Notice that with regular image loading the visual progress is linear, but with the progressive streaming it quickly jumps to mostly complete:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1lKfDWRojqzIpdPg7MwFGj/70c9f7df2546b5ea50d69e4bbafde7cc/image1-5.png" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/n9HPq3RQ8Ew4CTxAf8NP4/7ed0c32f1592de89fd2f3a95b18d74e4/image4.png" />
            
            </figure>
    <div>
      <h3>Getting the most out of progressive rendering</h3>
      <a href="#getting-the-most-out-of-progressive-rendering">
        
      </a>
    </div>
    <p>Avoid ruining the effect with JavaScript. Scripts that hide images and wait until the <code>onload</code> event to reveal them (with a fade in, etc.) will defeat progressive rendering. Progressive rendering works best with the good old <code>&lt;img&gt;</code> element.</p>
    <div>
      <h3>Is it JPEG-only?</h3>
      <a href="#is-it-jpeg-only">
        
      </a>
    </div>
    <p>Our implementation is format-independent, but progressive streaming is useful only for certain file types. For example, it wouldn't make sense to apply it to scripts or stylesheets: these resources are rendered as all-or-nothing.</p><p>Prioritizing of image headers (containing image size) works for all file formats.</p><p>The benefits of progressive rendering are unique to JPEG (supported in all browsers) and JPEG 2000 (supported in Safari). GIF and PNG have interlaced modes, but these modes come at a cost of worse compression. WebP doesn't even support progressive rendering at all. This creates a dilemma: WebP is usually 20%-30% smaller than a JPEG of equivalent quality, but progressive JPEG <i>appears</i> to load 50% faster. There are next-generation image formats that support progressive rendering better than JPEG, and compress better than WebP, but they're not supported in web browsers yet. In the meantime you can choose between the bandwidth savings of WebP or the better perceived performance of progressive JPEG by changing Polish settings in your Cloudflare dashboard.</p>
    <div>
      <h3>Custom header for experimentation</h3>
      <a href="#custom-header-for-experimentation">
        
      </a>
    </div>
    <p>We also support a custom HTTP header that allows you to experiment with, and optimize streaming of other resources on your site. For example, you could make our servers send the first frame of animated GIFs with high priority and deprioritize the rest. Or you could prioritize loading of resources mentioned in <code>&lt;head&gt;</code> of HTML documents before <code>&lt;body&gt;</code> is loaded.</p><p>The custom header can be set only from a Worker. The syntax is a comma-separated list of file positions with priority and concurrency. The priority and concurrency is the same as in the whole-file cf-priority header described in the previous blog post.</p>
            <pre><code>cf-priority-change: &lt;offset in bytes&gt;:&lt;priority&gt;/&lt;concurrency&gt;, ...</code></pre>
            <p>For example, for a progressive JPEG we use something like (this is a fragment of JS to use in a Worker):</p>
            <pre><code>let headers = new Headers(response.headers);
headers.set("cf-priority", "30/0");
headers.set("cf-priority-change", "512:20/1, 15000:10/n");
return new Response(response.body, {headers});</code></pre>
            <p>Which instructs the server to use priority 30 initially, while it sends the first 512 bytes. Then switch to priority 20 with some concurrency (<code>/1</code>), and finally after sending 15000 bytes of the file, switch to low priority and high concurrency (<code>/n</code>) to deliver the rest of the file.</p><p>We’ll try to split HTTP/2 frames to match the offsets specified in the header to change the sending priority as soon as possible. However, priorities don’t guarantee that data of different streams will be multiplexed exactly as instructed, since the server can prioritize only when it has data of multiple streams waiting to be sent at the same time. If some of the responses arrive much sooner from the upstream server or the cache, the server may send them right away, without waiting for other responses.</p>
    <div>
      <h3>Try it!</h3>
      <a href="#try-it">
        
      </a>
    </div>
    <p>Enable our <a href="/better-http-2-prioritization-for-a-faster-web"><i>Enhanced HTTP/2 Prioritization</i></a> feature, and JPEG images optimized by <a href="/introducing-polish-automatic-image-optimizati/"><i>Polish</i></a> or <a href="/announcing-cloudflare-image-resizing-simplifying-optimal-image-delivery/"><i>Image Resizing</i></a> will be streamed automatically.</p> ]]></content:encoded>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Speed Week]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">75xuDiShO45CbDQuf5hkLY</guid>
            <dc:creator>Andrew Galloni</dc:creator>
            <dc:creator>Kornel Lesiński</dc:creator>
        </item>
        <item>
            <title><![CDATA[Too Old To Rocket Load, Too Young To Die]]></title>
            <link>https://blog.cloudflare.com/too-old-to-rocket-load-too-young-to-die/</link>
            <pubDate>Wed, 04 Jul 2018 14:58:21 GMT</pubDate>
            <description><![CDATA[ Rocket Loader is in the news again. One of Cloudflare's earliest web performance products has been re-engineered for contemporary browsers and Web standards.

It controls the load and execution of your JavaScript, ensuring useful, meaningful page content is unblocked and displayed sooner. ]]></description>
            <content:encoded><![CDATA[ <p>Rocket Loader is in the news again. One of Cloudflare's earliest web performance products has been re-engineered for contemporary browsers and Web standards.</p><p>No longer a beta product, Rocket Loader controls the load and execution of your page JavaScript, ensuring useful and meaningful page content is unblocked and displayed sooner.</p><p>For a high-level discussion of Rocket Loader aims, please refer to our sister post, <a href="/we-have-lift-off-rocket-loader-ga-is-mobile/">We have lift off - Rocket Loader GA is mobile!</a></p><p>Below, we offer a lower-level outline of how Rocket Loader actually achieves its goals.</p>
    <div>
      <h3>Prehistory</h3>
      <a href="#prehistory">
        
      </a>
    </div>
    <p>Early humans looked upon Netscape 2.0, with its new ability to script HTML using LiveScript, and <code>&lt;BLINK&gt;</code>ed to ensure themselves they weren’t dreaming. They decided to use this technology, soon to be re-christened JavaScript (a story told often and elsewhere), for everything they didn’t know they needed: form input validation, image substitution, frameset manipulation, popup windows, and more. The sole requirement was a few interpreted commands enclosed in a <code>&lt;script&gt;</code> tag. The possibilities were endless.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3koyiGY3ofb0n0pf4qzuIn/abff6cdaf842ca710c41d560f4396328/prehistory-_4x.png" />
            
            </figure><p>Soon, the introduction of the <code>src</code> attribute allowed them to import a file full of JS into their pages. Little need to fiddle with the markup, when all the requisite JS for the page could be included in a single, or a few, external files, specified in the page’s <code>&lt;HEAD&gt;</code>. It didn’t take our ancestors long before they decided that the same JS file(s) should be in <i>all</i> pages, throughout their website, containing JS for the complete site. No worries about bloat; after all, the browser would cache it.</p><p>A clear, sunny, road to dynamic, interactive sites lay ahead. What could go wrong?</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3kYBa0v72xkrGHmbZlVApH/de4fe237c97739dc8b8359b4ec122ff1/blockage_4x.png" />
            
            </figure>
    <div>
      <h3>Blockage</h3>
      <a href="#blockage">
        
      </a>
    </div>
    <p>Those early JS adopters deduced that when the HTML parser encountered an external script, it suspended visual rendering of the page while it went off to retrieve and execute it. Simple. The more numerous and larger the scripts, the longer the wait for the page to paint. JavaScript, therefore, was very soon, most often unnecessarily, blocking page rendering.</p><p>The solutions poured in, both from the developer community and browser vendors:</p><ul><li><p><i>Community</i>: Move script location to end of HTML pageA classic <i>duh!</i> moment. Amazingly, this simple suggestion helped, unless the script was required to help build the page, eg. using <code>document.write</code> for markup.</p></li><li><p><i>Vendor</i>: Use <code>&lt;script defer&gt;</code>.It’s 1997, and IE4 introduces the <code>defer</code> attribute. Scripts that do not contribute to the initial rendering of the page should be marked with <code>defer</code>, and they will load in parallel, without blocking, and be executed in their markup order before <code>window.load</code> is fired (later, before <code>document.DOMContentLoaded</code>). Script tags could remain in the <code>&lt;head&gt;</code>, and execute as if they were at the end of page.The main benefit to page rendering was the saving in script retrieval time.</p></li><li><p><i>Community</i>: Reduce latency by reducing actual script size.What began as script <i>obfuscation</i> for intellectual property and vanity reasons, quickly became script <a href="https://en.wikipedia.org/wiki/Minification_(programming)"><i>minification</i></a>, still used widely.</p></li><li><p><i>Community</i>: Reduce latency and http handshake instances through concatenation of all scripts, delivered as one.</p></li><li><p><i>Vendor</i>: Use <code>&lt;script async&gt;</code>.In 2010, 13 years (yes, 13, thirteen) after <code>defer</code> was born, HTML5 provided <code>defer</code> with a sibling, <code>async</code>. Scripts can be loaded asynchronously, be non-blocking, and be executed when they load. Markup order is irrelevant to execution order. A clear benefit over <code>defer</code> was that <code>load/DOMContentLoaded</code> events were not delayed.</p></li><li><p><i>Community</i>: Lazy Loading.Use JS to load JS by dynamically creating non-blocking script tags.</p></li><li><p><i>Cloudflare</i>: Rocket LoaderIt's 2011, and Cloudflare enters the fray, leveraging our network to reduce http requests for 1st party scripts, “bag”ging 3rd party scripts into a single file, and delaying and controlling JS execution.See <a href="/we-have-lift-off-rocket-loader-ga-is-mobile/">Combining Javascript &amp; CSS, a Better Way</a></p></li><li><p><i>Vendor</i>: Use <code>&lt;link rel="preload"&gt;</code> in the <code>&lt;head&gt;</code>.Important resources like scripts, in our case, can be specified for <i>preload</i>. The browser will load scripts in parallel and not block render-parsing.</p></li></ul>
    <div>
      <h3>Rocket Loader, The Early Years</h3>
      <a href="#rocket-loader-the-early-years">
        
      </a>
    </div>
    <p>Much has been written in this blog space about <a href="/tag/rocketloader/">Rocket Loader</a>, from its <a href="/how-cloudflare-rocket-loader-redefines-the-modern-cdn/">initial launch</a>, to the <a href="/we-have-lift-off-rocket-loader-ga-is-mobile/">current one</a>.</p><p>If reading outdated blog posts is not your thing, perhaps watching an extremely short video of a high-profile early Rocket Loader success (June 9, 2011) is:<a href="https://vimeo.com/24900882">CloudFlare Rocket Loader makes the Financial Times website (FT.com) faster</a></p><p>Rocket Loader improved page load times by:</p><ol><li><p>Minimising network requests through the bundling of JS files, including third-party, speeding up page rendering</p></li><li><p>Asynchronously loading the bundles, avoiding HTML parsing blockage</p></li><li><p>Caching scripts locally (using LocalStorage), reducing refetch requests.</p></li></ol><p>As browsers matured, Rocket Loader fell behind, leading to several severe shortcomings:</p><ul><li><p>It did not honour Content-Security-Policy.Rocket Loader was unaware of CSP headers, and loaded scripts indiscriminately.</p></li><li><p>It did not honour Subresource IntegrityRocket Loader loaded scripts through XHR, so browsers could not validate the fetched script.</p></li><li><p>It allowed for <a href="https://www.cloudflare.com/learning/security/threats/cross-site-scripting/">XSS</a> PersistenceSince Rocket Loader stored scripts in LocalStorage, a site’s compromised script could exist as a trojan in a customer’s storage, loading whenever the customer visited the site.</p></li><li><p>It was just out-of-date</p><ul><li><p>Script bundling fell out of favour with the introduction of http2.</p></li><li><p>The use of <code>eval()</code> was finally recognised as <i>evil</i>.</p></li><li><p>Mobile use skyrocketed; mobile browsers became sophisticated; eventually Rocket Loader was unable to support mobile.</p></li></ul></li></ul>
    <div>
      <h3>New and Improved Rocket Loader</h3>
      <a href="#new-and-improved-rocket-loader">
        
      </a>
    </div>
    <p>We recently rebuilt Rocket Loader from the ground up.</p><p>Although our aim remains the same, to improve customer page performance, we incorporated lessons learned. Most importantly, we learned not to aim too high. In order to satisfy all permutations of page layout, the old Rocket Loader created a virtual DOM, a decision that ultimately led to fragility. We've gone the simple, elegant route, knowing full well that there will be a minority of websites that will not benefit.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/515Gnp9rXBxLS4eicMWX5A/49004b2f6d659dff5b9bf10fba657629/new-and-improved-_4x.png" />
            
            </figure><p>The main concept behind Rocket Loader is quite straightforward: execute blocking scripts after all other page assets have loaded.</p><p>The scripts need to be loaded and executed in the originally intended order. Only external blocking scripts curtail page resources, but any script may rely on another one. We must simulate the <i>loading process of scripts</i>, mimicing how the browser would handle them during page load, but do it <i>after the page is actually fully loaded</i>.</p>
    <div>
      <h3>On the Server</h3>
      <a href="#on-the-server">
        
      </a>
    </div>
    <p>Rocket Loader has both a server-side and a client-side component. The goal of the former is to</p><ol><li><p>rewrite <code>&lt;script&gt;</code> tags in the page markup to make them non-executable, and</p></li><li><p>insert the client-side component of Rocket Loader into the page.</p></li></ol><p>The server-side component is built on top of our CF-HTML pipeline. CF-HTML is an nginx module that provides streaming HTML parsing and rewriting functionality with a SAX-style (<a href="https://en.wikipedia.org/wiki/Simple_API_for_XML">Simple API for XML</a>) API on top of it.</p><p>To make the scripts non-executable, we simply prepend their <code>type</code> attribute value with a randomly generated value (<a href="https://en.wikipedia.org/wiki/Cryptographic_nonce">nonce</a>), unique for each page request. Having a unique prefix for each page prevents Rocket Loader from being used as an XSS gadget to bypass various XSS filters.</p><p>Markup that looked like this:</p>
            <pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;script src="example.org/1.js"&gt;&lt;/script&gt;
    &lt;script src="example.org/2.js" type="text/javascript"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    ...body markup... 
    &lt;script src="example.org/3.js" type="text/javascript"&gt;&lt;/script&gt;
    ...more body markup... 
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
            <p>becomes:</p>
            <pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;script src="example.org/1.js" type="42deadbeef-"&gt;&lt;/script&gt;
    &lt;script src="example.org/2.js" type="42deadbeef-text/javascript"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    ...body markup... 
    &lt;script src="example.org/3.js" type="42deadbeef-text/javascript"&gt;&lt;/script&gt;
    ...more body markup... 
    &lt;script src="https://ajax.cloudflare.com/rocket-loader.js"
            data-cf-nonce="42deadbeef" defer&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
            <p>So far, no rocket science, but by making most, or all, scripts non-executable, Rocket Loader has unblocked page-parsing. Browsers display content sooner, improving perceived page load metrics, and engaging the user.</p>
    <div>
      <h4>On The Client</h4>
      <a href="#on-the-client">
        
      </a>
    </div>
    <p>Generally, scripts can be divided into four categories, each having distinct load and execution behaviours when inserted into the DOM:</p><ol><li><p><i>Inline scripts</i> - executed immediately upon insertion.</p></li><li><p><i>External blocking scripts</i> - start loading upon insertion, preventing other scripts from loading and executing.</p></li><li><p><i>External</i> <code>defer</code> <i>scripts</i> - start loading upon insertion, without preventing other scripts from loading and executing. Execution should happen right before <code>DOMContentLoaded</code> event.</p></li><li><p><i>External </i><code><i>async</i></code><i> scripts</i> - start loading upon insertion, without preventing other scripts from loading and executing. Executed when loaded.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4jwj7cYA0lu9sgSsf9boRx/86993ee16e0cd1eda9a3178017961b53/loadExecute1.png" />
            
            </figure><p>Modified diagram from <a href="https://html.spec.whatwg.org/#attr-script-defer">HTML Standard</a></p><p>To handle load and execution of all script types, Rocket Loader needs two passes.</p>
    <div>
      <h4>Pass One</h4>
      <a href="#pass-one">
        
      </a>
    </div>
    <p>On the first pass, we collect all scripts with our nonce onto a stack, then re-insert them into the DOM, with nonce removed, and wrapped in a comment node. These serve as our placeholders.</p>
            <pre><code>&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;!--&lt;script src="example.org/1.js"&gt;&lt;/script&gt;--&gt; 
    &lt;!--&lt;script src="example.org/2.js" type="text/javascript"&gt;&lt;/script&gt;--&gt;
  &lt;/head&gt;
  &lt;body&gt;
    ...body markup...
    &lt;!--&lt;script src="example.org/3.js" type="text/javascript"&gt;&lt;/script&gt;--&gt;
    ...more body markup... 
    &lt;script src="https://ajax.cloudflare.com/rocket-loader.js"
            data-cf-nonce="42deadbeef" defer&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
            <p>Rocket Loader now iterates through the scripts in our stack and re-inserts them, maintaining their intended position in relevant DOM collections (<code>document.scripts</code>, <code>document.querySelectorAll("script")</code>, <code>document.getElementsByTagName("script")</code>, etc.).</p><p>This process of script insertion and execution differs for each script category:</p><p><i>Inline scripts</i> - Placeholder is replaced with the original script element, without nonce, making the script executable. Browsers execute such scripts immediately upon insertion, <i>in the same execution tick</i>.</p><p><i>External blocking scripts</i> - As above, but Rocket Loader waits for the script’s <code>load</code> event before unwinding the script stack further. This delay simulates the script's blocking behaviour <b><i>manually</i></b>. Only parser-inserted external scripts (i.e. scripts present in the original HTML markup) are naturally blocking. External scripts inserted or created via a DOM API are considered async. This behaviour can’t be overridden, so we need our simulation.</p><p><i>External </i><code><i>async</i></code><i> scripts</i> - The same insertion procedure as inline scripts. Browsers treat all inserted external scripts as async, so the default behaviour suits us.</p><p><i>External </i><code><i>defer</i></code><i> scripts</i> - These are not executed during the first pass, since in the simulated environment we haven’t reached the <code>DOMContentLoaded</code> event yet. If we encounter a <code>defer</code> script on the stack we re-insert it, as is, without removing the nonce prefix. It remains non-executable, but in the correct DOM position.</p>
    <div>
      <h4>Pass Two</h4>
      <a href="#pass-two">
        
      </a>
    </div>
    <p>The second pass loads the <code>defer</code> scripts. Again, Rocket Loader collects all scripts with the nonce prefix (these are now just <code>defer</code> scripts) onto the execution stack, but does not replace them with placeholders. They remain in the DOM, since at this point in our simulated environment the complete document has loaded. We then activate them by replacing the <code>&lt;script&gt;</code> elements with themselves, nonce removed, and let the browser do the rest.</p>
    <div>
      <h4>Quirks I: Taming the Waterfall</h4>
      <a href="#quirks-i-taming-the-waterfall">
        
      </a>
    </div>
    <p>Ostensibly, we have now simulated browser script loading and execution behaviours. However, there are some one-off issues we must deal with, quirks if you will.</p><p>There is one not-so-obvious difference between our algorithm and the real behaviour of browsers. Modern browsers try to be clever with the way they manage page resources, engaging various heuristics to improve performance during page load. These are, generally, implementation-specific and not set-in-stone by any specification.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1UoWRV4FFktlhVFLGetJeC/093faf054eb7ba695d3f1ef05a184d3a/noRocketCroppedSm.png" />
            
            </figure><p>One such optimisation that affects us, is <i>speculative parsing</i>. Despite the official specification requiring a browser to block parsing on script execution, browsers continue parsing received HTML markup speculatively, and prefetch found external resources. For example, even with blocking scripts on a page, Chrome loads them simultaneously, in parallel.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1uUQ3PUV8f5FCh9SCY2Y6V/370f69793a57e917b9765e28a7a99406/prevRocketCroppedSm-1.png" />
            
            </figure><p>With Rocket Loader, browsers don’t prefetch scripts, as our nonce makes them non-executable during page load. Later, when we sequentially re-insert activated scripts, we witness a sequential “waterfall” graph.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1tjsG9hdMmd8QDZduzsrXF/bfbd69cda8f237c8eed1826a1196b58f/withRocketCroppedSm.png" />
            
            </figure><p>In our attempt to improve page load performance, we significantly slowed down some script loading. Ironic. Fortunately, we have a workaround: we can insert <i>preload hints</i> (see <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content">Preloading content with rel="preload"</a>) before we begin unwinding our script stack, giving the browser notice that we’ll soon be requiring these scripts. It begins fetching them as it would do during speculative parsing.</p><p>Our waterfall is replaced with improved parallel loading and better load metrics.</p>
    <div>
      <h4>Quirks II: <code>document.write()</code> is not dead yet</h4>
      <a href="#quirks-ii-document-write-is-not-dead-yet">
        
      </a>
    </div>
    <p>We've simulated script execution and insertion. We still need to deal with dynamic markup insertion. We can’t use <code>document.write()</code> directly since the document is already parsed and <code>document.close()</code> has been implicitely executed. Calling <code>write()</code> will create a new document, erasing the entire current document. We must manually parse content created by the <code>document.write</code> function and insert it in the intended location.</p><p>Not so simple, if one considers that <code>document.write</code> can insert partial markup. In the following example, if we parse and insert content on the first <code>document.write</code> call, we’ll completely ignore the completion of the <code>id</code> attribute that should be inserted with the second call:</p>
            <pre><code>document.write('&lt;div id="elm');
document.write(Date.now());
document.write('"&gt;some content&lt;/div&gt;');</code></pre>
            <p>So, we have a hard choice:</p><ul><li><p>We can buffer <i>all</i> content inserted via <code>document.write</code> during script execution and flush it afterwards, in which case already executed code expecting elements to be in the DOM will fail, or</p></li><li><p>We can flush inserted markup immediately, but not handle partial markup writes.</p></li></ul><p>Choosing the lesser of two evils, we decided to go with the first option: our observations showed cases like these are more common.</p><p>(Actually, there is a third option that allows for handling of both cases, but it requires proxying of a significant number of DOM APIs, a rabbit hole that we don’t want to dive into, KISS FTW, you know…).</p>
    <div>
      <h4>Quirks III: I ain't got no<code>&lt;body&gt;</code></h4>
      <a href="#quirks-iii-i-aint-got-no-body">
        
      </a>
    </div>
    <p>As mentioned, it’s not enough to just insert parsed markup. There are various modifications of the DOM performed by the parser during full document parsing that contend with <i>malformed markup</i>. We felt we should simulate at least some of them, because, well… scripts may rely on malformed markup.</p><p>Our initial implementation even included simulation of relatively exotic mechanisms such as <a href="https://html.spec.whatwg.org/multipage/parsing.html#unexpected-markup-in-tables"><i>foster parenting</i></a>, but eventually we decided to keep things simple and the only thing that Rocket Loader simulates is the squeezing out of unallowed content from the <code>&lt;head&gt;</code> element.</p><p>To perform this simulation we wrap our <code>document.write</code> buffer in a <code>&lt;head&gt;</code> element and feed this markup to the <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMParser">DOM Parser</a>.</p><p>Using the resulting document from the parser, we identify all nodes in its <code>&lt;head&gt;</code> and move them into the page, immediately following the script that performed the <code>document.write</code>. If we encounter any nodes in the parsed document's <code>&lt;body&gt;</code> element, we copy all nodes that follow the current script to the <code>&lt;body&gt;</code> element, prepended with the nodes in the parsed document.</p><p>To illustrate this simulation, consider the following page markup:</p>
            <pre><code>&lt;!DOCTYPE&gt;
&lt;head&gt;
  &lt;script&gt;
    document.write(‘&lt;link rel=”stylesheet” href=”1.css”&gt;’);
    document.write(‘&lt;div&gt;&lt;/div&gt;’);
    document.write(‘&lt;link rel=”stylesheet” href=”2.css”&gt;’);
  &lt;/script&gt;
  &lt;link rel=”stylesheet” href=”3.css”&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div&gt;Hey!&lt;/div&gt;
&lt;/body&gt;</code></pre>
            <p>The buffered, dynamically inserted, markup after script execution will be</p>
            <pre><code>&lt;link rel=”stylesheet” href=”1.css”&gt;
&lt;div&gt;&lt;/div&gt;
&lt;link rel=”stylesheet” href=”2.css”&gt;</code></pre>
            <p>and the string that we’ll feed to the DOMParser will be</p>
            <pre><code>&lt;!DOCTYPE&gt;
&lt;head&gt;
  &lt;link rel=”stylesheet” href=”1.css”&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;link rel=”stylesheet” href=”2.css”&gt;
&lt;/head&gt;</code></pre>
            <p>The parser will produce the following document structure from the provided markup (note that <code>&lt;div&gt;</code> is not allowed in <code>&lt;head&gt;</code> and was squeezed out to the <code>&lt;body&gt;</code>):</p>
            <pre><code>&lt;!DOCTYPE&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;link rel=”stylesheet” href=”1.css”&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;link rel=”stylesheet” href=”2.css”&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
            <p>Now we move all nodes that we found in parsed document's <code>&lt;head&gt;</code> to the original document:</p>
            <pre><code>&lt;!DOCTYPE&gt;
&lt;head&gt;
  &lt;script&gt;
    document.write(‘&lt;link rel=”stylesheet” href=”1.css”&gt;’);
    document.write(‘&lt;div&gt;&lt;/div&gt;’);
    document.write(‘&lt;link rel=”stylesheet” href=”2.css”&gt;’);
  &lt;/script&gt;
  &lt;link rel=”stylesheet” href=”1.css”&gt;
  &lt;link rel=”stylesheet” href=”3.css”&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div&gt;Hey!&lt;/div&gt;
&lt;/body&gt;</code></pre>
            <p>We see that parsed document's <code>&lt;body&gt;</code> contains some nodes, so we prepend them to the original document’s <code>&lt;body&gt;</code>:</p>
            <pre><code>&lt;!DOCTYPE&gt;
&lt;head&gt;
  &lt;script&gt;
    document.write(‘&lt;link rel=”stylesheet” href=”1.css”&gt;’);
    document.write(‘&lt;div&gt;&lt;/div&gt;’);
    document.write(‘&lt;link rel=”stylesheet” href=”2.css”&gt;’);
  &lt;/script&gt;
  &lt;link rel=”stylesheet” href=”1.css”&gt;
  &lt;link rel=”stylesheet” href=”3.css”&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;link rel=”stylesheet” href=”2.css”&gt;
  &lt;div&gt;Hey!&lt;/div&gt;
&lt;/body&gt;</code></pre>
            <p>And as a final step, we move all nodes in the <code>&lt;head&gt;</code>, that initially followed the current script, to after the nodes that we’ve just inserted in the <code>&lt;body&gt;</code>:</p>
            <pre><code>&lt;!DOCTYPE&gt;
&lt;head&gt;
  &lt;script&gt;
    document.write(‘&lt;link rel=”stylesheet” href=”1.css”&gt;’);
    document.write(‘&lt;div&gt;&lt;/div&gt;’);
    document.write(‘&lt;link rel=”stylesheet” href=”2.css”&gt;’);
  &lt;/script&gt;
  &lt;link rel=”stylesheet” href=”1.css”&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;link rel=”stylesheet” href=”2.css”&gt;
  &lt;link rel=”stylesheet” href=”3.css”&gt;
  &lt;div&gt;Hey!&lt;/div&gt;
&lt;/body&gt;</code></pre>
            
    <div>
      <h4>Quirks IV: Handling handlers</h4>
      <a href="#quirks-iv-handling-handlers">
        
      </a>
    </div>
    <p>There is one edge case which drastically changes the behaviour of our script-loading simulation. If we encounter elements with inline event handlers in the HTML markup, we need to execute <i>all scripts that precede such elements</i> since the handlers may rely on them.</p><p>We insert the Rocket Loader client side script in special "bailout" mode immediately before such elements. In bailout mode, we activate scripts the same way as in regular mode, except we do it in a blocking manner (remember, we need to prevent element from being parsed while we activate all preceding scripts).</p><p>As noted, it’s impossible to dynamically create blocking external scripts using DOM APIs such as <code>document.appendChild</code>. However, we have a solution to overcome this limitation.</p><p>Since the page is still loading, we can <code>document.write</code> the <code>outerHTML</code> of activatable script into the document, forcing the browser to mark it as parser-inserted and, thus, blocking. However, the script will be inserted in a DOM position different from its original, intended, position, which may break traversing of surrounding nodes from within the script (e.g. using <code>document.currentScript</code> as a starting point).</p><p>There is a trick. We leverage browser behaviour which parses generated content in the same execution tick as the <code>document.write</code> that produced it. We have immediate access to the written element. The <i>execution</i> of the external script is always scheduled for one of the <i>next</i> execution ticks. So, we can just move script to its original position right after we write it and have it in the correct DOM position, awaiting its execution.</p>
    <div>
      <h4>"I can resist everything except temptation"<a href="#fn1">[1]</a></h4>
      <a href="#i-can-resist-everything-except-temptation">
        
      </a>
    </div>
    <p>The need to account for every quirk, every variation in browser parsing, is strong, but implementation would eventually only weaken our product. We've dealt with the best part of browser parser behaviours, enough to benefit the majority of our customers.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5vV1rYeaoGyrndNCqb0qyq/733478aacd708279692d6dc75d4d085b/rock-house-_4x.png" />
            
            </figure>
    <div>
      <h3>What's Next?</h3>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>As Rocket Loader matures, and inevitably is affected by changes in Web technologies, it may be expanded and improved. For now, we're monitoring its use, identifying issues, and ensuring that it's worthy of its predecessor, which lasted through so many advances and changes in Web technology.</p><hr /><ol><li><p>Oscar Wilde, Lady Windermere's Fan (1892), and apologies to <a href="http://jethrotull.com/too-old/">Jethro Tull</a> for the blog post title. <a href="#fnref1">↩︎</a></p></li></ol> ]]></content:encoded>
            <category><![CDATA[Rocket Loader]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">6cHm33ksyTK14oTt1047Nr</guid>
            <dc:creator>Peter Belesis</dc:creator>
            <dc:creator>Ivan Nikulin</dc:creator>
        </item>
        <item>
            <title><![CDATA[We have lift off - Rocket Loader GA is mobile!]]></title>
            <link>https://blog.cloudflare.com/we-have-lift-off-rocket-loader-ga-is-mobile/</link>
            <pubDate>Fri, 01 Jun 2018 16:31:00 GMT</pubDate>
            <description><![CDATA[ Today we’re excited to announce the official GA of Rocket Loader, our JavaScript optimisation feature that will prioritise getting your content in front of your visitors faster than ever before with improved Mobile device support.  ]]></description>
            <content:encoded><![CDATA[ <p>Today we’re excited to announce the official GA of Rocket Loader, our JavaScript optimisation feature that will prioritise getting your content in front of your visitors faster than ever before with improved Mobile device support. In tests on <a href="http://www.cloudflare.com">www.cloudflare.com</a> we saw reduction of 45% (almost 1 second) in First Contentful Paint times on our pages for visitors.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/25aMZaHJf0XUvF13Sao55h/704e6dd9f7ae3502d77dbedb1f9e2ae5/photo-1457364887197-9150188c107b" />
            
            </figure><p>Photo by <a href="https://unsplash.com/@spacex?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">SpaceX</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></p><p>We initially launched Rocket Loader as a beta in June 2011, to asynchronously load a website’s JavaScript to dramatically improve the page load time. Since then, hundreds of thousands of our customers have benefited from a one-click option to boost the speed of your content.</p><p>With this release, we’ve vastly improved and streamlined Rocket Loader so that it works in conjunction with mobile &amp; desktop browsers to prioritise what matters most when loading a webpage: your content.</p>
    <div>
      <h3>Visitors don’t wait for page “load”</h3>
      <a href="#visitors-dont-wait-for-page-load">
        
      </a>
    </div>
    <p>To put it very simplistically - load time is a measure of when the browser has finished loading the document (HTML) and all assets referenced by that document.</p><p>When you clicked to visit this blog post, did you wait for the spinning wheel on your browser tab to start reading this content? You probably didn’t, and neither do your visitors. We’re conditioned to start consuming content as soon as it appears. However the industry had been focused on the load event timing too much, ignoring user perception &amp; behaviour. Data from Google analytics has shown that 53% of visits are abandoned if a mobile site takes longer than 3 seconds to load. This makes sense if you think about the last time you browsed onto a website in a hurry - if nothing is rendered quickly on screen you’re much more likely to go elsewhere.</p><p>Paint timing metrics are a closer approximation of how your users perceive speed. Put simply, paint timing measures when something is displayed on screen, and there are various stages of paint that can be measured &amp; reported which we’ll explain as we go.</p>
    <div>
      <h3>Analysing your performance</h3>
      <a href="#analysing-your-performance">
        
      </a>
    </div>
    <p>One of the ways in which you can learn more about your website’s performance is to use one of the many great synthetic analysis tools out there. The <a href="https://developers.google.com/web/tools/lighthouse/">Lighthouse</a> tool in Chrome can run a performance audit on your page simulating a typical mobile device &amp; connection. I ran this on Cloudflare’s homepage to illustrate the way the page loads over time:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/48XQjYN7Z7oEu95mvSXfYM/10aa074fb1393452f26e120460cc6d6b/Lighthouse-performance-filmstrip-without-Rocket-Loader.png" />
            
            </figure><p>The First Meaningful Paint (FMP) takes 4.8 seconds on this mobile device simulation. FMP doesn’t measure the time of the first paint, but it waits for any web fonts to render and for the biggest above-the-fold layout change to happen. The red line drawn at 4.8 seconds shows our FMP in this test. So what can we do to improve?</p>
    <div>
      <h3>Render blocking scripts are a problem for paint times</h3>
      <a href="#render-blocking-scripts-are-a-problem-for-paint-times">
        
      </a>
    </div>
    <p>Most tools will give you suggestions, and Lighthouse calls these opportunities and orders these by their estimated time saving:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/79GA4jBO0JSKSVstyCGs6y/2c97d235cbc1482beb916dcc875beb2b/Lighthouse-performance-opportunities-without-Rocket-Loader.png" />
            
            </figure><p>Try running a lighthouse audit on your website and see what opportunities you get.</p>
    <div>
      <h4>The march of the scripts</h4>
      <a href="#the-march-of-the-scripts">
        
      </a>
    </div>
    <p>Now is a good time to quantify the spread of JavaScript on the web. Using the excellent <a href="https://httparchive.org">HTTP Archive</a> project we can review the make up of the Alexa top 500k websites over the years. Using <a href="https://httparchive.org/reports/state-of-javascript?start=2011_06_01&amp;end=2018_05_01">their data</a>, we can see that the median number of JavaScripts on mobile has increased from 5, at Rocket Loader’s launch in June 2011, increasing nearly four fold to a whopping 19 JavaScripts in May 2018. So most of us have plenty of JavaScript on our websites and there’s a good chance it will be very high on the list of performance opportunities you can seize to improve your visitors’ experience.</p><p>Implementing this recommendation would require you to make changes to your origin application’s code to asynchronously load, defer or inline your scripts. In some cases this might not be possible because you don’t control your application’s code or have the expertise to implement these strategies. Rocket Loader to the rescue!</p>
    <div>
      <h3>How Rocket Loader works</h3>
      <a href="#how-rocket-loader-works">
        
      </a>
    </div>
    <p>Without Rocket Loader</p><p>With Rocket Loader</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/73cenr7paH30Yn2VWxALxW/49b4759f66a080b839a9a4b976cc97df/Rocket-before.svg" />
            
            </figure>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6TVRmx5nlCPzaPampT8xDJ/7d60135dd5e75ca49673fe8d14b7fc70/Rocket-after.svg" />
            
            </figure><p>New Rocket Loader prioritises paint time by locating the JavaScripts inside your HTML page and hiding them from the browser temporarily during the page load. This allows the browser to continue parsing the rest of your HTML and begin discovering other assets such as CSS &amp; images that are required to render your page. Once that has completed, Rocket Loader dynamically inserts the scripts back into the page and so the browser can load these.</p>
    <div>
      <h4>Enabling Rocket Loader</h4>
      <a href="#enabling-rocket-loader">
        
      </a>
    </div>
    <p>This bit is quite labour-intensive, so watch closely:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5cElUV4Id3F8ybKjRpaGxS/81d040e078fadebb4ccb7d0718438600/Enabling-Rocket-Loader-animation.gif" />
            
            </figure>
    <div>
      <h3>Measuring the impact</h3>
      <a href="#measuring-the-impact">
        
      </a>
    </div>
    <p>Let’s run lighthouse again on our homepage now we have Rocket Loader enabled:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1LWqQJ9m4mSVMHm0n5YYQh/bac12594f3ee8a6a24a607dc1669ca3e/Lighthouse-performance-fimstrip-with-Rocket-Loader.png" />
            
            </figure><p>So Lighthouse has detected that First Meaningful Paint is just over 1.5 seconds faster in this test - a really impressive improvement delivered from a single click!</p><p>To drive this home, the opportunity lighthouse identified is now officially a “passed audit”:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/48gHKjbXHuGJ4m2pn1yOBx/85ebab62116894cf848d044d1999344f/Lighthouse-performance-audit-with-Rocket-Loader.png" />
            
            </figure>
    <div>
      <h4>Let’s get Real (User Measurement)</h4>
      <a href="#lets-get-real-user-measurement">
        
      </a>
    </div>
    <p>To measure this with real users &amp; devices, last week we ran a simple A/B test on <a href="http://www.cloudflare.com">www.cloudflare.com</a> where 50% of page views were optimised using Rocket Loader so we could compare performance with and without it enabled. As shown by the lighthouse audits above, our main website is a great use-case for Rocket Loader because while we do use a lot of JavaScript for some interactive aspects of our pages, most important to our visitors is reading information about Cloudflare’s network, products &amp; features. So in short, content should be prioritised over JavaScript.</p><p>To illustrate the changes we observed, below is a graph of the Time To First Contentful Paint (TTFCP) for <a href="http://www.cloudflare.com">www.cloudflare.com</a> visits by real users during our test. TTFCP measures the first time something in the Document Object Model (DOM) is painted on the page. For websites that are primarily for consumption of content rather than heavy interactions, this is a closer representation of a user’s perception of your website speed than measuring load time.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/69COKNcTYBszg9BxaQNTZM/0fef1b1230c18d1c1c216de0cf7cfaef/Distribution-of-Time-To-First-Contentful-Paint-in-Baseline-vs-Rocket-Loader.png" />
            
            </figure><p>With Rocket Loader you can see those orange bars are grouped more to the left (faster) and higher meaning many more of our visitors were getting content on screen in under a second. In fact, the median improvement delivered by Rocket Loader during our <a href="http://www.cloudflare.com">www.cloudflare.com</a> test lands at a 0.93 second reduction in Time To First Contentful paint or around a 45% improvement over the baseline. Boom!</p>
    <div>
      <h3>What’s new in Rocket Loader</h3>
      <a href="#whats-new-in-rocket-loader">
        
      </a>
    </div>
    <p>So Rocket Loader continues to drive performance improvements on JavaScript heavy websites, but lots has changed under the hood. Here is a summary of the key changes:</p><ul><li><p>Improves time to first paint speed not just load time</p></li><li><p>Now compatible with over 93% of mobile devices<a href="#fn1">[1]</a></p></li><li><p>Tiny! Less than 10% of the size of prior version</p></li><li><p>Reduced complexity &amp; better compatibility with your on-site &amp; 3rd party JavaScripts</p></li><li><p>Compliant with stricter Content Security Policies (CSP)</p></li></ul>
    <div>
      <h4>More mobile users now get optimised</h4>
      <a href="#more-mobile-users-now-get-optimised">
        
      </a>
    </div>
    <p>We’ve already predicted that by the end of 2018 mobile usage will reach 60%. Additionally as of July 2018 Google will begin using page speed in <a href="https://webmasters.googleblog.com/2018/01/using-page-speed-in-mobile-search.html">mobile search ranking</a>. With that in mind providing a fast experience for mobile devices is more important than ever.</p><p>Rocket Loader beta was first launched back in 2012 at a time when mobile device usage on the web was around 15%. That version of Rocket Loader intercepted JavaScript on the page, and executed it in a virtual sandbox: a world familiar, but with behavior changed behind the scenes. Unfortunately, the technique we chose to create this virtual sandbox didn't work well on all mobile browsers. That was considered an undesirable but acceptable trade off 6 years ago, but today it’s vital customers can leverage this technique on mobile. Thanks to the reduced complexity of our approach, new Rocket Loader works on over 93%<a href="#fn1">[1:1]</a> of mobile devices in use today. For those devices that are not compatible, we simply deliver the website normally without this optimisation.</p>
    <div>
      <h4>Leaner &amp; Meaner</h4>
      <a href="#leaner-meaner">
        
      </a>
    </div>
    <p>New Rocket Loader’s JS weighs in at a lightweight 2.3KB. We did some <a href="/making-page-load-even-faster/">extensive refactoring</a> during 2017 that reduced the size of the JS required to run Rocket Loader from 47KB to 32KB and saved a staggering 213 terabytes of transfer across the globe. Due to the simplicity of the way New Rocket Loader works rocket-loader.min.js resulting in a JS file that is less than 10% of the size, saving approximately another 417 Terabytes of transfer each year when Rocket Loader does its thing.<a href="#fn2">[2]</a></p>
    <div>
      <h4>Compatibility with Content Security Policy</h4>
      <a href="#compatibility-with-content-security-policy">
        
      </a>
    </div>
    <p>New Rocket Loader does not modify the content of your JavaScript, it only changes the time at which it is loaded which means it plays nicely with any Content Security Policy (CSP) you have defined. With Rocket Loader beta, if you wanted to set a CSP that only allowed execution scripts hosted on your domain you would need to disable Rocket Loader as that would also combine &amp; load external JavaScripts through your domain. New Rocket Loader does not use this approach and instead lets the browser load &amp; cache the files normally. As we also enable HTTP/2 for all of our customers, any first party scripts will load over a single TCP connection, and 3rd party scripts are still asynchronously loaded meaning we can optimise their loading without proxying this content. All of this means modifying your CSP to accommodate Rocket Loader is as simple as allowing <code>script-src</code> for <code>https://ajax.cloudflare.com</code> so that Rocket Loader itself can load.</p>
    <div>
      <h3>How can I enable new Rocket Loader?</h3>
      <a href="#how-can-i-enable-new-rocket-loader">
        
      </a>
    </div>
    <p>If you already have Rocket Loader enabled as of today your site is using the new version. You can modify your settings at any time by visiting the Speed section of your Cloudflare settings.</p><p>If you had Rocket Loader disabled or in Manual mode just click the button in the Speed section to turn Rocket Loader on.</p>
    <div>
      <h3>What else can I do with Cloudflare to optimise my website?</h3>
      <a href="#what-else-can-i-do-with-cloudflare-to-optimise-my-website">
        
      </a>
    </div>
    <p>As always achieving good performance typically takes a variety of approaches and Rocket Loader tackles JavaScript specifically. There are some other very simple optimisations you should also ensure are enabled:</p><ul><li><p><b>Caching</b> - cache everything you can so content is served directly from any one of our 150+ data centres without waiting for your origin.</p></li><li><p><b>Minify &amp; Compress</b> - Enable minification of your HTML, <a href="https://www.cloudflare.com/learning/performance/how-to-minify-css/">CSS</a> &amp; JS in your Speed settings to losslessly reduce the total byte size of your web pages and enable Brotli compression so browsers that support this new compression method receive smaller responses.</p></li><li><p><b>Optimise your images</b> - Polish will automatically reduce the size of images on your website with support for highly efficient formats such as WebP. You can also turn on Mirage to optimise images for mobile devices with poor connectivity.</p></li><li><p><b>Use HTTP/2</b> - You get HTTP/2 support automatically as long as your site is served over HTTPS. Move as much as your content as you can onto your Cloudflare enabled URLs so all of that content can be multiplexed down a single TCP connection.</p></li><li><p><b>Use Argo &amp; Railgun</b> - For dynamic content Argo and Railgun can help optimise the connection &amp; transfer between Cloudflare and your origin server.</p></li></ul><hr /><ol><li><p>Rocket Loader utilises a browser API called <code>document.currentScript</code> which is currently supported by 93.7% of mobile devices and growing: <a href="https://caniuse.com/#feat=document-currentscript">https://caniuse.com/#feat=document-currentscript</a> <a href="#fnref1">↩︎</a> <a href="#fnref1:1">↩︎</a></p></li><li><p>Back of the envelope calculations based on 270 million rocket-loader.min.js responses served per week with a 29.7KB saving per serving. <a href="#fnref2">↩︎</a></p></li></ol> ]]></content:encoded>
            <category><![CDATA[Rocket Loader]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">2nGGFnv0vU43zJm2VyHzAK</guid>
            <dc:creator>Simon Moore</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing the Cloudflare Warp Ingress Controller for Kubernetes]]></title>
            <link>https://blog.cloudflare.com/cloudflare-ingress-controller/</link>
            <pubDate>Tue, 05 Dec 2017 14:00:00 GMT</pubDate>
            <description><![CDATA[ It’s ironic that the one thing most programmers would really rather not have to spend time dealing with is... a computer.  ]]></description>
            <content:encoded><![CDATA[ <p><i>NOTE: Prior to launch, this product was renamed Argo Tunnel. Read more in the </i><a href="/argo-tunnel/"><i>launch announcement</i></a><i>.</i></p><p>It’s ironic that the one thing most programmers would really rather not have to spend time dealing with is... a computer. When you write code it’s written in your head, transferred to a screen with your fingers and then it has to be run. On. A. Computer. Ugh.</p><p>Of course, code has to be run and typed on a computer so programmers spend hours configuring and optimizing shells, window managers, editors, build systems, IDEs, compilation times and more so they can minimize the friction all those things introduce. Optimizing your editor’s macros, fonts or colors is a battle to find the most efficient path to go from idea to running code.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/313EyDPITzKIECDYuj2qzq/a5eec59921a13d8aadfe7ed0e6ccc305/4532962327_c5a219d992_b.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a> <a href="https://www.flickr.com/photos/ivyfield/4532962327/in/photolist-7UyBLT-87ERdK-cCZVyC-8E715f-dbdKCZ-nGKair-cQay4s-ebwmMy-nAuTVv-jw9hxd-nqxc9h-nH1hJw-cp4c1Q-8B3PLE-PUxit-6gY6pQ-4P2q52-cCZVWL-6eRJAH-kNHY-nj1peY-nqxyHa-iNw9jP-5boJ6P-J3KVad-nj1hAZ-7yYuBu-8PCwt2-aJptFP-b4WLoM-nysiQJ-b8kxAV-BtcWbK-7yKiEj-cABXZ1-b8RR72-9LbLum-a6n7fX-X3SERX-br1nSQ-qdLBYQ-4XJsbd-5zXtUQ-dWePHa-qAi9Jt-awuoCM-cACicL-cA43Y1-nGQWPs-dotR4Y">image</a> by <a href="https://www.flickr.com/photos/ivyfield/">Yutaka Tsutano</a></p><p>Once the developer is managing their own universe they can write code at the speed of their mind. But when it comes to putting their code into production (which necessarily requires running their programs on machines that they don’t control) things inevitably go wrong. Production machines are never the same as developer machines.</p><p>If you’re not a developer, here’s an analogy. Imagine carefully writing an essay on a subject dear to your heart and then publishing it only to be told “unfortunately, the word ‘the’ is not available in the version of English the publisher uses and so your essay is unreadable”. That’s the sort of problem developers face when putting their code into production.</p><p>Over time different technologies have tried to deal with this problem: dual booting, different sorts of isolation (e.g. virtualenv, chroot), totally static binaries, virtual machines running on a developer desktop, elastic computing resources in clouds, and more recently <a href="https://en.wikipedia.org/wiki/Operating-system-level_virtualization">containers</a>.</p><p>Ultimately, using containers is all about a developer being able to say “it ran on my machine” and be sure that it’ll run in production, because fighting incompatibilities between operating systems, libraries and runtimes that differ from development to production is a waste of time (in particular developer brain time).</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3w1Pjk0GrYgcVwz3LiliHI/9c14d99ad7b4366a4db6d961f6a5e472/14403331148_bf25864944_k-1.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a> <a href="https://www.flickr.com/photos/jumilla/14403331148/in/photolist-nWLQxE-3eHezK-a85qp6-iEoK39-iYjss-9at6Z7-iYjsr-9gYg6a-bW3NeL-6AMx91-6rJaN3-28rg3L-TvqJmB-GjMFdK-2UrVHv-fs2WCj-4LGK2-2UrYz4-2Us2tK-Eeqeo-85HX8j-rF6SGG-o9rBXe-fWrkwA-dcGZAo-aoHuTF-SGpPT3-boaQy8-u8Bei-62JAWa-s9fFGo-61fNWq-fJYrjR-axYxm-2h42pU-2h42w7-rRNyES-fKfUKQ-6YXYGU-VjiSN1-4Xcg61-7YmaKY-WyF1oq-bE83qB-dvQoQw-CRQx6-82fwLo-fvJhXq-gmkcM-U3mP5E">image</a> by <a href="https://www.flickr.com/photos/jumilla/">Jumilla</a></p><p>In parallel, the rise of microservices is also a push to optimize developer brain time. The reality is that we all have limited brain power and ability to comprehend the complex systems that we build in their entirety and so we break them down into small parts that we can understand and test: functions, modules and services.</p><p>A microservice with a well-defined API and related tests running in a container is the ultimate developer fantasy. An entire program, known to operate correctly, that runs on their machine and in production.</p><p>Of course, no silver lining is without its cloud and containers beget a coordination problem: how do all these little programs find each other, scale, handle failure, log messages, communicate and remain secure. The answer, of course, is a coordination system like <a href="https://kubernetes.io/">Kubernetes</a>.</p><p>Kubernetes completes the developer fantasy by allowing them to write and deploy a service and have it take part in a whole.</p><p>Sadly, these little programs have one last hurdle before they turn into useful Internet services: they have to be connected to the brutish outside world. Services must be safely and scalably exposed to the Internet.</p><p>Recently, Cloudflare introduced a new service that can be used to connect a web server to Cloudflare without needing to have a public IP address for it. That service, <a href="https://warp.cloudflare.com">Cloudflare Warp</a>, maintains a connection from the server into the Cloudflare network. The server is then only exposed to the Internet through Cloudflare with no way for attackers to reach the server directly.</p><p>That means that any connection to it is protected and accelerated by Cloudflare’s service.</p>
    <div>
      <h3>Cloudflare Warp Ingress Controller and StackPointCloud</h3>
      <a href="#cloudflare-warp-ingress-controller-and-stackpointcloud">
        
      </a>
    </div>
    <p>Today, we are extending Warp’s reach by announcing the Cloudflare Warp <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress Controller</a> for Kubernetes (it’s an open source project and can be found <a href="https://github.com/cloudflare/cloudflare-warp-ingress">here</a>). We worked closely with the team at <a href="https://stackpoint.io/">StackPointCloud</a> to integrate Warp, Kubernetes and their universal control plane for Kubernetes.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/P1ACOSWhcrBdZU7WlNcHi/798b6421302ef4c4c070607c2df97fb8/Screen-Shot-2017-12-04-at-5.28.18-PM-2.png" />
            
            </figure><p>Within Kubernetes creating an ingress with annotation <code>kubernetes.io/ingress.class: cloudflare-warp</code> will automatically create secure Warp tunnels to Cloudflare for any service using that ingress. The entire lifecycle of tunnels is transparently managed by the ingress controller making exposing Kubernetes-managed services securely via Cloudflare Warp trivially easy.</p><p>The Warp Ingress Controller is responsible for finding Warp-enabled services and registering them with Cloudflare using the hostname(s) specified in the Ingress resource. It is added to a Kubernetes cluster by creating a file called warp-controller.yaml with the content below:</p>
            <pre><code>apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: null
  generation: 1
  labels:
    run: warp-controller
  name: warp-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      run: warp-controller
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: warp-controller
    spec:
      containers:
      - command:
        - /warp-controller
        - -v=6
        image: quay.io/stackpoint/warp-controller:beta
        imagePullPolicy: Always
        name: warp-controller
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - name: cloudflare-warp-cert
          mountPath: /etc/cloudflare-warp
          readOnly: true
      volumes:
        - name: cloudflare-warp-cert
          secret:
            secretName: cloudflare-warp-cert
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30</code></pre>
            <p>The full documentation is <a href="https://developers.cloudflare.com/argo-tunnel/reference/kubernetes/">here</a> and shows how to get up and running with Kubernetes and Cloudflare Warp on StackPointCloud, Google GKE, Amazon EKS or even <a href="https://kubernetes.io/docs/getting-started-guides/minikube/">minikube</a>.</p>
    <div>
      <h3>One Click with StackPointCloud</h3>
      <a href="#one-click-with-stackpointcloud">
        
      </a>
    </div>
    <p>Within StackPointCloud adding the Cloudflare Warp Ingress Controller requires just <i>a single click</i>. And one more click and you've deployed a Kubernetes cluster.</p><p>The connection between the Kubernetes cluster and Cloudflare is made using a TLS tunnel ensuring that all communication between the cluster and the outside world is secure.</p><p>Once connected the cluster and its services then benefit from Cloudflare's DDoS protection, WAF, global load balancing and health checks and huge global network.</p><p>The combination of Kubernetes and Cloudflare makes managing, scaling, accelerating and protecting Internet facing services simple and fast.</p> ]]></content:encoded>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Argo Smart Routing]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Cloudflare Tunnel]]></category>
            <guid isPermaLink="false">3JzDlyg9g51wqTsZdMuHW3</guid>
            <dc:creator>John Graham-Cumming</dc:creator>
        </item>
        <item>
            <title><![CDATA[Delivering Dot]]></title>
            <link>https://blog.cloudflare.com/f-root/</link>
            <pubDate>Sun, 10 Sep 2017 17:04:26 GMT</pubDate>
            <description><![CDATA[ Since March 30, 2017, Cloudflare has been providing DNS Anycast service as additional F-Root instances under contract with ISC (the F-Root operator).


F-Root is a single IPv4 address plus a single IPv6 address which both ISC and Cloudflare announce to the global Internet as a shared Anycast. This document reviews how F-Root has performed since that date in March 2017.


The DNS root servers are an important utility provided to all clients on the Internet for free - all F root instances includin ]]></description>
            <content:encoded><![CDATA[ <p>Since March 30, 2017, Cloudflare has been providing DNS Anycast service as additional F-Root instances under contract with ISC (the F-Root operator).</p><p>F-Root is a single IPv4 address plus a single IPv6 address which both ISC and Cloudflare announce to the global Internet as a shared Anycast. This document reviews how F-Root has performed since that date in March 2017.</p><p>The DNS root servers are an important utility provided to all clients on the Internet for free - all F root instances including those hosted on the Cloudflare network are a free service provided by both ISC and Cloudflare for public benefit. Because every online request begins with a DNS lookup, and every DNS lookup requires the retrieval of information stored on the DNS root servers, the DNS root servers plays an invaluable role to the functioning of the internet.</p><p>At Cloudflare, we were excited to work with ISC to bring greater security, speed and new software diversity to the root server system. First, the root servers, because of their crucial role, are often the subject of large scale volumetric DDoS attacks, which Cloudflare specializes in mitigating (Cloudflare is currently mitigating two concurrently ongoing DDoS attacks as we write this). Second, with a distributed network of data centers in well over 100 global cities, Cloudflare DNS is close to the end client which reduces <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">round trip times</a>. And lastly, the F-root nodes hosted by Cloudflare also run Cloudflare’s in-house DNS software, written in Go, which brings new code diversity to the root server system.</p><p>Throughout the deployment, ISC and Cloudflare paid close attention to telemetry measurements to ensure positive impact on the global DNS and root server system. Here is what both organizations observed when transit was enabled to Cloudflare DNS servers for F-Root.</p><p>Using <a href="https://atlas.ripe.net/">RIPE atlas probe measurements</a>, we can see an immediate performance benefit to the F-Root server, from 8.24 median RTT to 4.24 median RTT.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/13L4zY61yMlB1DhhOhsUZk/f0ad9663fd5784f54d843cf7f0f5dfd1/Screen-Shot-2017-03-30-at-5.03.20-PM.png" />
            
            </figure><p>F-Root actually became one of the fastest performing root servers:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/RcvHB8xBIzPqC6ImemPj2/170f12f49f51779083c0fa174d26efb6/Screen-Shot-2017-03-30-at-4.54.03-PM.png" />
            
            </figure><p>The biggest performance improvement was in the 90th percentile, or what are the top 10% of queries that received the slowest replies. This graph below shows the 90th percentile response time for any given RIPE atlas probe. Each probe is represented by two markers, a red X for before Cloudflare enabled transit and a blue X for after Cloudflare began announcing. You can see a drop in 90th percentile response times, the blue X’s are much lower than the red X’s.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3PtFnGp2WgTaocUCbcGDsT/b4bf46ac1949b419db1c6902045e4cde/image2017-4-4-14-11-9.png" />
            
            </figure><p>One of the optimizations that DNS resolvers do is preferring the faster root servers. As F-Root picked up speed, DNS resolvers also started sending it more traffic. Here you can see the aggregate number of queries received by each root letter per day, with an increase to F starting on March 30th.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3g8Idpfyj3eTGjHg6oICIo/037be9c789d68d5c5d9c199618b239bf/root-servers---stacked.png" />
            
            </figure><p>One large public DNS resolver shared with us their internal metrics, where you can also see a large shift of traffic to F-Root as F-Root increased in speed.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3KpIbZqEsaMnJReKarZRs3/bd29090c946e5f274fe9121f104816a8/resolver-traffictocfroot.png" />
            
            </figure><p>When one external DNS monitor published their <a href="https://blog.thousandeyes.com/2017-update-comparing-root-server-performance-globally/">Root Server measurement report</a> in June 2017, they mentioned F-Root’s increased performance. “9 more countries than in 2015 observed F-Root as the fastest — this is the biggest change across all of the root servers, so in this sense F-Root is “most improved.” F-Root has increasingly become the fastest root server in significant portions of Asia Pacific, Latin America and Eastern Europe.” They noted that “F-Root is now the fastest for roughly one quarter of the countries we tested from.”</p><p>We are happy to be working with ISC on delivering answers for F-Root and aim in the process to improve the speed and security of the F-Root server.</p> ]]></content:encoded>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[Anycast]]></category>
            <category><![CDATA[Reliability]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">6HZGHulKO43flc4dFeD4pv</guid>
            <dc:creator>Dani Grant</dc:creator>
        </item>
        <item>
            <title><![CDATA[How we built rate limiting capable of scaling to millions of domains]]></title>
            <link>https://blog.cloudflare.com/counting-things-a-lot-of-different-things/</link>
            <pubDate>Wed, 07 Jun 2017 12:47:51 GMT</pubDate>
            <description><![CDATA[ Back in April we announced Rate Limiting of requests for every Cloudflare customer. Being able to rate limit at the edge of the network has many advantages: it’s easier for customers to set up and operate, their origin servers are not bothered by excessive traffic or layer 7 attacks. ]]></description>
            <content:encoded><![CDATA[ <p>Back in April we announced <a href="/rate-limiting/">Rate Limiting</a> of requests for every Cloudflare customer. Being able to rate limit at the edge of the network has many advantages: it’s easier for customers to set up and operate, their origin servers are not bothered by excessive traffic or layer 7 attacks, the performance and memory cost of rate limiting is offloaded to the edge, and more.</p><p>In a nutshell, <a href="https://www.cloudflare.com/learning/bots/what-is-rate-limiting/">rate limiting</a> works like this:</p><ul><li><p>Customers can define one or more rate limit rules that match particular HTTP requests (failed login attempts, expensive API calls, etc.)</p></li><li><p>Every request that matches the rule is counted per client IP address</p></li><li><p>Once that counter exceeds a threshold, further requests are not allowed to reach the origin server and an error page is returned to the client instead</p></li></ul><p>This is a simple yet effective protection against brute force attacks on login pages and other sorts of abusive traffic like <a href="https://www.cloudflare.com/learning/ddos/application-layer-ddos-attack/">L7 DoS attacks</a>.</p><p>Doing this with possibly millions of domains and even more millions of rules immediately becomes a bit more complicated. This article is a look at how we implemented a rate limiter able to run quickly and accurately at the edge of the network which is able to cope with the colossal volume of traffic we see at Cloudflare.</p>
    <div>
      <h3>Let’s just do this locally!</h3>
      <a href="#lets-just-do-this-locally">
        
      </a>
    </div>
    <p>As the Cloudflare edge servers are running NGINX, let’s first see how the stock <a href="https://nginx.org/en/docs/http/ngx_http_limit_req_module.html">rate limiting</a> module works:</p>
            <pre><code>http {
    limit_req_zone $binary_remote_addr zone=ratelimitzone:10m rate=15r/m;
    ...
    server {
        ...
        location /api/expensive_endpoint {
            limit_req zone=ratelimitzone;
        }
    }
}</code></pre>
            <p>This module works great: it is reasonably simple to use (but requires a config reload for each change), and very efficient. The only problem is that if the incoming requests are spread across a large number of servers, this doesn’t work any more. The obvious alternative is to use some kind of centralized data store. Thanks to NGINX’s Lua scripting module, that we already use extensively, we could easily implement similar logic using any kind of central data backend.</p><p>But then another problem arises: how to make this fast and efficient?</p>
    <div>
      <h3>All roads lead to Rome? Not with anycast!</h3>
      <a href="#all-roads-lead-to-rome-not-with-anycast">
        
      </a>
    </div>
    <p>Since Cloudflare has a vast and diverse network, reporting all counters to a single central point is not a realistic solution as the latency is far too high and guaranteeing the availability of the central service causes more challenges.</p><p>First let’s take a look at how the traffic is routed in the Cloudflare network. All the traffic going to our edge servers is <a href="https://en.wikipedia.org/wiki/Anycast">anycast</a> traffic. This means that we announce the same IP address for a given web application, site or API worldwide, and traffic will be automatically and consistently routed to the closest live data center.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3MXm1hRKiJKtqZuSG8Ie0j/9b1869712dd6b0538cf7e3615fe4136d/world.png" />
            
            </figure><p>This property is extremely valuable: we are sure that, under normal conditions<a href="#fn1">[1]</a>, the traffic from a single IP address will always reach the same PoP. Unfortunately each new TCP connection might hit a different server inside that PoP. But we can still narrow down our problem: we can actually create an isolated counting system inside each PoP. This mostly solves the latency problem and greatly improves the availability as well.</p>
    <div>
      <h3>Storing counters</h3>
      <a href="#storing-counters">
        
      </a>
    </div>
    <p>At Cloudflare, each server in our edge network is as independent as possible to make their administration simple. Unfortunately for rate limiting, we saw that we do need to share data across many different servers.</p><p>We actually had a similar problem in the past with <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security#Session_IDs">SSL session IDs</a>: each server needed to fetch TLS connection data about past connections. To solve that problem we created a <a href="https://github.com/twitter/twemproxy">Twemproxy</a> cluster inside each of our PoPs: this allows us to split a memcache<a href="#fn2">[2]</a> database across many servers. A <a href="https://en.wikipedia.org/wiki/Consistent_hashing">consistent hashing</a> algorithm ensures that when the cluster is resized, only a few number of keys are hashed differently.</p><p>In our architecture, each server hosts a shard of the database. As we already had experience with this system, we wanted to leverage it for the rate limit as well.</p>
    <div>
      <h3>Algorithms</h3>
      <a href="#algorithms">
        
      </a>
    </div>
    <p>Now let’s take a deeper look at how the different rate limit algorithms work. What we call the <i>sampling period</i> in the next paragraph is the reference unit of time for the counter (1 second for a 10 req/sec rule, 1 minute for a 600 req/min rule, ...).</p><p>The most naive implementation is to simply increment a counter that we reset at the start of each sampling period. This works but is not terribly accurate as the counter will be arbitrarily reset at regular intervals, allowing regular traffic spikes to go through the rate limiter. This can be a problem for resource intensive endpoints.</p><p>Another solution is to store the timestamp of every request and count how many were received during the last sampling period. This is more accurate, but has huge processing and memory requirements as checking the state of the counter require reading and processing a lot of data, especially if you want to rate limit over long period of time (for instance 5,000 req per hour).</p><p>The <a href="https://en.wikipedia.org/wiki/Leaky_bucket">leaky bucket</a> algorithm allows a great level of accuracy while being nicer on resources (this is what the stock NGINX module is using). Conceptually, it works by incrementing a counter when each request comes in. That same counter is also decremented over time based on the allowed rate of requests until it reaches zero. The capacity of the bucket is what you are ready to accept as “burst” traffic (important given that legitimate traffic is not always perfectly regular). If the bucket is full despite its decay, further requests are mitigated.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/nur2wjEAyjE0S6VYblkr3/080ad1cbb09aa1bf6a294ffb4955e9e0/leakybucket.svg.png" />
            
            </figure><p>However, in our case, this approach has two drawbacks:</p><ul><li><p>It has two parameters (average rate and burst) that are not always easy to tune properly</p></li><li><p>We were constrained to use the memcached protocol and this algorithm requires multiple distinct operations that we cannot do atomically<a href="#fn3">[3]</a></p></li></ul><p>So the situation was that the only operations available were <code>GET</code>, <code>SET</code> and <code>INCR</code> (atomic increment).</p>
    <div>
      <h3>Sliding windows to the rescue</h3>
      <a href="#sliding-windows-to-the-rescue">
        
      </a>
    </div>
    
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2gvU2RabELQEWsauNMm8cu/ac296e8bf4e122f18935500ef0bd1c2e/14247536929_1a6315311d_z.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA 2.0</a> <a href="https://www.flickr.com/photos/halfrain/14247536929/">image</a> by <a href="https://www.flickr.com/photos/halfrain/">halfrain</a></p><p>The naive fixed window algorithm is actually not that bad: we just have to solve the problem of completely resetting the counter for each sampling period. But actually, can’t we just use the information from the previous counter in order to extrapolate an accurate approximation of the request rate?</p><p>Let’s say I set a limit of 50 requests per minute on an API endpoint. The counter can be thought of like this:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4SfLGYqNjjjxfJo0C9rU4H/e000ba22680e27910c8963a7111a97c8/sliding.svg.png" />
            
            </figure><p>In this situation, I did 18 requests during the current minute, which started 15 seconds ago, and 42 requests during the entire previous minute. Based on this information, the rate approximation is calculated like this:</p>
            <pre><code>rate = 42 * ((60-15)/60) + 18
     = 42 * 0.75 + 18
     = 49.5 requests</code></pre>
            <p>One more request during the next second and the rate limiter will start being very angry!</p><p>This algorithm assumes a constant rate of requests during the previous sampling period (which can be any time span), this is why the result is only an approximation of the actual rate. This algorithm can be improved, but in practice it proved to be good enough:</p><ul><li><p>It smoothes the traffic spike issue that the fixed window method has</p></li><li><p>It very easy to understand and configure: no average vs. burst traffic, longer sampling periods can be used to achieve the same effect</p></li><li><p>It is still very accurate, as an analysis on 400 million requests from 270,000 distinct sources shown:</p><ul><li><p>0.003% of requests have been wrongly allowed or rate limited</p></li><li><p>An average difference of 6% between real rate and the approximate rate</p></li><li><p>3 sources have been allowed despite generating traffic slightly above the threshold (false negatives), the actual rate was less than 15% above the threshold rate</p></li><li><p>None of the mitigated sources was below the threshold (false positives)</p></li></ul></li></ul><p>Moreover, it offers interesting properties in our case:</p><ul><li><p>Tiny memory usage: only two numbers per counter</p></li><li><p>Incrementing a counter can be done by sending a single <code>INCR</code> command</p></li><li><p>Calculating the rate is reasonably easy: one GET command<a href="#fn4">[4]</a> and some very simple, fast math</p></li></ul><p>So here we are: we can finally implement a good counting system using only a few memcache primitives and without much contention. Still we were not happy with that: it requires a memcached query to get the rate. At Cloudflare we’ve seen a few of the largest L7 attacks ever. We knew that large scale attacks would have crushed the memcached cluster like this. More importantly, such operations would slow down legitimate requests a little, even under normal conditions. This is not acceptable.</p><p>This is why the increment jobs are run asynchronously without slowing down the requests. If the request rate is above the threshold, another piece of data is stored asking all servers in the PoP to start applying the mitigation for that client. Only this bit of information is checked during request processing.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3pCPtEGvM5WPv1vUPzc0MH/ba54eae2f0aeaa2ae960c84bfe32be8c/el-sequence.svg.png" />
            
            </figure><p>Even more interesting: once a mitigation has started, we know exactly when it will end. This means that we can cache that information in the server memory itself. Once a server starts to mitigate a client, it will not even run another query for the subsequent requests it might see from that source!</p><p>This last tweak allowed us to efficiently mitigate large L7 attacks without noticeably penalizing legitimate requests.</p>
    <div>
      <h3>Conclusion</h3>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Despite being a young product, the rate limiter is already being used by many customers to control the rate of requests that their origin servers receive. The rate limiter already handles several billion requests per day and we recently mitigated attacks with as many as 400,000 requests per second to a single domain without degrading service for legitimate users.</p><p>We just started to explore how we can efficiently protect our customers with this new tool. We are looking into more advanced optimizations and create new features on the top of the existing work.</p><p>Interested in working on high-performance code running on thousands of servers at the edge of the network? Consider <a href="https://www.cloudflare.com/careers/">applying</a> to one of our open positions!</p><hr /><hr /><ol><li><p>The inner workings of anycast route changes are outside of the scope of this article, but we can assume that they are rare enough in this case. <a href="#fnref1">↩︎</a></p></li><li><p>Twemproxy also supports Redis, but our existing infrastructure was backed by <a href="https://github.com/twitter/twemcache">Twemcache</a> (a Memcached fork) <a href="#fnref2">↩︎</a></p></li><li><p>Memcache does support <a href="https://github.com/memcached/memcached/wiki/Commands#cas">CAS</a> (Compare-And-Set) operations and so optimistic transactions are possible, but it is hard to use in our case: during attacks, we will have a lot of requests, creating a lot of contention, in turn resulting in a lot of CAS transactions failing. <a href="#fnref3">↩︎</a></p></li><li><p>The counters for the previous and current minute can be retrieved with a single GET command <a href="#fnref4">↩︎</a></p></li></ol> ]]></content:encoded>
            <category><![CDATA[Attacks]]></category>
            <category><![CDATA[Rate Limiting]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Network]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Reliability]]></category>
            <guid isPermaLink="false">3zVAZ4bZB7IdYAVjbqN8RP</guid>
            <dc:creator>Julien Desgats</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing Accelerated Mobile Links: Making the Mobile Web App-Quick]]></title>
            <link>https://blog.cloudflare.com/accelerated-mobile/</link>
            <pubDate>Thu, 12 Jan 2017 06:00:00 GMT</pubDate>
            <description><![CDATA[ We've predicted that more than half of the traffic to Cloudflare's network will come from mobile devices. Even if they are formatted to be displayed on a small screen, the mobile web is built on traditional web protocols and technologies that were designed for desktop. ]]></description>
            <content:encoded><![CDATA[ <p>In 2017, we've predicted that more than half of the traffic to Cloudflare's network will come from mobile devices. Even if they are formatted to be displayed on a small screen, the mobile web is built on traditional web protocols and technologies that were designed for desktop CPUs, network connections, and displays. As a result, browsing the mobile web feels sluggish compared with using native mobile apps.</p><p>In October 2015, the team at Google announced <a href="http://ampproject.org">Accelerated Mobile Pages (AMP)</a>, a new, open technology to make the mobile web as fast as native apps. Since then, a large number of publishers have adopted AMP. Today, 600 million pages across 700,000 different domains are available in the AMP format.</p><p>The majority of traffic to this AMP content comes from people running searches on Google.com. If a visitor finds content through some source other than a Google search, even if the content can be served from AMP, it typically won't be. As a result, the mobile web continues to be slower than it needs to be.</p>
    <div>
      <h4>Making the Mobile Web App-Quick</h4>
      <a href="#making-the-mobile-web-app-quick">
        
      </a>
    </div>
    <p>Cloudflare's <a href="https://www.cloudflare.com/website-optimization/accelerated-mobile-links/">Accelerated Mobile Links</a> helps solve this problem, making content, regardless of how it's discovered, app-quick. Once enabled, Accelerated Mobile Links automatically identifies links on a Cloudflare customer's site to content with an <a href="https://www.cloudflare.com/website-optimization/accelerated-mobile-links/">AMP</a> version available. If a link is clicked from a mobile device, the AMP content will be loaded nearly instantly.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5ieCvJzcYWwJ2YOKbUrKFZ/b68843dc767ab04a1d6e1d5ccb08b44f/amp-configuration-1.png" />
            
            </figure><p>To see how it works, try viewing this post from your mobile device and clicking any of these links:</p><ul><li><p><b>TechCrunch:</b> <a href="https://techcrunch.com/2017/01/11/cloudflare-explains-how-fbi-gag-order-impacted-business/">Cloudflare explains how FBI gag order impacted business</a></p></li><li><p><b>ZDNet:</b> <a href="http://www.zdnet.com/article/cloudflare-offers-http2-server-push-to-boost-internet-speeds/">CloudFlare figured out how to make the Web one second faster</a></p></li><li><p><b>The Register:</b> <a href="http://www.theregister.co.uk/2016/06/21/cloudflare_apologizes_for_telia_screwing_you_over/">CloudFlare apologizes for Telia screwing you over</a></p></li><li><p><b>AMP 8 Ball:</b> <a href="https://amp8ball.com/">Ask The Amp Magic 8-Ball A Yes Or No Question.</a></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4LVE0NPtwr0c0kluYKwhAR/506cdb4a92c3c32b632e5cfdfc03d429/AML_animated_demo.gif" />
            
            </figure></li></ul>
    <div>
      <h4>Increasing User Engagement</h4>
      <a href="#increasing-user-engagement">
        
      </a>
    </div>
    <p>One of the benefits of Accelerated Mobile Links is that AMP content is loaded in a viewer directly on the site that linked to the content. As a result, when a reader is done consuming the AMP content closing the viewer returns them to the original source of the link. In that way, every Cloudflare customers' site can be more like a native mobile app, with the corresponding increase in user engagement.</p><p>For large publishers that want an even more branded experience, Cloudflare will offer the ability to customize the domain of the viewer to match the publisher's domain. This, for the first time, provides a seamless experience where AMP content can be consumed without having to send visitors to a Google owned domain. If you're a large publisher interested in customizing the Accelerated Mobile Links viewer, you can contact <a>Cloudflare's team</a>.</p>
    <div>
      <h4>Innovating on AMP</h4>
      <a href="#innovating-on-amp">
        
      </a>
    </div>
    <p>While Google was the initial champion of AMP, the technologies involved are <a href="https://github.com/ampproject/amphtml">open</a>. We worked closely with the Google team in developing Cloudflare's Accelerated Mobile Links as well as our own AMP cache. Malte Ubl, the technical lead for the AMP Project at Google said of our collaboration:</p><p><i>"Working with Cloudflare on its AMP caching solution was as seamless as open-source development can be. Cloudflare has become a regular contributor on the project and made the code base better for all users of AMP. It is always a big step for a software project to go from supporting specific caches to many, and it is awesome to see Cloudflare’s elegant solution for this."</i></p><p>Cloudflare now powers the only <a href="https://github.com/ampproject/amphtml/blob/master/spec/amp-cache-guidelines.md">compliant</a> non-Google AMP cache with all the same performance and security benefits as Google.</p><p>In the spirit of open source, we're working to help develop updates to the project to address some of publishers' and end users' concerns. Specifically, here are some features we're developing to address concerns that have been expressed about AMP:</p><ul><li><p>Easier ways to share AMP content using publisher's original domains</p></li><li><p>Automatically redirecting desktop visitors from the AMP version back to the original version of the content</p></li><li><p>A way for end users who would prefer not to be redirected to the AMP version of content to opt out</p></li><li><p>The ability for publishers to brand the AMP viewer and serve it from their own domain</p></li></ul><p>Cloudflare is committed to the AMP project. Accelerated Mobile Links is the first AMP feature we're releasing, but we'll be doing more over the months to come. As of today, Accelerated Mobile Links is available to all Cloudflare customers for free. You can enable it in your <a href="https://www.cloudflare.com/a/performance/">Cloudflare Performance dashboard</a>. Stay tuned for more AMP features that will continue to increase the speed of the mobile web.</p> ]]></content:encoded>
            <category><![CDATA[Mobile]]></category>
            <category><![CDATA[Google]]></category>
            <category><![CDATA[AMP]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">7ztp6Ye5JzaL00PeVFH5va</guid>
            <dc:creator>Matthew Prince</dc:creator>
        </item>
        <item>
            <title><![CDATA[A Very WebP New Year from Cloudflare]]></title>
            <link>https://blog.cloudflare.com/a-very-webp-new-year-from-cloudflare/</link>
            <pubDate>Wed, 21 Dec 2016 14:00:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare has an automatic image optimization feature called Polish, available for paid plan users. It recompresses images and stripping excess data, speeding up delivery to browsers. ]]></description>
            <content:encoded><![CDATA[ <p>Cloudflare has an automatic image optimization feature called <a href="/introducing-polish-automatic-image-optimizati/">Polish</a>, available to customers on paid plans. It recompresses images and removes unnecessary data so that they are delivered to browsers more quickly.</p><p>Up until now, Polish has not changed image types when optimizing (even if, for example, a PNG might sometimes have been smaller than the equivalent JPEG). But a new feature in Polish allows us to swap out an image for an equivalent image compressed using Google’s WebP format when the browser is capable of handling WebP and delivering that type of image would be quicker.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6RCs2VzrEL7pYO3RNWBRYa/5434d04bd47f702aa548ea48985c2aff/holly.jpg" />
            
            </figure><p><a href="https://creativecommons.org/licenses/by/2.0/">CC-BY 2.0</a> <a href="https://www.flickr.com/photos/john47kent/5307525503/">image</a> by <a href="https://www.flickr.com/photos/john47kent/">John Stratford</a></p>
    <div>
      <h3>What is WebP?</h3>
      <a href="#what-is-webp">
        
      </a>
    </div>
    <p>The main image formats used on the web haven’t changed much since the early days (apart from the SVG vector format, PNG was the last one to establish itself, <a href="https://en.wikipedia.org/wiki/Portable_Network_Graphics#History_and_development">almost two decades ago</a>).</p><p><a href="https://en.wikipedia.org/wiki/WebP">WebP</a> is a newer image format for the web, proposed by Google. It takes advantage of progress in <a href="https://www.cloudflare.com/learning/performance/glossary/what-is-image-compression/">image compression techniques</a> since formats such as JPEG and PNG were designed. It is often able to compress the images into a significantly smaller amount of data than the older formats.</p><p>WebP is versatile and able to replace the three main raster image formats used on the web today:</p><ul><li><p>WebP can do lossy compression, so it can be used instead of JPEG for photographic and photo-like images.</p></li><li><p>WebP can do lossless compression, and supports an alpha channel meaning images can have transparent regions. So it can be used instead of PNG, such as for images with sharp transitions that should be reproduced exactly (e.g. line art and graphic design elements).</p></li><li><p>WebP images can be animated, so it can be used as a replacement for animated GIF images.</p></li></ul><p>Currently, the main browser that supports WebP is Google’s Chrome (both on desktop and mobile devices). See <a href="http://caniuse.com/#feat=webp">the WebP page on caniuse.com</a> for more details.</p>
    <div>
      <h3>Polish WebP conversion</h3>
      <a href="#polish-webp-conversion">
        
      </a>
    </div>
    <p>Customers on the Pro, Business, and Enterprise plans can enable the automatic creation of WebP images by checking the WebP box in the Polish settings for a zone (these are found on the “Speed” page of the dashboard):</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5Beloplqsl1SNVyjFSOyl4/d6853afc514b5a6fc9cf94758d629695/polish-webp.png" />
            
            </figure><p>When this is enabled, Polish will optimize images just as it always has. But it will also convert the image to WebP, if WebP can shrink the image data more than the original format. These WebP images are only returned to web browsers that indicate they support WebP (e.g. Google Chrome), so most websites using Polish should be able to benefit from WebP conversion.</p><p>(Although Polish can now produce WebP images by converting them from other formats, it can't consume WebP images to optimize them. If you put a WebP image on an origin site, Polish won't do anything with it. Until the WebP ecosystem grows and matures, it is uncertain that attempting to optimize WebP is worthwhile.)</p><p>Polish has two modes: <i>lossless</i> and <i>lossy</i>. In lossless mode, JPEG images are optimized to remove unnecessary data, but the image displayed is unchanged. In lossy mode, Polish reduces the quality of JPEG images in a way that should not have a significant visible effect, but allows it to further reduce the size of the image data.</p><p>These modes are respected when JPEG images are converted to WebP. In lossless mode, the conversion is done in a way that preserves the image as faithfully as possible (due to the nature of the conversion, the resulting WebP might not be exactly identical, but there are unlikely to be any visible differences). In lossy mode, the conversion sacrifices a little quality in order to shrink the image data further, but as before, there should not be a significant visible effect.</p><p>These modes do not affect PNGs and GIFs, as these are lossless formats and so Polish will preserve images in those formats exactly.</p><p>Note that WebP conversion does not change the URLs of images, even if the file extension in the URL implies a different format. For example, a JPEG image at <code>https://example.com/picture.jpg</code> that has been converted to WebP will still have that same URL. The “Content-Type” HTTP header tells the browser the true format of an image.</p>
    <div>
      <h3>By the Numbers</h3>
      <a href="#by-the-numbers">
        
      </a>
    </div>
    <p>A few studies have been published of how well WebP compresses images compared with established formats. These studies provide a useful picture of how WebP performs. But before we released our WebP support, we decided to do a survey based on the context on which we planned to use WebP:</p><ul><li><p>We evaluated WebP based on a collection of images gathered from the websites of our customers. The corpus consisted of 23,500 images (JPEG, PNG and GIFs).</p></li><li><p>Some studies compare WebP with JPEG by taking uncompressed images and compressing them to JPEG and WebP directly. But we wanted to know what happens when we convert an image that was has already been compressed as a JPEG. In a sense this is an unfair test, because a JPEG may contain artifacts due to compression that would not be present in the original raw image, and conversion to WebP may try to retain those artifacts. But it is such conversions matter for our use of WebP (this consideration does not apply to PNG and GIF conversions, because they are lossless).</p></li><li><p>We’re not just interested in whether WebP conversion can shrink images found on the web. We want to know how much WebP allows Polish to reduce the size further than it already does, thus providing a real end-user benefit. So our survey also includes the results of Polish without WebP.</p></li><li><p>In some cases, converting to WebP does not produce a result smaller than the optimized image in the original format. In such cases, we discard the WebP image. So the figures presented below do not penalize WebP for such cases.</p></li></ul><p>Here is a chart showing the results of Polish, with and without WebP conversion. For each format, the average original image size is normalized to 100%, and the average sizes after Polishing are shown relative to that.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5YazeQmjUBmLEir39hYrUh/bab2f4768bf57f5a1f8aecd3f07d9d9a/webp-chart.png" />
            
            </figure><p>Here are the average savings corresponding to the chart:</p><table><tr><td><p><b>Original Format</b></p></td><td><p><b>Polish without WebP</b></p></td><td><p><b>Polish using WebP</b></p></td></tr><tr><td><p>JPEG (with Polish lossless mode)</p></td><td><p>9%</p></td><td><p>19%</p></td></tr><tr><td><p>JPEG (with Polish lossy mode)</p></td><td><p>34%</p></td><td><p>47%</p></td></tr><tr><td><p>PNG</p></td><td><p>16%</p></td><td><p>38%</p></td></tr><tr><td><p>GIF</p></td><td><p>3%</p></td><td><p>16%</p></td></tr></table><p>(The saving is calculated as 100% - (polished size) / (original size).)</p><p>As you can see, WebP conversion achieves significant size improvements not only for JPEG images, but also for PNG and GIF images. We believe supporting WebP will result in lower bandwidth and faster website delivery.</p>
    <div>
      <h3>… and a WebP New Year</h3>
      <a href="#and-a-webp-new-year">
        
      </a>
    </div>
    <p>WebP does not yet have the same level of browser support as JPEG, PNG and GIF, but we are excited about its potential to streamline web pages. Polish WebP conversion allows our customers to adopt WebP with a simple change to the settings in the Cloudflare dashboard. So, if you are on one of our <a href="https://www.cloudflare.com/plans/">paid plans</a>, we encourage you to try it out today.</p><p>PS — Want to help optimize the web? We’re <a href="https://www.cloudflare.com/join-our-team/">hiring</a>.</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare Polish]]></category>
            <category><![CDATA[Compression]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[WebP]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <guid isPermaLink="false">7M6kU8b93kMUiHWhgQTkjv</guid>
            <dc:creator>David Wragg</dc:creator>
        </item>
        <item>
            <title><![CDATA[You Can Finally Get More Page Rules For 5 For $5]]></title>
            <link>https://blog.cloudflare.com/5-more-page-rules-for-5-dollars/</link>
            <pubDate>Thu, 25 Aug 2016 16:01:53 GMT</pubDate>
            <description><![CDATA[ Since CloudFlare launched Page Rules in 2012, our Free, Pro and Business users have been asking for a way to get more Page Rules without committing to the next plan up. Starting today, anyone on CloudFlare can add 5 additional Page Rules for just $5/month. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Since CloudFlare <a href="/introducing-pagerules-fine-grained-feature-co/">launched Page Rules in 2012</a>, our Free, Pro and Business users have been asking for a way to get more Page Rules without committing to the next plan up. Starting today, anyone on CloudFlare can <a href="https://cloudflare.com/a/page-rules">add 5 additional Page Rules</a> for just $5/month.</p><p>Page Rules allows you to fine tune your site speed and to apply CloudFlare’s wide range of features to specific parts of your site. Page Rules are also <a href="https://api.cloudflare.com/#page-rules-for-a-zone-properties">accessible over our API</a>, so you can integrate them into your build process or sync them across your domains.</p><p>To help you get the most out of Page Rules, we’re also launching a <a href="https://www.cloudflare.com/features-page-rules/">tutorial site</a> that features videos to help you setup Page Rules for specific content management systems like <a href="https://www.cloudflare.com/features-page-rules/optimize-wordpress/">WordPress</a>, <a href="https://www.cloudflare.com/features-page-rules/optimize-magento/">Magento</a> and <a href="https://www.cloudflare.com/features-page-rules/optimize-drupal/">Drupal</a>, and for specific goals like <a href="https://www.cloudflare.com/features-page-rules/optimize-speed/">optimizing your website's speed</a>, <a href="https://www.cloudflare.com/features-page-rules/harden-security/">increasing security</a>, and <a href="https://www.cloudflare.com/features-page-rules/maximize-bandwidth/">saving on your bandwidth costs</a>.</p> ]]></content:encoded>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Page Rules]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">3tobeIKGZHstJAvZtu3t5R</guid>
            <dc:creator>Dani Grant</dc:creator>
        </item>
        <item>
            <title><![CDATA[Optimizing TLS over TCP to reduce latency]]></title>
            <link>https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency/</link>
            <pubDate>Fri, 10 Jun 2016 13:08:49 GMT</pubDate>
            <description><![CDATA[ The layered nature of the Internet (HTTP on top of some reliable transport (e.g. TCP), TCP on top of some datagram layer (e.g. IP), IP on top of some link (e.g. Ethernet)) has been very important in its development.  ]]></description>
            <content:encoded><![CDATA[ <p>The layered nature of the Internet (HTTP on top of some reliable transport (e.g. TCP), TCP on top of some datagram layer (e.g. IP), IP on top of some link (e.g. Ethernet)) has been very important in its development. Different link layers have come and gone over time (any readers still using 802.5?) and this flexibility also means that a connection from your web browser might traverse your home network over WiFi, then down a DSL line, across fiber and finally be delivered over Ethernet to the web server. Each layer is blissfully unaware of the implementation of the layer below it.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2NXh661kZVo5dom0ouMvxC/1d6ad85f6e85cf9e17c32d992d23e57a/640px-EISA_TokenRing_NIC.jpeg.jpeg" />
            
            </figure><p>But there are some disadvantages to this model. In the case of TLS (the most common standard used for sending encrypted data across in the Internet and the protocol your browser uses with visiting an https:// web site) the layering of TLS on top of TCP can cause delays to the delivery of a web page.</p><p>That’s because TLS divides the data being transmitted into records of a fixed (maximum) size and then hands those records to TCP for transmission. TCP promptly divides those records up into segments which are then transmitted. Ultimately, those segments are sent inside IP packets which traverse the Internet.</p><p>In order to prevent congestion on the Internet and to ensure reliable delivery, TCP will only send a limited number of segments before waiting for the receiver to acknowledge that the segments have been received. In addition, TCP guarantees that segments are delivered in order to the application. Thus if a packet is dropped somewhere between sender and receiver it’s possible for a whole bunch of segments to be held in a buffer waiting for the missing segment to be retransmitted before the buffer can be released to the application.</p>
    <div>
      <h3>TLS and TCP</h3>
      <a href="#tls-and-tcp">
        
      </a>
    </div>
    <p>What this means for TLS is that a large record that is split across multiple TCP segments can encounter unexpected delays. TLS can only handle <i>complete</i> records and so a missing TCP segment delays the whole TLS record.</p><p>At the start of a TCP connection as the TCP slow start occurs the record could be split across multiple segments that are delivered relatively slowly. During a TCP connection one of the segments that a TLS record has been split into may get lost causing the record to be delayed until the missing segment is retransmitted.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6F7nfcHkR6p8ECHSkpKBtx/8b38f045585fd3b17a4176d600ade7bc/Screen-Shot-2016-06-10-at-10-57-21.png" />
            
            </figure><p>Thus it’s preferable to not use a fixed TLS record size but <a href="http://chimera.labs.oreilly.com/books/1230000000545/ch04.html#TLS_RECORD_SIZE">adjust the record size</a> as the underlying TCP connection spins up (and down in the case of congestion). Starting with a small record size helps match the record size to the segments that TCP is sending at the start of a connection. Once the connection is running the record size can be increased.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/12L5mXigeSmG8mAExKMVe5/c975187228d920fd3d64748a13e6d484/Screen-Shot-2016-06-10-at-11-37-25.png" />
            
            </figure><p>CloudFlare uses NGINX to handle web requests. By default NGINX does not support dynamic TLS record sizes. NGINX has a fixed TLS record size with a default of 16KB that can be adjusted with the <a href="http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size"><code>ssl_buffer_size</code></a> parameter.</p>
    <div>
      <h3>Dynamic TLS Records in NGINX</h3>
      <a href="#dynamic-tls-records-in-nginx">
        
      </a>
    </div>
    <p>We modified NGINX to add support for dynamic TLS record sizes and are open sourcing our patch. You can find it <a href="https://github.com/cloudflare/sslconfig/blob/master/patches/nginx__dynamic_tls_records.patch">here</a>. The patch adds parameters to the NGINX <a href="http://nginx.org/en/docs/http/ngx_http_ssl_module.html">ssl</a> module.</p><p><code>ssl_dyn_rec_size_lo</code>: the TLS record size to start with. Defaults to 1369 bytes (designed to fit the entire record in a single TCP segment: 1369 = 1500 - 40 (IPv6) - 20 (TCP) - 10 (Time) - 61 (Max TLS overhead))</p><p><code>ssl_dyn_rec_size_hi</code>: the TLS record size to grow to. Defaults to 4229 bytes (designed to fit the entire record in 3 TCP segments)</p><p><code>ssl_dyn_rec_threshold</code>: the number of records to send before changing the record size.</p><p>Each connection starts with records of the size <code>ssl_dyn_rec_size_lo</code>. After sending <code>ssl_dyn_rec_threshold</code> records the record size is increased to <code>ssl_dyn_rec_size_hi</code>. After sending an additional <code>ssl_dyn_rec_threshold</code> records with size <code>ssl_dyn_rec_size_hi</code> the record size is increased to <code>ssl_buffer_size</code>.</p><p><code>ssl_dyn_rec_timeout</code>: if the connection idles for longer than this time (in seconds) that the TLS record size is reduced to <code>ssl_dyn_rec_size_lo</code> and the logic above is repeated. If this value is set to 0 then dynamic TLS record sizes are disabled and the fixed <code>ssl_buffer_size</code> will be used instead.</p>
    <div>
      <h3>Conclusion</h3>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>We hope people find our NGINX patch useful and would be very happy to hear from people who use it and/or improve it.</p> ]]></content:encoded>
            <category><![CDATA[TLS]]></category>
            <category><![CDATA[Optimization]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Security]]></category>
            <guid isPermaLink="false">3ygn5wGPwWpBV2zm7jADnA</guid>
            <dc:creator>John Graham-Cumming</dc:creator>
        </item>
        <item>
            <title><![CDATA[HTTP/2 For Web Developers]]></title>
            <link>https://blog.cloudflare.com/http-2-for-web-developers/</link>
            <pubDate>Thu, 10 Dec 2015 12:10:51 GMT</pubDate>
            <description><![CDATA[ HTTP/2 revolutionizes web optimization. Unlike HTTP/1.1's reliance on hacks like spriting and inlining for speed, HTTP/2 offers inherent performance improvements, simplifying development. ]]></description>
            <content:encoded><![CDATA[ <p>HTTP/2 changes the way web developers optimize their websites. In HTTP/1.1, it’s become common practice to eek out an extra 5% of page load speed by hacking away at your TCP connections and HTTP requests with techniques like spriting, inlining, domain sharding, and concatenation.</p><p>Life’s a little bit easier in HTTP/2. It gives the typical website a <a href="http://blog.chromium.org/2013/11/making-web-faster-with-spdy-and-http2.html">30% performance gain</a> without a complicated build and deploy process. In this article, we’ll discuss the new best practices for website optimization in HTTP/2.</p>
    <div>
      <h3>Web Optimization in HTTP/1.1</h3>
      <a href="#web-optimization-in-http-1-1">
        
      </a>
    </div>
    <p>Most of the <a href="https://www.cloudflare.com/application-services/products/website-optimization/">website optimization techniques</a> in HTTP/1.1 revolved around minimizing the number of HTTP requests to an origin server. A browser can only open a limited number of simultaneous TCP connections to an origin, and downloading assets over each of those connections is a serial process: the response for one asset has to be returned before the next one can be sent. This is called head-of-line blocking.</p><p>As a result, web developers began squeezing as many assets as they could into a single connection and finding other ways to trick browsers into avoiding head-of-line blocking. In HTTP/2, some of these practices can actually hurt page load times.</p>
    <div>
      <h3>The New Web Optimization Mindset for HTTP/2</h3>
      <a href="#the-new-web-optimization-mindset-for-http-2">
        
      </a>
    </div>
    <p>Optimizing for HTTP/2 requires a different mindset. Instead of worrying about reducing HTTP requests, web developers should focus on tuning their website’s caching behavior. The general rule is to <b>ship small, granular resources</b> so they can be cached independently and transferred in parallel.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/24AGzHZfCsK26xRbyOolht/4231f1d4ffc688eb34df4e9fa7f12cfd/http-2-multiplexing.png" />
            
            </figure><p>This shift occurs because of the <b>multiplexing</b> and <b>header compression</b> features of HTTP/2. Multiplexing lets multiple requests share a single TCP connection, allowing several assets to download in parallel without the unnecessary overhead of establishing multiple connections. This eliminates the head-of-line blocking issue of HTTP/1.1. Header compression further reduces the penalty of multiple HTTP requests, since the overhead for each request is smaller than the uncompressed HTTP/1.1 equivalent.</p><p>There are two other features of HTTP/2 that also change how you approach web optimization: <b>stream prioritization</b> and <b>server push</b>. The former lets browsers specify what order they want to receive resources, and the latter lets the server send extra resources that the browser doesn’t yet know it needs. To make the most of these new features, web developers need to unlearn some of the HTTP/1.1 best practices that have become second nature to them.</p>
    <div>
      <h3>Best Practices for HTTP/2 Web Optimization</h3>
      <a href="#best-practices-for-http-2-web-optimization">
        
      </a>
    </div>
    
    <div>
      <h4>Stop Concatenating Files</h4>
      <a href="#stop-concatenating-files">
        
      </a>
    </div>
    <p>In HTTP/1.1, web developers often combined all of the CSS for their entire website into a single file. Similarly, JavaScript was also condensed into a single file, and images were combined into a spritesheet. Having a single file for your CSS, JavaScript, and images vastly reduced the amount of HTTP requests, making it a significant performance gain for HTTP/1.1.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7Iecd2TKCbAeC1psMbKgkm/f1174abd7cf8789ae31bd8fb6b46bdf4/http-1-1-file-concatenation.png" />
            
            </figure><p>However, concatenating files is no longer a best practice in HTTP/2. While concatenation can still improve compression ratios, it forces expensive cache invalidations. Even if only a single line of CSS is changed, browsers are forced to reload <i>all</i> of your CSS declarations.</p><p>In addition, not every page in your website uses all of the declarations or functions in your concatenated CSS or <a href="https://www.cloudflare.com/learning/performance/why-minify-javascript-code/">JavaScript</a> files. After it’s been cached, this isn’t a big deal, but it means that unnecessary bytes are being transferred, processed, and executed in order to render the first page a user visits. While the overhead of a request in HTTP/1.1 made this a worthwhile tradeoff, it can actually slow down time-to-first-paint in HTTP/2.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5vGVOAARP8UNVvPIB70fyb/24482128b09677fc85c025401d584692/http-2-file-concatenation.png" />
            
            </figure><p>Instead of concatenating files, web developers should focus more on optimizing caching policy. By isolating files that change frequently from ones that rarely change, it’s possible to serve as much content as possible from your <a href="https://www.cloudflare.com/learning/cdn/what-is-a-cdn/">CDN</a> or the user’s browser cache.</p>
    <div>
      <h4>Stop Inlining Assets</h4>
      <a href="#stop-inlining-assets">
        
      </a>
    </div>
    <p>Inlining assets is a special case of file concatenation. It’s the practice of embedding CSS stylesheets, external JavaScript files, and images directly into an HTML page. For example, if you have web page that looks like this:</p>
            <pre><code>&lt;html&gt;
  &lt;head&gt;
	&lt;link rel="stylesheet" href="/style.css"&gt;
  &lt;/head&gt;
  &lt;body&gt;
	&lt;img src="logo.png"&gt;
	&lt;script src="scripts.min.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
            <p>You could run it through an inlining tool to get something like this:</p>
            <pre><code>&lt;html&gt;
  &lt;head&gt;
	&lt;style&gt;

	  body {
		font-size: 18px;

		color: #999;
	  }
	&lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
	&lt;img src="data:image/png;base64,Rw0KGgoAAAANSUhEUgAAAEAABA..."&gt;
	&lt;script&gt;console.log('Hello, World!');&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
            <p>In extreme cases, this can actually reduce the number of HTTP requests for a given web page to one. But, like file concatenation, you shouldn’t inline files you’re trying to optimize for HTTP/2.</p><p>Inlining means that browsers can’t cache individual assets. If you have CSS declarations that apply to your entire website embedded into every single HTML file, those declarations get sent back from the server every time. This results in redundant bytes being sent over the wire for every page that a user visits.</p><p>Inlining also has the unfortunate side effect of breaking stream prioritization. Because your CSS, JavaScript, and images are now embedded in your HTML, you’re effectively raising their priority to the same level as HTML content. This means that browsers can’t request assets in their preferred order, potentially increasing time-to-first-paint.</p><p>Instead of inlining assets, web developers should leverage HTTP/2’s server push functionality. Server push lets your web server say stuff like, "Hold on, you’re going to need this image and this CSS file to render that HTML page you just requested." Conceptually, this is the same as inlining an asset, but it doesn’t break stream prioritization, and it allows you to leverage a CDN and the user’s local browser cache, too.</p>
    <div>
      <h4>Stop Sharding Domains</h4>
      <a href="#stop-sharding-domains">
        
      </a>
    </div>
    <p>Domain sharding is a common technique for tricking browsers into opening more TCP connections than they’re supposed to. Browsers limit the number of connections to a single origin server, but by splitting your website’s assets across multiple domains, you can get it to open an extra set of TCP connections. This helps avoid head-of-line blocking, but it comes with significant tradeoffs.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/15BHvjDZ3lyXVv5ADPOI1a/3b95cf0335a377f66c18517cf0bf22c1/domain-sharding-1.png" />
            
            </figure><p>Domain sharding should be avoided in HTTP/2. Each shard results in an unnecessary DNS query, TCP connection, and <a href="https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/">TLS handshake</a> (assuming the servers use different <a href="https://www.cloudflare.com/application-services/products/ssl/">TLS certificates</a>). In HTTP/1.1, that overhead was compensated for by allowing several assets to download in parallel. But, this is no longer the case in HTTP/2: multiplexing allows multiple assets to download in parallel over a single connection. And, similar to asset inlining, domain sharding breaks HTTP/2 stream prioritization because browsers can’t prioritize streams across multiple domains.</p><p>If you’re currently sharding but want to leverage HTTP/2, you don’t necessarily need to go about <a href="https://www.cloudflare.com/learning/cloud/how-to-refactor-applications/">refactoring</a> your entire codebase. As discussed in our blog post on <a href="/using-cloudflare-to-mix-domain-sharding-and-spdy/">mixing domain sharding and SPDY</a>, browsers recognize when multiple origin servers use the same TLS certificate. When they do, the browser will reuse the same SPDY or HTTP/2 connection for both servers. This still results in multiple DNS queries, but it’s a decent compromise solution if you want the best performance under <a href="https://www.cloudflare.com/learning/performance/http2-vs-http1.1/">both HTTP/1.1 and HTTP/2</a>.</p>
    <div>
      <h3>Some Best Practices Are Still Best Practices</h3>
      <a href="#some-best-practices-are-still-best-practices">
        
      </a>
    </div>
    <p>Fortunately, not everything about web optimization changes in HTTP/2. There’s still several HTTP/1.1 best practices that carry over to HTTP/2. The rest of this article discusses techniques that you should be leveraging regardless of whether you’re optimizing for HTTP/1.1 or HTTP/2.</p>
    <div>
      <h4>Keep Reducing DNS Lookups</h4>
      <a href="#keep-reducing-dns-lookups">
        
      </a>
    </div>
    <p>Before a browser can request your website’s resources, it needs to find the IP address of your server using the <a href="https://www.cloudflare.com/learning/dns/what-is-dns/">domain name system (DNS)</a>. Until the DNS responds, the user is left staring at a blank screen. HTTP/2 is designed to optimize how web browsers communicate with web servers—it doesn’t affect the performance of the domain name system.</p><p>Since DNS queries can be expensive, especially if you have to start your query at the root nameservers, it’s still prudent to minimize the number of DNS lookups that your website uses. DNS prefetching via a <code>&lt;link rel='dns-prefetch' href='...' /&gt;</code> line in your document’s <code>&lt;head&gt;</code> can help, but it’s not a one-size-fits all solution.</p>
    <div>
      <h4>Keep Using a Content Delivery Network</h4>
      <a href="#keep-using-a-content-delivery-network">
        
      </a>
    </div>
    <p>It takes around 130ms for light to travel around the circumference of the earth. That’s latency that you <i>can’t</i> get rid of—it’s just physics. The imperfect nature of fiber optic cables and wireless connections, as well as the topology of the global Internet means that it actually takes closer to 300-400ms to transmit a network packet from your computer to a server that’s halfway around the world. User’s can perceive <a href="https://www.cloudflare.com/learning/performance/glossary/what-is-latency/">latency</a> as small as 100ms, and the only way to get around the laws of physics is to put your web assets geographically closer to your visitors via a CDN.</p>
    <div>
      <h4>Keep Leveraging Browser Caching</h4>
      <a href="#keep-leveraging-browser-caching">
        
      </a>
    </div>
    <p>You can take content delivery networks one step further by caching assets in the user’s local browser cache. This avoids any kind of data transfer over the network, aside from a 304 Not Modified response.</p>
    <div>
      <h4>Keep Minimizing the Size of HTTP Requests</h4>
      <a href="#keep-minimizing-the-size-of-http-requests">
        
      </a>
    </div>
    <p>Even though HTTP/2 requests are multiplexed, it takes time to transmit data over the wire. Accordingly, it’s still beneficial to reduce the amount of data you need to transfer. On the request end, this means minimizing the size of cookies, URL and query strings, and referrer URLs as much as possible.</p>
    <div>
      <h4>Keep Minimizing the Size of HTTP Responses</h4>
      <a href="#keep-minimizing-the-size-of-http-responses">
        
      </a>
    </div>
    <p>Of course, this holds true in the opposite direction. As a web developer, you want to make your server’s response as small as possible by minifying HTML, <a href="https://www.cloudflare.com/learning/performance/how-to-minify-css/">CSS</a>, and JavaScript Files, optimizing images for the web, and compressing resources with gzip.</p>
    <div>
      <h4>Keep Eliminating Unnecessary Redirects</h4>
      <a href="#keep-eliminating-unnecessary-redirects">
        
      </a>
    </div>
    <p>HTTP 301 and 302 redirects are sometimes a fact of life when migrating to a new platform or re-designing your website, but they should be eliminated wherever possible. Redirects result in an extra round trip from the browser to the server, and this adds unnecessary latency. You should be particularly wary of redirect chains where it takes more than a single redirect to get to the destination URL.</p><p>Server-side redirects like 301 and 302 responses aren’t ideal, but they aren’t the worst thing in the world, either. They can still be cached locally, so a browser can recognize the URL as a redirect and avoid an unnecessary round trip. Meta refreshes (e.g., <code>&lt;meta http-equiv="refresh"...</code>), on the other hand, are much more costly because they can’t be cached, and they can have performance issues in certain browsers.</p>
    <div>
      <h3>Conclusion (and Caveats)</h3>
      <a href="#conclusion-and-caveats">
        
      </a>
    </div>
    <p>And that’s HTTP/2 web optimization in a nutshell. Avoiding file concatenation, asset inlining, and domain sharding not only speeds up your website, but also makes for a much simpler build and deploy process.</p><p>There are, however, a few caveats to this discussion. Most servers, content delivery networks (including CloudFlare), and existing applications don’t support server push. Servers and CDNs will catch up soon enough, but for your application to benefit from server push, you’re going to have to make some changes to your codebase.</p><p>Also keep in mind is that HTTP/2 performance gains depend on the type of content you serve. For example, websites that rely on more external assets will see larger performance gains from HTTP/2 multiplexing than ones that have fewer HTTP requests.</p> ]]></content:encoded>
            <category><![CDATA[HTTP2]]></category>
            <category><![CDATA[spdy]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Optimization]]></category>
            <guid isPermaLink="false">7ASnS08pg4sCtRL32v054u</guid>
            <dc:creator>Ryan Hodson</dc:creator>
        </item>
    </channel>
</rss>