6. State of the World

AMPS State of the World (SOW) allows you to automatically keep and query the latest information about a topic on the AMPS server, without building a separate database. Using SOW lets you build impressively high-performance applications that provide rich experiences to users. The AMPS C# client lets you query SOW topics and subscribe to changes with ease.

Performing SOW Queries

To begin, we will look at a simple example of issuing a SOW query.

public void ExecuteSOWQuery(Client client)
{

    foreach (Message m in client.sow("messages-sow", "/id > 20"))
    {
        if (m.Command == Message.Command.BeginGroup)
        {
            System.Console.WriteLine("--- Begin SOW Results ---");
        }
        if (m.Command == Message.Command.EndGroup)
        {
            System.Console.WriteLine("--- End SOW Results ---");
        }
        if (m.Command == Message.Command.SOW)
        {
            System.Console.WriteLine(m.Data);
        }
    }
}

Example 6.1: Basic SOW query

In Example 6.1 the ExecuteSOWQuery() function invokes Client.sow() to initiate a SOW query on the orders topic, for all entries that have a symbol of ’ROL’.

As the query executes, the body of the loop is invoked for each matching entry in the topic. Messages containing the data of matching entries have a Command of value sow; as those arrive, we write them to the console. AMPS sends a begin_group message at the beginning of the results and an end_group message at the end of the results. We use those messages to delimit the results of the query.

As with subscribe, the sow command also provides an asynchronous version, as well as versions that accept a Command. For example, the listing below shows an asynchronous SOW command that specifies the batch size, or the maximum number of records that AMPS will return at a time.

private void HandleSOW(Message message)
{
    if (message.Command == Message.Commands.SOW)
    {
        Console.WriteLine(message.Data);
    }
}

public void ExecuteSOWQuery(Client client)
{
    Command command = new Command(Message.Commands.SOW)
                             .setTopic("messages-sow")
                             .setFilter("/id > 20")
                             .setBatchSize(100);

    client.executeAsync(command, message => HandleSOW(message));

}

Example 6.2: Asynchronous SOW Query

In Example 6.2 the ExecuteSOWQuery() function invokes Client.executeAsync() to initiate a SOW query on the messages-sow topic, for all entries that have an id greater than 20. The SOW query is requested with a batch size of 100, meaning that AMPS will attempt to send 100 messages at a time as results are returned.

As the query executes, the HandleSOW() method is invoked for each matching entry in the topic. Messages containing the data of matching entries have a Command of value sow; as those arrive, we write them to the console.

SOW and Subscribe

Imagine an application that displays real-time information about the position and status of a fleet of delivery vans. When the application starts, it should display the current location of each of the vans, along with their current status. As vans move around the city and post other status updates, the application should keep its display up to date. Vans upload information to the system by posting message to a van location topic, configured with a key of van_id on the AMPS server.

In this application, it is important to not only stay up-to-date on the latest information about each van, but to ensure all of the active vans are displayed as soon as the application starts. Combining a SOW with a subscription to the topic is exactly what is needed, and that is accomplished by the AMPS sow_and_subscribe command. Now we will look at an example:

private void UpdateVanPosition(Message message)
{
    switch (message.Command) {
        case Message.Commands.SOW:
        case Message.Commands.Publish:

            // For each of these messages we call AddOrUpdateVan(), that presumably adds the
            // van to our application’s display. As vans send updates to the AMPS server, those
            // are also received by the client because of the subscription placed by
            // sowAndSubscribe(). Our application does not need to distinguish between updates
            // and the original set of vans we found via the SOW query, so we use
            // addOrUpdateVan() to display the new position of vans as well.
            AddOrUpdateVan(message);
            break;
        case Message.Commands.OOF:
            RemoveVan(message);
        break;
    }
}

public void SubscribeToVanLocation(Client client) {
    Command command = new Command("sow_and_subscribe")
                            .setTopic("van_location")
                            .setFilter("/status = 'ACTIVE'")
                            .setBatchSize(100)
                            .setOptions("oof");

    // Notice here that we specified an Option of "oof". Including this option causes us
    // to receive Out-of-Focus ("OOF") messages for topic.
    // OOF messages are sent when an entry that was sent to us in the past
    // no longer matches our query. This happens when an entry is removed from the SOW
    // cache via a sowDelete() operation, when the entry expires (as specified by the
    // expiration time on the message or by the configuration of that topic on the AMPS
    // server), or when the entry no longer matches the content filter specified. In
    // our case, if a van’s status changes to something other than ACTIVE, it no longer
    // matches the content filter, and becomes out of focus. When this occurs, a
    // Message is sent with Command set to oof. We use OOF messages to remove vans from
    // the display as they become inactive, expire, or are deleted.


    foreach (Message msg in client.execute(command))
    {
        updateVanPosition(message);
    }
}

public void addOrUpdateVan(message) {
   // use information in the message to add the van or update
   // the van position
}

public void removeVan(message) {
   // use information in the message to remove information on
   // the van position
}

Example 6.3: Using sowAndSubscribe

Now we will look at an example that uses the asynchronous form of execute to place a sow_and_subscribe command:

