<?xml version="1.0" encoding="UTF-8"?>
<rss 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/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ availability - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ availability - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 06 Jun 2026 20:03:45 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/availability/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ High Availability vs Fault Tolerance vs Disaster Recovery – Explained with an Analogy ]]>
                </title>
                <description>
                    <![CDATA[ High availability, fault tolerance and disaster recovery are important things to consider when designing a system. These terms are sometimes used interchangeably by architects and developers. They are not, however, the same thing – and understanding ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/high-availability-fault-tolerance-and-disaster-recovery-explained/</link>
                <guid isPermaLink="false">66d45e10f855545810e9342b</guid>
                
                    <category>
                        <![CDATA[ availability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Disaster recovery ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Architecture ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Mon, 07 Nov 2022 17:10:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/11/Slide6.JPG" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>High availability, fault tolerance and disaster recovery are important things to consider when designing a system.</p>
<p>These terms are sometimes used interchangeably by architects and developers. They are not, however, the same thing – and understanding the differences can save you many headaches, as well as time and money.</p>
<p>This article will go through the differences between the three terms and explain how you can implement them in AWS.</p>
<h2 id="heading-highly-available-vs-fault-tolerant-vs-disaster-recovery">Highly Available vs Fault Tolerant vs Disaster Recovery</h2>
<p>A highly available system is one that aims to be online as often as possible. While downtime can still occur in a highly available system, the aim of high availability is to limit the duration of the downtime, not to completely eliminate it.</p>
<p>A fault tolerant system is one that can operate through a fault without any downtime. Fault tolerance aims to avoid downtime completely.</p>
<p>In a complete system failure however, high availability and fault tolerance are not enough. Disaster recovery describes how the system can continue to operate when the cushion of high availability and fault tolerance disappears in a system wide failure.</p>
<h2 id="heading-what-does-high-availability-mean">What Does High Availability Mean?</h2>
<p>First, let's describe what high availability is not. High availability does not mean that the system never fails or never experiences downtime. A highly available system is simply one that aims to be online as often as possible.</p>
<p>Imagine we have a pizza restaurant that is open 24 hours every day for 365 days. If that restaurant only has one chef, then its availability – that is, its ability to process orders – will not be 100%. This is because a single chef can only work for about eight hours a day with a one hour break – or effectively seven hours a day, for seven days in a week.</p>
<p>The chef can therefore only work for 49 hours in a week out of a possible 168 hours. This restaurant has an availability of 29%.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Ff3e3d174-070d-447d-9088-92c6f7377c99_1504x861.jpeg" alt="Image" width="1456" height="834" loading="lazy"></p>
<p><em>A low availability restaurant</em></p>
<p>This is of course not a high enough availability for a restaurant that wants to be open for 24 hours in a day throughout the year.</p>
<p>So how do we get a higher availability for the restaurant? Hire more chefs. If we have four chefs working six hour shifts in a day for seven days in a week, this gives us a theoretical availability of 100%.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F8b1538ad-5148-44f1-b025-c6705a5c3780_1504x830.jpeg" alt="Image" width="1456" height="804" loading="lazy"></p>
<p><em>A higher availability restaurant</em></p>
<p>This 100% availability is only theoretical because it assumes no chef misses work in an entire year. This is a poor assumption as chefs can get sick, their cars can break down on the way to work, or they may have to leave work early to pick up their kids.</p>
<p>Let's say all this chef downtime adds up to five hours in a year. This gives you an availability of 99.94%.</p>
<p>How can you make the restaurant even more available? Hire standby chefs that are ready to come to the restaurant at a moment's notice. But this comes at a steep price since you have to pay these chefs to wait until they are needed.</p>
<p>What these standby chefs give you is <strong>the ability to quickly recover from not having enough chefs to meet customer orders</strong>. You can never have 100% availability because of the constraints of reality. You can only approach an availability of 100% at an increasingly steep price.</p>
<h3 id="heading-what-is-availability-in-a-system">What is Availability in a System?</h3>
<p>Availability is the probability that a system will be able to respond to a request.</p>
<p>Note that high availability has nothing to say about the quality of the pizzas or how quickly they are delivered. High availability is simply concerned with the ability of the restaurant to respond to pizza orders from customers.</p>
<p>The major cloud providers typically have <a target="_blank" href="https://www.cio.com/article/274740/outsourcing-sla-definitions-and-solutions.html">SLAs</a> that describe the availability of a system.</p>
<p>Take a blob storage system, for example. AWS S3 standard has an availability SLA of 99.99%. This is the same figure for Azure blob storage and Google cloud storage.</p>
<p>What exactly does 99.99% availability mean? It means that in any year, there is a 99.99% probability that the system will be online. An uptime of 99.99% equals a downtime of 0.01%. This is equivalent to a downtime of approximately 53 minutes - just under an hour for an entire year.</p>
<p>How about an availability of 99.9%? Such a system would have a downtime of 0.1% which is 8.8 hours in a year.</p>
<p>While 99.9% availability may seem high, for a bank processing payments, air traffic control system, or any other critical system, such amount of downtime may simply be unacceptable.</p>
<p>What is the right amount of availability you should target? That depends on the requirements of the system you are building.</p>
<p>You are of course constrained by the availability SLAs of the cloud providers, so there is limited flexibility in achieving say 99.999% availability for a blob storage system, for example. And, the higher the availability you want to achieve, the more expensive and complex the solution becomes.</p>
<h2 id="heading-what-does-fault-tolerance-mean">What Does Fault Tolerance Mean?</h2>
<p>If a failure within a system occurs, can the system continue to operate without any disruption? If it can, then the system is fault tolerant.</p>
<p>So what is the difference between high availability and fault tolerance? With a highly available system, failures that cause downtime will occur, but rarely. The system is also able to recover from such failures. <strong>But when the system is down, it cannot respond to requests.</strong></p>
<p>In a fault tolerant system, the system <strong>can continue to operate in spite of a failure.</strong></p>
<p>Let's use the pizza restaurant as an example again. If the restaurant experiences a power outage, then no amount of chefs in the kitchen or chefs on standby will help with making pizzas for customers since the ovens need a power supply.</p>
<p>A backup generator that kicks in immediately when a power loss is experienced makes the restaurant fault tolerant.</p>
<p>Another good example of this is a commercial aircraft powered by jet engines. These aircraft are built to be fault tolerant so in the event that one engine fails, the aircraft can continue to fly and land without disruption or having to fix the failed engine in flight.</p>
<p>Helicopters or single engine aircraft, on the other hand, are not fault tolerant. A failure of the engine means the aircraft cannot fly. Such failures are usually catastrophic and partly explain the higher rate of helicopter and single engine aircraft crashes compared to dual engine aircraft.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfafca0a-1940-4215-ac2b-39a7a84b9660_1504x861.jpeg" alt="Image" width="1456" height="834" loading="lazy"></p>
<p><em>An aircraft with two engines is fault tolerant</em></p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F964241a9-8fc6-4d65-86c2-48f509a5951a_1504x861.jpeg" alt="Image" width="1456" height="834" loading="lazy"></p>
<p><em>Helicopters and single engine aircraft are not fault tolerant</em></p>
<h2 id="heading-what-does-disaster-recovery-mean">What Does Disaster Recovery Mean?</h2>
<p>If the scale of the system failure is so large that the high availability and fault tolerance of the system are effectively neutralised, can the system continue to operate?</p>
<p>Let's go back to the restaurant example. If a fire, flood, or any other disaster befalls your pizza restaurant, how can you continue to make pizzas for your customers?</p>
<p>This is a somewhat facetious example since in the event of a fire, worrying about customer orders is not the main priority – but the logic of the example still holds.</p>
<p>In this instance, high availability is of no help. Having an infinite number of chefs in the kitchen or on standby in a restaurant engulfed in flames = no pizzas for customers.</p>
<p>Fault tolerance is also of no help. A backup generator is useless for the appliances it is meant to power if they have been destroyed.</p>
<p>The only way the system (restaurant) can continue to operate is by routing orders to another nearby restaurant unaffected by the fire. Disaster recovery is a proactive plan of action that details how to recover <strong>after a disaster has happened</strong>.</p>
<h2 id="heading-bringing-it-all-together">Bringing it All Together</h2>
<p>Now, let's look at a single architecture that is simultaneously highly available, fault tolerant, and has built-in disaster recovery.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F3995b811-f03f-44b5-8118-8f7965e7f09a_893x560.jpeg" alt="Image" width="893" height="560" loading="lazy"></p>
<p><em>All in one - high availability, fault tolerance, and disaster recovery in a single architecture</em></p>
<p>The architecture above shows a multi-availability zone (AZ) Relational Database Service (Amazon RDS) deployment. It shows an RDS database with a standby instance in a separate AZ, a single read-only replica, and an S3 bucket used to store backups of the database on a daily basis.</p>
<p>This RDS is a fully managed DB as a service offering from AWS where AWS manages the underlying hardware, software, and application of the DB. You can find more information here on <a target="_blank" href="https://aws.amazon.com/rds/">AWS RDS</a> and <a target="_blank" href="https://aws.amazon.com/about-aws/global-infrastructure/regions_az/">availability zones</a>.</p>
<p>Now let's dissect how this system would work and how the design ensures it is highly available, fault tolerant and can recover from a disaster.</p>
<h3 id="heading-how-high-availability-is-achieved">How High Availability is Achieved</h3>
<p>The primary RDS instance in AZ A synchronously replicates its data to the standby instance in AZ B.</p>
<p>With synchronous replication, the primary instance waits until the standby has received the latest write operation before the transaction is recorded as successful. This ensures that both databases have identical information – that is, they are consistent, admittedly at the expense of increased transaction latency.</p>
<p>The primary and standby instances are in an active-passive configuration. Only the primary receives read and write request. The job of the standby is to simply take over as the primary in the event of a failure of the primary instance.</p>
<p>The time it takes to failover from the primary to the standby instance is called the Recovery Time Objective (RTO). The RTO simply describes how long it takes to recover from a failure. In this case, the failover time for RDS in a multi-AZ configuration is currently between 1-2 minutes.</p>
<p>The standby instance has one purpose: to increase the availability of the system. If the primary instance fails, or if the entire AZ A goes down, the standby instance in a separate AZ will be promoted to the primary. This failover process takes 1-2 minutes. That is 1-2 minutes of downtime.</p>
<p>Recall that high availability is not about preventing downtime, but simply reducing it. Without a standby instance, there is a high probability that downtime will exceed the 1-2 mins it takes to recover with a standby instance.</p>
<p>Note that the standby instance does not help with fault tolerance, since the failure of the primary will still lead to downtime.</p>
<h3 id="heading-how-fault-tolerance-is-achieved">How Fault Tolerance is Achieved</h3>
<p>To eliminate downtime, you need a configuration that involves no failover. This is a job for read-only replicas. These are asynchronously replicated copies of the primary instance. Writes are only made to the primary instance. Read replicas are, as the name implies, read only.</p>
<p>Such an approach is ideal for read heavy application since read replicas can remove the additional burden of read requests from the primary instance.</p>
<p>In asynchronous replication, writes to a primary instance do not wait for a response from the read-only replica before the transaction is recorded as a success. This means that, for a time, data across the primary and read replica may not be identical (but rather, inconsistent) after a write to the primary.</p>
<p>This eventual consistency (a topic for another article) is a drawback of asynchronous replication. The benefit of asynchronous replication is that it does not wait for the read replica to respond before the transaction is recorded as a success.</p>
<p>This is important because if the read replica is down or there is a network failure, the primary can still accept subsequent writes without waiting for a response from the read replica, confirming that the previous write was successfully replicated.</p>
<p>The architecture above has two replicas: one synchronous and the other asynchronous. If all replicas are synchronous, then a failure in the standby replica or the read only replica, or even a network failure, brings the entire cluster down. This is a fragile design that exposes the entire system to failure if a single component fails. Having some replicas that are synchronous and others that are asynchronous improves the fault tolerance of the system.</p>
<p>Where else does fault tolerance come in? Like an aircraft with two jet engines that provide thrust, a read replica and a primary can work together simultaneously. The the primary instance processes writes and the read replica responds to read requests.</p>
<p>Failure of the primary instance has no effect on the read replica's ability to respond to read requests. There is no downtime for reads since only the read replica responds to reads.</p>
<p>How about writes? The read replica can be promoted to a primary, although with RDS, this is currently a manual process.</p>
<h3 id="heading-how-disaster-recovery-is-achieved">How Disaster Recovery is Achieved</h3>
<p>With the architecture above, you can handle disaster recovery in two ways. There is no constraint to limit disaster recovery to only one approach, so you can use both at the same time. And in fact, the more approaches you have, the better, since this provides extra redundancy.</p>
<p>Ultimately, you should weigh all this against cost, as implementing disaster recovery strategies can be expensive.</p>
<p>The first method is through automatic backups. Backups are taken from the standby instance, preventing performance degradation of the primary instance that has to serve writes (and reads if not configured with a read replica). Since there is synchronous replication between the primary and the standby, we have a guarantee that the standby is an up to date copy of the primary, so it's ideal to take backups from.</p>
<p>With RDS, backups are taken on a fixed schedule once a day (specified by you) and stored in an S3 bucket. Since this is an entirely separate component, any RDS-related system-wide failures will not affect the durability of the backups.</p>
<p>With backups, a loss of the primary, standby, and read-replicas does not equal a permanent loss of data. Backups can then be used to restore the database to a new DB instance.</p>
<p>The second method is to promote the read-only replica to a standalone instance if the primary instance fails. The read replica can be configured in another <a target="_blank" href="https://aws.amazon.com/about-aws/global-infrastructure/regions_az/">AWS region</a>. This way, if there is a disaster on a regional scale where multiple AZs are down, a cross regional read replica will ensure that another instance is available in a different AWS region to serve read and write requests.</p>
<p>This is analogous to diverting orders to another restaurant in the event of a fire.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb353885-8d22-4542-8155-2f92e3d49314_943x635.jpeg" alt="Image" width="943" height="635" loading="lazy"></p>
<p><em>How different components improve the availability, fault tolerance and disaster recovery of a solution</em></p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Availability is measured in percentages - the larger the number, the more available the system is (hence less downtime).</p>
<p>Very few systems aim for 100% availability – although pacemakers are a notable exception. An availability of 99.999% has a downtime of 0.001% = 5 minutes of downtime in a year. This tends to be the upper limit for most software systems.</p>
<p>Aiming for higher levels of availability above this is increasingly complicated, expensive, and often unnecessary. This is especially true when you consider that the software system you are building relies on infrastructure like the power grid and internet service providers, which may have lower availability levels.</p>
<p>Fault tolerance, on other hand, cannot be measured. Your design is either fault tolerant or it is not. Similarly, disaster recovery cannot be measured. You either have a plan of action that precisely outlines how your system can recover from a disaster or you do not.</p>
<p>Knowing the difference between high availability, fault tolerance, and disaster recovery is important. It ensures you are building the correct architecture based on customer needs.</p>
<p>Over-engineering a solution by providing disaster recovery when all that is required is high availability or fault tolerance is often an expensive and complex exercise.</p>
<p>On the other hand, under-engineering a solution by only providing high availability when fault tolerance is required can lead to severe consequences for some critical systems that cannot afford any downtime.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to migrate from Elasticsearch 1.7 to 6.8 with zero downtime ]]>
                </title>
                <description>
                    <![CDATA[ By dor sever My last task at BigPanda was to upgrade an existing service that was using Elasticsearch version 1.7 to a newer Elasticsearch version, 6.8.1. In this post, I will share how we migrated from Elasticsearch 1.6 to 6.8 with harsh constraints... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-migrate-from-elasticsearch-1-7-to-6-8-with-zero-downtime/</link>
                <guid isPermaLink="false">66d45e414a7504b7409c338a</guid>
                
                    <category>
                        <![CDATA[ availability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data migration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ elasticsearch ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 25 Dec 2019 09:45:32 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/12/es-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By dor sever</p>
<p>My last task at <a target="_blank" href="https://www.bigpanda.io">BigPanda</a> was to upgrade an existing service that was using Elasticsearch version 1.7 to a newer Elasticsearch version, 6.8.1.</p>
<p>In this post, I will share how we migrated from Elasticsearch 1.6 to 6.8 with harsh constraints like zero downtime, no data loss, and zero bugs. I'll also provide you with a script that does the migration for you.</p>
<p>This post contains 6 chapters (and one is optional):</p>
<ul>
<li>What’s in it for me? --&gt; What were the new features that led us to upgrade our version?</li>
<li>The constraints --&gt; What were our business requirements?</li>
<li>Problem solving --&gt; How did we address the constraints?</li>
<li>Moving forward --&gt; The plan.</li>
<li>[Optional chapter] --&gt; How did we handle the infamous mapping explosion problem?</li>
<li>Finally --&gt; How to do data migration between clusters.</li>
</ul>
<h1 id="heading-chapter-1-whats-in-it-for-me">Chapter 1 — What’s in it for me?</h1>
<p>What benefits were we expecting to solve by upgrading our data store?</p>
<p>There were a couple of reasons:</p>
<ol>
<li>Performance and stability issues — We were experiencing a huge number of outages with long MTTR that caused us a lot of headaches. This was reflected in frequent high latencies, high CPU usage, and more issues.</li>
<li>Non-existent support in old Elasticsearch versions — We were missing some operative knowledge in Elasticsearch, and when we searched for outside consulting we were encouraged to migrate forward to receive support.</li>
<li>Dynamic mappings in our schema — Our current schema in Elasticsearch 1.7 used a feature called dynamic mappings that made our cluster <a target="_blank" href="https://www.elastic.co/guide/en/elasticsearch/reference/6.1/mapping.html#mapping-limit-settings">explode</a> multiple times. So we wanted to address this issue.</li>
<li>Poor visibility on our existing cluster — We wanted a better view under the hood and saw that later versions had great metrics exporting tools.</li>
</ol>
<h1 id="heading-chapter-2-the-constraints">Chapter 2 — The constraints</h1>
<ul>
<li>ZERO downtime migration — We have active users on our system, and we could not afford for the system to be down while we were migrating.</li>
<li>Recovery plan — We could not afford to “lose” or “corrupt” data, no matter the cost. So we needed to prepare a recovery plan in case our migration failed.</li>
<li>Zero bugs — We could not change existing search functionality for end-users.</li>
</ul>
<h1 id="heading-chapter-3-problem-solving-and-thinking-of-a-plan">Chapter 3 — Problem solving and thinking of a plan</h1>
<p>Let’s tackle the constraints from the simplest to the most difficult:</p>
<h2 id="heading-zero-bugs">Zero bugs</h2>
<p>In order to address this requirement, I studied all the possible requests the service receives and what its outputs were. Then I added unit-tests where needed.</p>
<p>In addition, I added multiple metrics (to the <code>Elasticsearch Indexer</code> and the <code>new Elasticsearch Indexer</code> ) to track latency, throughput, and performance, which allowed me to validate that we only improved them.</p>
<h2 id="heading-recovery-plan">Recovery plan</h2>
<p>This means that I needed to address the following situation: I deployed the new code to production and stuff was not working as expected. What can I do about it then</p>
<p>Since I was working in a service that used <a target="_blank" href="https://www.youtube.com/watch?v=STKCRSUsyP0">event-sourcing,</a> I could add another listener (diagram attached below) and start writing to a new Elasticsearch cluster without affecting production status</p>
<h2 id="heading-zero-downtime-migration">Zero downtime migration</h2>
<p>The current service is in live mode and cannot be “deactivated” for periods longer than 5–10 minutes. The trick to getting this right is this:</p>
<ul>
<li>Store a log of all the actions your service is handling (we use Kafka in production)</li>
<li>Start the migration process offline (and keep track of the offset before you started the migration)</li>
<li>When the migration ends, start the new service against the log with the recorded offset and catch up the lag</li>
<li>When the lag finishes, change your frontend to query against the new service and you are done</li>
</ul>
<h1 id="heading-chapter-4-the-plan">Chapter 4 — The plan</h1>
<p>Our current service uses the following architecture (based on message passing in Kafka):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/12/indxr2.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<ol>
<li><code>Event topic</code> contains events produced by other applications (for example, <code>UserId 3 created</code>)</li>
<li><code>Command topic</code> contains the translation of these events into specific commands used by this application (for example: <code>Add userId 3</code>)</li>
<li>Elasticsearch 1.7 — The datastore of the <code>command Topic</code> read by the <code>Elasticsearch Indexer</code>.</li>
</ol>
<p>We planned to add another consumer (<code>new Elasticsearch Indexer</code>) to the <code>command topic</code>, which will read the same exact messages and write them in parallel to Elasticsearch 6.8.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/12/indxr.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<h1 id="heading-where-should-i-start">Where should I start?</h1>
<p>To be honest, I considered myself a newbie Elasticsearch user. To feel confident to perform this task, I had to think about the best way to approach this topic and learn it. A few things that helped were:</p>
<ol>
<li>Documentation — It’s an insanely useful resource for everything Elasticsearch. Take the time to read it and take notes (don’t miss: <a target="_blank" href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">Mapping</a> and <a target="_blank" href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html">QueryDsl</a>).</li>
<li>HTTP API — everything under <a target="_blank" href="https://www.elastic.co/guide/en/elasticsearch/reference/current/cat.html">CAT</a> API. This was super useful to debug things locally and see how Elasticsearch responds (don’t miss: cluster health, cat indices, search, delete index).</li>
<li>Metrics (❤️) — From the first day, we configured a shiny new dashboard with lots of cool metrics (taken from <a target="_blank" href="https://github.com/justwatchcom/elasticsearch_exporter"><em>elasticsearch-exporter-for-Prometheus</em></a>) that helped and pushed us to understand more about Elasticsearch.</li>
</ol>
<h1 id="heading-the-code">The code</h1>
<p>Our codebase was using a library called <a target="_blank" href="https://github.com/sksamuel/elastic4s">elastic4s</a> and was using the oldest release available in the library — a really good reason to migrate! So the first thing to do was just to migrate versions and see what broke.</p>
<p>There are a few tactics on how to do this code migration. The tactic we chose was to try and restore existing functionality first in the new Elasticsearch version without re-writing the all code from the start. In other words, to reach existing functionality but on a newer version of Elasticsearch.</p>
<p>Luckily for us, the code already contained almost full testing coverage so our task was much much simpler, and that took around 2 weeks of development time.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/12/you_need_some_tests_yo.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>It's important to note that, if that wasn't the case, we would have had to invest some time in filling that coverage up. Only then would we be able to migrate since one of our constraints was to not break existing functionality.</em></p>
<h1 id="heading-chapter-5-the-mapping-explosion-problem">Chapter 5 — The mapping explosion problem</h1>
<p>Let’s describe our use-case in more detail. This is our model:</p>
<p><code>class InsertMessageCommand(tags: Map[String,String])</code></p>
<p>And for example, an instance of this message would be:</p>
<p><code>new InsertMessageCommand(Map("name"-&gt;"dor","lastName"-&gt;"sever"))</code></p>
<p>And given this model, we needed to support the following query requirements:</p>
<ol>
<li>Query by value</li>
<li>Query by tag name and value</li>
</ol>
<p>The way this was modeled in our Elasticsearch 1.7 schema was using a dynamic template schema (since the tag keys are dynamic, and cannot be modeled in advanced).</p>
<p>The dynamic template caused us multiple outages due to the mapping explosion problem, and the schema looked like this:</p>
<pre><code class="lang-bash">curl -X PUT <span class="hljs-string">"localhost:9200/_template/my_template?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d <span class="hljs-string">'
{
    "index_patterns": [
        "your-index-names*"
    ],
    "mappings": {
            "_doc": {
                "dynamic_templates": [
                    {
                        "tags": {
                            "mapping": {
                                "type": "text"
                            },
                            "path_match": "actions.tags.*"
                        }
                    }
                ]
            }
        },
    "aliases": {}
}'</span>  

curl -X PUT <span class="hljs-string">"localhost:9200/your-index-names-1/_doc/1?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "actions": {
    "tags" : {
        "name": "John",
        "lname" : "Smith"
    }
  }
}
'</span>

curl -X PUT <span class="hljs-string">"localhost:9200/your-index-names-1/_doc/2?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "actions": {
    "tags" : {
        "name": "Dor",
        "lname" : "Sever"
  }
}
}
'</span>

curl -X PUT <span class="hljs-string">"localhost:9200/your-index-names-1/_doc/3?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "actions": {
    "tags" : {
        "name": "AnotherName",
        "lname" : "AnotherLastName"
  }
}
}
'</span>
</code></pre>
<pre><code class="lang-bash">
curl -X GET <span class="hljs-string">"localhost:9200/_search?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
    "query": {
        "match" : {
            "actions.tags.name" : {
                "query" : "John"
            }
        }
    }
}
'</span>
<span class="hljs-comment"># returns 1 match(doc 1)</span>


