
<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>Sat, 04 Apr 2026 12:42:30 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Performance isolation in a multi-tenant database environment]]></title>
            <link>https://blog.cloudflare.com/performance-isolation-in-a-multi-tenant-database-environment/</link>
            <pubDate>Fri, 26 Aug 2022 15:08:06 GMT</pubDate>
            <description><![CDATA[ Enforcing stricter performance isolation across neighboring tenants who rely on our storage infrastructure ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Operating at Cloudflare scale means that across the technology stack we spend a great deal of time handling different load conditions. In this blog post we talk about how we solved performance difficulties with our Postgres clusters. These clusters support a large number of tenants and highly variable load conditions leading to the need to isolate activity to prevent tenants taking too much time from others. Welcome to real-world, large database cluster management!</p><p>As an intern at Cloudflare I got to work on improving how our database clusters behave under load and open source the resulting code.</p><p>Cloudflare operates production Postgres clusters across multiple regions in data centers. Some of our earliest service offerings, such as our DNS Resolver, Firewall, and DDoS Protection, depend on our Postgres clusters' high availability for OLTP workloads. The high availability cluster manager, <a href="https://github.com/sorintlab/stolon">Stolon</a>, is employed across all clusters to independently control and replicate data across Postgres instances and elect Postgres leaders and failover under high load scenarios.</p><p>PgBouncer and HAProxy act as the gateway layer in each cluster. Each tenant acquires client-side connections from PgBouncer instead of Postgres directly. PgBouncer holds a pool of maximum server-side connections to Postgres, allocating those across multiple tenants to prevent Postgres connection starvation. From here, PgBouncer forwards queries to HAProxy, which load balances across Postgres primary and read replicas.</p>
    <div>
      <h2>Problem</h2>
      <a href="#problem">
        
      </a>
    </div>
    <p>Our multi-tenant Postgres instances operate on bare metal servers in non-containerized environments. Each backend application service is considered a single tenant, where they may use one of multiple Postgres roles. Due to each cluster serving multiple tenants, all tenants share and contend for available system resources such as CPU time, memory, disk IO on each cluster machine, as well as finite database resources such as server-side Postgres connections and table locks. Each tenant has a unique workload that varies in system level resource consumption, making it impossible to enforce throttling using a global value.</p><p>This has become problematic in production affecting neighboring tenants:</p><ul><li><p><b>Throughput</b>. A tenant may issue a burst of transactions, starving shared resources from other tenants and degrading their performance.</p></li><li><p><b>Latency</b>: A single tenant may issue very long or expensive queries, often concurrently, such as large table scans for ETL extraction or queries with lengthy table locks.</p></li></ul><p>Both of these scenarios can result in degraded query execution for neighboring tenants. Their transactions may hang or take significantly longer to execute (higher latency) due to either reduced CPU share time, or slower disk IO operations due to many seeks from misbehaving tenant(s). Moreover, other tenants may be blocked from acquiring database connections from the database proxy level (PgBouncer) due to existing ones being held during long and expensive queries.</p>
    <div>
      <h2>Previous solution</h2>
      <a href="#previous-solution">
        
      </a>
    </div>
    <p>When database cluster load significantly increases, finding which tenants are responsible is the first challenge. Some techniques include searching through all tenants' previous queries under typical system load and determining whether any new expensive queries have been introduced under the Postgres' pg_stat_activity view.</p>
    <div>
      <h3>Database concurrency throttling</h3>
      <a href="#database-concurrency-throttling">
        
      </a>
    </div>
    <p>Once the misbehaving tenants are identified, Postgres server-side connection limits are manually enforced using the Postgres query.</p>
            <pre><code>ALTER USER "some_bad-user" WITH CONNECTION LIMIT 123;</code></pre>
            <p>This essentially restricts or “squeezes” the concurrent throughput for a single user, where each tenant will only be able to exhaust their share of connections.</p><p>Manual concurrency (connection) throttling has shown improvements in shedding load in Postgres during high production workloads:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/W3HCXcAwPCBrsjKXc6eF6/612ff61445829d5f6f5bf9034ac1f869/2.png" />
            
            </figure><p>While we have seen success with this approach, it is not perfect and is horribly manual. It also suffers from the following:</p><ul><li><p>Postgres does not immediately kill existing tenant connections when a new user limit is set; the user may continue to issue bursty or expensive queries.</p></li><li><p>Tenants may still issue very expensive, resource intensive queries (affecting neighboring tenants) even if their concurrency (connection pool size) is reduced.</p></li><li><p>Manually applying connection limits against a misbehaving tenant is toil; an SRE could be paged to physically apply the new limit at any time of the day.</p></li><li><p>Manually analyzing and detecting misbehaving tenants based on queries can be time-consuming and stressful especially during an incident, requiring production SQL analysis experience.</p></li><li><p>Additionally, applying new throttling limits per user/pool, such as the allocated connection count, can be arbitrary and experimental while requiring extensive understanding of tenant workloads.</p></li><li><p>Oftentimes, Postgres may be under so much load that it begins to hang (CPU starvation). SREs may be unable to manually throttle tenants through native interfaces once a high load situation occurs.</p></li></ul>
    <div>
      <h2>New solution</h2>
      <a href="#new-solution">
        
      </a>
    </div>
    
    <div>
      <h3>Gateway concurrency throttling</h3>
      <a href="#gateway-concurrency-throttling">
        
      </a>
    </div>
    <p>Typically, the system level resource consumption of a query is difficult to control and isolate once submitted to the server or database system for execution. However, a common approach is to intercept and throttle connections or queries at the gateway layer, controlling per user/pool traffic characteristics based on system resource consumption.</p><p>We have implemented connection throttling at our database proxy server/connection pooler, PgBouncer. Previously, PgBouncer’s user level connection limits would not kill existing connections, but only prevent exceeding it. We now support the ability to throttle and kill existing connections owned by each user or each user’s connection pool statically via configuration or at runtime via new administrative commands.</p>
    <div>
      <h5>PgBouncer Configuration</h5>
      <a href="#pgbouncer-configuration">
        
      </a>
    </div>
    
            <pre><code>[users]
dns_service_user = max_user_connections=60
firewall_service_user = max_user_connections=80
[pools]
user1.database1 = pool_size=90</code></pre>
            
    <div>
      <h5>PgBouncer Runtime Commands</h5>
      <a href="#pgbouncer-runtime-commands">
        
      </a>
    </div>
    
            <pre><code>SET USER dns_service_user = ‘max_user_connections=40’;
SET POOL dns_service_user.dns_db = ‘pool_size=30’;</code></pre>
            <p>This required major bug fixes, <a href="https://www.cloudflare.com/learning/cloud/how-to-refactor-applications/">refactoring</a> and implementation work in our fork of PgBouncer. We’ve also raised multiple pull requests to contribute all of our features to PgBouncer open source. To read about all of our work in PgBouncer, read <a href="/open-sourcing-our-fork-of-pgbouncer/">this blog</a>.</p><p>These new features now allow for faster and more granular "load shedding" against a misbehaving tenant’s concurrency (connection pool, user and database pair), while enabling stricter performance isolation.</p>
    <div>
      <h2>Future solutions</h2>
      <a href="#future-solutions">
        
      </a>
    </div>
    <p>We are continuing to build infrastructure components that monitor per-tenant resource consumption and detect which tenants are misbehaving based on system resource indicators against historical baselines. We aim to automate connection and query throttling against tenants using these new administrative commands.</p><p>We are also experimenting with various automated approaches to enforce strict tenant performance isolation.</p>
    <div>
      <h3>Congestion avoidance</h3>
      <a href="#congestion-avoidance">
        
      </a>
    </div>
    <p>An adaptation of the TCP Vegas congestion avoidance algorithm can be employed to adaptively estimate and enforce each tenant’s optimal concurrency while still maintaining low latency and high throughput for neighboring tenants. This approach does not require resource consumption profiling, manual threshold tuning, knowledge of underlying system hardware, or expensive computation.</p><p>Traditionally, TCP Vegas converges to the initially unknown and optimal congestion window (max packets that can be sent concurrently). In the same spirit, we can treat the unknown congestion window as the optimal concurrency or connection pool size for database queries. At the gateway layer, PgBouncer, each tenant will begin with a small connection pool size, while we dynamically sample each tenant’s transaction’s <a href="https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/">round trip time (RTT)</a> against Postgres. We gradually increase the connection pool size (congestion window) of a tenant so long as their transaction RTTs do not deteriorate.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6AnIgPtx8mcQHSs2YTXji9/c1ef3fe2dbf6e15fb45f056c579b7974/3.png" />
            
            </figure><p>When a tenant's sampled transaction latency increases, the formula's minimum by sampled request latency ratio will decrease, naturally reducing the tenant's available concurrency which reduces database load.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2zAs4XrTUxebX1K8TnngYX/928836fda5fc65505d26ebe248de38e3/4.png" />
            
            </figure><p>Essentially, this algorithm will "back off" when observing high query latencies as the indicator of high database load, regardless of whether the latency is due to CPU time or disk/network IO blocking, etc. This formula will converge to find the optimal concurrency limit (connection pool size) since the latency ratio always converges to 0 with sufficiently large sample request latencies. The square root of the current tenant pool size is chosen as a constant request "burst" headroom because of its fast growth and being relatively large for small pool sizes (when latencies are low) but converges when the pool size is reduced (when latencies are high).</p><p>Rather than reactively shedding load, congestion avoidance preventatively or “smoothly” <b>throttles traffic before load induced performance degradation becomes an issue</b>. This algorithm aims to prevent database server resource starvation which causes other queries to hang.</p><p>Theoretically, if one tenant misbehaves and causes load induced latency for others, this TCP congestion algorithm may incorrectly blindly throttle all tenants. Hence why it may be necessary to apply this adaptive throttling only against tenants with high CPU to latency correlation when the system performance is degrading.</p>
    <div>
      <h3>Tenant resource quotas</h3>
      <a href="#tenant-resource-quotas">
        
      </a>
    </div>
    <p>Configurable resource quotas can be introduced per each tenant. Upstream application service tenants are restricted to their allocated share of resources expressed as CPU % utilized per second and max memory. If a tenant overuses their share, the database gateway (PgBouncer) should throttle their concurrency, queries per second and ingress bytes to force consumption within their allocated slice.</p><p>Resource throttling a tenant must not "spillover" or affect other tenants accessing the same cluster. This could otherwise reduce the availability of other customer-facing applications and violate SLO (service-level objectives). Resource restriction must be isolated to each tenant.</p><p>If traffic is low against Postgres instances, tenants should be permitted to exceed their allocation limit. However, when load against the cluster degrades the entire performance of the system (latency), the tenant's limit must be re-enforced at the gateway layer, PgBouncer. We can make deductions around the health of the entire database server based on indicators such as average query latency’s rate of change against a predefined threshold. All tenants should agree that a surplus in resource consumption may result in query throttling of any pattern.</p><p>Each tenant has a <b>unique and variable workload</b>, which may degrade multi tenant performance at any time. Quick detection requires profiling the baseline resource consumption of each tenant’s (or tenant’s connection pooled) workload against each local Postgres server (backend pids) in near real-time. From here, we can correlate the “baseline” traffic characteristics with system level resource consumption per database instance.</p><p>Taking an average or generalizing statistical measures across distributed nodes (each tenant's resource consumption on Postgres instances in this case) can be inaccurate due to high variance in traffic against leader vs replica instances. This would lead to faulty throttling decisions applied against users. For instance, we should not throttle a user’s concurrency on an idle read replica even if the user consumes excessive resources on the primary database instance. It is preferable to capture tenant consumption on a per Postgres instance level, and enforce throttling per instance rather than across the entire cluster.</p><p>Multivariable regression can be employed to model the relationship between independent variables (concurrency, queries per second, ingested bytes) against the dependent variables (system level resource consumption). We can calculate and enforce the optimal independent variables per tenant under high load scenarios. To account for workload changes, regression <b>adaptability vs accuracy</b> will need to be tuned by adjusting the sliding window size (amount of time to retain profiled data points) when capturing workload consumption.</p>
    <div>
      <h3>Gateway query queuing</h3>
      <a href="#gateway-query-queuing">
        
      </a>
    </div>
    <p>User queries can be prioritized for submission to Postgres at the gateway layer (PgBouncer). Within a one or multiple global priority queues, query submissions by all tenants are ordered based on the current resource consumption of the tenant’s connection pool or the tenant itself. Alternatively, ordering can be based on each query’s historical resource consumption, where each query is independently profiled. Based on changes in tenant resource consumption captured from each Postgres instance’s server, all queued queries can be reordered every time the scheduler forwards a query to be submitted.</p><p>To prevent priority queue starvation (one tenant’s query is at the end of the queue and is never executed), the gateway level query queuing can be configured to only enable when there is peak load/traffic against the Postgres instance. Or, the time of enqueueing a query can be factored into the priority ordering.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7twYWGaewKPLe7xRCpnIBG/f47ea6a51eaf09d7e44982a9128365a0/6.png" />
            
            </figure><p>This approach would isolate tenant performance by allowing non-offending tenants to continue reserving connections and executing queries (such as critical health monitoring queries). <b>Higher latency would only be observed from the tenants that are utilizing more resources</b> (from many/expensive transactions). This approach is straightforward to understand, generic in application (can queue transactions based on other input metrics), and <b>non-destructive</b> as it does not kill client/server connections, and should only drop queries when the in-memory priority queue reaches capacity.</p>
    <div>
      <h3>Conclusion</h3>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Performance isolation in our multi-tenant storage environment continues to be a very interesting challenge that touches areas including OS resource management, database internals, queueing theory, congestion algorithms and even statistics. We’d love to hear how the community has tackled the “noisy neighbor” problem by isolating tenant performance at scale!</p> ]]></content:encoded>
            <category><![CDATA[Edge Database]]></category>
            <category><![CDATA[Internship Experience]]></category>
            <guid isPermaLink="false">5dmpsqleBtIJ2FoixbLOCK</guid>
            <dc:creator>Justin Kwan</dc:creator>
            <dc:creator>Vignesh Ravichandran</dc:creator>
        </item>
        <item>
            <title><![CDATA[Using Cloudflare Tunnel and Access with Postgres]]></title>
            <link>https://blog.cloudflare.com/cloudflare-tunnel-for-postgres/</link>
            <pubDate>Thu, 23 Jun 2022 13:35:14 GMT</pubDate>
            <description><![CDATA[ How the Database team uses Cloudflare Tunnels internally to improve security and usability of adhoc DB queries ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6m1wkgdmUtbzfBvUkNir4P/28533b00766735dfa116235de718481c/image1-41.png" />
            
            </figure><p>For a long time we used the traditional method of accessing internal database clusters by SSHing to a bastion host. Due to the overhead and limitations of maintaining the SSH configuration, we’ve moved to using Cloudflare Tunnels combined with Cloudflare Access to dramatically improve the user experience and onboarding times related to database access.</p>
    <div>
      <h3>How we used to work</h3>
      <a href="#how-we-used-to-work">
        
      </a>
    </div>
    <p>Internally we rely heavily on PostgreSQL to power many services at Cloudflare – including Stream, Images and the Cloudflare Dashboard itself. We run our Postgres clusters on our own hardware within our data centers, and they are not accessible to the public Internet, including employee laptops.</p><p>When an employee requires access to one of these databases – be it for staging environments, incident management, or supporting production services – an SSH user account is required. This SSH account has limited access on a bastion host, purely for querying databases within the data center.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6r0mqLUa3j8D6MOhcCCV7N/c6720dde115f0fb07f21c8eb00258a8d/image2-39.png" />
            
            </figure>
    <div>
      <h3>The pain we experienced</h3>
      <a href="#the-pain-we-experienced">
        
      </a>
    </div>
    <p>Provisioning an SSH account to these bastion hosts requires submitting a pull request to our main Infrastructure-as-Code git repository. For engineers this is a cumbersome process, and for non-engineers it is either an unnecessary learning experience, or a burden to whomever they have to ask to complete the work for them.</p><p>Both the Security and Site Reliability Engineering (SRE) teams had tolerated this solution as a necessary evil, but had reservations about handing out shell access to machines for this purpose. While the user accounts had few privileges within the bastion hosts, this still allowed users to run commands within a host and requires a lot of trust that the whole stack is secure.</p>
    <div>
      <h3>The solution: Cloudflare Zero Trust</h3>
      <a href="#the-solution-cloudflare-zero-trust">
        
      </a>
    </div>
    <p>As it turns out, the problems we encountered were the same as problems many of our customers encounter as well. We also knew these concerns could be easily addressed with our own products, Cloudflare Tunnel and Cloudflare Access.</p><p>To get started, we deployed Cloudflare Tunnel on a pod set up within our internal Kubernetes cluster that maintains access to the database clusters. This established connectivity from our origin to the Cloudflare global network. At this point, our newly created Tunnel was ready to serve traffic to our origin, in this case our PostgreSQL database server. This already simplified orchestration and management as we no longer needed to manage any Access Control List (ACL) changes for the pod itself in order for cloudflared to connect to it.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/23MJHI3T6PVEZ4x8iuSuaT/a9e61ae171762020c052307ce56a4671/image4-24.png" />
            
            </figure><p>Next, to ensure that only eligible Cloudflare employees could access the database endpoints, we implemented Cloudflare Access and created identity-driven <a href="https://www.cloudflare.com/learning/security/glossary/what-is-zero-trust/">Zero Trust</a> policies. Access then handled all user authentication for each incoming request over Tunnel and enforced a set of pre-defined identity-based policies to ensure that only certain Cloudflare employees could make connections to our database.</p><p>We were also able to better delineate access to staging and production databases by creating independent Tunnels for each. This allowed us to enforce more granular restrictions for production access without impeding our more accessible staging environments. It also had the added benefit of clearly separating the network policies used internally.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4TQil9zBDTq9GJzhc8e5yQ/87885d43aad47945dfd2420c3ca66095/image3-26.png" />
            
            </figure><p>Finally, in order for our internal users to connect to these databases, they simply needed to install cloudflared client side on their machine. Once installed, they could run cloudflared access from their endpoint to establish a long-lived TCP connection from their local laptop to the desired database cluster. Each request was then routed to Cloudflare first for policy evaluation through Cloudflare Access. This prompted the user to complete an authentication event which ensured only the Cloudflare engineers defined within our Zero Trust policies were able to establish a connection to the database.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5XV7TH3eeSDtE7PYMkCO9r/41f0835c7f4d2c48f8611cc88756a8a9/image5-13.png" />
            
            </figure><p>With cloudflared running locally, the user is then free to fire up their favorite database client to connect to the local port and run queries against the remote database cluster as if it is running locally. In short, our users were now able to run a lightweight daemon, cloudflared, on their local machine to route traffic to Cloudflare. Cloudflare Access then evaluated each request against the identity-driven Zero Trust security policies we defined. If the user met these requirements, the request was forwarded onto Cloudflare Tunnel which securely connected internal users to our databases behind Tunnel.</p><p>While we enjoy the benefits this workflow gives us, we needed to include a <i>break glass</i> procedure to ensure that we aren’t locked out of fixing our infrastructure if our infrastructure itself is having issues. For this reason, we continue to maintain SSH-based jump-hosts for a limited number of senior staff members to get in and re-establish connectivity.</p>
    <div>
      <h3>What we learned</h3>
      <a href="#what-we-learned">
        
      </a>
    </div>
    <p>By implementing our own solutions, we were able to enhance our security posture and improve the overall experience for our internal users. We were also able to become a customer of our own products and provide value feedback, insight, and feature requests to the Access and Tunnel teams internally. Oftentimes, we get to be the first to try new features or report regressions in beta builds which ultimately leads to a better experience for our customers as well.</p><p>Overall, by implementing Access and Tunnel to forward arbitrary TCP connections, users are able to focus on their job rather than worrying about the nuances of sending strings of complex commands through an SSH client. Our Security and SRE teams are also happier knowing that any connection to our data centers have been authenticated, authorized and logged by Cloudflare Access. If you’d like to get started Cloudflare Tunnel is free for any user and any use case. To get started, <a href="https://dash.cloudflare.com/sign-up/teams">sign-up</a> for a Cloudflare Zero Trust account and <a href="/ridiculously-easy-to-use-tunnels/">create your first Tunnel</a> directly from the Zero Trust dashboard.</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare One Week]]></category>
            <guid isPermaLink="false">4RwxmhkER26d1Nl5EMAqm7</guid>
            <dc:creator>Robert McNeil</dc:creator>
            <dc:creator>Vignesh Ravichandran</dc:creator>
        </item>
    </channel>
</rss>