Table of Contents

Using the Notify Coherency Policy

To have multiple datacenters to synchronize updates to the same object, use be-directional GeoServer Pull replication with the Notify coherency policy.

Context/Background

GeoServer Pull replication causes local "proxy objects" to be created in the local datacenter when remote objects are replicated from another datacenter. Consider two datacenters that must share objects under the following requirements:

  • Local proxy objects must immediately reflect changes made to the primary objects.
  • Multiple datacenters must be able to update the objects in a synchronized manner.

Using GeoServer's Notify coherency policy will satisfy both requirements:

  • The Notify coherency policy sends updates to local proxy objects as soon as the primary object is updated.
  • Bi-directional replication (where both datacenters have Pull replication configured) allows the primary object to migrate between datacenters so that it can be updated in different geographic locations.

Why not use Push Replication?

GeoServer Push replication is intended for disaster recovery scenarios, where one datacenter acts as a failover for another datacenter. Push replication does not maintain the notion of a "primary" object, so if the same object is updated simultaneously in two different datacenters then one of the updates could be lost.

Prerequisites

  • GeoServer replication requires two independent ScaleOut stores. Each store must have at least one instance of the ScaleOut service running and active.
  • Both of the stores must have GeoServer Pull replication configured and started.

GeoServer Configuration

GeoServer is configured in both locations to perform Pull replication:

  • Local Store: "NewYork"

    Console Polling Screenshot
  • Remote Store: "LosAngeles"

    Console Polling Screenshot

Procedure

  1. Create the object in the remote store (LosAngeles) and configure its coherency policy.
  • Use CacheBuilder methods to grant remote access to objects in a cache and set their coherency policies.
  • Because bi-directional replication is used, we also call CacheBuilder.SetRemoteStoreAccess in case we need to access objects whose primary copy resides in the "NewYork" store.
var laConn = GridConnection.Connect("bootstrapGateways=LosAngelesServer1:721");

var builder = new CacheBuilder<string, decimal>("stock prices", laConn)
                  .SetGeoServerPullPolicy(GeoServerPullPolicy.AllowRemoteAccess)
                  .SetGeoServerCoherencyPolicy(GeoServerCoherencyPolicy.Notify)
                  .SetRemoteStoreAccess(new[] { "NewYork" });

var laCache = builder.Build();

// Add object(s) to be accessed remotely by NewYork datacenter:
laCache.AddOrUpdate("MSFT", 126.90m);
  1. Read the object in the local store (NewYork) to pull the object across the WAN.
  • Use CacheBuilder.SetRemoteStoreAccess to supply the name of the ScaleOut store that you want to pull objects from. Remote store names must match the configured remote store name in the ScaleOut Console management tool.
    • Not shown in this example: More than one remote store name can be supplied to CacheBuilder.SetRemoteStoreAccess if you have multiple datacenters that need to be checked for objects.
  • Reading a remote object causes the local "NewYork" store to create a proxy object. (If running version 5.10 or later of the ScaleOut service, the object is preemptively pushed from LosAngeles, causing the proxy to be created in conjunction with the primary object).
  • If the remote store does not contain the requested object, clients accessing the NewYork store will receive a NotFound result.
var nyConn = GridConnection.Connect("bootstrapGateways=NewYorkServer1:721");

var builder = new CacheBuilder<string, decimal>("stock prices", nyConn)
                  .SetGeoServerPullPolicy(GeoServerPullPolicy.AllowRemoteAccess)
                  .SetGeoServerCoherencyPolicy(GeoServerCoherencyPolicy.Notify)
                  .SetRemoteStoreAccess(new[] { "LosAngeles" });

var nyCache = builder.Build();
 
var readResponse = nyCache.Read("MSFT");
switch (readResponse.Result)
{
    case ServerResult.Retrieved:
        Console.WriteLine($"{readResponse.Key} price: {readResponse.Value}");
        break;
    case ServerResult.NotFound:
        Console.WriteLine($"Price not found in NewYork or LosAngeles.");
        break;
    default:
        Console.WriteLine($"Unexpected {readResponse.Result} error.");
        break;
}
  1. Use a read-and-lock call like Cache.ReadExclusive to migrate the primary object from LosAngeles to NewYork. This allows a NewYork client application to update the object.
  • Be sure to use Cache.UpdateAndReleaseExclusive to perform an update so as not to leave the object locked.
  • This ownership transfer is the reason that bi-directional replication was configured for this sample: it allows the primary object to migrate back and forth between datacenters. If only one datacenter will be updating objects then replication only needs to be set up in one direction.
var readLockResponse = nyCache.ReadExclusive("MSFT");
switch (readLockResponse.Result)
{
    case ServerResult.Retrieved:
        Console.WriteLine($"{readLockResponse.Key} price: {readLockResponse.Value}");
        break;
    case ServerResult.NotFound:
        Console.WriteLine($"Price not found in NewYork or LosAngeles.");
        return;
    default:
        Console.WriteLine($"Unexpected {readLockResponse.Result} error.");
        return;
}

var updateResponse = nyCache.UpdateAndReleaseExclusive("MSFT", 
                                                       130.10m,
                                                       readLockResponse.LockToken);
switch (updateResponse.Result)
{
    case ServerResult.Updated:
        Console.WriteLine("Object updated.");
        break;
    case ServerResult.NotFound:
        // This should not have happened since we had the exclusive lock, 
        // but the store may have been cleared via management command, or
        // else another client (that doesn't use locking) removed the object.
        Console.WriteLine($"Price not found.");
        break;
    default:
        Console.WriteLine($"Unexpected {updateResponse.Result} error.");
        break;
}