curl -X GET <span class="hljs-string">"localhost:9200/_search?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
    "query": {
        "match" : {
            "actions.tags.lname" : {
                "query" : "John"
            }
        }
    }
}
'</span>
<span class="hljs-comment"># returns zero matches</span>

<span class="hljs-comment"># search by value</span>
curl -X GET <span class="hljs-string">"localhost:9200/_search?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
    "query": {
        "query_string" : {
            "fields": ["actions.tags.*" ],
            "query" : "Dor"
        }
    }
}
'</span>
</code></pre>
<h2 id="heading-nested-documents-solution">Nested documents solution</h2>
<p>Our first instinct in solving the mapping explosion problem was to use nested documents.</p>
<p>We read the nested data type tutorial in the Elastic docs and defined the following schema and queries:</p>
<pre><code class="lang-bash">curl -X PUT <span class="hljs-string">"localhost:9200/my_index?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
        "mappings": {
            "_doc": {
            "properties": {
            "tags": {
                "type": "nested" 
                }                
            }
        }
        }
}
'</span>

curl -X PUT <span class="hljs-string">"localhost:9200/my_index/_doc/1?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "tags" : [
    {
      "key" : "John",
      "value" :  "Smith"
    },
    {
      "key" : "Alice",
      "value" :  "White"
    }
  ]
}
'</span>