private void UpdateVanPosition(Message message)
{
    switch (message.Command) {
        case Message.Commands.SOW:
        case Message.Commands.Publish:
           AddOrUpdateVan(message);
            break;
        case Message.Commands.OOF:
            RemoveVan(message);
        break;
    }
}


public void SubscribeToVanLocation(Client client) {
    Command command = new Command("sow_and_subscribe")
                            .setTopic("van_location")
                            .setFilter("/status = 'ACTIVE'")
                            .setBatchSize(100)
                            .setOptions("oof");

    client.executeAsync(command, msg => UpdateVanPosition(msg));
}

Example 6.4: Asynchronous SOW and subscribe

Setting Batch Size

The AMPS clients include a batch size parameter that specifies how many messages the AMPS server will return to the client in a single batch when returning the results of a SOW query. The 60East clients set a batch size of 10 by default. This batch size works well for common message sizes and network configurations.

Adjusting the batch size may produce better network utilitization and produce better performance overall for the application. The larger the batch size, the more messages AMPS will send to the network layer at a time. This can result in fewer packets being sent, and therefore less overhead in the network layer. The effect on performance is generally most noticeable for small messages, where setting a larger batch size will allow several messages to fit into a single packet. For larger messages, a batch size may still improve performance, but the improvement is less noticeable.

In general, 60East recommends setting a batch size that is large enough to produce few partially-filled packets. Bear in mind that AMPS holds the messages in memory while batching them, and the client must also hold the messages in memory while receiving the messages. Using batch sizes that require large amounts of memory for these operations can reduce overall application peformance, even if network utilization is good.

For smaller message sizes, 60East recommends using the default batch size, and experimenting with tuning the batch size if performance improvements are necessary. For relatively large messages (especially messages with sizes over 1MB), 60East recommends explicitly setting a batch size of 1 as an initial value, and increasing the batch size only if performance testing with a larger batch size shows improved network utilization or faster overall performance.

Client-Side Conflation

In many cases, applications that use SOW topics only need the current value of a message at the time the message is processed, rather than processing each change that lead to the current value. On the server side, AMPS provides conflated topics to meet this need. Conflated topics are described in more detail in the AMPS User Guide, and require no special handling on the client side.

In some cases, though, it’s important to conflate messages on the client side. This can be particularly useful for applications that do expensive processing on each message, applications that are more efficient when processing batches of messages, or for situations where you cannot provide an appropriate conflation interval for the server to use.

A MessageStream has the ability to conflate messages recieved for a subscription to a SOW topic, view, or conflated topic. When conflation is enabled, for each message recieved, the client checks to see whether it has already received an unprocessed message with the same SowKey. If so, the client replaces the unprocessed message with the new message. The application never receives the message that has been replaced.

To enable client-side conflation, you call conflate() on the MessageStream, and then use the MessageStream as usual:

// Query and subscribe
MessageStream results = ampsClient.sowAndSubscribe("orders", "/symbol == 'ROL'");

// Turn on conflation
results.conflate();

// Process the results
foreach (Message m in results)
{
   // Process message here
}

Notice that if the MessageStream is used for a subscription that does not include SowKeys (such as a subscription to a topic that does not have a SOW), no conflation will occur.

When using client-side conflation with delta subscriptions, bear in mind that client-side conflation replaces the whole message, and does not attempt to merge deltas. This means that updates can be lost when messages are replaced. For some applications (for example, a ticker application that simply sends delta updates that replace the current price), this causes no problems. For other applications (for example, when several processors may be updating different fields of a message simultaneously), using conflation with deltas could result in lost data, and server-side conflation is a safer alternative.

Managing SOW Contents

AMPS allows applications to manage the contents of the SOW by explicitly deleting messages that are no longer relevant. For example, if a particular delivery van is retired from service, the application can remove the record for the van by deleting the record for the van.

The client provides the following methods for deleting records from the SOW:

  • sowDelete accepts a filter, and deletes all messages that match the filter
  • sowDeleteByKeys accepts a set of SOW keys as a comma-delimited string and deletes messages for those keys, regardless of the contents of the messages. SOW keys are provided in the header of a SOW message, and is the internal identifier AMPS uses for that SOW message
  • sowDeleteByData accepts a message, and deletes the record that would be updated by that message

Most applications use sowDelete, since this is the most useful and flexible method for removing items from the SOW. However, this operation is relatively expensive in cases where the application has the data or the SOW keys of the messages to be removed. In some cases, particularly when working with extremely large SOW databases, sowDeleteByKeys can provide better performance.

Regardless of the command used, AMPS sends an OOF message to all subscribers who have received updates for the messages removed, as described in the previous section.

The simple form of the sowDelete command returns a MessageStream that receives the response. This response is an acknowledgment message that contains information on the delete command. For example, the following snippet simply prints informational text with the number of messages deleted:

foreach (Message msg in client.SowDelete("sow_topic", "/id IN (42, 64, 37)")
{
   System.Console.WriteLine("Got an {0} containing {1} : " +
                            "deleted {2} messages.",
                            msg.Command,
                            msg.AckType,
                            msg.Matches);
}

In either case, AMPS sends an OOF message to all subscribers who have received updates for the messages removed, as described in the previous section.

Acknowledging messages from a queue uses a form of the sow_delete command that is only supported for queues. Acknowledgment is discussed in the chapter on queues.