Chapter 4. Replica versus Master Processes

Table of Contents

Determining State
Processing Loop
Example Processing Loop
Running It

Every environment participating in a replicated application must know whether it is a master or replica. The reason for this is because, simply, the master can modify the database while replicas cannot. As a result, not only will you open databases differently depended on whether the environment is running as a master, but the environment will frequently behave quite a bit differently depending on whether it thinks it is operating as the read/write interface for your database.

Moreover, an environment must also be capable of gracefully switching between master and replica states. This means that the environment must be able to detect when it has switched states.

Not surprisingly, a large part of your application's code will be tied up in knowing which state a given environment is in and then in the logic of how to behave depending on its state.

As an alternative, write forwarding simplifies adding replication to an application. Once configured, there is no need to confine write operations to the master or even to know which site is the master. For more information, see Configuring for Write Forwarding.

This chapter shows you how to determine your environment's state, and it then shows you some sample code on how an application might behave depending on whether it is a master or a replica in a replicated application.

Determining State

In order to determine whether your code is running as a master or a replica, you implement an event handling callback, which we initially describe in Event Handling. When the current environment becomes a client — including at application startup — the DB_EVENT_REP_CLIENT event is raised. When an election is held and a replica is elected to be a master, the DB_EVENT_REP_MASTER event is raised on the newly elected master and the DB_EVENT_REP_NEWMASTER is raised on the other replicas.

The EventHandler implementation is fairly simple. First you detect the event, and then you record the state change in some data member maintained in a location that is convenient to you.

For example:

package db.repquote;

// We make our main class an EventHandler implementation
...
import com.sleepycat.db.EventHandler;
...

public class MyReplicationClass implements EventHandler
{

...

// Somewhere we provide a data member that is used to track
// whether we are a master server. This could be in our main
// class, or it could be part of a supporting class.
private boolean isMaster;

...

isMaster = false;

...

// In the code where we open our environment and start replication,
// we must identify the class that is the event handler. In this
// example, we are performing this from within the class that 
// implements com.sleepycat.db.EventHandler so we identify
// "this" class as the event handler
envConfig.setEventHandler(this); 

That done, we still need to implement the methods required for handling replication events. For a simple application like this one, these implementations can be trivial.

    public void handleRepClientEvent()
    {
        dbenv.setIsMaster(false);
    }

    public void handleRepConnectBrokenEvent()
    {
        // Ignored for now.
    }

    public void handleRepConnectEstablishedEvent()
    {
        // Ignored for now.
    }

    public void handleRepConnectTryFailedEvent()
    {
        // Ignored for now.
    }

    public void handleRepMasterEvent()
    {
        dbenv.setIsMaster(true);
    }

    public void handleRepNewMasterEvent(int envId)
    {
        // Ignored for now
    }

    public void handleWriteFailedEvent(int errorCode)
    {
        System.err.println("Write to stable storage failed!" +
            "Operating system error code:" + errorCode);
        System.err.println("Continuing....");
    }

    public void handleRepStartupDoneEvent()
    {
        System.out.println("Replication startup is completed.");
    }

    public void handleRepPermFailedEvent()
    {
        System.out.println("This application failed to receive enough" +
            "acks for a permanent message. The transaction is flushed" + 
            "to disk on this master host.");
    }

    public void handleRepLocalSiteRemovedEvent()
    {
        // Ignored for now.
    }

    public void handleRepSiteAddedEvent()
    {
        // Ignored for now.
    }

    public void handleRepSiteRemovedEvent()
    {
        // Ignored for now.
    }

    public void handleRepElectedEvent()
    {
        // Safely ignored for Replication Manager applications.
    }

    public void handleRepElectionFailedEvent()
    {
        // Safely ignored for Replication Manager applications that do
        // not manage their own master selection.
    }

    public void handleRepJoinFailureEvent()
    {
        // Safely ignored since this application did not turn off AUTOINIT.
    }

    public void handleRepMasterFailureEvent()
    {
        // Safely ignored for Replication Manager applications that do
        // not manage their own master selection.
    }

    public void handleRepDupmasterEvent()
    {
        // Safely ignored for Replication Manager applications that do
        // not manage their own master selection.
    }

    public void handlePanicEvent()
    {
        System.err.println("Panic encountered!");
        System.err.println("Shutting down.");
        System.err.println("You should restart, running recovery.");
        try {
            terminate();
        } catch (DatabaseException dbe) {
            System.err.println("Caught an exception during " +
                "termination in handlePanicEvent: " + dbe.toString());
        }
        System.exit(-1);
    }

Of course, this only gives us the current state of the environment. We still need the code that determines what to do when the environment changes state and how to behave depending on the state (described in the next section).