<span class="hljs-comment"># Query by tag key and value</span>
curl -X GET <span class="hljs-string">"localhost:9200/my_index/_search?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "query": {
    "nested": {
      "path": "tags",
      "query": {
        "bool": {
          "must": [
            { "match": { "tags.key": "Alice" }},
            { "match": { "tags.value":  "White" }} 
          ]
        }
      }
    }
  }
}
'</span>

<span class="hljs-comment"># Returns 1 document</span>


curl -X GET <span class="hljs-string">"localhost:9200/my_index/_search?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "query": {
    "nested": {
      "path": "tags",
      "query": {
        "bool": {
          "must": [
            { "match": { "tags.value":  "Smith" }} 
          ]
        }
      }
    }
  }
}
'</span>

<span class="hljs-comment"># Query by tag value</span>
<span class="hljs-comment"># Returns 1 result</span>
</code></pre>
<p>And this solution worked. However, when we tried to insert real customer data we saw that the number of documents in our index increased by around 500 times.</p>
<p>We thought about the following problems and went on to find a better solution:</p>
<ol>
<li>The amount of documents we had in our cluster was around 500 million documents. This meant that, with the new schema, we were going to reach two hundred fifty billion documents (that’s 250,000,000,000 documents ?).</li>
<li>We read this really good blog post — <a target="_blank" href="https://blog.gojekengineering.com/elasticsearch-the-trouble-with-nested-documents-e97b33b46194">https://blog.gojekengineering.com/elasticsearch-the-trouble-with-nested-documents-e97b33b46194</a> which highlights that nested documents can cause high latency in queries and heap usage problems.</li>
<li>Testing — Since we were converting 1 document in the old cluster to an unknown number of documents in the new cluster, it would be much harder to track if the migration process worked without any data loss. If our conversion was 1:1, we could assert that the count in the old cluster equalled the count in the new cluster.</li>
</ol>
<h2 id="heading-avoiding-nested-documents">Avoiding nested documents</h2>
<p>The real trick in this was to focus on what supported queries we were running: search by tag value, and search by tag key and value.</p>
<p>The first query does not require nested documents since it works on a single field. For the latter, we did the following trick. We created a field that contains the combination of the key and the value. Whenever a user queries on a key, value match, we translate their request to the corresponding text and query against that field.</p>
<p>Example:</p>
<pre><code class="lang-bash">curl -X PUT <span class="hljs-string">"localhost:9200/my_index_2?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
    "mappings": {
        "_doc": {
            "properties": {
                "tags": {
                    "type": "object",
                    "properties": {
                        "keyToValue": {
                            "type": "keyword"
                        },
                        "value": {
                            "type": "keyword"
                        }
                    }
                }
            }
        }
    }
}
'</span>


