Tag: replication

  • Replication: Complementing Disaster Recovery, not Replacing

    Replication: Complementing Disaster Recovery, not Replacing

    Many people believe that replicating a distributed database is a replacement for having a solid disaster recovery (DR) plan or backup system. While there are certainly elements of truth to this, it is missing the mark. Replication is a complement to a well thought-out DR plan, not a replacement.

    What is Replication?

    Replication is the act of copying data from one database server or data center to another database server or data center. In other words, replication creates an exact copy of the data in a different storage array. Replication always copies exactly what is in your database. This is important because if your data is corrupt or somehow bad data has gotten into your system, then you are basically just replicating garbage.

    What is Backup?

    Backups, on the other hand, are creating a point in time snapshot of the operational state, architecture and stored data in the database. This distinction is incredibly important given that a backup could be the difference between restoring good data or bad data into your database to recover from an outage (or even just bad data).

    Backup is also important from a regulatory standpoint. Many organizations have contractual clauses in their MSAs (master service agreements) that require a vendor to have backups. While this certainly in the best interest of the vendors to do anyway, this is a function of how organizations are attempting to protect themselves from external events that they have little control over. Many of the backups requested are ones that fall under the requirements of regulations including HIPAA or Sarbanes Oxley, which have important requirements such as the following:

    • Data must be backed up offsite frequently and be fully recoverable at a point in time
    • Backed up data must be encrypted at rest and transported in an encrypted fashion
    • Backups must be stored in multiple locations and each location must be deemed physically safe
    • Backups must be tested regularly for validity

    What is the Difference Between Replication and Backup?

    Replication can and sometimes does handle parts of the backup solution. Data is stored and transported in an encrypted fashion between physical locations. The location of the data is spread out and your servers are likely in locations that are deemed physically safe.  Most importantly, with replication, your data is available in another location almost instantly.

    However, replication also means that any errors or corruption issues are also propagated across all replicas. Thus, replication cannot be used to hedge against issues such as logical corruption, application schema corruption, ransomware, fat finger mistakes, operator errors, compliance and so on.

    After all this, there is a caveat that one version of replication comes very close to backups: CDP or continuous data protection. This is where backups and replication meet. CDP logs the changes in the data and ships them off to a secondary storage location in near-real time. This allows for point-in-time recovery, one of the reasons that backups are so important.

    Backups and Replication are Not Interchangeable

    They are complementary processes that are components of having a complete disaster recovery plan. Knowing when to take proper advantage of each type is important when setting up your application and your organization for success.

    — Michael Colby

  • DevOps resilience: going active-active with an existing application

    DevOps resilience: going active-active with an existing application

    What does it take to run an active-active architecture? It boils down to data, and how you interact with it. The most common disaster recovery strategies I encounter with folks are either “hope and pray” on a single location for running an application, or “we have a backup facility we can switch to in a disaster” with an active-passive configuration. This is a shame, as running an active-active architecture is not science fiction, and while it takes some work, for many business use cases the pros greatly outweigh the cons.

    The Devil’s in the Data

    If you work your architecture from the outside in, an active-active architecture is relatively easy to understand: you have more than physical datacenter or cloud region where you run your application, you have your application running in each, and you have traffic management to load balance users to the closest, fastest and most available location for them. Where folks get stuck is in the database. How do you make sure a change made in one location gets propagated to the others? What happens if a user “bounces” from one facility to the next and attempts to read back data they just wrote? How do you avoid race conditions where user A at location 1 and user B at location 2 make a conflicting change to the state of the application?

    The key to handling the scenarios above is understanding the business requirements of your data, and treating each type of data appropriately for an active-active architecture. If you try to “make all data available everywhere”, you’ll quickly run into either CAP theorem constraints or financial constraints on the high costs of this approach. Instead, let’s break the bigger problem down into smaller, more tractable problems.

    Three Classes of Data

    For each type of data your application accesses or produces, let’s classify it according to one of three types, using a banking metaphor for simplicity (and for easier explanation to your business peers):

    1) Is this data like a bank account balance? It changes frequently, and it’s incredibly important to make sure two conflicting changes don’t “break” the balance. We don’t want users A and users B making a withdrawal from two locations at the same time, thereby potentially overdrawing the account.

    2) Is this data like your address and mailing information your bank keeps for your account? It changes, but not frequently, and during a change, it’s OK if there’s some short period of time where the multiple locations that host your application are out of sync for this data. They’ll eventually gain consistency.

    3) Is this data like your banking statements? Once produced, they don’t change. They can be archived.

    Account balance. Mailing address. Historic statement. Three categories, and for each, we have a data replication strategy that can support active-active. By categorizing the data and treating each with a different replication strategy, we’re accomplishing two very important things:

    1) We’re being thoughtful about what data REALLY needs near realtime replication and race condition protection. By doing so, we’re reducing the amount of overall data that needs high priority replication. The less data to replicate, the better and more efficiently replication works.

    2) Rather than try to replicate all data with the same strategy, we’re willing to use the right tool in the toolbox for each type of data, which means we’ll save costs by using archival and eventually consistent strategies where they make sense.

    For each class, we’ll use an appropriate replication strategy:

    1) For “account balance” information, we’ll replicate changes as quickly as possible, and make sure our application is aware and capable of resolving change conflicts. This is certainly one of the hardest pieces to get right, but by reducing the scope of how much data we need to address with this strategy, the problem is much easier to solve than by trying to apply this strategy to all three categories of data.

    2) For “mailing address” information, we’ll use an eventually consistent replication strategy.

    3) For “historic statements” information, we may choose NOT to replicate this to all of our facilities. We may revisit some old assumptions on what’s important to save and what’s not, and decide to discard some of this information. We may choose to simply regenerate the statements should they be lost. We may choose to bulk replicate this data at certain times of the day.

    The Top Three Benefits

    After we migrate an application to active-active, we can expect three key benefits:

    1) Faster and more reliable failover in the event of a disaster. As opposed to an active-passive disaster recovery strategy, which depends on idle infrastructure coming up to full production speed in the event of a disaster, we avoid the risk of “we thought it was ready to handle production but something broke in between disaster recovery drills”. In an active-active scenario, we’re sending production traffic to each location all of the time, so we don’t have an “idle to full capacity” ramp-up problem; instead, we’ll have a “50% to 100%” (for two locations going to one) or “33% to 50%” (for three locations going to two” capacity challenge. We’ll need to make sure we always have spare capacity in an active-active configuration to handle the additional load during a failure.

    2) More flexibility for making changes to applications and infrastructure. During a site maintenance event, we can shift all traffic away from our facility, perform our maintenance, and then pull the traffic back, without downtime.

    3) Improved user experience. By connecting users with an application instance that is near where they are in the world, they’ll receive a better and faster user experience than if they were to be all sent to a single location.

    There are others, but those are the biggest three.

    The Top Three Drawbacks

    Of course, it’s not all sunshine and kittens, and it’s not without work and investment:

    1) You’re going to need spare capacity available, and be diligent and have conviction about maintaining a safe buffer for spare capacity. If you run two facilities each at 75% load, you’re going to have a big problem when one fails. Know your limits, and stick to them. Don’t fall behind in investing in this capacity.

    2) You’re going to need to change your application. If you were looking for a “drop in solution” to active-active, this is not it. You’re going to need to change your application. The most common scenario is you’re going from a single database instance on a single database technology to three separate data services that treat each type of data with the appropriate data management strategy. The most common problem is “I used to be able to JOIN these two tables to get a result, but one was an ‘account balance’ type, and the other was a “mailing address” type, and now they’re stored separately”. In this case, your application will need awareness to retrieve the data from each appropriate location, and “JOIN” in software.

    This sounds hard and time consuming, but there’s a great strategy for how to implement these changes. Presumably, your application accesses data using a core set of libraries (often an object relational mapping or ORM approach). In these libraries, you’ll want to make the changes for “what type of data is retrieved from where”. This will help minimize the impact to the rest of the application in terms of the interfaces to retrieve data, and keep the “data load balancing” functionality in one location in your codebase. Add in helper functions as appropriate to implement the JOIN functionality you need that previously was done in the database. Keep in mind, if you have a LOT of JOINs across categories of data, this strategy as a whole may not be viable for you.

    It’s highly recommended you take a dark architecture approach to these modifications. For some period of time after migrating data to a new strategy, have your application perform reads and writes to BOTH the legacy and new implementations for a single data interaction use case. Compare the values and results, and log/alert when different, while returning the legacy result. This gives you operational experience with the new data strategy while minimizing risk, since the application is still using the legacy implementation for all functionality. After you have confidence that the two approaches are functionally equivalent, you can start using the values from the new implementation, and turn down the legacy implementation. For more details on dark architecture, see here: http://gigaom.com/2013/06/20/making-it-change-less-scary-using-dark-architecture/

    3) You’re going to be running multiple database and data storage implementations. There’s a definite cost here in training your team, licensing, infrastructure, and having the monitoring and associated operational infrastructure required to run a dedicated strategy for each type of data. You’ll have to understand for your business whether the benefits outweigh the costs.

    There are others, but those are the biggest three.

    Your Next Step

    How important is uptime, infrastructure agility and user experience to your business? Are these investments worthwhile? Only you will be able to tell for your business. Once you quantify the value of uptime (e.g., how much money do you lose and damage to your brand is there during an outage?), the value of agility (e.g., how much faster could your team evolve your application if they had more flexibility to make changes anytime to production without affecting customers?), and the value of user experience (e.g., how much faster could your user experience be by splitting your infrastructure into multiple active-active locations, and how much would your users appreciate that improved experience?). Come up with those figures, and compare them to the technical investments required above, and you’ll have a pretty clear GO/NO-GO for evolving your application to active-active.

  • MongoDB Replication Pro-tips: Reporting Instances

    MongoDB Replication Pro-tips: Reporting Instances

    Any shop that has a DevOps culture, and that has MongoDB backed applications, uses replica sets… not just for production applications, but for those still in the prototyping phase. Replication is critical for uptime, so your Ops team has been working with your Devs all through the development of your project, and you didn’t defer a replication strategy until after you had a working app.

    Nor did you neglect to plan for your reporting needs. It is a given that beyond running your app, you need to get analytics on user behavior, general use, and other business-specific metrics. You can’t just run reports against your live database, (for reasons I detail below, in case they are not yet obvious to you). In your DevOps shop, developers and admins discussed, at the inception of the project, the plan for sequestering reporting tasks so that they do not interfere with production tasks.

    Keeping Reporting Reads Away From Production

    Limiting reporting queries to dedicated nodes is a canonical example, used all over the MongoDB replication documentation. Reporting does not require writes, and permits eventually consistent data. Daily summaries do not suffer if they are derived from data which is seconds or minutes stale. It does not change the fundamental meaning of user behavior reports if your counts are missing a few actions, and some tallies are slightly misaligned.

    But why is it so important to take advantage of this?

    Aside: The Problem With Mingling Production and Analytics

    Skip this section if this is already a no-brainer for you. If you want some background, read on.

    Consider the working set, the subset of your entire database that MongoDB reads from and writes to over any given time span. Your production environment will have a set of active users, and the documents pertaining to their data will be what the OS attempts to keep in physical memory.

    (Do not let your working set grow larger than RAM! If it’s heading there, you need to shard, so capacity planning is critical. But that’s a separate lesson. Monitor your instances with MongoDB’s free monitoring servicee, and maybe take an upcoming webinar in capacity planning.)

    Even if your database is hundreds or thousands of times the size of your available memory, if you have planned your schema and indices well, MongoDB will run efficiently because the data outside the working set can remain untouched on disk. As users become idle, their documents will fall out of use, and the RAM they occupy will become available to contain documents for new active users.

    Reporting jobs, though, read a wide range of data, do not visit the same data repeatedly, and each reporting task may address completely different data sets. On any database of decent size, this means these jobs require constant ejection of recently used documents from RAM to make room for new documents to be read. If you run these jobs on the same instance that is backing your production work load, your reporting jobs will fight with your production apps for RAM footprint, continuously ejecting your live users’ data, while your app continuously reloads it. Congratulations, you have built a thrashing machine.

    Replica Sets With Dedicated Reporting Instances

    You can build dedicated reporting nodes atop MongoDB replication by taking advantage of hidden replica set members, or tag sets in concert with read preferences. The first method is simpler, the second is more flexible.

    Reviewing MongoDB Replica Sets

    MongoDB Replica Sets create uptime durability by replicating data to all the nodes in a set, and providing seamless fail-over to clients. They contain one primary node that allows writes, while the rest are read-only secondaries. They manage among themselves which is primary, holding elections to determine which node should be primary when conditions require. Replica sets should contain an odd number of members to facilitate rapid elections without ties.

    It is fundamentally not knowable whether unreachable machines are down or if the network has been partitioned, so if a majority of the nodes in a replica set go offline (say, 2 out of a 3-member set), even if a healthy primary remains, it will step down to a read-only secondary. Not doing so could lead to multiple machines declaring themselves primary in the case of a network partition, and horrific data inconsistencies.

    Thus a replica set contains a minimum of 3 members, providing a fault tolerance of one machine failure.

    Reporting Instance, Hidden Member

    Hidden members of a replica set are configured to be priority: 0, to prevent them from ever being elected primary, and to be hidden: true, which prevents clients connected to the replica set from routing reads to it, even if they specify a read preference of secondary.

    To read from this hidden member, you will use a standalone connection, rather than the MongoReplicaSetClient type, and specify slave_ok.

    Hidden Member Setup

    We can use the mongo shell to hide a member of an existing replica set:

    # connect to primary directly
    Kusanagi:~ avery$ mongo --port 29019
    MongoDB shell version: 2.4.3
    connecting to: 127.0.0.1:29019/test
    
    // using my local replica set playground
    PRIMARY> conf = rs.config()
    {
        "_id" : "test",
        "version" : 21,
        "members" : [
            {
                "_id" : 0,
                "host" : "Kusanagi.local:29017",
            },
            {
                "_id" : 1,
                "host" : "Kusanagi.local:29018",
            },
            {
                "_id" : 2,
                "host" : "Kusanagi.local:29019",
            }
        ]
    }
    
    // we'll use members[1], the instance on port 29018
    PRIMARY> conf.members[1].priority = 0
    PRIMARY> conf.members[1].hidden = true
    PRIMARY> conf.version += 1
    PRIMARY> rs.reconfig(conf)
    Tue Apr  1 12:24:34.045 DBClientCursor::init call() failed
    Tue Apr  1 12:24:34.046 trying reconnect to 127.0.0.1:29019
    Tue Apr  1 12:24:34.047 reconnect 127.0.0.1:29019 ok
    reconnected to server after rs command (which is normal)

    Kusanagi.local:29018 is now hidden. It will continue to replicate and vote in elections as usual, but clients connecting to the replica set will never read from it, even if Kusanagi.local:29019 is taken down:

    irb(main):012:0> rs = Mongo::MongoReplicaSetClient.new(["Kusanagi.local:29017", "Kusanagi.local:29018", "Kusanagi.local:29019"])
    => <Mongo::MongoReplicaSetClient:0x3fe06e4fe564 @seeds=[["Kusanagi.local", 29017], ["Kusanagi.local", 29018], ["Kusanagi.local", 29019]] @connected=true>
    irb(main):013:0> rs.primary
    => ["Kusanagi.local", 29017]
    irb(main):014:0> rs.secondaries
    => #<Set: {}>
    # an empty set -- as far as this connection is concerned, there are no secondaries.

    Reporting code would look like this (in Ruby):

    require 'mongo'
    reporting = Mongo::MongoClient.new("Kusanagi.local", "29018", slave_ok: true)
    
    # error checking goes here
    
    reporting['my_application']['users'].aggregate(...)

    Considerations

    Using a hidden member is the simplest way to set up an instance for a dedicated workload such as reporting, however:

    Hidden Members Cannot Be Read From In Case of Emergency

    With 2 ordinary and one hidden member in a replica set, fault tolerance for writing is identical to a regular 3-member set. However, should you lose two nodes, your production application will not be able to gracefully degrade to read-only mode, because your hidden member will not allow replica set client reads. If you just like the simplicity of a hidden member, and cost is not an issue, use a 5-member set (with one member hidden) instead.

    Wrapper Code for Replica Sets Cannot Be Used

    Many teams create application-specific wrappers to add infrastructure knowledge to the clients provided by MongoDB drivers. Since you need to address your reporting instance with a stand-alone connection, you will not be able to reuse this investment, which will make you sad.

    Reporting Instance, Tagged Member

    The more complex, but flexible, method for routing reporting queries to a dedicated node is to use tagging and read preferences.

    As with the hidden member, set one member to priority: 0, but do not set it to be hidden. Instead, assign a tag of use: reporting:

    PRIMARY> conf = rs.config()
    {
        "_id" : "test",
        "version" : 21,
        "members" : [
            {
                "_id" : 0,
                "host" : "Kusanagi.local:29017",
            },
            {
                "_id" : 1,
                "host" : "Kusanagi.local:29018",
            },
            {
                "_id" : 2,
                "host" : "Kusanagi.local:29019",
            }
        ]
    }
    
    // we'll use members[1], the instance on port 29018
    PRIMARY> conf.members[1].priority = 0
    PRIMARY> conf.members[1].tags = { "use": "reporting" }
    PRIMARY> conf.version += 1
    PRIMARY> rs.reconfig(conf)
    [...]

    As before, Kusanagi.local:29018 will never become primary; however, in the case that the other two machines become unreachable, your application will be able to issue reads to the reporting server. It should go without saying that your reporting should be suspended during such an event.

    Your reporting code will look something like this (in Python, this time):

    from pymongo import MongoReplicaSetClient
    from pymongo.read_preferences import ReadPreference
    
    rep_set = MongoReplicaSetClient(
        'Kusanagi.local:29017,Kusanagi.local:29018,Kusanagi.local:29019',
        replicaSet = 'test',
        read_preference = ReadPreference.SECONDARY,
        tag_sets = [{'use':'reporting'}]
    )
    # check to ensure we're not running reporting against the sole remaining secondary
    if rep_set.primary is not None:
        rep_set.my_application.users.aggregate(...)

    The above will only send reporting queries to secondaries tagged use: reporting, and it guards against running at all if there is no available primary. In practice, you should throw exceptions and handle them in your escalation code if you find no primaries! Better still, your monitoring could set runtime-available values so you can branch on, for example, reporting_system.ok().

    Benefits and Considerations

    Using tags and read preferences allows a few degrees of flexibility that are not possible with hidden members.

    Reporting Instances Can Be Easily Added

    Because your connection code is declarative, rather than specific to a particular host, to add more nodes for reporting jobs, just add them and tag them, like so:

    PRIMARY> rs.add({_id:3, host:"Kusanagi.local:29020", priority:0, tags:{'use':'reporting'}})

    Your existing code will make use of the new capacity, and the replica set will continue operating without triggering an election and disconnecting your clients.

    Reporting Instances Can Be Shifted or Dropped

    The reporting tag can be moved, or even removed if you need to offer read bandwidth to other jobs in a pinch. A reconfiguration like this will trigger an election and disconnect all your clients, but this is no worse than any other option. Note: It is an anti-pattern to increase your general capacity by distributing production reads to secondaries. This is only an emergency measure.

    Some Drivers Require Manual Sync

    The Ruby driver (as of 1.9.2), for example, does not refresh its view of the replica set unless the client is initialized explicitly to do so with refresh_mode: :sync. Check your driver documentation.

    Conclusion

    Simple replication setup has been one of my favorite operational aspects of MongoDB since its introduction pre–1.0, when it made MySQL replication look like something out of the stone age. It was a little rough around the edges back then, but has truly come into its own. Whether you use tag sets or hidden members, building a reporting infrastructure atop MongoDB’s replication features simplifies operations, letting you focus on building a great application.