One-time Object Creation

When using ScaleOut’s C++ Native Client API as a cache, the API supports the popular and straightforward "cache-aside" access pattern, where a client application is responsible for inserting objects into the cache whenever the application determines that an object is missing from the cache. The cache-aside pattern, however, is not well-suited for all caching scenarios, especially under loads where multiple clients/threads may simultaneously encounter a cache miss and try to insert the same expensive object into the cache at the same time. To address this scenario, the C++ Native Client API provides a one-time object insertion mechanism that coordinates which client/thread inserts the object in the event of a cache miss.

Cache-wide Configuration

To use the one-time object insertion feature on a named cache, applications must use the set_missed_object_callback() method to provide a callback that returns a pointer to the object to be inserted when a cache miss occurs. When the application attempts to retrieve a missing object using a get call, the server allows one thread on one client machine to invoke this callback—the API takes the returned object and transparently inserts it into the cache so that it can be accessed by other callers.

#include "soss_client/named_protobuf_cache.h"
#include "StockQuote.pb.h"

using namespace NativeClientSample;

// Functor for one-time insert feature. A callback that returns an object to be
// inserted into the cache by a NC.get() operation in the event of a cache miss.
struct InsertOnceCallback
{
  boost::shared_ptr<StockQuote> operator()(const sosscli::SossKey &key)
  {
    // Get the ticker symbol from the SossKey argument:
    std::wstring ticker_wide = key.retrieve_original_string_key();
    // Convert to single-byte characters:
    std::string ticker;
    ticker.assign(ticker_wide.begin(), ticker_wide.end());

    // In real life we'd be hitting a web service or some other expensive resource
    // for the financial data that we'd like cached:
    auto quote = boost::make_shared<StockQuote>();
    quote->set_ticker(ticker);
    quote->set_price(999.99f);
    quote->set_volume(999999);
    return quote;
  }
};

int main(int argc, char* argv[])
{
  sosscli::NamedProtobufCache<StockQuote> quote_cache("Stock Quotes");

  // Make sure the object isn't already in the cache:
  quote_cache.clear();

  // Register the missed-object callback:
  InsertOnceCallback callback;
  quote_cache.set_missed_object_callback(callback);

  // Attempting to get the missing object will result in it being
  // automatically & transparently inserted, allowing the StockQuote
  // to be returned here:
  auto get_result = quote_cache.get("GOOG");
  std::string ticker = get_result.object_ptr()->ticker();
  float price = get_result.object_ptr()->price();

  std::cout << ticker << ": $" << price << std::endl;

  return 0;
}

// Output:
// GOOG: $999.99

In the example above, if multiple instances of this program ran simultaneously, only one would be permitted to execute the callback in order to prevent multiple clients/threads from simultaneously inserting the object into the cache—this behavior is valuable when creating the cached object involves expensive calls to a database, web service, or other expensive resource (or when it is otherwise undesirable for an object to be repeatedly created in the cache). Other clients/threads that try to retrieve the object while the callback is executing will be blocked while the object is being created, and, once the object has been added to the cache, those other threads will be unblocked and the newly-cached object will be returned to all of them.

The missed-object callback must accept a constant reference to a SossKey object (the key of the missing object that is to be returned), and it must return a shared pointer to a newly-allocated instance of the object to be inserted into the cache. Like the expiration event callbacks discussed in the prior section, the API accepts the callback target as a boost::function object, allowing some flexibility with respect to how the callback is implemented.

The object returned from the callback will be inserted into the StateServer Service using the default policies defined on the NamedCache instance being used (see Policies for an Entire Named Cache Instance).

Individual Call Configuration

Like all options and policies in the C++ Native Client API, the missed-object callback can be configured for individual calls instead of an entire NamedCache instance. A set_missed_object_callback() method is available on the GetOptions class and may be provided to the get method as an argument for a single access of the object. If a callback is also registered for the entire named cache then the callback registered on the GetOptions object will take precedence. See Named Cache Options for details on overriding cache-wide behavior for individual calls.

InsertOnceCallback callback;
sosscli::GetOptions<StockQuote> options;
options.set_missed_object_callback(callback);
auto get_result = quote_cache.get("GOOG", options);

The GetOptions class also provides a missing_object_policy field that can be used to override the named cache’s default object policies when a missed-object callback inserts an object.