curl -X PUT <span class="hljs-string">"localhost:9200/my_index_2/_doc/1?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "tags" : [
    {
      "keyToValue" : "John:Smith",
      "value" : "Smith"
    },
    {
      "keyToValue" : "Alice:White",
      "value" : "White"
    }
  ]
}
'</span>

<span class="hljs-comment"># Query by key,value</span>
<span class="hljs-comment"># User queries for key: Alice, and value : White , we then query elastic with this query:</span>

curl -X GET <span class="hljs-string">"localhost:9200/my_index_2/_search?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "query": {
        "bool": {
          "must": [ { "match": { "tags.keyToValue": "Alice:White" }}]
  }}}
'</span>

<span class="hljs-comment"># Query by value only</span>
curl -X GET <span class="hljs-string">"localhost:9200/my_index_2/_search?pretty"</span> -H <span class="hljs-string">'Content-Type: application/json'</span> -d<span class="hljs-string">'
{
  "query": {
        "bool": {
          "must": [ { "match": { "tags.value": "White" }}]
  }}}
'</span>
</code></pre>
<h1 id="heading-chapter-6-the-migration-process">Chapter 6 — The migration process</h1>
<p>We planned to migrate about 500 million documents with zero downtime. To do that we needed:</p>
<ol>
<li>A strategy on how to transfer data from the old Elastic to the new Elasticsearch</li>
<li>A strategy on how to close the lag between the start of the migration and the end of it</li>
</ol>
<p>And our two options in closing the lag:</p>
<ol>
<li>Our messaging system is Kafka based. We could have just taken the current offset before the migration started, and after the migration ended, start consuming from that specific offset. This solution requires some manual tweaking of offsets and some other stuff, but will work.</li>
<li>Another approach to solving this issue was to start consuming messages from the beginning of the topic in Kafka and make our actions on Elasticsearch idempotent — meaning, if the change was “applied” already, nothing would change in Elastic store.</li>
</ol>
<p>The requests made by our service against Elastic were already idempotent, so we choose option 2 because it required zero manual work (no need to take specific offsets, and then set them afterward in a new consumer group).</p>
<h2 id="heading-how-can-we-migrate-the-data">How can we migrate the data?</h2>
<p>These were the options we thought of:</p>
<ol>
<li>If our Kafka contained all messages from the beginning of time, we could just play from the start and the end state would be equal. But since we apply retention to out topics, this was not an option.</li>
<li>Dump messages to disk and then ingest them to Elastic directly – This solution looked kind of weird. Why store them in disk instead of just writing them directly to Elastic?</li>
<li>Transfer messages between old Elastic to new Elastic — This meant, writing some sort of “script” (did anyone say Python? ?) that will connect to the old Elasticsearch cluster, query for items, transform them to the new schema, and index them in the cluster.</li>
</ol>
<p>We choose the last option. These were the design choices we had in mind:</p>
<ol>
<li>Let’s not try to think about error handling unless we need to. Let’s try to write something super simple, and if errors occur, let’s try to address them. In the end, we did not need to address this issue since no errors occurred during the migration.</li>
<li>It’s a one-off operation, so whatever works first / KISS.</li>
<li>Metrics — Since the migration processes can take hours to days, we wanted the ability from day 1 to be able to monitor the error count and to track the current progress and copy rate of the script.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/12/python.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We thought long and hard and choose Python as our weapon of choice. The final version of the code is below:</p>
<pre><code class="lang-yml"><span class="hljs-string">dictor==0.1.2</span> <span class="hljs-bullet">-</span> <span class="hljs-string">to</span> <span class="hljs-string">copy</span> <span class="hljs-string">and</span> <span class="hljs-string">transform</span> <span class="hljs-string">our</span> <span class="hljs-string">Elasticsearch</span> <span class="hljs-string">documentselasticsearch==1.9.0</span> <span class="hljs-bullet">-</span> <span class="hljs-string">to</span> <span class="hljs-string">connect</span> <span class="hljs-string">to</span> <span class="hljs-string">"old"</span> <span class="hljs-string">Elasticsearchelasticsearch6==6.4.2</span> <span class="hljs-bullet">-</span> <span class="hljs-string">to</span> <span class="hljs-string">connect</span> <span class="hljs-string">to</span> <span class="hljs-string">the</span> <span class="hljs-string">"new"</span> <span class="hljs-string">Elasticsearchstatsd==3.3.0</span> <span class="hljs-bullet">-</span> <span class="hljs-string">to</span> <span class="hljs-string">report</span> <span class="hljs-string">metrics</span>
</code></pre>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> elasticsearch <span class="hljs-keyword">import</span> Elasticsearch
<span class="hljs-keyword">from</span> elasticsearch6 <span class="hljs-keyword">import</span> Elasticsearch <span class="hljs-keyword">as</span> Elasticsearch6
<span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">from</span> elasticsearch.helpers <span class="hljs-keyword">import</span> scan
<span class="hljs-keyword">from</span> elasticsearch6.helpers <span class="hljs-keyword">import</span> parallel_bulk
<span class="hljs-keyword">import</span> statsd

