Table of Contents

Write-Behind Events

When an object's state needs to be persisted to a backing store such as a database or web service, write-behind events can be used to write an object's changes on a scheduled basis. This feature allows you to decrease database load by consolidating writes to your database instead of making a round trip to it every time a cached object is modified.

Use the ServiceEvents.SetStoreObjectHandler method to register a "store object" handler for write-behind events.

The ScaleOut service will only fire a write-behind event for an object if it has been modified since the last write-behind event occurred. This further reduces load on your database by eliminating unnecessary writes.

In addition to persisting modifications to an object, the service will fire an erase-behind event when an object is removed from the ScaleOut cache. Register a handler using ServiceEvents.SetEraseObjectHandler to be notified of removals.

Registering an Event Handler

The following example illustrates how to register handlers for write-behind and erase-behind events, allowing game players' statistics to be periodically stored in a database. The handlers are responsible for updating a database to reflect changes that have occurred in the ScaleOut cache.

Note

Arbitrary-length key strings are not currently supported by the ScaleOut service when erase-behind events are used. Short key strings (26 bytes or less) can be used instead--use the ShortStringKeyEncoder as illustrated below if string keys are needed.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Data.SqlClient;
using Scaleout.Client;
using Scaleout.Client.KeyEncoding;

class WriteBehindHandler
{
    static readonly string connString = "bootstrapGateways=localhost:721";

    static async Task Main(string[] args)
    {
        var conn = await GridConnection.ConnectAsync(connString);
        var cache = new CacheBuilder<string, PlayerStats>("player stats", conn)
                           .SetKeyEncoder(new ShortStringKeyEncoder())
                           .Build();

        // Inform the ScaleOut service that this client will be processing write-behind
        // events by providing a lambda callback for the cache:
        ServiceEvents.SetStoreObjectHandler(cache, (playerId, playerStats) =>
        {
            var playerSqlParam = new SqlParameter("@player_id", playerId);
            var lossesSqlParam = new SqlParameter("@wins", (object)playerStats.Losses);
            // (...remainder of DB code elided.)

            Console.WriteLine("Write behind.");
        });

        // Also register a handler for erase-behind events to mark a player as 'offline'
        // in the database upon removal from the ScaleOut cache:
        ServiceEvents.SetEraseObjectHandler(cache, (playerId) =>
        {
            var playerSqlParm = new SqlParameter("@player_id", playerId);
            // UPDATE players SET status = "offline" where player_id = ...
        });

        // Wait indefinitely for events for fire.
        Thread.Sleep(Timeout.Infinite);
    }
}

[Serializable]
class PlayerStats
{
    public int Wins   { get; set; }
    public int Losses { get; set; }
}

When the "store object" callback is invoked by the ScaleOut service, it is supplied with the key of the object (here, a player ID) and the cached object that needs to be persisted to the backing store. The erase-behind callback is supplied with the key of the object that has been removed.

Note

All event registration methods exposed by the ServiceEvents class are overloaded to accept either synchronous or async callback methods/lambdas.

Deployment

As a best practice, applications that register handlers with the ServiceEvents class typically run event-handling code like the sample above in long-running, dedicated processes (for example, a Windows Service or a Linux daemon) that reside locally on each machine hosting the ScaleOut service. See Expiration Events for more information about deployment considerations.

Configuring Objects for Write-Behind/Erase-Behind

Clients that add objects to the cache must configure policies to make cached objects eligible for write-behind/erase behind events. This can be accomplished at the cache level as follows:

var conn = await GridConnection.ConnectAsync("bootstrapGateways=localhost:721");

var cache = new CacheBuilder<string, PlayerStats>("player stats", conn)
                   .SetKeyEncoder(new ShortStringKeyEncoder())
                   .SetBackingStoreEvent(TimeSpan.FromSeconds(5), 
                                         BackingStoreMode.WriteBehind)
                   .Build();

As an alternative to a cache-wide configuration, an individual object can be configured for periodic write-behind/erase-behind by supplying the appropriate cache policy when the object is added to the cache:

var playerStats = new PlayerStats { Wins = 33, Losses = 5 };

var policyBuilder = cache.GetPolicyBuilder();
policyBuilder.BackingStoreMode = BackingStoreMode.WriteBehind;
policyBuilder.BackingStoreEventInterval = TimeSpan.FromMinutes(1);

await cache.AddAsync("player1", playerStats, policyBuilder.Build());