
<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, 15 Apr 2026 19:49:46 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Migrating billions of records: moving our active DNS database while it’s in use]]></title>
            <link>https://blog.cloudflare.com/migrating-billions-of-records-moving-our-active-dns-database-while-in-use/</link>
            <pubDate>Tue, 29 Oct 2024 14:00:00 GMT</pubDate>
            <description><![CDATA[ DNS records have moved to a new database, bringing improved performance and reliability to all customers. ]]></description>
            <content:encoded><![CDATA[ <p>According to a survey done by <a href="https://w3techs.com/technologies/overview/dns_server"><u>W3Techs</u></a>, as of October 2024, Cloudflare is used as an <a href="https://www.cloudflare.com/en-gb/learning/dns/dns-server-types/"><u>authoritative DNS</u></a> provider by 14.5% of all websites. As an authoritative DNS provider, we are responsible for managing and serving all the DNS records for our clients’ domains. This means we have an enormous responsibility to provide the best service possible, starting at the data plane. As such, we are constantly investing in our infrastructure to ensure the reliability and performance of our systems.</p><p><a href="https://www.cloudflare.com/learning/dns/what-is-dns/"><u>DNS</u></a> is often referred to as the phone book of the Internet, and is a key component of the Internet. If you have ever used a phone book, you know that they can become extremely large depending on the size of the physical area it covers. A <a href="https://www.cloudflare.com/en-gb/learning/dns/glossary/dns-zone/#:~:text=What%20is%20a%20DNS%20zone%20file%3F"><u>zone file</u></a> in DNS is no different from a phone book. It has a list of records that provide details about a domain, usually including critical information like what IP address(es) each hostname is associated with. For example:</p>
            <pre><code>example.com      59 IN A 198.51.100.0
blog.example.com 59 IN A 198.51.100.1
ask.example.com  59 IN A 198.51.100.2</code></pre>
            <p>It is not unusual for these zone files to reach millions of records in size, just for a single domain. The biggest single zone on Cloudflare holds roughly 4 million DNS records, but the vast majority of zones hold fewer than 100 DNS records. Given our scale according to W3Techs, you can imagine how much DNS data alone Cloudflare is responsible for. Given this volume of data, and all the complexities that come at that scale, there needs to be a very good reason to move it from one database cluster to another. </p>
    <div>
      <h2>Why migrate </h2>
      <a href="#why-migrate">
        
      </a>
    </div>
    <p>When initially measured in 2022, DNS data took up approximately 40% of the storage capacity in Cloudflare’s main database cluster (<b>cfdb</b>). This database cluster, consisting of a primary system and multiple replicas, is responsible for storing DNS zones, propagated to our <a href="https://www.cloudflare.com/network/"><u>data centers in over 330 cities</u></a> via our distributed KV store <a href="https://blog.cloudflare.com/introducing-quicksilver-configuration-distribution-at-internet-scale/"><u>Quicksilver</u></a>. <b>cfdb</b> is accessed by most of Cloudflare's APIs, including the <a href="https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/"><u>DNS Records API</u></a>. Today, the DNS Records API is the API most used by our customers, with each request resulting in a query to the database. As such, it’s always been important to optimize the DNS Records API and its surrounding infrastructure to ensure we can successfully serve every request that comes in.</p><p>As Cloudflare scaled, <b>cfdb</b> was becoming increasingly strained under the pressures of several services, many unrelated to DNS. During spikes of requests to our DNS systems, other Cloudflare services experienced degradation in the database performance. It was understood that in order to properly scale, we needed to optimize our database access and improve the systems that interact with it. However, it was evident that system level improvements could only be just so useful, and the growing pains were becoming unbearable. In late 2022, the DNS team decided, along with the help of 25 other teams, to detach itself from <b>cfdb</b> and move our DNS records data to another database cluster.</p>
    <div>
      <h2>Pre-migration</h2>
      <a href="#pre-migration">
        
      </a>
    </div>
    <p>From a DNS perspective, this migration to an improved database cluster was in the works for several years. Cloudflare initially relied on a single <a href="https://www.postgresql.org/"><u>Postgres</u></a> database cluster, <b>cfdb</b>. At Cloudflare's inception, <b>cfdb</b> was responsible for storing information about zones and accounts and the majority of services on the Cloudflare control plane depended on it. Since around 2017, as Cloudflare grew, many services moved their data out of <b>cfdb</b> to be served by a <a href="https://en.wikipedia.org/wiki/Microservices"><u>microservice</u></a>. Unfortunately, the difficulty of these migrations are directly proportional to the amount of services that depend on the data being migrated, and in this case, most services require knowledge of both zones and DNS records.</p><p>Although the term “zone” was born from the DNS point of view, it has since evolved into something more. Today, zones on Cloudflare store many different types of non-DNS related settings and help link several non-DNS related products to customers' websites. Therefore, it didn’t make sense to move both zone data and DNS record data together. This separation of two historically tightly coupled DNS concepts proved to be an incredibly challenging problem, involving many engineers and systems. In addition, it was clear that if we were going to dedicate the resources to solving this problem, we should also remove some of the legacy issues that came along with the original solution. </p><p>One of the main issues with the legacy database was that the DNS team had little control over which systems accessed exactly what data and at what rate. Moving to a new database gave us the opportunity to create a more tightly controlled interface to the DNS data. This was manifested as an internal DNS Records <a href="https://blog.cloudflare.com/moving-k8s-communication-to-grpc/"><u>gRPC API</u></a> which allows us to make sweeping changes to our data while only requiring a single change to the API, rather than coordinating with other systems.  For example, the DNS team can alter access logic and auditing procedures under the hood. In addition, it allows us to appropriately rate-limit and cache data depending on our needs. The move to this new API itself was no small feat, and with the help of several teams, we managed to migrate over 20 services, using 5 different programming languages, from direct database access to using our managed gRPC API. Many of these services touch very important areas such as <a href="https://developers.cloudflare.com/dns/dnssec/"><u>DNSSEC</u></a>, <a href="https://developers.cloudflare.com/ssl/"><u>TLS</u></a>, <a href="https://developers.cloudflare.com/email-routing/"><u>Email</u></a>, <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/"><u>Tunnels</u></a>, <a href="https://developers.cloudflare.com/workers/"><u>Workers</u></a>, <a href="https://developers.cloudflare.com/spectrum/"><u>Spectrum</u></a>, and <a href="https://developers.cloudflare.com/r2/"><u>R2 storage</u></a>. Therefore, it was important to get it right. </p><p>One of the last issues to tackle was the logical decoupling of common DNS database functions from zone data. Many of these functions expect to be able to access both DNS record data and DNS zone data at the same time. For example, at record creation time, our API needs to check that the zone is not over its maximum record allowance. Originally this check occurred at the SQL level by verifying that the record count was lower than the record limit for the zone. However, once you remove access to the zone itself, you are no longer able to confirm this. Our DNS Records API also made use of SQL functions to audit record changes, which requires access to both DNS record and zone data. Luckily, over the past several years, we have migrated this functionality out of our monolithic API and into separate microservices. This allowed us to move the auditing and zone setting logic to the application level rather than the database level. Ultimately, we are still taking advantage of SQL functions in the new database cluster, but they are fully independent of any other legacy systems, and are able to take advantage of the latest Postgres version.</p><p>Now that Cloudflare DNS was mostly decoupled from the zones database, it was time to proceed with the data migration. For this, we built what would become our <b>Change Data Capture and Transfer Service (CDCTS).</b></p>
    <div>
      <h2>Requirements for the Change Data Capture and Transfer Service</h2>
      <a href="#requirements-for-the-change-data-capture-and-transfer-service">
        
      </a>
    </div>
    <p>The Database team is responsible for all Postgres clusters within Cloudflare, and were tasked with executing the data migration of two tables that store DNS data: <i>cf_rec</i> and <i>cf_archived_rec</i>, from the original <b>cfdb </b>cluster to a new cluster we called <b>dnsdb</b>.  We had several key requirements that drove our design:</p><ul><li><p><b>Don’t lose data. </b>This is the number one priority when handling any sort of data. Losing data means losing trust, and it is incredibly difficult to regain that trust once it’s lost.  Important in this is the ability to prove no data had been lost.  The migration process would, ideally, be easily auditable.</p></li><li><p><b>Minimize downtime</b>.  We wanted a solution with less than a minute of downtime during the migration, and ideally with just a few seconds of delay.</p></li></ul><p>These two requirements meant that we had to be able to migrate data changes in near real-time, meaning we either needed to implement logical replication, or some custom method to capture changes, migrate them, and apply them in a table in a separate Postgres cluster.</p><p>We first looked at using Postgres logical replication using <a href="https://github.com/2ndQuadrant/pglogical"><u>pgLogical</u></a>, but had concerns about its performance and our ability to audit its correctness.  Then some additional requirements emerged that made a pgLogical implementation of logical replication impossible:</p><ul><li><p><b>The ability to move data must be bidirectional.</b> We had to have the ability to switch back to <b>cfdb</b> without significant downtime in case of unforeseen problems with the new implementation. </p></li><li><p><b>Partition the </b><b><i>cf_rec</i></b><b> table in the new database.</b> This was a long-desired improvement and since most access to <i>cf_rec</i> is by zone_id, it was decided that <b>mod(zone_id, num_partitions)</b> would be the partition key.</p></li><li><p><b>Transferred data accessible from original database.  </b>In case we had functionality that still needed access to data, a foreign table pointing to <b>dnsdb</b> would be available in <b>cfdb</b>. This could be used as emergency access to avoid needing to roll back the entire migration for a single missed process.</p></li><li><p><b>Only allow writes in one database. </b> Applications should know where the primary database is, and should be blocked from writing to both databases at the same time.</p></li></ul>
    <div>
      <h2>Details about the tables being migrated</h2>
      <a href="#details-about-the-tables-being-migrated">
        
      </a>
    </div>
    <p>The primary table, <i>cf_rec</i>, stores DNS record information, and its rows are regularly inserted, updated, and deleted. At the time of the migration, this table had 1.7 billion records, and with several indexes took up 1.5 TB of disk. Typical daily usage would observe 3-5 million inserts, 1 million updates, and 3-5 million deletes.</p><p>The second table, <i>cf_archived_rec</i>, stores copies of <i>cf_rec</i> that are obsolete — this table generally only has records inserted and is never updated or deleted.  As such, it would see roughly 3-5 million inserts per day, corresponding to the records deleted from <i>cf_rec</i>. At the time of the migration, this table had roughly 4.3 billion records.</p><p>Fortunately, neither table made use of database triggers or foreign keys, which meant that we could insert/update/delete records in this table without triggering changes or worrying about dependencies on other tables.</p><p>Ultimately, both of these tables are highly active and are the source of truth for many highly critical systems at Cloudflare.</p>
    <div>
      <h2>Designing the Change Data Capture and Transfer Service</h2>
      <a href="#designing-the-change-data-capture-and-transfer-service">
        
      </a>
    </div>
    <p>There were two main parts to this database migration:</p><ol><li><p><b>Initial copy:</b> Take all the data from <b>cfdb </b>and put it in <b>dnsdb.</b></p></li><li><p><b>Change copy:</b> Take all the changes in <b>cfdb </b>since the initial copy and update <b>dnsdb</b> to reflect them. This is the more involved part of the process.</p></li></ol><p>Normally, logical replication replays every insert, update, and delete on a copy of the data in the same transaction order, making a single-threaded pipeline.  We considered using a queue-based system but again, speed and auditability were both concerns as any queue would typically replay one change at a time.  We wanted to be able to apply large sets of changes, so that after an initial dump and restore, we could quickly catch up with the changed data. For the rest of the blog, we will only speak about <i>cf_rec</i> for simplicity, but the process for <i>cf_archived_rec</i> is the same.</p><p>What we decided on was a simple change capture table. Rows from this capture table would be loaded in real-time by a database trigger, with a transfer service that could migrate and apply thousands of changed records to <b>dnsdb</b> in each batch. Lastly, we added some auditing logic on top to ensure that we could easily verify that all data was safely transferred without downtime.</p>
    <div>
      <h3>Basic model of change data capture </h3>
      <a href="#basic-model-of-change-data-capture">
        
      </a>
    </div>
    <p>For <i>cf_rec</i> to be migrated, we would create a change logging table, along with a trigger function and a  table trigger to capture the new state of the record after any insert/update/delete.  </p><p>The change logging table named <i>log_cf_rec</i> had the same columns as <i>cf_rec</i>, as well as four new columns:</p><ul><li><p><b>change_id</b>:  a sequence generated unique identifier of the record</p></li><li><p><b>action</b>: a single character indicating whether this record represents an [i]nsert, [u]pdate, or [d]elete</p></li><li><p><b>change_timestamp</b>: the date/time when the change record was created</p></li><li><p><b>change_user:</b> the database user that made the change.  </p></li></ul><p>A trigger was placed on the <i>cf_rec</i> table so that each insert/update would copy the new values of the record into the change table, and for deletes, create a 'D' record with the primary key value. </p><p>Here is an example of the change logging where we delete, re-insert, update, and finally select from the <i>log_cf_rec</i><b> </b>table. Note that the actual <i>cf_rec</i> and <i>log_cf_rec</i> tables have many more columns, but have been edited for simplicity.</p>
            <pre><code>dns_records=# DELETE FROM  cf_rec WHERE rec_id = 13;

dns_records=# SELECT * from log_cf_rec;
Change_id | action | rec_id | zone_id | name
----------------------------------------------
1         | D      | 13     |         |   

dns_records=# INSERT INTO cf_rec VALUES(13,299,'cloudflare.example.com');  

dns_records=# UPDATE cf_rec SET name = 'test.example.com' WHERE rec_id = 13;

dns_records=# SELECT * from log_cf_rec;
Change_id | action | rec_id | zone_id | name
----------------------------------------------
1         | D      | 13     |         |  
2         | I      | 13     | 299     | cloudflare.example.com
3         | U      | 13     | 299     | test.example.com </code></pre>
            <p>In addition to <i>log_cf_rec</i>, we also introduced 2 more tables in <b>cfdb </b>and 3 more tables in <b>dnsdb:</b></p><p><b>cfdb</b></p><ol><li><p><i>transferred_log_cf_rec</i>: Responsible for auditing the batches transferred to <b>dnsdb</b>.</p></li><li><p><i>log_change_action</i>:<i> </i>Responsible for summarizing the transfer size in order to compare with the <i>log_change_action </i>in <b>dnsdb.</b></p></li></ol><p><b>dnsdb</b></p><ol><li><p><i>migrate_log_cf_rec</i>:<i> </i>Responsible for collecting batch changes in <b>dnsdb</b>, which would later be applied to <i>cf_rec </i>in <b>dnsdb</b><i>.</i></p></li><li><p><i>applied_migrate_log_cf_rec</i>:<i> </i>Responsible for auditing the batches that had been successfully applied to cf_rec in <b>dnsdb.</b></p></li><li><p><i>log_change_action</i>:<i> </i>Responsible for summarizing the transfer size in order to compare with the <i>log_change_action </i>in <b>cfdb.</b></p></li></ol>
    <div>
      <h3>Initial copy</h3>
      <a href="#initial-copy">
        
      </a>
    </div>
    <p>With change logging in place, we were now ready to do the initial copy of the tables from <b>cfdb</b> to <b>dnsdb</b>. Because we were changing the structure of the tables in the destination database and because of network timeouts, we wanted to bring the data over in small pieces and validate that it was brought over accurately, rather than doing a single multi-hour copy or <a href="https://www.postgresql.org/docs/current/app-pgdump.html"><u>pg_dump</u></a>.  We also wanted to ensure a long-running read could not impact production and that the process could be paused and resumed at any time.  The basic model to transfer data was done with a simple psql copy statement piped into another psql copy statement.  No intermediate files were used.</p><p><code>psql_cfdb -c "COPY (SELECT * FROM cf_rec WHERE id BETWEEN n and n+1000000 TO STDOUT)" | </code></p><p><code>psql_dnsdb -c "COPY cf_rec FROM STDIN"</code></p><p>Prior to a batch being moved, the count of records to be moved was recorded in <b>cfdb</b>, and after each batch was moved, a count was recorded in <b>dnsdb</b> and compared to the count in <b>cfdb</b> to ensure that a network interruption or other unforeseen error did not cause data to be lost. The bash script to copy data looked like this, where we included files that could be touched to pause or end the copy (if they cause load on production or there was an incident).  Once again, this code below has been heavily simplified.</p>
            <pre><code>#!/bin/bash
for i in "$@"; do
   # Allow user to control whether this is paused or not via pause_copy file
   while [ -f pause_copy ]; do
      sleep 1
   done
   # Allow user to end migration by creating end_copy file
   if [ ! -f end_copy ]; then
      # Copy a batch of records from cfdb to dnsdb
      # Get count of records from cfdb 
	# Get count of records from dnsdb
 	# Compare cfdb count with dnsdb count and alert if different 
   fi
done
</code></pre>
            <p><sup><i>Bash copy script</i></sup></p>
    <div>
      <h3>Change copy</h3>
      <a href="#change-copy">
        
      </a>
    </div>
    <p>Once the initial copy was completed, we needed to update <b>dnsdb</b> with any changes that had occurred in <b>cfdb</b> since the start of the initial copy. To implement this change copy, we created a function <i>fn_log_change_transfer_log_cf_rec </i>that could be passed a <i>batch_id</i> and <i>batch_size</i>, and did 5 things, all of which were executed in a single database <a href="https://www.postgresql.org/docs/current/tutorial-transactions.html"><u>transaction</u></a>:</p><ol><li><p>Select a <i>batch_size</i> of records from <i>log_cf_rec</i> in <b>cfdb</b>.</p></li><li><p>Copy the batch to <i>transferred_log_cf_rec</i> in <b>cfdb </b>to mark it as transferred.</p></li><li><p>Delete the batch from <i>log_cf_rec</i>.</p></li><li><p>Write a summary of the action to <i>log_change_action</i> table. This will later be used to compare transferred records with <b>cfdb</b>.</p></li><li><p>Return the batch of records.</p></li></ol><p>We then took the returned batch of records and copied them to <i>migrate_log_cf_rec </i>in <b>dnsdb</b>. We used the same bash script as above, except this time, the copy command looked like this:</p><p><code>psql_cfdb -c "COPY (SELECT * FROM </code><code><i>fn_log_change_transfer_log_cf_rec(&lt;batch_id&gt;,&lt;batch_size&gt;</i></code><code>) TO STDOUT" | </code></p><p><code>psql_dnsdb -c "COPY migrate_log_cf_rec FROM STDIN"</code></p>
    <div>
      <h3>Applying changes in the destination database</h3>
      <a href="#applying-changes-in-the-destination-database">
        
      </a>
    </div>
    <p>Now, with a batch of data in the <i>migrate_log_cf_rec </i>table, we called a newly created function <i>log_change_apply</i> to apply and audit the changes. Once again, this was all executed within a single database transaction. The function did the following:</p><ol><li><p>Move a batch from the <i>migrate_log_cf_rec</i> table to a new temporary table.</p></li><li><p>Write the counts for the batch_id to the <i>log_change_action</i> table.</p></li><li><p>Delete from the temporary table all but the latest record for a unique id (last action). For example, an insert followed by 30 updates would have a single record left, the final update. There is no need to apply all the intermediate updates.</p></li><li><p>Delete any record from <i>cf_rec</i> that has any corresponding changes.</p></li><li><p>Insert any [i]nsert or [u]pdate records in <i>cf_rec</i>.</p></li><li><p>Copy the batch to <i>applied_migrate_log_cf_rec</i> for a full audit trail.</p></li></ol>
    <div>
      <h3>Putting it all together</h3>
      <a href="#putting-it-all-together">
        
      </a>
    </div>
    <p>There were 4 distinct phases, each of which was part of a different database transaction:</p><ol><li><p>Call <i>fn_log_change_transfer_log_cf_rec </i>in <b>cfdb </b>to get a batch of records.</p></li><li><p>Copy the batch of records to <b>dnsdb.</b></p></li><li><p>Call <i>log_change_apply </i>in <b>dnsdb </b>to apply the batch of records.</p></li><li><p>Compare the <i>log_change_action</i> table in each respective database to ensure counts match.</p></li></ol>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2REIq71tc7M4jKPLZSJzS9/11f22f700300f2ad3a5ee5ca85a75480/Applying_changes_in_the_destination_database.png" />
          </figure><p>This process was run every 3 seconds for several weeks before the migration to ensure that we could keep <b>dnsdb</b> in sync with <b>cfdb</b>.</p>
    <div>
      <h2>Managing which database is live</h2>
      <a href="#managing-which-database-is-live">
        
      </a>
    </div>
    <p>The last major pre-migration task was the construction of the request locking system that would be used throughout the actual migration. The aim was to create a system that would allow the database to communicate with the DNS Records API, to allow the DNS Records API to handle HTTP connections more gracefully. If done correctly, this could reduce downtime for DNS Record API users to nearly zero.</p><p>In order to facilitate this, a new table called <i>cf_migration_manager</i> was created. The table would be periodically polled by the DNS Records API, communicating two critical pieces of information:</p><ol><li><p><b>Which database was active.</b> Here we just used a simple A or B naming convention.</p></li><li><p><b>If the database was locked for writing</b>. In the event the database was locked for writing, the DNS Records API would hold HTTP requests until the lock was released by the database.</p></li></ol><p>Both pieces of information would be controlled within a migration manager script.</p><p>The benefit of migrating the 20+ internal services from direct database access to using our internal DNS Records gRPC API is that we were able to control access to the database to ensure that no one else would be writing without going through the <i>cf_migration_manager</i>.</p>
    <div>
      <h2>During the migration </h2>
      <a href="#during-the-migration">
        
      </a>
    </div>
    <p>Although we aimed to complete this migration in a matter of seconds, we announced a DNS maintenance window that could last a couple of hours just to be safe. Now that everything was set up, and both <b>cfdb</b> and <b>dnsdb</b> were roughly in sync, it was time to proceed with the migration. The steps were as follows:</p><ol><li><p>Lower the time between copies from 3s to 0.5s.</p></li><li><p>Lock <b>cfdb</b> for writes via <i>cf_migration_manager</i>. This would tell the DNS Records API to hold write connections.</p></li><li><p>Make <b>cfdb</b> read-only and migrate the last logged changes to <b>dnsdb</b>. </p></li><li><p>Enable writes to <b>dnsdb</b>. </p></li><li><p>Tell DNS Records API that <b>dnsdb</b> is the new primary database and that write connections can proceed via the <i>cf_migration_manager</i>.</p></li></ol><p>Since we needed to ensure that the last changes were copied to <b>dnsdb</b> before enabling writing, this entire process took no more than 2 seconds. During the migration we saw a spike of API latency as a result of the migration manager locking writes, and then dealing with a backlog of queries. However, we recovered back to normal latencies after several minutes. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6agUpD8BQVxgDupBrwtTw3/38c96f91879c6539011866821ad6f11a/image3.png" />
          </figure><p><sup><i>DNS Records API Latency and Requests during migration</i></sup></p><p>Unfortunately, due to the far-reaching impact that DNS has at Cloudflare, this was not the end of the migration. There were 3 lesser-used services that had slipped by in our scan of services accessing DNS records via <b>cfdb</b>. Fortunately, the setup of the foreign table meant that we could very quickly fix any residual issues by simply changing the table name. </p>
    <div>
      <h2>Post-migration</h2>
      <a href="#post-migration">
        
      </a>
    </div>
    <p>Almost immediately, as expected, we saw a steep drop in usage across <b>cfdb</b>. This freed up a lot of resources for other services to take advantage of.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/Xfnbc9MZLwJB91ypItWsi/1eb21362893b31a1e3c846d1076a9f5b/image6.jpg" />
          </figure><p><sup><i><b>cfdb</b></i></sup><sup><i> usage dropped significantly after the migration period.</i></sup></p><p>Since the migration, the average <b>requests</b> per second to the DNS Records API has more than <b>doubled</b>. At the same time, our CPU usage across both <b>cfdb</b> and <b>dnsdb</b> has settled at below 10% as seen below, giving us room for spikes and future growth. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/39su35dkb5Pl8uwYfYjHLg/0eb26ced30b44efb71abb73830e01f3a/image2.png" />
          </figure>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5AdlLKXtD68QWCsMVLKnkt/9137beee9c941827eb57c53825ffe209/image4.png" />
          </figure><p><sup><i><b>cfdb</b></i></sup><sup><i> and </i></sup><sup><i><b>dnsdb</b></i></sup><sup><i> CPU usage now</i></sup></p><p>As a result of this improved capacity, our database-related incident rate dropped dramatically.</p><p>As for query latencies, our latency post-migration is slightly lower on average, with fewer sustained spikes above 500ms. However, the performance improvement is largely noticed during high load periods, when our database handles spikes without significant issues. Many of these spikes come as a result of clients making calls to collect a large amount of DNS records or making several changes to their zone in short bursts. Both of these actions are common use cases for large customers onboarding zones.</p><p>In addition to these improvements, the DNS team also has more granular control over <b>dnsdb</b> cluster-specific settings that can be tweaked for our needs rather than catering to all the other services. For example, we were able to make custom changes to replication lag limits to ensure that services using replicas were able to read with some amount of certainty that the data would exist in a consistent form. Measures like this reduce overall load on the primary because almost all read queries can now go to the replicas.</p><p>Although this migration was a resounding success, we are always working to improve our systems. As we grow, so do our customers, which means the need to scale never really ends. We have more exciting improvements on the roadmap, and we are looking forward to sharing more details in the future.</p><p>The DNS team at Cloudflare isn’t the only team solving challenging problems like the one above. If this sounds interesting to you, we have many more tech deep dives on our blog, and we are always looking for curious engineers to join our team — see open opportunities <a href="https://www.cloudflare.com/en-gb/careers/jobs/"><u>here</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[API]]></category>
            <category><![CDATA[Database]]></category>
            <category><![CDATA[Kafka]]></category>
            <category><![CDATA[Postgres]]></category>
            <category><![CDATA[Tracing]]></category>
            <category><![CDATA[Quicksilver]]></category>
            <guid isPermaLink="false">24rozMdbFQ7jmUgRNMF4RU</guid>
            <dc:creator>Alex Fattouche</dc:creator>
            <dc:creator>Corey Horton</dc:creator>
        </item>
        <item>
            <title><![CDATA[Making zone management more efficient with batch DNS record updates]]></title>
            <link>https://blog.cloudflare.com/batched-dns-changes/</link>
            <pubDate>Mon, 23 Sep 2024 13:00:00 GMT</pubDate>
            <description><![CDATA[ In response to customer demand, we now support the ability to DELETE, PATCH, PUT and POST multiple DNS records in a single API call, enabling more efficient and reliable zone management.
 ]]></description>
            <content:encoded><![CDATA[ <p>Customers that use Cloudflare to manage their DNS often need to create a whole batch of records, enable <a href="https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/"><u>proxying</u></a> on many records, update many records to point to a new target at the same time, or even delete all of their records. Historically, customers had to resort to bespoke scripts to make these changes, which came with their own set of issues. In response to customer demand, we are excited to announce support for batched API calls to the <a href="https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/"><u>DNS records API</u></a> starting today. This lets customers make large changes to their zones much more efficiently than before. Whether sending a POST, PUT, PATCH or DELETE, users can now execute these four different <a href="https://en.wikipedia.org/wiki/HTTP#Request_methods"><u>HTTP methods</u></a>, and multiple HTTP requests all at the same time.</p>
    <div>
      <h2>Efficient zone management matters</h2>
      <a href="#efficient-zone-management-matters">
        
      </a>
    </div>
    <p><a href="https://www.cloudflare.com/en-gb/learning/dns/dns-records/"><u>DNS records</u></a> are an essential part of most web applications and websites, and they serve many different purposes. The most common use case for a DNS record is to have a hostname point to an <a href="https://en.wikipedia.org/wiki/IPv4"><u>IPv4</u></a> address, this is called an <a href="https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-a-record/"><u>A record</u></a>:</p><p><b>example.com</b> 59 IN A <b>198.51.100.0</b></p><p><b>blog.example.com</b> 59 IN A <b>198.51.100.1</b></p><p><b>ask.example.com</b> 59 IN A <b>198.51.100.2</b></p><p>In its most simple form, this enables Internet users to connect to websites without needing to memorize their IP address. </p><p>Often, our customers need to be able to do things like create a whole batch of records, or enable <a href="https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/"><u>proxying</u></a> on many records, or update many records to point to a new target at the same time, or even delete all of their records. Unfortunately, for most of these cases, we were asking customers to write their own custom scripts or programs to do these tasks for them, a number of which are open sourced and whose content has not been checked by us. These scripts are often used to avoid needing to repeatedly make the same API calls manually. This takes time, not only for the development of the scripts, but also to simply execute all the API calls, not to mention it can leave the zone in a bad state if some changes fail while others succeed.</p>
    <div>
      <h2>Introducing /batch</h2>
      <a href="#introducing-batch">
        
      </a>
    </div>
    <p>Starting today, everyone with a <a href="https://developers.cloudflare.com/dns/zone-setups/"><u>Cloudflare zone</u></a> will have access to this endpoint, with free tier customers getting access to 200 changes in one batch, and paid plans getting access to 3,500 changes in one batch. We have successfully tested up to 100,000 changes in one call. The API is simple, expecting a POST request to be made to the <a href="https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-batch-dns-records"><u>new API endpoint</u></a> /dns_records/batch, which passes in a JSON object in the body in the format:</p>
            <pre><code>{
    deletes:[]Record
    patches:[]Record
    puts:[]Record
    posts:[]Record
}
</code></pre>
            <p>Each list of records []Record will follow the same requirements as the regular API, except that the record ID on deletes, patches, and puts will be required within the Record object itself. Here is a simple example:</p>
            <pre><code>{
    "deletes": [
        {
            "id": "143004ef463b464a504bde5a5be9f94a"
        },
        {
            "id": "165e9ef6f325460c9ca0eca6170a7a23"
        }
    ],
    "patches": [
        {
            "id": "16ac0161141a4e62a79c50e0341de5c6",
            "content": "192.0.2.45"
        },
        {
            "id": "6c929ea329514731bcd8384dd05e3a55",
            "name": "update.example.com",
            "proxied": true
        }
    ],
    "puts": [
        {
            "id": "ee93eec55e9e45f4ae3cb6941ffd6064",
            "content": "192.0.2.50",
            "name": "no-change.example.com",
            "proxied": false,
            "ttl:": 1
        },
        {
            "id": "eab237b5a67e41319159660bc6cfd80b",
            "content": "192.0.2.45",
            "name": "no-change.example.com",
            "proxied": false,
            "ttl:": 3000
        }
    ],
    "posts": [
        {
            "name": "@",
            "type": "A",
            "content": "192.0.2.45",
            "proxied": false,
            "ttl": 3000
        },
        {
            "name": "a.example.com",
            "type": "A",
            "content": "192.0.2.45",
            "proxied": true
        }
    ]
}</code></pre>
            <p>Our API will then parse this and execute these calls in the following order: </p><ol><li><p>deletes</p></li><li><p>patches</p></li><li><p>puts</p></li><li><p>posts</p></li></ol><p>Each of these respective lists will be executed in the order given. This ordering system is important because it removes the need for our clients to worry about conflicts, such as if they need to create a CNAME on the same hostname as a to-be-deleted A record, which is not allowed in <a href="https://datatracker.ietf.org/doc/html/rfc1912#section-2.4"><u>RFC 1912</u></a>. In the event that any of these individual actions fail, the entire API call will fail and return the first error it sees. The batch request will also be executed inside a single database <a href="https://en.wikipedia.org/wiki/Database_transaction"><u>transaction</u></a>, which will roll back in the event of failure.</p><p>After the batch request has been successfully executed in our database, we then propagate the changes to our edge via <a href="https://blog.cloudflare.com/introducing-quicksilver-configuration-distribution-at-internet-scale"><u>Quicksilver</u></a>, our distributed KV store. Each of the individual record changes inside the batch request is treated as a single key-value pair, and database transactions are not supported. As such, <b>we cannot guarantee that the propagation to our edge servers will be atomic</b>. For example, if replacing a <a href="https://developers.cloudflare.com/dns/manage-dns-records/how-to/subdomains-outside-cloudflare/"><u>delegation</u></a> with an A record, some resolvers may see the <a href="https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-ns-record/"><u>NS</u></a> record removed before the A record is added. </p><p>The response will follow the same format as the request. Patches and puts that result in no changes will be placed at the end of their respective lists.</p><p>We are also introducing some new changes to the Cloudflare dashboard, allowing users to select multiple records and subsequently:</p><ol><li><p>Delete all selected records</p></li><li><p>Change the proxy status of all selected records</p></li></ol>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ZU7nvMlcH2L51IqJrS1zC/db7ac600e503a72bb0c25679d63394e7/BLOG-2495_2.png" />
          </figure><p>We plan to continue improving the dashboard to support more batch actions based on your feedback.</p>
    <div>
      <h2>The journey</h2>
      <a href="#the-journey">
        
      </a>
    </div>
    <p>Although at the surface, this batch endpoint may seem like a fairly simple change, behind the scenes it is the culmination of a multi-year, multi-team effort. Over the past several years, we have been working hard to improve the DNS pipeline that takes our customers' records and pushes them to <a href="https://blog.cloudflare.com/introducing-quicksilver-configuration-distribution-at-internet-scale"><u>Quicksilver</u></a>, our distributed database. As part of this effort, we have been improving our <a href="https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-list-dns-records"><u>DNS Records API</u></a> to reduce the overall latency. The DNS Records API is Cloudflare's most used API externally, serving twice as many requests as any other API at peak. In addition, the DNS Records API supports over 20 internal services, many of which touch very important areas such as DNSSEC, TLS, Email, Tunnels, Workers, Spectrum, and R2 storage. Therefore, it was important to build something that scales. </p><p>To improve API performance, we first needed to understand the complexities of the entire stack. At Cloudflare, we use <a href="https://www.jaegertracing.io/"><u>Jaeger tracing</u></a> to debug our systems. It gives us granular insights into a sample of requests that are coming into our APIs. When looking at API request latency, the <a href="https://www.jaegertracing.io/docs/1.23/architecture/#span"><u>span</u></a> that stood out was the time spent on each individual database lookup. The latency here can vary anywhere from ~1ms to ~5ms. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/61f3sKGUs9oWMPT9P4au6R/a91d8291b626f4bab3ac1c69adf62a5d/BLOG-2495_3.png" />
          </figure>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3L3OaTb9cTKKKcIjCm1RLq/86ffd63116988025fd52105e316c5b5a/BLOG-2495_4.png" />
          </figure><p><sub><i>Jaeger trace showing variable database latency</i></sub></p><p>Given this variability in database query latency, we wanted to understand exactly what was going on within each DNS Records API request. When we first started on this journey, the breakdown of database lookups for each action was as follows:</p><table><tr><th><p><b>Action</b></p></th><th><p><b>Database Queries</b></p></th><th><p><b>Reason</b></p></th></tr><tr><td><p>POST</p></td><td><p>2 </p></td><td><p>One to write and one to read the new record.</p></td></tr><tr><td><p>PUT</p></td><td><p>3</p></td><td><p>One to collect, one to write, and one to read back the new record.</p></td></tr><tr><td><p>PATCH</p></td><td><p>3</p></td><td><p>One to collect, one to write, and one to read back the new record.</p></td></tr><tr><td><p>DELETE</p></td><td><p>2</p></td><td><p>One to read and one to delete.</p></td></tr></table><p>The reason we needed to read the newly created records on POST, PUT, and PATCH was because the record contains information filled in by the database which we cannot infer in the API. </p><p>Let’s imagine that a customer needed to edit 1,000 records. If each database lookup took 3ms to complete, that was 3ms * 3 lookups * 1,000 records = 9 seconds spent on database queries alone, not taking into account the round trip time to and from our API or any other processing latency. It’s clear that we needed to reduce the number of overall queries and ideally minimize per query latency variation. Let’s tackle the variation in latency first.</p><p>Each of these calls is not a simple INSERT, UPDATE, or DELETE, because we have functions wrapping these database calls for sanitization purposes. In order to understand the variable latency, we enlisted the help of <a href="https://www.postgresql.org/docs/current/auto-explain.html"><u>PostgreSQL’s “auto_explain”</u></a>. This module gives a breakdown of execution times for each statement without needing to EXPLAIN each one by hand. We used the following settings:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2myvmIREh2Q9yl30HbRus/29f085d40ba7dde34e9a46c27e3c6ba2/BLOG-2495_5.png" />
          </figure><p>A handful of queries showed durations like the one below, which took an order of magnitude longer than other queries.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/557xg66x8OiHM6pcAG4svk/56157cd0e5b6d7fd47f0152798598729/BLOG-2495_6.png" />
          </figure><p>We noticed that in several locations we were doing queries like:</p><p><code>IF (EXISTS (SELECT id FROM table WHERE row_hash = __new_row_hash))</code></p><p>If you are trying to insert into very large zones, such queries could mean even longer database query times, potentially explaining the discrepancy between 1ms and 5ms in our tracing images above. Upon further investigation, we already had a unique index on that exact hash. <a href="https://www.postgresql.org/docs/current/indexes-unique.html"><u>Unique indexes</u></a> in PostgreSQL enforce the uniqueness of one or more column values, which means we can safely remove those existence checks without risk of inserting duplicate rows.</p><p>The next task was to introduce database batching into our DNS Records API. In any API, external calls such as SQL queries are going to add substantial latency to the request. Database batching allows the DNS Records API to execute multiple SQL queries within one single network call, subsequently lowering the number of database round trips our system needs to make. </p><p>According to the table above, each database write also corresponded to a read after it had completed the query. This was needed to collect information like creation/modification timestamps and new IDs. To improve this, we tweaked our database functions to now return the newly created DNS record itself, removing a full round trip to the database. Here is the updated table:</p><table><tr><th><p><b>Action</b></p></th><th><p><b>Database Queries</b></p></th><th><p><b>Reason</b></p></th></tr><tr><td><p>POST</p></td><td><p>1 </p></td><td><p>One to write</p></td></tr><tr><td><p>PUT</p></td><td><p>2</p></td><td><p>One to read, one to write.</p></td></tr><tr><td><p>PATCH</p></td><td><p>2</p></td><td><p>One to read, one to write.</p></td></tr><tr><td><p>DELETE</p></td><td><p>2</p></td><td><p>One to read, one to delete.</p></td></tr></table><p>We have room for improvement here, however we cannot easily reduce this further due to some restrictions around auditing and other sanitization logic.</p><p><b>Results:</b></p><table><tr><th><p><b>Action</b></p></th><th><p><b>Average database time before</b></p></th><th><p><b>Average database time after</b></p></th><th><p><b>Percentage Decrease</b></p></th></tr><tr><td><p>POST</p></td><td><p>3.38ms</p></td><td><p>0.967ms</p></td><td><p>71.4%</p></td></tr><tr><td><p>PUT</p></td><td><p>4.47ms</p></td><td><p>2.31ms</p></td><td><p>48.4%</p></td></tr><tr><td><p>PATCH</p></td><td><p>4.41ms</p></td><td><p>2.24ms</p></td><td><p>49.3%</p></td></tr><tr><td><p>DELETE</p></td><td><p>1.21ms</p></td><td><p>1.21ms</p></td><td><p>0%</p></td></tr></table><p>These are some pretty good improvements! Not only did we reduce the API latency, we also reduced the database query load, benefiting other systems as well.</p>
    <div>
      <h2>Weren’t we talking about batching?</h2>
      <a href="#werent-we-talking-about-batching">
        
      </a>
    </div>
    <p>I previously mentioned that the /batch endpoint is fully atomic, making use of a single database transaction. However, a single transaction may still require multiple database network calls, and from the table above, that can add up to a significant amount of time when dealing with large batches. To optimize this, we are making use of <a href="https://pkg.go.dev/github.com/jackc/pgx/v4#Batch"><u>pgx/batch</u></a>, a Golang object that allows us to write and subsequently read multiple queries in a single network call. Here is a high level of how the batch endpoint works:</p><ol><li><p>Collect all the records for the PUTs, PATCHes and DELETEs.</p></li><li><p>Apply any per record differences as requested by the PATCHes and PUTs.</p></li><li><p>Format the batch SQL query to include each of the actions.</p></li><li><p>Execute the batch SQL query in the database.</p></li><li><p>Parse each database response and return any errors if needed.</p></li><li><p>Audit each change.</p></li></ol><p>This takes at most only two database calls per batch. One to fetch, and one to write/delete. If the batch contains only POSTs, this will be further reduced to a single database call. Given all of this, we should expect to see a significant improvement in latency when making multiple changes, which we do when observing how these various endpoints perform: </p><p><i>Note: Each of these queries was run from multiple locations around the world and the median of the response times are shown here. The server responding to queries is located in Portland, Oregon, United States. Latencies are subject to change depending on geographical location.</i></p><p><b>Create only:</b></p><table><tr><th><p>
</p></th><th><p><b>10 Records</b></p></th><th><p><b>100 Records</b></p></th><th><p><b>1,000 Records</b></p></th><th><p><b>10,000 Records</b></p></th></tr><tr><td><p><b>Regular API</b></p></td><td><p>7.55s</p></td><td><p>74.23s</p></td><td><p>757.32s</p></td><td><p>7,877.14s</p></td></tr><tr><td><p><b>Batch API - Without database batching</b></p></td><td><p>0.85s</p></td><td><p>1.47s</p></td><td><p>4.32s</p></td><td><p>16.58s</p></td></tr><tr><td><p><b>Batch API - with database batching</b></p></td><td><p>0.67s</p></td><td><p>1.21s</p></td><td><p>3.09s</p></td><td><p>10.33s</p></td></tr></table><p><b>Delete only:</b></p><table><tr><th><p>
</p></th><th><p><b>10 Records</b></p></th><th><p><b>100 Records</b></p></th><th><p><b>1,000 Records</b></p></th><th><p><b>10,000 Records</b></p></th></tr><tr><td><p><b>Regular API</b></p></td><td><p>7.28s</p></td><td><p>67.35s</p></td><td><p>658.11s</p></td><td><p>7,471.30s</p></td></tr><tr><td><p><b>Batch API - without database batching</b></p></td><td><p>0.79s</p></td><td><p>1.32s</p></td><td><p>3.18s</p></td><td><p>17.49s</p></td></tr><tr><td><p><b>Batch API - with database batching</b></p></td><td><p>0.66s</p></td><td><p>0.78s</p></td><td><p>1.68s</p></td><td><p>7.73s</p></td></tr></table><p><b>Create/Update/Delete:</b></p><table><tr><th><p>
</p></th><th><p><b>10 Records</b></p></th><th><p><b>100 Records</b></p></th><th><p><b>1,000 Records</b></p></th><th><p><b>10,000 Records</b></p></th></tr><tr><td><p><b>Regular API</b></p></td><td><p>7.11s</p></td><td><p>72.41s</p></td><td><p>715.36s</p></td><td><p>7,298.17s</p></td></tr><tr><td><p><b>Batch API - without database batching</b></p></td><td><p>0.79s</p></td><td><p>1.36s</p></td><td><p>3.05s</p></td><td><p>18.27s</p></td></tr><tr><td><p><b>Batch API - with database batching</b></p></td><td><p>0.74s</p></td><td><p>1.06s</p></td><td><p>2.17s</p></td><td><p>8.48s</p></td></tr></table><p><b>Overall Average:</b></p><table><tr><th><p>
</p></th><th><p><b>10 Records</b></p></th><th><p><b>100 Records</b></p></th><th><p><b>1,000 Records</b></p></th><th><p><b>10,000 Records</b></p></th></tr><tr><td><p><b>Regular API</b></p></td><td><p>7.31s</p></td><td><p>71.33s</p></td><td><p>710.26s</p></td><td><p>7,548.87s</p></td></tr><tr><td><p><b>Batch API - without database batching</b></p></td><td><p>0.81s</p></td><td><p>1.38s</p></td><td><p>3.51s</p></td><td><p>17.44s</p></td></tr><tr><td><p><b>Batch API - with database batching</b></p></td><td><p>0.69s</p></td><td><p>1.02s</p></td><td><p>2.31s</p></td><td><p>8.85s</p></td></tr></table><p>We can see that on average, the new batching API is significantly faster than the regular API trying to do the same actions, and it’s also nearly twice as fast as the batching API without batched database calls. We can see that at 10,000 records, the batching API is a staggering 850x faster than the regular API. As mentioned above, these numbers are likely to change for a number of different reasons, but it’s clear that making several round trips to and from the API adds substantial latency, regardless of the region.</p>
    <div>
      <h2>Batch overload</h2>
      <a href="#batch-overload">
        
      </a>
    </div>
    <p>Making our API faster is awesome, but we don’t operate in an isolated environment. Each of these records needs to be processed and pushed to <a href="https://blog.cloudflare.com/introducing-quicksilver-configuration-distribution-at-internet-scale"><u>Quicksilver</u></a>, our distributed database. If we have customers creating tens of thousands of records every 10 seconds, we need to be able to handle this downstream so that we don’t overwhelm our system. In a May 2022 blog post titled <a href="https://blog.cloudflare.com/dns-build-improvement"><i><u>How we improved DNS record build speed by more than 4,000x</u></i></a>, I noted<i> </i>that:</p><blockquote><p><i>We plan to introduce a batching system that will collect record changes into groups to minimize the number of queries we make to our database and Quicksilver.</i></p></blockquote><p>This task has since been completed, and our propagation pipeline is now able to batch thousands of record changes into a single database query which can then be published to Quicksilver in order to be propagated to our global network. </p>
    <div>
      <h2>Next steps</h2>
      <a href="#next-steps">
        
      </a>
    </div>
    <p>We have a couple more improvements we may want to bring into the API. We also intend to improve the UI to bring more usability improvements to the dashboard to more easily manage zones. <a href="https://research.rallyuxr.com/cloudflare/lp/cm0zu2ma7017j1al98l1m8a7n?channel=share&amp;studyId=cm0zu2ma4017h1al9byak79iw"><u>We would love to hear your feedback</u></a>, so please let us know what you think and if you have any suggestions for improvements.</p><p>For more details on how to use the new /batch API endpoint, head over to our <a href="https://developers.cloudflare.com/dns/manage-dns-records/how-to/batch-record-changes/"><u>developer documentation</u></a> and <a href="https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-batch-dns-records"><u>API reference</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[Birthday Week]]></category>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[API]]></category>
            <category><![CDATA[Kafka]]></category>
            <category><![CDATA[Database]]></category>
            <guid isPermaLink="false">op0CI3wllMcGjptdRb2Ce</guid>
            <dc:creator>Alex Fattouche</dc:creator>
        </item>
        <item>
            <title><![CDATA[Connection errors in Asia Pacific region on July 9, 2023]]></title>
            <link>https://blog.cloudflare.com/connection-errors-in-asia-pacific-region-on-july-9-2023/</link>
            <pubDate>Tue, 11 Jul 2023 08:48:13 GMT</pubDate>
            <description><![CDATA[ On July 9, 2023, users in the Asia Pacific region experienced connection errors due to origin DNS resolution failures to .com and .net TLD nameservers ]]></description>
            <content:encoded><![CDATA[ <p></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4XSclbffVsXyJvs6H28PzZ/8ca3e3e580eecf4e762af00eb94eb8d4/image2-5.png" />
            
            </figure><p>On Sunday, July 9, 2023, early morning UTC time, we observed a high number of DNS resolution failures — up to 7% of all DNS queries across the Asia Pacific region — caused by invalid DNSSEC signatures from Verisign .com and .net Top Level Domain (TLD) nameservers. This resulted in connection errors for visitors of Internet properties on Cloudflare in the region.</p><p>The local instances of Verisign’s nameservers started to respond with expired DNSSEC signatures in the Asia Pacific region. In order to remediate the impact, we have rerouted upstream DNS queries towards Verisign to locations on the US west coast which are returning valid signatures.</p><p>We have already reached out to Verisign to get more information on the root cause. Until their issues have been resolved, we will keep our DNS traffic to .com and .net TLD nameservers rerouted, which might cause slightly increased latency for the first visitor to domains under .com and .net in the region.</p>
    <div>
      <h3>Background</h3>
      <a href="#background">
        
      </a>
    </div>
    <p>In order to proxy a domain’s traffic through Cloudflare’s network, there are two components involved with respect to the Domain Name System (DNS) from the perspective of a Cloudflare data center: external DNS resolution, and upstream or origin DNS resolution.</p><p>To understand this, let’s look at the domain name <code>blog.cloudflare.com</code> — which is proxied through Cloudflare’s network — and let’s assume it is configured to use <code>origin.example</code> as the origin server:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5nnwNUFQmflIHHioGDISlg/250c388a2d796d0dc8139b4eddda6c05/image5-1.png" />
            
            </figure><p>Here, the external DNS resolution is the part where DNS queries to <code>blog.cloudflare.com</code> sent by public resolvers like <code>1.1.1.1</code> or <code>8.8.8.8</code> on behalf of a visitor return a set of Cloudflare Anycast IP addresses. This ensures that the visitor’s browser knows where to send an HTTPS request to load the website. Under the hood your browser performs a DNS query that looks something like this (the trailing dot indicates the <a href="https://en.wikipedia.org/wiki/DNS_root_zone">DNS root zone</a>):</p>
            <pre><code>$ dig blog.cloudflare.com. +short
104.18.28.7
104.18.29.7</code></pre>
            <p>(Your computer doesn’t actually use the dig command internally; we’ve used it to illustrate the process) Then when the next closest Cloudflare data center receives the HTTPS request for blog.cloudflare.com, it needs to fetch the content from the origin server (assuming it is not cached).</p><p>There are two ways Cloudflare can reach the origin server. If the DNS settings in Cloudflare contain IP addresses then we can connect directly to the origin. In some cases, our customers use a CNAME which means Cloudflare has to perform another DNS query to get the IP addresses associated with the CNAME. In the example above, <code>blog.cloudflare.com</code> has a CNAME record instructing us to look at <code>origin.example</code> for IP addresses. During the incident, only customers with CNAME records like this going to .com and .net domain names may have been affected.</p><p>The domain <code>origin.example</code> needs to be resolved by Cloudflare as part of the upstream or origin DNS resolution. This time, the Cloudflare data center needs to perform an outbound DNS query that looks like this:</p>
            <pre><code>$ dig origin.example. +short
192.0.2.1</code></pre>
            <p>DNS is a hierarchical protocol, so the recursive DNS resolver, which usually handles DNS resolution for whoever wants to resolve a <a href="https://www.cloudflare.com/learning/dns/glossary/what-is-a-domain-name/">domain name</a>, needs to talk to several involved nameservers until it finally gets an answer from the authoritative nameservers of the domain (assuming no DNS responses are cached). This is the same process during the external DNS resolution and the origin DNS resolution. Here is an example for the origin DNS resolution:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7E1GLN7i8qGi3oB6Zentug/8b55136d3c67a79d0d9c711c428911b4/image6-1.png" />
            
            </figure><p>Inherently, DNS is a public system that was initially published without any means to validate the integrity of the DNS traffic. So in order to prevent someone from spoofing DNS responses, <a href="/dnssec-an-introduction/">DNS Security Extensions (DNSSEC)</a> were introduced as a means to authenticate that DNS responses really come from who they claim to come from. This is achieved by generating cryptographic signatures alongside existing DNS records like A, AAAA, MX, CNAME, etc. By validating a DNS record’s associated signature, it is possible to verify that a requested DNS record really comes from its authoritative nameserver and wasn’t altered en-route. If a signature cannot be validated successfully, recursive resolvers usually return an error indicating the invalid signature. This is exactly what happened on Sunday.</p>
    <div>
      <h3>Incident timeline and impact</h3>
      <a href="#incident-timeline-and-impact">
        
      </a>
    </div>
    <p>On Saturday, July 8, 2023, at 21:10 UTC our logs show the first instances of DNSSEC validation errors that happened during upstream DNS resolution from multiple Cloudflare data centers in the Asia Pacific region. It appeared DNS responses from the TLD nameservers of .com and .net of the type NSEC3 (a DNSSEC mechanism to <a href="/black-lies/">prove non-existing DNS records</a>) included invalid signatures. About an hour later at 22:16 UTC, the first internal alerts went off (since it required issues to be consistent over a certain period of time), but error rates were still at a level at around 0.5% of all upstream DNS queries.</p><p>After several hours, the error rate had increased to a point in which ~13% of our upstream DNS queries in affected locations were failing. This percentage continued to fluctuate over the duration of the incident between the ranges of 10-15% of upstream DNS queries, and roughly 5-7% of all DNS queries (external &amp; upstream resolution) to affected Cloudflare data centers in the Asia Pacific region.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/acWvj718KdxYfGx33fBZT/7ee25b63cf83ee9ff18e6734aeb1cc3e/image1-6.png" />
            
            </figure><p>Initially it appeared as though only a single upstream nameserver was having issues with DNS resolution, however upon further investigation it was discovered that the issue was more widespread. Ultimately, we were able to verify that the Verisign nameservers for .com and .net were returning expired DNSSEC signatures on a portion of DNS queries in the Asia Pacific region. Based on our tests, other nameserver locations were correctly returning valid DNSSEC signatures.</p><p>In response, we rerouted our DNS traffic to the .com and .net TLD nameserver IP addresses to Verisign’s US west locations. After this change was propagated, the issue very quickly subsided and origin resolution error rates returned to normal levels.</p><p>All times are in UTC:</p><p><b>2023-07-08 21:10</b> First instances of DNSSEC validation errors appear in our logs for origin DNS resolution.</p><p><b>2023-07-08 22:16</b> First internal alerts for Asia Pacific data centers go off indicating origin DNS resolution error on our test domain. Very few errors for other domains at this point.</p><p><b>2023-07-09 02:58</b> Error rates have increased substantially since the first instance. An incident is declared.</p><p><b>2023-07-09 03:28</b> DNSSEC validation issues seem to be isolated to a single upstream provider. It is not abnormal that a single upstream has issues that propagate back to us, and in this case our logs were predominantly showing errors from domains that resolve to this specific upstream.</p><p><b>2023-07-09 04:52</b> We realize that DNSSEC validation issues are more widespread and affect multiple .com and .net domains. Validation issues continue to be isolated to the Asia Pacific region only, and appear to be intermittent.</p><p><b>2023-07-09 05:15</b> DNS queries via popular recursive resolvers like 8.8.8.8 and 1.1.1.1 do not return invalid DNSSEC signatures at this point. DNS queries using the local stub resolver continue to return DNSSEC errors.</p><p><b>2023-07-09 06:24</b> Responses from .com and .net Verisign nameservers in Singapore contain expired DNSSEC signatures, but responses from Verisign TLD nameservers in other locations are fine.</p><p><b>2023-07-09 06:41</b> We contact Verisign and notify them about expired DNSSEC signatures.</p><p><b>2023-07-09 06:50</b> In order to remediate the impact, we reroute DNS traffic via IPv4 for the .com and .net Verisign nameserver IPs to US west IPs instead. This immediately leads to a substantial drop in the error rate.</p><p><b>2023-07-09 07:06</b> We also reroute DNS traffic via IPv6 for the .com and .net Verisign nameserver IPs to US west IPs. This leads to the error rate going down to zero.</p><p><b>2023-07-10 09:23</b> The rerouting is still in place, but the underlying issue with expired signatures in the Asia Pacific region has still not been resolved.</p><p><b>2023-07-10 18:23</b> Verisign gets back to us confirming that they “were serving stale data” at their local site and have resolved the issues.</p>
    <div>
      <h3>Technical description of the error and how it happened</h3>
      <a href="#technical-description-of-the-error-and-how-it-happened">
        
      </a>
    </div>
    <p>As mentioned in the introduction, the underlying cause for this incident was expired DNSSEC signatures for .net and .com zones. Expired DNSSEC signatures will cause a DNS response to be interpreted as invalid. There are two main scenarios in which this error was observed by a user:</p><ol><li><p><a href="https://developers.cloudflare.com/dns/cname-flattening/">CNAME flattening</a> for external DNS resolution. This is when our authoritative nameservers attempt to return the IP address(es) that a CNAME record target resolves to rather than the CNAME record itself.</p></li><li><p>CNAME target lookup for origin DNS resolution. This is most commonly used when an HTTPS request is sent to a Cloudflare anycast IP address, and we need to determine what IP address to forward the request to. See <a href="https://developers.cloudflare.com/fundamentals/get-started/concepts/how-cloudflare-works/">How Cloudflare works</a> for more details.</p></li></ol><p>In both cases, behind the scenes the DNS query goes through an in-house recursive DNS resolver in order to lookup what a hostname resolves to. The purpose of this resolver is to cache queries, optimize future queries and provide DNSSEC validation. If the query from this resolver fails for whatever reason, our authoritative DNS system will not be able to perform the two scenarios outlined above.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5qE0JFXaLHPwt3orOsNBm5/37d2d34d396d4cc4c2a22b7241e4120f/image3-1.png" />
            
            </figure><p>During the incident, the recursive resolver was failing to validate the DNSSEC signatures in DNS responses for domains ending in .com and .net. These signatures are sent in the form of an RRSIG record alongside the other DNS records they cover. Together they form a Resource Record set (RRset). Each RRSIG has the corresponding fields:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3aZjsnpM6WSE70sPrnkHbr/6af366bd2accafc3f06296e241ceaba5/image4.png" />
            
            </figure><p>As you can see, the main part of the payload is associated with the signature and its corresponding metadata. Each recursive resolver is responsible for not only checking the signature but also the expiration time of the signature. It is important to obey the expiration time in order to avoid returning responses for RRsets that have been signed by old keys, which could have potentially been compromised by that time.</p><p>During this incident, Verisign, the authoritative operator for the .com and .net TLD zones, was occasionally returning expired signatures in its DNS responses in the Asia Pacific region. As a result our recursive resolver was not able to validate the corresponding RRset. Ultimately this meant that a percentage of DNS queries would return SERVFAIL as response code to our authoritative nameserver. This in turn caused connection issues for users trying to connect to a domain on Cloudflare, because we weren't able to resolve the upstream target of affected domain names and thus didn’t know where to send proxied HTTPS requests to upstream servers.</p>
    <div>
      <h3>Remediation and follow-up steps</h3>
      <a href="#remediation-and-follow-up-steps">
        
      </a>
    </div>
    <p>Once we had identified the root cause we started to look at different ways to remedy the issue. We came to the conclusion that the fastest way to work around this very regionalized issue was to stop using the local route to Verisign's nameservers. This means that, at the time of writing this, our outgoing DNS traffic towards Verisign's nameservers in the Asia Pacific region now traverses the Pacific and ends up at the US west coast, rather than being served by closer nameservers. Due to the nature of DNS and the important role of DNS caching, this has less impact than you might initially expect. Frequently looked up names will be cached after the first request, and this only needs to happen once per data center, as we share and pool the local DNS recursor caches to maximize their efficiency.</p><p>Ideally, we would have been able to fix the issue right away as it potentially affected others in the region too, not just our customers. We will therefore work diligently to improve our incident communications channels with other providers in order to ensure that the DNS ecosystem remains robust against issues such as this. Being able to quickly get hold of other providers that can take action is vital when urgent issues like these arise.</p>
    <div>
      <h3>Conclusion</h3>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>This incident <a href="/october-2021-facebook-outage/">once again</a> shows how impactful DNS failures are and how crucial this technology is for the Internet. We will investigate how we can improve our systems to detect and resolve issues like this more efficiently and quickly if they occur again in the future.</p><p>While Cloudflare was not the cause of this issue, and we are certain that we were not the only ones affected by this, we are still sorry for the disruption to our customers and to all the users who were unable to access Internet properties during this incident.</p><p><b>Update</b>: On Tue Jul 11 22:24:21 UTC 2023_,_ Verisign posted an <a href="https://lists.dns-oarc.net/pipermail/dns-operations/2023-July/022174.html">announcement</a>, providing more details:</p><blockquote><p><i>Last week, during a migration of one of our DNS resolution sites in Singapore, from one provider to another, we unexpectedly lost management access and the ability to deliver changes and DNS updates to the site. Following our standard procedure, we disabled all transit links to the affected site. Unfortunately, a peering router remained active, which was not immediately obvious to our teams due to the lack of connectivity there.</i></p></blockquote><blockquote><p><i>Over the weekend, this caused an issue that may have affected the ability of some internet users in the region to reach some .com and .net domains, as DNSSEC signatures on the site began expiring. The issue was resolved by powering off the site’s peering router, causing the anycast route announcement to be withdrawn and traffic to be directed to other sites.</i></p></blockquote><blockquote><p><i>We are updating our processes and procedures and will work to prevent such issues from recurring in the future.</i></p></blockquote><blockquote><p><i>The Singapore site is part of a highly redundant constellation of more than 200 sites that make up our global network. This issue had no effect on the core resolution of .com and .net resolution globally. We apologize to those who may have been affected.</i></p></blockquote><p></p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[DNSSEC]]></category>
            <category><![CDATA[Outage]]></category>
            <category><![CDATA[Post Mortem]]></category>
            <guid isPermaLink="false">3ZlvMILKrfS2Z4IQ0qumTD</guid>
            <dc:creator>Christian Elmerot</dc:creator>
            <dc:creator>Alex Fattouche</dc:creator>
            <dc:creator>Hannes Gerhart</dc:creator>
        </item>
        <item>
            <title><![CDATA[How we improved DNS record build speed by more than 4,000x]]></title>
            <link>https://blog.cloudflare.com/dns-build-improvement/</link>
            <pubDate>Wed, 25 May 2022 12:59:04 GMT</pubDate>
            <description><![CDATA[ How we redesigned our DNS pipeline to significantly improve DNS propagation speed across all zones. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Since my previous blog about <a href="/secondary-dns-deep-dive/">Secondary DNS</a>, <a href="https://www.cloudflare.com/dns/">Cloudflare's DNS</a> traffic has more than doubled from 15.8 trillion DNS queries per month to 38.7 trillion. Our network now spans over 270 cities in over 100 countries, interconnecting with more than 10,000 networks globally. According to <a href="https://w3techs.com/technologies/overview/dns_server">w3 stats</a>, “Cloudflare is used as a DNS server provider by 15.3% of all the websites.” This means we have an enormous responsibility to serve <a href="https://www.cloudflare.com/learning/dns/what-is-dns/">DNS</a> in the fastest and most reliable way possible.</p><p>Although the response time we have on DNS queries is the most important performance metric, there is another metric that sometimes goes unnoticed. DNS Record Propagation time is how long it takes changes submitted to our API to be reflected in our DNS query responses. Every millisecond counts here as it allows customers to quickly change configuration, making their systems much more agile. Although our DNS propagation pipeline was already known to be very fast, we had identified several improvements that, if implemented, would massively improve performance. In this blog post I’ll explain how we managed to drastically improve our DNS record propagation speed, and the impact it has on our customers.</p>
    <div>
      <h3>How DNS records are propagated</h3>
      <a href="#how-dns-records-are-propagated">
        
      </a>
    </div>
    <p>Cloudflare uses a multi-stage pipeline that takes our customers’ DNS record changes and pushes them to our global network, so they are available all over the world.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/621S7OYC5i9VEHUMnpruti/322ede366828e0669c16443d861608f8/image3-37.png" />
            
            </figure><p>The steps shown in the diagram above are:</p><ol><li><p>Customer makes a change to a record via our DNS Records API (or UI).</p></li><li><p>The change is persisted to the database.</p></li><li><p>The database event triggers a Kafka message which is consumed by the Zone Builder.</p></li><li><p>The Zone Builder takes the message, collects the contents of the zone from the database and pushes it to Quicksilver, our distributed KV store.</p></li><li><p>Quicksilver then propagates this information to the network.</p></li></ol><p>Of course, this is a simplified version of what is happening. In reality, our API receives thousands of requests per second. All POST/PUT/PATCH/DELETE requests ultimately result in a DNS record change. Each of these changes needs to be actioned so that the information we show through our API and in the <a href="https://dash.cloudflare.com/?to=/:account/:zone/dns">Cloudflare dashboard</a> is eventually consistent with the information we use to respond to DNS queries.</p><p>Historically, one of the largest bottlenecks in the DNS propagation pipeline was the Zone Builder, shown in step 4 above. Responsible for collecting and organizing records to be written to our global network, our Zone Builder often ate up most of the propagation time, especially for larger zones. As we continue to scale, it is important for us to remove any bottlenecks that may exist in our systems, and this was clearly identified as one such bottleneck.</p>
    <div>
      <h3>Growing pains</h3>
      <a href="#growing-pains">
        
      </a>
    </div>
    <p>When the pipeline shown above was <a href="/how-we-made-our-dns-stack-3x-faster/">first announced</a>, the Zone Builder received somewhere between 5 and 10 DNS record changes per second. Although the Zone Builder at the time was a massive improvement on the previous system, it was not going to last long given the growth that Cloudflare was and still is experiencing. Fast-forward to today, we receive on average 250 DNS record changes per second, a staggering 25x growth from when the Zone Builder was first announced.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/MlSZs6szqObivtM8cC1rV/3b50129c5d7a2691e893242d1b683a0a/image4-30.png" />
            
            </figure><p>The way that the Zone Builder was initially designed was quite simple. When a zone changed, the Zone Builder would grab all the records from the database for that zone and compare them with the records stored in Quicksilver. Any differences were fixed to maintain consistency between the database and Quicksilver.</p><p>This is known as a full build. Full builds work great because each DNS record change corresponds to one zone change event. This means that multiple events can be batched and subsequently dropped if needed. For example, if a user makes 10 changes to their zone, this will result in 10 events. Since the Zone Builder grabs all the records for the zone anyway, there is no need to build the zone 10 times. We just need to build it once after the final change has been submitted.</p><p>What happens if the zone contains one million records or 10 million records? This is a very real problem, because not only is Cloudflare scaling, but our customers are scaling with us. Today our largest zone currently has millions of records. Although our database is optimized for performance, even one full build containing one million records took up to <b>35 seconds</b>, largely caused by database query latency. In addition, when the Zone Builder compares the zone contents with the records stored in Quicksilver, we need to fetch all the records from Quicksilver for the zone, adding time. However, the impact doesn’t just stop at the single customer. This also eats up more resources from other services reading from the database and slows down the rate at which our Zone Builder can build other zones.</p>
    <div>
      <h3>Per-record build: a new build type</h3>
      <a href="#per-record-build-a-new-build-type">
        
      </a>
    </div>
    <p>Many of you might already have the solution to this problem in your head:</p><p><i>Why doesn’t the Zone Builder just query the database for the record that has changed and propagate just the single record?</i></p><p>Of course this is the correct solution, and the one we eventually ended up at. However, the road to get there was not as simple as it might seem.</p><p>Firstly, our database uses a series of functions that, at zone touch time, create a PostgreSQL Queue (PGQ) event that ultimately gets turned into a Kafka event. Initially, we had no distinction for individual record events, which meant our Zone Builder had no idea what had actually changed until it queried the database.</p><p>Next, the Zone Builder is still responsible for DNS zone settings in addition to records. Some examples of DNS zone settings include custom nameserver control and DNSSEC control. As a result, our Zone Builder needed to be aware of specific build types to ensure that they don’t step on each other. Furthermore, per-record builds cannot be batched in the same way that zone builds can because each event needs to be actioned separately.</p><p>As a result, a brand new scheduling system needed to be written. Lastly, Quicksilver interaction needed to be re-written to account for the different types of schedulers. These issues can be broken down as follows:</p><ol><li><p>Create a new Kafka event pipeline for record changes that contain information about the changed record.</p></li><li><p>Separate the Zone Builder into a new type of scheduler that implements some defined scheduler interface.</p></li><li><p>Implement the per-record scheduler to read events one by one in the correct order.</p></li><li><p>Implement the new Quicksilver interface for the per-record scheduler.</p></li></ol><p>Below is a high level diagram of how the new Zone Builder looks internally with the new scheduler types.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/23Y8EYi1qubiNN16dVq62j/338ca37bd56d92d157eb09c016841f9c/image6-20.png" />
            
            </figure><p>It is critically important that we lock between these two schedulers because it would otherwise be possible for the full build scheduler to overwrite the per-record scheduler’s changes with stale data.</p><p>It is important to note that none of this per-record architecture would be possible without the use of Cloudflare’s <a href="/black-lies/">black lie approach</a> to negative answers with DNSSEC. Normally, in order to properly serve negative answers with DNSSEC, all the records within the zone must be canonically sorted. This is needed in order to maintain a list of references from the apex record through all the records in the zone. With this normal approach to negative answers, a single record that has been added to the zone requires collecting all records to determine its insertion point within this sorted list of names.</p>
    <div>
      <h3>Bugs</h3>
      <a href="#bugs">
        
      </a>
    </div>
    <p>I would love to be able to write a Cloudflare blog where everything went smoothly; however, that is never the case. Bugs happen, but we need to be ready to react to them and set ourselves up so that next time this specific bug cannot happen.</p><p>In this case, the major bug we discovered was related to the cleanup of old records in Quicksilver. With the full Zone Builder, we have the luxury of knowing exactly what records exist in both the database and in Quicksilver. This makes writing and cleaning up a fairly simple task.</p><p>When the per-record builds were introduced, record events such as creates, updates, and deletes all needed to be treated differently. Creates and deletes are fairly simple because you are either adding or removing a record from Quicksilver. Updates introduced an unforeseen issue due to the way that our PGQ was producing Kafka events. Record updates only contained the new record information, which meant that when the record name was changed, we had no way of knowing what to query for in Quicksilver in order to clean up the old record. This meant that any time a customer changed the name of a record in the DNS Records API, the old record would not be deleted. Ultimately, this was fixed by replacing those specific update events with both a creation and a deletion event so that the Zone Builder had the necessary information to clean up the stale records.</p><p>None of this is rocket surgery, but we spend engineering effort to continuously improve our software so that it grows with the scaling of Cloudflare. And it’s challenging to change such a fundamental low-level part of Cloudflare when millions of domains depend on us.</p>
    <div>
      <h3>Results</h3>
      <a href="#results">
        
      </a>
    </div>
    <p>Today, all DNS Records API record changes are treated as per-record builds by the Zone Builder. As I previously mentioned, we have not been able to get rid of full builds entirely; however, they now represent about 13% of total DNS builds. This 13% corresponds to changes made to DNS settings that require knowledge of the entire zone's contents.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1mmDhQTWMR03iIWHL5oRNB/e953a2be78239e765d8986ecfa7fdf47/image1-56.png" />
            
            </figure><p>When we compare the two build types as shown below we can see that per-record builds are on average <b>150x</b> faster than full builds. The build time below includes both database query time and Quicksilver write time.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/48Umk7PrqhCT7J1kBSjXCg/6adab8baa50d861e5852d841357f31eb/image2-51.png" />
            
            </figure><p>From there, our records are propagated to our global network through Quicksilver.</p><p>The 150x improvement above is with respect to averages, but what about that 4000x that I mentioned at the start? As you can imagine, as the size of the zone increases, the difference between full build time and per-record build time also increases. I used a test zone of one million records and ran several per-record builds, followed by several full builds. The results are shown in the table below:</p><table><tr><td><p><b>Build Type</b></p></td><td><p><b>Build Time (ms)</b></p></td></tr><tr><td><p>Per Record #1</p></td><td><p>6</p></td></tr><tr><td><p>Per Record #2</p></td><td><p>7</p></td></tr><tr><td><p>Per Record #3</p></td><td><p>6</p></td></tr><tr><td><p>Per Record #4</p></td><td><p>8</p></td></tr><tr><td><p>Per Record #5</p></td><td><p>6</p></td></tr><tr><td><p>Full #1</p></td><td><p>34032</p></td></tr><tr><td><p>Full #2</p></td><td><p>33953</p></td></tr><tr><td><p>Full #3</p></td><td><p>34271</p></td></tr><tr><td><p>Full #4</p></td><td><p>34121</p></td></tr><tr><td><p>Full #5</p></td><td><p>34093</p></td></tr></table><p>We can see that, given five per-record builds, the build time was no more than 8ms. When running a full build however, the build time lasted on average 34 seconds. That is a build time reduction of <b>4250x</b>!</p><p>Given the full build times for both average-sized zones and large zones, it is apparent that all Cloudflare customers are benefitting from this improved performance, and the benefits only improve as the size of the zone increases. In addition, our Zone Builder uses less database and Quicksilver resources meaning other Cloudflare systems are able to operate at increased capacity.</p>
    <div>
      <h3>Next Steps</h3>
      <a href="#next-steps">
        
      </a>
    </div>
    <p>The results here have been very impactful, though we think that we can do even better. In the future, we plan to get rid of full builds altogether by replacing them with zone setting builds. Instead of fetching the zone settings in addition to all the records, the zone setting builder would just fetch the settings for the zone and propagate that to our global network via Quicksilver. Similar to the per-record builds, this is a difficult challenge due to the complexity of zone settings and the number of actors that touch it. Ultimately if this can be accomplished, we can officially retire the full builds and leave it as a reminder in our git history of the scale at which we have grown over the years.</p><p>In addition, we plan to introduce a batching system that will collect record changes into groups to minimize the number of queries we make to our database and Quicksilver.</p><p>Does solving these kinds of technical and operational challenges excite you? Cloudflare is always hiring for talented specialists and generalists within our <a href="https://www.cloudflare.com/careers/jobs/?department=Engineering&amp;location=default">Engineering</a> and <a href="https://www.cloudflare.com/careers">other teams</a>.</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[Kafka]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Product News]]></category>
            <guid isPermaLink="false">1TgZJPuWF9cbCw5YAo3emU</guid>
            <dc:creator>Alex Fattouche</dc:creator>
        </item>
        <item>
            <title><![CDATA[Moving k8s communication to gRPC]]></title>
            <link>https://blog.cloudflare.com/moving-k8s-communication-to-grpc/</link>
            <pubDate>Sat, 20 Mar 2021 14:00:00 GMT</pubDate>
            <description><![CDATA[ How we use gRPC in combination with Kubernetes to improve the performance and usability of internal APIs. ]]></description>
            <content:encoded><![CDATA[ <p></p><p>Over the past year and a half, Cloudflare has been hard at work moving our back-end services running in our non-edge locations from bare metal solutions and Mesos Marathon to a more unified approach using <a href="https://kubernetes.io/">Kubernetes(K8s)</a>. We chose Kubernetes because it allowed us to split up our monolithic application into many different microservices with granular control of communication.</p><p>For example, a <a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/">ReplicaSet</a> in Kubernetes can provide high availability by ensuring that the correct number of pods are always available. A <a href="https://kubernetes.io/docs/concepts/workloads/pods/">Pod</a> in Kubernetes is similar to a container in <a href="https://www.docker.com/">Docker</a>. Both are responsible for running the actual application. These pods can then be exposed through a Kubernetes <a href="https://kubernetes.io/docs/concepts/services-networking/service/">Service</a> to abstract away the number of replicas by providing a single endpoint that load balances to the pods behind it. The services can then be exposed to the Internet via an <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress</a>. Lastly, a network policy can protect against unwanted communication by ensuring the correct policies are applied to the application. These policies can include L3 or L4 rules.</p><p>The diagram below shows a simple example of this setup.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/33zWCqFZw2iuXfhllsHgdk/e290742e17a975a98a195bccc283297f/2-3.png" />
            
            </figure><p>Though Kubernetes does an excellent job at providing the tools for communication and traffic management, it does not help the developer decide the best way to communicate between the applications running on the pods. Throughout this blog we will look at some of the decisions we made and why we made them to discuss the pros and cons of two commonly used API architectures, REST and gRPC.</p>
    <div>
      <h3>Out with the old, in with the new</h3>
      <a href="#out-with-the-old-in-with-the-new">
        
      </a>
    </div>
    <p>When the DNS team first moved to Kubernetes, all of our pod-to-pod communication was done through REST APIs and in many cases also included Kafka. The general communication flow was as follows:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3GQIkngkqEYcBJNFzv1xNU/fe13bc8911a11a9c26dc5925b4fe6a19/1-5.png" />
            
            </figure><p>We use Kafka because it allows us to handle large spikes in volume without losing information. For example, during a Secondary DNS Zone zone transfer, Service A tells Service B that the zone is ready to be published to the edge. Service B then calls Service A’s REST API, generates the zone, and pushes it to the edge. If you want more information about how this works, I wrote an entire blog post about the <a href="/secondary-dns-deep-dive/">Secondary DNS pipeline</a> at Cloudflare.</p><p>HTTP worked well for most communication between these two services. However, as we scaled up and added new endpoints, we realized that as long as we control both ends of the communication, we could improve the usability and performance of our communication. In addition, sending large DNS zones over the network using HTTP often caused issues with sizing constraints and compression.</p><p>In contrast, gRPC can easily stream data between client and server and is commonly used in microservice architecture. These qualities made gRPC the obvious replacement for our REST APIs.</p>
    <div>
      <h3>gRPC Usability</h3>
      <a href="#grpc-usability">
        
      </a>
    </div>
    <p>Often overlooked from a developer’s perspective, HTTP client libraries are clunky and require code that defines paths, handles parameters, and deals with responses in bytes. gRPC abstracts all of this away and makes network calls feel like any other function calls defined for a struct.</p><p>The example below shows a very basic schema to set up a GRPC client/server system. As a result of gRPC using <a href="https://developers.google.com/protocol-buffers">protobuf</a> for serialization, it is largely language agnostic. Once a schema is defined, the <i>protoc</i> command can be used to generate code for <a href="https://grpc.io/docs/languages/">many languages</a>.</p><p>Protocol Buffer data is structured as <i>messages,</i> with each <i>message</i> containing information stored in the form of fields. The fields are strongly typed, providing type safety unlike JSON or XML. Two messages have been defined, <i>Hello</i> and <i>HelloResponse</i>. Next we define a service called <i>HelloWorldHandler</i> which contains one RPC function called <i>SayHello</i> that must be implemented if any object wants to call themselves a <i>HelloWorldHandler</i>.</p><p>Simple Proto:</p>
            <pre><code>message Hello{
   string Name = 1;
}

message HelloResponse{}

service HelloWorldHandler {
   rpc SayHello(Hello) returns (HelloResponse){}
}</code></pre>
            <p>Once we run our <i>protoc</i> command, we are ready to write the server-side code. In order to implement the <i>HelloWorldHandler</i>, we must define a struct that implements all of the RPC functions specified in the protobuf schema above_._ In this case, the struct <i>Server</i> defines a function <i>SayHello</i> that takes in two parameters, context and <i>*pb.Hello</i>. <i>*pb.Hello</i> was previously specified in the schema and contains one field, <i>Name. SayHello</i> must also return the <i>*pbHelloResponse</i> which has been defined without fields for simplicity.</p><p>Inside the main function, we create a TCP listener, create a new gRPC server, and then register our handler as a <i>HelloWorldHandlerServer</i>. After calling <i>Serve</i> on our gRPC server, clients will be able to communicate with the server through the function <i>SayHello</i>.</p><p>Simple Server:</p>
            <pre><code>type Server struct{}

func (s *Server) SayHello(ctx context.Context, in *pb.Hello) (*pb.HelloResponse, error) {
    fmt.Println("%s says hello\n", in.Name)
    return &amp;pb.HelloResponse{}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    gRPCServer := gRPC.NewServer()
    handler := Server{}
    pb.RegisterHelloWorldHandlerServer(gRPCServer, &amp;handler)
    if err := gRPCServer.Serve(lis); err != nil {
        panic(err)
    }
}</code></pre>
            <p>Finally, we need to implement the gRPC Client. First, we establish a TCP connection with the server. Then, we create a new <i>pb.HandlerClient</i>. The client is able to call the server's <i>SayHello</i> function by passing in a *<i>pb.Hello</i> object.</p><p>Simple Client:</p>
            <pre><code>conn, err := gRPC.Dial("127.0.0.1:8080", gRPC.WithInsecure())
if err != nil {
    panic(err)
}
client := pb.NewHelloWorldHandlerClient(conn)
client.SayHello(context.Background(), &amp;pb.Hello{Name: "alex"})</code></pre>
            <p>Though I have removed some code for simplicity, these <i>services</i> and <i>messages</i> can become quite complex if needed. The most important thing to understand is that when a server attempts to announce itself as a <i>HelloWorldHandlerServer</i>, it is required to implement the RPC functions as specified within the protobuf schema. This agreement between the client and server makes cross-language network calls feel like regular function calls.</p><p>In addition to the basic Unary server described above, gRPC lets you decide between four types of service methods:</p><ul><li><p><b>Unary</b> (example above): client sends a single request to the server and gets a single response back, just like a normal function call.</p></li><li><p><b>Server Streaming:</b> server returns a stream of messages in response to a client's request.</p></li><li><p><b>Client Streaming:</b> client sends a stream of messages to the server and the server replies in a single message, usually once the client has finished streaming.</p></li><li><p><b>Bi-directional Streaming:</b> the client and server can both send streams of messages to each other asynchronously.</p></li></ul>
    <div>
      <h3>gRPC Performance</h3>
      <a href="#grpc-performance">
        
      </a>
    </div>
    <p>Not all HTTP connections are created equal. Though Golang natively supports HTTP/2, the HTTP/2 transport must be set by the client and the server must also support HTTP/2. Before moving to gRPC, we were still using HTTP/1.1 for client connections. We could have switched to HTTP/2 for performance gains, but we would have lost some of the benefits of native protobuf compression and usability changes.</p><p>The best option available in HTTP/1.1 is pipelining. Pipelining means that although requests can share a connection, they must queue up one after the other until the request in front completes. HTTP/2 improved pipelining by using connection multiplexing. Multiplexing allows for multiple requests to be sent on the same connection and at the same time.</p><p>HTTP REST APIs generally use JSON for their request and response format. Protobuf is the native request/response format of gRPC because it has a standard schema agreed upon by the client and server during registration. In addition, protobuf is known to be significantly faster than JSON due to its serialization speeds. I’ve run some benchmarks on my laptop, source code can be found <a href="https://github.com/Fattouche/protobuf-benchmark">here</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/23TkQ80Iruo8IYflqIuobN/f7323dd5817ca47f9b203b8b63a3a980/image1-26.png" />
            
            </figure><p>As you can see, protobuf performs better in small, medium, and large data sizes. It is faster per operation, smaller after marshalling, and scales well with input size. This becomes even more noticeable when unmarshaling very large data sets. Protobuf takes 96.4ns/op but JSON takes 22647ns/op, a 235X reduction in time! For large DNS zones, this efficiency makes a massive difference in the time it takes us to go from record change in our API to serving it at the edge.</p><p>Combining the benefits of HTTP/2 and protobuf showed almost no performance change from our application’s point of view. This is likely due to the fact that our pods were already so close together that our connection times were already very low. In addition, most of our gRPC calls are done with small amounts of data where the difference is negligible. One thing that we did notice <b>—</b> likely related to the multiplexing of HTTP/2 <b>—</b> was greater efficiency when writing newly created/edited/deleted records to the edge. Our latency spikes dropped in both amplitude and frequency.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6PDR6bZkh8zzVVv2j6tmE5/9ce516e00daa832135964246eaf7b95c/image2-19.png" />
            
            </figure>
    <div>
      <h3>gRPC Security</h3>
      <a href="#grpc-security">
        
      </a>
    </div>
    <p>One of the best features in Kubernetes is the NetworkPolicy. This allows developers to control what goes in and what goes out.</p>
            <pre><code>apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978</code></pre>
            <p>In this example, taken from the <a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">Kubernetes docs</a>, we can see that this will create a network policy called test-network-policy. This policy controls both ingress and egress communication to or from any pod that matches the role <i>db</i> and enforces the following rules:</p><p>Ingress connections allowed:</p><ul><li><p>Any pod in default namespace with label “role=frontend”</p></li><li><p>Any pod in any namespace that has a label “project=myproject”</p></li><li><p>Any source IP address in 172.17.0.0/16 except for 172.17.1.0/24</p></li></ul><p>Egress connections allowed:</p><ul><li><p>Any dest IP address in 10.0.0.0/24</p></li></ul><p>NetworkPolicies do a fantastic job of protecting APIs at the network level, however, they do nothing to protect APIs at the application level. If you wanted to control which endpoints can be accessed within the API, you would need k8s to be able to not only distinguish between pods, but also endpoints within those pods. These concerns led us to <a href="https://grpc.io/docs/guides/auth/">per RPC credentials</a>. Per RPC credentials are easy to set up on top of the pre-existing gRPC code. All you need to do is add interceptors to both your stream and unary handlers.</p>
            <pre><code>func (s *Server) UnaryAuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    // Get the targeted function
    functionInfo := strings.Split(info.FullMethod, "/")
    function := functionInfo[len(functionInfo)-1]
    md, _ := metadata.FromIncomingContext(ctx)

    // Authenticate
    err := authenticateClient(md.Get("username")[0], md.Get("password")[0], function)
    // Blocked
    if err != nil {
        return nil, err
    }
    // Verified
    return handler(ctx, req)
}</code></pre>
            <p>In this example code snippet, we are grabbing the username, password, and requested function from the info object. We then authenticate against the client to make sure that it has correct rights to call that function. This interceptor will run before any of the other functions get called, which means one implementation protects all functions. The client would initialize its secure connection and send credentials like so:</p>
            <pre><code>transportCreds, err := credentials.NewClientTLSFromFile(certFile, "")
if err != nil {
    return nil, err
}
perRPCCreds := Creds{Password: grpcPassword, User: user}
conn, err := grpc.Dial(endpoint, grpc.WithTransportCredentials(transportCreds), grpc.WithPerRPCCredentials(perRPCCreds))
if err != nil {
    return nil, err
}
client:= pb.NewRecordHandlerClient(conn)
// Can now start using the client</code></pre>
            <p>Here the client first verifies that the server matches with the certFile. This step ensures that the client does not accidentally send its password to a bad actor. Next, the client initializes the <i>perRPCCreds</i> struct with its username and password and dials the server with that information. Any time the client makes a call to an rpc defined function, its credentials will be verified by the server.</p>
    <div>
      <h3>Next Steps</h3>
      <a href="#next-steps">
        
      </a>
    </div>
    <p>Our next step is to remove the need for many applications to access the database and ultimately DRY up our codebase by pulling all DNS-related code into a single API, accessed from one gRPC interface. This removes the potential for mistakes in individual applications and makes updating our database schema easier. It also gives us more granular control over which functions can be accessed rather than which tables can be accessed.</p><p>So far, the DNS team is very happy with the results of our gRPC migration. However, we still have a long way to go before we can move entirely away from REST. We are also patiently waiting for <a href="https://github.com/grpc/grpc/issues/19126">HTTP/3 support</a> for gRPC so that we can take advantage of those super <a href="https://en.wikipedia.org/wiki/QUIC">quic</a> speeds!</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[gRPC]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[HTTP2]]></category>
            <category><![CDATA[QUIC]]></category>
            <guid isPermaLink="false">6oeh7RRYqhqnS7vtJ2BpwP</guid>
            <dc:creator>Alex Fattouche</dc:creator>
        </item>
        <item>
            <title><![CDATA[Secondary DNS - Deep Dive]]></title>
            <link>https://blog.cloudflare.com/secondary-dns-deep-dive/</link>
            <pubDate>Tue, 15 Sep 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ The goal of Cloudflare operated Secondary DNS is to allow our customers with custom DNS solutions, be it on-premise or some other DNS provider, to be able to take advantage of Cloudflare's DNS performance and more recently, through Secondary Override, our proxying and security capabilities too. ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h2>How Does Secondary DNS Work?</h2>
      <a href="#how-does-secondary-dns-work">
        
      </a>
    </div>
    <p>If you already understand how Secondary DNS works, please feel free to skip this section. It does not provide any Cloudflare-specific information.</p><p>Secondary DNS has many use cases across the Internet; however, traditionally, it was used as a synchronized backup for when the primary DNS server was unable to respond to queries. A more modern approach involves focusing on redundancy across many different nameservers, which in many cases broadcast the same anycasted IP address.</p><p>Secondary DNS involves the unidirectional transfer of DNS zones from the primary to the Secondary DNS server(s). One primary can have any number of Secondary DNS servers that it must communicate with in order to keep track of any zone updates. A zone update is considered a change in the contents of a  zone, which ultimately leads to a Start of Authority (SOA) serial number increase. The zone’s SOA serial is one of the key elements of Secondary DNS; it is how primary and secondary servers synchronize zones. Below is an example of what an SOA record might look like during a dig query.</p>
            <pre><code>example.com	3600	IN	SOA	ashley.ns.cloudflare.com. dns.cloudflare.com. 
2034097105  // Serial
10000 // Refresh
2400 // Retry
604800 // Expire
3600 // Minimum TTL</code></pre>
            <p>Each of the numbers is used in the following way:</p><ol><li><p>Serial - Used to keep track of the status of the zone, must be incremented at every change.</p></li><li><p>Refresh - The maximum number of seconds that can elapse before a Secondary DNS server must check for a SOA serial change.</p></li><li><p>Retry - The maximum number of seconds that can elapse before a Secondary DNS server must check for a SOA serial change, after previously failing to contact the primary.</p></li><li><p>Expire - The maximum number of seconds that a Secondary DNS server can serve stale information, in the event the primary cannot be contacted.</p></li><li><p>Minimum TTL - Per <a href="https://tools.ietf.org/html/rfc2308">RFC 2308</a>, the number of seconds that a DNS negative response should be cached for.</p></li></ol><p>Using the above information, the Secondary DNS server stores an SOA record for each of the zones it is tracking. When the serial increases, it knows that the zone must have changed, and that a zone transfer must be initiated.  </p>
    <div>
      <h2>Serial Tracking</h2>
      <a href="#serial-tracking">
        
      </a>
    </div>
    <p>Serial increases can be detected in the following ways:</p><ol><li><p>The fastest way for the Secondary DNS server to keep track of a serial change is to have the primary server NOTIFY them any time a zone has changed using the DNS protocol as specified in <a href="https://www.ietf.org/rfc/rfc1996.txt">RFC 1996</a>, Secondary DNS servers will instantly be able to initiate a zone transfer.</p></li><li><p>Another way is for the Secondary DNS server to simply poll the primary every “Refresh” seconds. This isn’t as fast as the NOTIFY approach, but it is a good fallback in case the notifies have failed.</p></li></ol><p>One of the issues with the basic NOTIFY protocol is that anyone on the Internet could potentially notify the Secondary DNS server of a zone update. If an initial SOA query is not performed by the Secondary DNS server before initiating a zone transfer, this is an easy way to perform an <a href="https://www.cloudflare.com/learning/ddos/dns-amplification-ddos-attack/">amplification attack</a>. There is two common ways to prevent anyone on the Internet from being able to NOTIFY Secondary DNS servers:</p><ol><li><p>Using transaction signatures (TSIG) as per <a href="https://tools.ietf.org/html/rfc2845">RFC 2845</a>. These are to be placed as the last record in the extra records section of the DNS message. Usually the number of extra records (or ARCOUNT) should be no more than two in this case.</p></li><li><p>Using IP based access control lists (ACL). This increases security but also prevents flexibility in server location and IP address allocation.</p></li></ol><p>Generally NOTIFY messages are sent over UDP, however TCP can be used in the event the primary server has reason to believe that TCP is necessary (i.e. firewall issues).</p>
    <div>
      <h2>Zone Transfers</h2>
      <a href="#zone-transfers">
        
      </a>
    </div>
    <p>In addition to serial tracking, it is important to ensure that a standard protocol is used between primary and Secondary DNS server(s), to efficiently transfer the zone. DNS zone transfer protocols do not attempt to solve the confidentiality, authentication and integrity triad (CIA); however, the use of TSIG on top of the basic zone transfer protocols can provide integrity and authentication. As a result of <a href="https://www.cloudflare.com/learning/dns/what-is-dns/">DNS</a> being a public protocol, confidentiality during the zone transfer process is generally not a concern.</p>
    <div>
      <h3>Authoritative Zone Transfer (AXFR)</h3>
      <a href="#authoritative-zone-transfer-axfr">
        
      </a>
    </div>
    <p>AXFR is the original zone transfer protocol that was specified in <a href="https://tools.ietf.org/html/rfc1034">RFC 1034</a> and <a href="https://tools.ietf.org/html/rfc1035">RFC 1035</a> and later further explained in <a href="https://tools.ietf.org/html/rfc5936">RFC 5936</a>. AXFR is done over a TCP connection because a reliable protocol is needed to ensure packets are not lost during the transfer. Using this protocol, the primary DNS server will transfer all of the zone contents to the Secondary DNS server, in one connection, regardless of the serial number. AXFR is recommended to be used for the first zone transfer, when none of the records are propagated, and IXFR is recommended after that.</p>
    <div>
      <h3>Incremental Zone Transfer (IXFR)</h3>
      <a href="#incremental-zone-transfer-ixfr">
        
      </a>
    </div>
    <p>IXFR is the more sophisticated zone transfer protocol that was specified in <a href="https://tools.ietf.org/html/rfc1995">RFC 1995</a>. Unlike the AXFR protocol, during an IXFR, the primary server will only send the secondary server the records that have changed since its current version of the zone (based on the serial number). This means that when a Secondary DNS server wants to initiate an IXFR, it sends its current serial number to the primary DNS server. The primary DNS server will then format its response based on previous versions of changes made to the zone. IXFR messages must obey the following pattern:</p><ol><li><p><b><i>Current latest SOA</i></b></p></li><li><p><b><i>Secondary server current SOA</i></b></p></li><li><p><b><i>DNS record deletions</i></b></p></li><li><p><b><i>Secondary server current SOA + changes</i></b></p></li><li><p><b><i>DNS record additions</i></b></p></li><li><p><b><i>Current latest SOA</i></b></p></li></ol><p>Steps 2,3,4,5,6 can be repeated any number of times, as each of those represents one change set of deletions and additions, ultimately leading to a new serial.</p><p>IXFR can be done over UDP or TCP, but again TCP is generally recommended to avoid packet loss.</p>
    <div>
      <h2>How Does Secondary DNS Work at Cloudflare?</h2>
      <a href="#how-does-secondary-dns-work-at-cloudflare">
        
      </a>
    </div>
    <p>The DNS team loves microservice architecture! When we initially implemented Secondary DNS at Cloudflare, it was done using <a href="https://mesosphere.github.io/marathon/">Mesos Marathon</a>. This allowed us to separate each of our services into several different marathon apps, individually scaling apps as needed. All of these services live in our core data centers. The following services were created:</p><ol><li><p>Zone Transferer - responsible for attempting IXFR, followed by AXFR if IXFR fails.</p></li><li><p>Zone Transfer Scheduler - responsible for periodically checking zone SOA serials for changes.</p></li><li><p>Rest API - responsible for registering new zones and primary nameservers.</p></li></ol><p>In addition to the marathon apps, we also had an app external to the cluster:</p><ol><li><p>Notify Listener - responsible for listening for notifies from primary servers and telling the Zone Transferer to initiate an AXFR/IXFR.</p></li></ol><p>Each of these microservices communicates with the others through <a href="https://kafka.apache.org/">Kafka</a>.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6cS9HY6kpiqdrdyD3tqQjI/55d2a781d351cc854f416dd9b1e7d4f3/image8-1.png" />
            
            </figure><p>Figure 1: Secondary DNS Microservice Architecture‌‌</p><p>Once the zone transferer completes the AXFR/IXFR, it then passes the zone through to our zone builder, and finally gets pushed out to our edge at each of our <a href="https://www.cloudflare.com/network/">200 locations.</a></p><p>Although this current architecture worked great in the beginning, it left us open to many vulnerabilities and scalability issues down the road. As our Secondary DNS product became more popular, it was important that we proactively scaled and reduced the technical debt as much as possible. As with many companies in the industry, Cloudflare has recently migrated all of our core data center services to <a href="https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/">Kubernetes</a>, moving away from individually managed apps and Marathon clusters.</p><p>What this meant for Secondary DNS is that all of our Marathon-based services, as well as our NOTIFY Listener, had to be migrated to Kubernetes. Although this long migration ended up paying off, many difficult challenges arose along the way that required us to come up with unique solutions in order to have a seamless, zero downtime migration.</p>
    <div>
      <h2>Challenges When Migrating to Kubernetes</h2>
      <a href="#challenges-when-migrating-to-kubernetes">
        
      </a>
    </div>
    <p>Although the entire DNS team agreed that kubernetes was the way forward for Secondary DNS, it also introduced several challenges. These challenges arose from a need to properly scale up across many distributed locations while also protecting each of our individual data centers. Since our core does not rely on anycast to automatically distribute requests, as we introduce more customers, it opens us up to denial-of-service attacks.</p><p>The two main issues we ran into during the migration were:</p><ol><li><p>How do we create a distributed and reliable system that makes use of kubernetes principles while also making sure our customers know which IPs we will be communicating from?</p></li><li><p>When opening up a public-facing UDP socket to the Internet, how do we protect ourselves while also preventing unnecessary spam towards primary nameservers?.</p></li></ol>
    <div>
      <h3>Issue 1:</h3>
      <a href="#issue-1">
        
      </a>
    </div>
    <p>As was previously mentioned, one form of protection in the Secondary DNS protocol is to only allow certain IPs to initiate zone transfers. There is a fine line between primary servers allow listing too many IPs and them having to frequently update their IP ACLs. We considered several solutions:</p><ol><li><p><a href="https://github.com/nirmata/kube-static-egress-ip">Open source k8s controllers</a></p></li><li><p>Altering <a href="https://en.wikipedia.org/wiki/Network_address_translation">Network Address Translation(NAT)</a> entries</p></li><li><p>Do not use k8s for zone transfers</p></li><li><p>Allowlist all Cloudflare IPs and dynamically update</p></li><li><p>Proxy egress traffic</p></li></ol><p>Ultimately we decided to proxy our egress traffic from k8s, to the DNS primary servers, using static proxy addresses. <a href="https://github.com/shadowsocks/shadowsocks-libev">Shadowsocks-libev</a> was chosen as the <a href="https://en.wikipedia.org/wiki/SOCKS">SOCKS5</a> implementation because it is fast, secure and known to scale. In addition, it can handle both UDP/TCP and IPv4/IPv6.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2JcJoxa44FoxHtMrWBXCP5/62805a1a8091ba1597f33614bece420b/image1-8.png" />
            
            </figure><p>Figure 2: Shadowsocks proxy Setup</p><p>The partnership of k8s and Shadowsocks combined with a large enough IP range brings many benefits:</p><ol><li><p>Horizontal scaling</p></li><li><p>Efficient load balancing</p></li><li><p>Primary server ACLs only need to be updated once</p></li><li><p>It allows us to make use of kubernetes for both the Zone Transferer and the Local ShadowSocks Proxy.</p></li><li><p>Shadowsocks proxy can be reused by many different Cloudflare services.</p></li></ol>
    <div>
      <h3>Issue 2:</h3>
      <a href="#issue-2">
        
      </a>
    </div>
    <p>The Notify Listener requires listening on static IPs for NOTIFY Messages coming from primary DNS servers. This is mostly a solved problem through the use of <a href="https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer">k8s services of type loadbalancer</a>, however exposing this service directly to the Internet makes us uneasy because of its susceptibility to <a href="https://www.cloudflare.com/learning/ddos/dns-flood-ddos-attack/">attacks</a>. Fortunately <a href="https://www.cloudflare.com/ddos/">DDoS protection</a> is one of Cloudflare's strengths, which lead us to the likely solution of <a href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food">dogfooding</a> one of our own products, <a href="https://www.cloudflare.com/products/cloudflare-spectrum/">Spectrum</a>.</p><p>Spectrum provides the following features to our service:</p><ol><li><p>Reverse proxy TCP/UDP traffic</p></li><li><p>Filter out Malicious traffic</p></li><li><p>Optimal routing from edge to core data centers</p></li><li><p><a href="https://www.cisco.com/c/dam/en_us/solutions/industries/docs/gov/IPV6at_a_glance_c45-625859.pdf">Dual Stack</a> technology</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1S3Ha1uEWxV2bbrzSJY9y2/84a6c819cf54b3ade09d9e36ecad8ac2/image5-3.png" />
            
            </figure><p>Figure 3: Spectrum interaction with Notify Listener</p><p>Figure 3 shows two interesting attributes of the system:</p><ol><li><p><b>Spectrum &lt;-&gt; k8s IPv4 only:</b></p></li><li><p>This is because our custom k8s load balancer currently only supports IPv4; however, Spectrum has no issue terminating the IPv6 connection and establishing a new IPv4 connection.</p></li><li><p><b>Spectrum &lt;-&gt; k8s routing decisions based of L4 protocol</b>:</p></li><li><p>This is because k8s only supports one of TCP/UDP/SCTP per service of type load balancer. Once again, spectrum has no issues proxying this correctly.</p></li></ol><p>One of the problems with using a L4 proxy in between services is that source IP addresses get changed to the source IP address of the proxy (Spectrum in this case). Not knowing the source IP address means we have no idea who sent the NOTIFY message, opening us up to attack vectors. Fortunately, Spectrum’s <a href="https://developers.cloudflare.com/spectrum/getting-started/proxy-protocol/">proxy protocol</a> feature is capable of adding custom headers to TCP/UDP packets which contain source IP/Port information.</p><p>As we are using <a href="https://github.com/miekg/dns">miekg/dns</a> for our Notify Listener, adding proxy headers to the DNS NOTIFY messages would cause failures in validation at the DNS server level. Alternatively, we were able to implement custom <a href="https://github.com/miekg/dns/blob/master/server.go#L156-L162">read and write decorators</a> that do the following:</p><ol><li><p><b>Reader:</b> Extract source address information on inbound NOTIFY messages. Place extracted information into new DNS records located in the additional section of the message.</p></li><li><p><b>Writer:</b> Remove additional records from the DNS message on outbound NOTIFY replies. Generate a new reply using proxy protocol headers.</p></li></ol><p>There is no way to spoof these records, because the server only permits two extra records, one of which is the optional TSIG. Any other records will be overwritten.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5pg2MCnr4MUQapXAtQRLeK/365f1bdaec1c60c88ae408b5c0dea128/image4-6.png" />
            
            </figure><p>Figure 4: Proxying Records Between Notifier and Spectrum‌‌</p><p>This custom decorator approach abstracts the proxying away from the Notify Listener through the use of the DNS protocol.  </p><p>Although knowing the source IP will block a significant amount of bad traffic, since NOTIFY messages can use both UDP and TCP, it is prone to <a href="/the-root-cause-of-large-ddos-ip-spoofing/">IP spoofing</a>. To ensure that the primary servers do not get spammed, we have made the following additions to the Zone Transferer:</p><ol><li><p>Always ensure that the SOA has actually been updated before initiating a zone transfer.</p></li><li><p>Only allow at most one working transfer and one scheduled transfer per zone.</p></li></ol>
    <div>
      <h2>Additional Technical Challenges</h2>
      <a href="#additional-technical-challenges">
        
      </a>
    </div>
    
    <div>
      <h3>Zone Transferer Scheduling</h3>
      <a href="#zone-transferer-scheduling">
        
      </a>
    </div>
    <p>As shown in figure 1, there are several ways of sending Kafka messages to the Zone Transferer in order to initiate a zone transfer. There is no benefit in having a large backlog of zone transfers for the same zone. Once a zone has been transferred, assuming no more changes, it does not need to be transferred again. This means that we should only have at most one transfer ongoing, and one scheduled transfer at the same time, for any zone.</p><p>If we want to limit our number of scheduled messages to one per zone, this involves ignoring Kafka messages that get sent to the Zone Transferer. This is not as simple as ignoring specific messages in any random order. One of the benefits of Kafka is that it holds on to messages until the user actually decides to acknowledge them, by committing that messages offset. Since Kafka is just a queue of messages, it has no concept of order other than first in first out (FIFO). If a user is capable of reading from the Kafka topic concurrently, it is entirely possible that a message in the middle of the queue be committed before a message at the end of the queue.</p><p>Most of the time this isn’t an issue, because we know that one of the concurrent readers has read the message from the end of the queue and is processing it. There is one Kubernetes-related catch to this issue, though: pods are ephemeral. The kube master doesn’t care what your concurrent reader is doing, it will kill the pod and it’s up to your application to handle it.</p><p>Consider the following problem:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2LbPUGXSdf0uzNeI68U06Q/13295276ff697029d06c58b2e4a9df81/image2-2.png" />
            
            </figure><p>Figure 5: Kafka Partition‌‌</p><ol><li><p>Read offset 1. Start transferring zone 1.</p></li><li><p>Read offset 2. Start transferring zone 2.</p></li><li><p>Zone 2 transfer finishes. Commit offset 2, essentially also marking offset 1.</p></li><li><p>Restart pod.</p></li><li><p>Read offset 3 Start transferring zone 3.</p></li></ol><p>If these events happen, zone 1 will never be transferred. It is important that zones stay up to date with the primary servers, otherwise stale data will be served from the Secondary DNS server. The solution to this problem involves the use of a list to track which messages have been read and completely processed. In this case, when a zone transfer has finished, it does not necessarily mean that the kafka message should be immediately committed. The solution is as follows:</p><ol><li><p>Keep a list of Kafka messages, sorted based on offset.</p></li><li><p>If finished transfer, remove from list:</p></li><li><p>If the message is the oldest in the list, commit the messages offset.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1g6Ku6Is2fixeHyiTIoEwC/0bb4026298db401018aefc89b027a717/image9-2.png" />
            
            </figure><p>Figure 6: Kafka Algorithm to Solve Message Loss</p><p>This solution is essentially soft committing Kafka messages, until we can confidently say that all other messages have been acknowledged. It’s important to note that this only truly works in a distributed manner if the Kafka messages are keyed by zone id, this will ensure the same zone will always be processed by the same Kafka consumer.</p>
    <div>
      <h2>Life of a Secondary DNS Request</h2>
      <a href="#life-of-a-secondary-dns-request">
        
      </a>
    </div>
    <p>Although Cloudflare has a <a href="https://www.cloudflare.com/network/">large global network</a>, as shown above, the zone transferring process does not take place at each of the edge datacenter locations (which would surely overwhelm many primary servers), but rather in our core data centers. In this case, how do we propagate to our edge in seconds? After transferring the zone, there are a couple more steps that need to be taken before the change can be seen at the edge.</p><ol><li><p>Zone Builder - This interacts with the Zone Transferer to build the zone according to what Cloudflare edge understands. This then writes to <a href="/introducing-quicksilver-configuration-distribution-at-internet-scale/">Quicksilver</a>, our super fast, distributed KV store.</p></li><li><p>Authoritative Server - This reads from Quicksilver and serves the built zone.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3INig0h7w1oSks6FwirZls/c99581c1c0019f5fc5dd2a80884dc943/image3-6.png" />
            
            </figure><p>Figure 7: End to End Secondary DNS‌‌</p>
    <div>
      <h2>What About Performance?</h2>
      <a href="#what-about-performance">
        
      </a>
    </div>
    <p>At the time of writing this post, according to <a href="http://dnsperf.com">dnsperf.com</a>, Cloudflare leads in global performance for both <a href="https://www.dnsperf.com/">Authoritative</a> and <a href="https://www.dnsperf.com/#!dns-resolvers">Resolver</a> DNS. Here, Secondary DNS falls under the authoritative DNS category here. Let’s break down the performance of each of the different parts of the Secondary DNS pipeline, from the primary server updating its records, to them being present at the Cloudflare edge.</p><ol><li><p>Primary Server to Notify Listener - Our most accurate measurement is only precise to the second, but we know UDP/TCP communication is likely much faster than that.</p></li><li><p>NOTIFY to Zone Transferer - This is negligible</p></li><li><p>Zone Transferer to Primary Server - 99% of the time we see ~800ms as the average latency for a zone transfer.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/70uQkuuFtfY5UPVrza1ZpK/ccfa5d2a7c522369d34c8b0671056167/image7-2.png" />
            
            </figure><p>Figure 8: Zone XFR latency</p><p>4. Zone Transferer to Zone Builder - 99% of the time we see ~10ms to build a zone.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3cfNN2ilrDOES8QwG5ok4I/ab8e3bafc66ec05e55530d997f628a68/image11-1.png" />
            
            </figure><p>Figure 9: Zone Build time</p><p>5. Zone Builder to Quicksilver edge: 95% of the time we see less than 1s propagation.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1imVuigo0GB7gLug2ISqL7/2ad64a24445b9aa75094d78437710d16/image6-2.png" />
            
            </figure><p>Figure 10: Quicksilver propagation time</p><p>End to End latency: less than 5 seconds on average. Although we have several external probes running around the world to test propagation latencies, they lack precision due to their sleep intervals, location, provider and number of zones that need to run. The actual propagation latency is likely much lower than what is shown in figure 10. Each of the different colored dots is a separate data center location around the world.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5BjcchQWzBqyYbX6ksIYMj/0f167f037efdeaf5cece6a054b3095ab/image10.png" />
            
            </figure><p>Figure 11: End to End Latency</p><p>An additional test was performed manually to get a real world estimate, the test had the following attributes:</p><p>Primary server: NS1Number of records changed: 1Start test timer event: Change record on NS1Stop test timer event: Observe record change at Cloudflare edge using digRecorded timer value: 6 seconds</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>Cloudflare serves 15.8 trillion DNS queries per month, operating within 100ms of 99% of the Internet-connected population. The goal of Cloudflare operated Secondary DNS is to allow our customers with custom DNS solutions, be it on-premise or some other DNS provider, to be able to take advantage of Cloudflare's DNS performance and more recently, through <a href="/orange-clouding-with-secondary-dns/">Secondary Override</a>, our proxying and security capabilities too. Secondary DNS is currently available on the Enterprise plan, if you’d like to take advantage of it, please let your account team know. For additional documentation on Secondary DNS, please refer to our <a href="https://support.cloudflare.com/hc/en-us/articles/360001356152-How-do-I-setup-and-manage-Secondary-DNS-">support article</a>.</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[Growth]]></category>
            <category><![CDATA[Kubernetes]]></category>
            <category><![CDATA[Speed & Reliability]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">2W2no7YfwWXkJXtgYtK4CU</guid>
            <dc:creator>Alex Fattouche</dc:creator>
        </item>
        <item>
            <title><![CDATA[Orange Clouding with Secondary DNS]]></title>
            <link>https://blog.cloudflare.com/orange-clouding-with-secondary-dns/</link>
            <pubDate>Thu, 20 Aug 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ Secondary DNS Override is a great option for any users that want to take advantage of the Cloudflare network, without transferring all of their zones to Cloudflare DNS as a primary provider. ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h3>What is secondary DNS?</h3>
      <a href="#what-is-secondary-dns">
        
      </a>
    </div>
    <p>In a traditional sense, secondary DNS servers act as a backup to the primary authoritative DNS server.  When a change is made to the records on the primary server, a zone transfer occurs, synchronizing the secondary DNS servers with the primary server. The secondary servers can then serve the records as if they were the primary server, however changes can only be made by the primary server, not the secondary servers. This creates redundancy across many different servers that can be distributed as necessary.</p><p>There are many common ways to take advantage of Secondary DNS, some of which are:</p><ol><li><p>Secondary DNS as passive backup - The secondary DNS server sits idle until the primary server goes down, at which point a failover can occur and the secondary can start serving records.</p></li><li><p>Secondary DNS as active backup - The secondary DNS server works alongside the primary server to serve records.</p></li><li><p>Secondary DNS with a hidden primary - The nameserver records at the <a href="https://www.cloudflare.com/learning/dns/glossary/what-is-a-domain-name-registrar/">registrar</a> point towards the secondary servers only, essentially treating them as the primary nameservers.</p></li></ol>
    <div>
      <h3>What is secondary DNS Override?</h3>
      <a href="#what-is-secondary-dns-override">
        
      </a>
    </div>
    <p>Secondary DNS Override builds on the Secondary DNS with a hidden primary model by allowing our customers to not only serve records as they tell us to, but also enable them to proxy any A/AAAA/CNAME records through <a href="https://www.cloudflare.com/network/">Cloudflare's network</a>. This is similar to how Cloudflare as a primary DNS provider currently works.</p><p>Consider the following example:</p><p>example.com Cloudflare IP - 192.0.2.0example.com origin IP - 203.0.113.0</p><p>In order to take advantage of Cloudflare's security and performance services, we need to make sure that the origin IP stays hidden from the Internet.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2YAO6PZ3BqRCD5IK5NEL1t/fc4d45f62075d3d0531734273c707e20/image1-10.png" />
            
            </figure><p>Figure 1: Secondary DNS without a hidden primary nameserver</p><p>Figure 1 shows that without a hidden primary nameserver, the resolver can choose to query either one. This opens up two issues:</p><ol><li><p>Violates <a href="https://tools.ietf.org/html/rfc1034">RFC 1034</a> and <a href="https://tools.ietf.org/html/rfc2182">RFC 2182</a> because the Cloudflare server will be responding differently than the primary nameserver.</p></li><li><p>The origin IP will be exposed to the Internet.</p></li></ol>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3WlzPk7YGcEbVgVPWRsVNP/b36c9763423f18a1559922a772a8e5ce/image2-6.png" />
            
            </figure><p>Figure 2: Secondary DNS with a hidden primary nameserver</p><p>Figure 2 shows the resolver always querying the Cloudflare Secondary DNS server.</p>
    <div>
      <h3>How Does Secondary DNS Override work</h3>
      <a href="#how-does-secondary-dns-override-work">
        
      </a>
    </div>
    <p>The Secondary DNS Override UI looks similar to the primary UI, the only difference is that records cannot be edited.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/23aOCWhXUNvk3rlDKomIUH/134a8410ac677f4c9341bd4ca757225f/image3-5.png" />
            
            </figure><p>Figure 3: Secondary DNS Override Dashboard</p><p>In figure 3, all of the records have been transferred from the primary DNS server. test-orange and test-aaaa-orange have been overridden to proxy through the cloudflare network, while test-grey and test-mx are treated as regular DNS records.</p><p>Behind the scenes we store override records that pair with transferred records based on the name. For secondary override we don’t care about the type when overriding records, because of two things:</p><ol><li><p>According to <a href="https://tools.ietf.org/html/rfc1912#section-2.4">RFC 1912</a> you cannot have a CNAME record with the same name as any other record. (This does not apply to some DNSSEC records, see <a href="https://tools.ietf.org/html/rfc2181">RFC 2181</a>)</p></li><li><p>A and AAAA records are both address type records which should be either all proxied or all not proxied under the same name.</p></li></ol><p>This means if you have several A and several AAAA records all with the name “example.com”, if one of them is proxied, all of them will be proxied. The UI helps abstract the idea that we are storing additional override records through the “orange cloud” button, which when clicked, will create an override record which applies to all A/AAAA or CNAME records with that name.</p>
    <div>
      <h3>CNAME at the Apex</h3>
      <a href="#cname-at-the-apex">
        
      </a>
    </div>
    <p>Normally, putting a CNAME at the apex of a zone is not allowed. For example:</p><p><code>example.com CNAME other-domain.com</code></p><p>Is not allowed because this means that there will be at least one other SOA record and one other NS record with the same name, disobeying RFC 1912 as mentioned above. Cloudflare can overcome this through the use of <a href="https://support.cloudflare.com/hc/en-us/articles/200169056-CNAME-Flattening-RFC-compliant-support-for-CNAME-at-the-root">CNAME Flattening</a>, which is a common technique used within the primary DNS product today. CNAME flattening allows us to return address records instead of the CNAME record when a query comes into our authoritative server.</p><p>Contrary to what was said above regarding the prevention of editing records through the Secondary DNS Override UI, the CNAME at the apex is the one exception to this rule. Users are able to create a CNAME at the apex in addition to the regular secondary DNS records, however the same rules defined in RFC 1912 also apply here. What this means is that the CNAME at the apex record can be treated as a regular DNS record or a proxied record, depending on what the user decides. Regardless of the proxy status of the CNAME at the apex record, it will override any other A/AAAA records that have been transferred from the primary DNS server.</p>
    <div>
      <h3>Merging Secondary, Override and CNAME at Apex Records</h3>
      <a href="#merging-secondary-override-and-cname-at-apex-records">
        
      </a>
    </div>
    <p>At record edit time we do all of the merging of the secondary, override and CNAME at the apex records. This means that when a DNS request comes in to our authoritative server at the edge, we can still return the records in <a href="https://www.dnsperf.com/">blazing fast times</a>. The workflow is shown in figure 4.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/28Q1ApTislcuCz1JZlE57M/ce770c426fe865fbed639a06440d8412/image4-6.png" />
            
            </figure><p>Figure 4: Record Merging process</p><p>Within the zone builder the steps are as follows:</p><ol><li><p>Check if there is any CNAME at the apex, if so, override all other A/AAAA secondary records at the apex.</p></li><li><p>For each secondary record, check if there is a matching override record, if so, apply the proxy status of the override record to all secondary records with that name.</p></li><li><p>Leave all other secondary records as is.</p></li></ol>
    <div>
      <h3>Getting Started</h3>
      <a href="#getting-started">
        
      </a>
    </div>
    <p>Secondary DNS Override is a great option for any users that want to take advantage of the Cloudflare network, without transferring all of their zones to Cloudflare DNS as a primary provider. Security and access control can be managed on the primary side, without worrying about unauthorized edits of information on the Cloudflare side.</p><p>Secondary DNS Override is currently available on the Enterprise plan, if you’d like to take advantage of it, please let your account team know. For additional documentation on Secondary DNS Override, please refer to our <a href="https://support.cloudflare.com/hc/en-us/articles/360042169091-Understanding-Secondary-DNS-Override#:~:text=Secondary%20Override%20allows%20customers%20to,record%20at%20the%20root%20domain.">support article</a>.</p> ]]></content:encoded>
            <category><![CDATA[DNS]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Proxying]]></category>
            <guid isPermaLink="false">3RBRtub1POeiDTrcMdBcCN</guid>
            <dc:creator>Alex Fattouche</dc:creator>
        </item>
    </channel>
</rss>