ES_SOURCE = Elasticsearch(sys.argv[<span class="hljs-number">1</span>])
ES_TARGET = Elasticsearch6(sys.argv[<span class="hljs-number">2</span>])
INDEX_SOURCE = sys.argv[<span class="hljs-number">3</span>]
INDEX_TARGET = sys.argv[<span class="hljs-number">4</span>]
QUERY_MATCH_ALL = {<span class="hljs-string">"query"</span>: {<span class="hljs-string">"match_all"</span>: {}}}
SCAN_SIZE = <span class="hljs-number">1000</span>
SCAN_REQUEST_TIMEOUT = <span class="hljs-string">'3m'</span>
REQUEST_TIMEOUT = <span class="hljs-number">180</span>
MAX_CHUNK_BYTES = <span class="hljs-number">15</span> * <span class="hljs-number">1024</span> * <span class="hljs-number">1024</span>
RAISE_ON_ERROR = <span class="hljs-literal">False</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform_item</span>(<span class="hljs-params">item, index_target</span>):</span>
    <span class="hljs-comment"># implement your logic transformation here</span>
    transformed_source_doc = item.get(<span class="hljs-string">"_source"</span>)
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"_index"</span>: index_target,
            <span class="hljs-string">"_type"</span>: <span class="hljs-string">"_doc"</span>,
            <span class="hljs-string">"_id"</span>: item[<span class="hljs-string">'_id'</span>],
            <span class="hljs-string">"_source"</span>: transformed_source_doc}


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transformedStream</span>(<span class="hljs-params">es_source, match_query, index_source, index_target, transform_logic_func</span>):</span>
    <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> scan(es_source, query=match_query, index=index_source, size=SCAN_SIZE,
                     timeout=SCAN_REQUEST_TIMEOUT):
        <span class="hljs-keyword">yield</span> transform_logic_func(item, index_target)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index_source_to_target</span>(<span class="hljs-params">es_source, es_target, match_query, index_source, index_target, bulk_size, statsd_client,
                           logger, transform_logic_func</span>):</span>
    ok_count = <span class="hljs-number">0</span>
    fail_count = <span class="hljs-number">0</span>
    count_response = es_source.count(index=index_source, body=match_query)
    count_result = count_response[<span class="hljs-string">'count'</span>]
    statsd_client.gauge(stat=<span class="hljs-string">'elastic_migration_document_total_count,index={0},type=success'</span>.format(index_target),
                        value=count_result)
    <span class="hljs-keyword">with</span> statsd_client.timer(<span class="hljs-string">'elastic_migration_time_ms,index={0}'</span>.format(index_target)):
        actions_stream = transformedStream(es_source, match_query, index_source, index_target, transform_logic_func)
        <span class="hljs-keyword">for</span> (ok, item) <span class="hljs-keyword">in</span> parallel_bulk(es_target,
                                        chunk_size=bulk_size,
                                        max_chunk_bytes=MAX_CHUNK_BYTES,
                                        actions=actions_stream,
                                        request_timeout=REQUEST_TIMEOUT,
                                        raise_on_error=RAISE_ON_ERROR):
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> ok:
                logger.error(<span class="hljs-string">"got error on index {} which is : {}"</span>.format(index_target, item))
                fail_count += <span class="hljs-number">1</span>
                statsd_client.incr(<span class="hljs-string">'elastic_migration_document_count,index={0},type=failure'</span>.format(index_target),
                                   <span class="hljs-number">1</span>)
            <span class="hljs-keyword">else</span>:
                ok_count += <span class="hljs-number">1</span>
                statsd_client.incr(<span class="hljs-string">'elastic_migration_document_count,index={0},type=success'</span>.format(index_target),
                                   <span class="hljs-number">1</span>)

    <span class="hljs-keyword">return</span> ok_count, fail_count


statsd_client = statsd.StatsClient(host=<span class="hljs-string">'localhost'</span>, port=<span class="hljs-number">8125</span>)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    index_source_to_target(ES_SOURCE, ES_TARGET, QUERY_MATCH_ALL, INDEX_SOURCE, INDEX_TARGET, BULK_SIZE,
                           statsd_client, transform_item)
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Migrating data in a live production system is a complicated task that requires a lot of attention and careful planning. I recommend taking the time to work through the steps listed above and figure out what works best for your needs.</p>
<p>As a rule of thumb, always try to reduce your requirements as much as possible. For example, is a zero downtime migration required? Can you afford data-loss?</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/12/enjoy-the-ride.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Upgrading data stores is usually a marathon and not a sprint, so take a deep breath and try to enjoy the ride.</p>
<ul>
<li>The whole process listed above took me around 4 months of work</li>
<li>All of the Elasticsearch examples that appear in this post have been tested against version 6.8.1</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
