Distributed Data Grids - Share Objects Between Windows Service and ASP.NET

I've included a sample that demonstrates how to share in memory objects from their server store between windows services, windows forms, wcf, and asp.net applications using ScaleOut StateServer. It is much simpler to implement than named pipes and incredibly reliable.

I've known the folks at ScaleOut Software since 2005. Our team used their StateServer product to manage in memory session state across web farms. Just a simple web.config change and installing the product on each server was all it took to get going. No training, special server configuration, or hassle. It is one of those applications that just works.

What problems did ScaleOut StateServer (SOSS) solve 5 years ago?

We bought SOSS initially to resolve issues with losing session when the app pool recycles as well as a major business to business traffic issue concerning network proxy server farms. There was talk of a need to manage session in Java applications too and SOSS's ability to be easily ported to Linux was a big plus.

More and more companies are having their outgoing network traffic routed through network proxy server farms. Essentially, these proxy server farms are designed to hide the real ip address of the visitor from the web server. Thus, the network load balancer has the possibility of routing subsequent requests to other web servers in the farm which don't have access to that session data. So, if you were trying to figure out why quite a few of your users were intermittently losing session for no apparent reason even though you have session affinity enabled, this is likely the cause. You may even see this in the business to consumer market since some ISPs route their traffic through these farms as well. The phone calls from annoyed IT executives of some of our biggest clients stopped immediately after deploying SOSS to production.

What's new in ScaleOut StateServer 5.0?

Flash forward to 2010 and ScaleOut Software is seemingly light years ahead of where they were. Managing session state across web farms is kids stuff compared to what they are doing with caching across distributed data grids.

To make a simplistic analogy, it is a little like having an ASP.NET Application Cache that is replicated across any server, anywhere on your network, that is running a SOSS instance on it. Just like in ASP.NET, you give the cache item a name and shove objects in it.

However, unlike ASP.NET, the objects in cache can be seamlessly referenced across application domains and across servers. In version 5.0 of StateServer, you can even run LINQ queries against the cache across all of the servers in the data store. Can you imagine writing LINQ queries to find the exact result sets you want across an in-memory data store that spans across multiple servers? StateServer does this with lightning speed and handles all of the complex merging operations for you. It is a bit like having LINQ To SQL but instead you get LINQ to SOSS.

Connect SOSS up to a backing store like NHibernate or direct to SQL Server, Oracle, DB2, or Informix and you've got a powerful and stable foundation for LINQ without the worry of future changes to things like Microsoft's Entity Framework.

We don't run a web farm. So, why do we need SOSS?

ScaleOut StateServer isn't just for web farms. As an example, the code at the bottom of this page demonstrates how SOSS can be used to share objects in its data store between a windows service (shown here in the form of a console app for ease of testing) and an ASP.NET application. The windows service acts as a listener waiting for the ASP.NET app to simply add an object to the cache. When that occurs, the windows service gets the object and processes it. In this case, the service changes the object so that the next time the ASP.NET app retrieves it, it will see the changes.

This application could easily be extended to multiple WCF services, windows forms applications, or even Java applications. All connecting to the same in memory data store and passing objects around. Of course, you could write all the plumbing for this yourself via something like sockets or named pipes. However, I'd bet you couldn't do it for less than the cost of licenses to ScaleOut and with as little code, testing, or hassle.

What types of applications could benefit from SOSS?

  • Reliable shopping cart applications. Whether it is used in a web farm or not, you will not lose your shopping cart session if the app pool recycles or even if IIS is completely restarted.


  • ECommerce applications or any sort of real-time asset tracking. Any application that has large volumes of real-time data that needs to be analyzed and rendered to the UI with real-time updates is going to benefit. You could build incredibly powerful real-time ecommerce visitor analysis engines and the means to make on the fly changes to logic flows without bogging down the ecommerce website.


  • High volume financial transaction web sites.


  • High traffic social network oriented games.


  • Any ASP.NET or WCF Service application that needs to offload an object for heavy duty processing offline. This is immediately applicable to applications I'm working on right now.


  • NHibernate driven applications that could benefit from a middleware caching approach. ScaleOut offers samples for using their NamedCache API directly with NHibernate. It is incredibly easy to implement over existing NHibernate layers.


  • Create your own grid computing cloud versus sending your data to other providers such as Microsoft. You can also wire up SOSS to interact with Microsoft's cloud via Windows Azure.

How do I know this will work and is reliable?

When comparing distributed data grid vendors like ScaleOut, there are a few questions you need to ask each vendor and insist on proof "before" you spend your money.

  • Is there a single point of failure in the data replication and retrieval process? Some vendors use a form of load balancer to direct traffic to each server in the data grid. ScaleOut uses a very different approach they refer to as a scalable heart-beating algorithm. Think of it as peer to peer health monitor. If one or more servers fail, ScaleOut just lets one of the other peers handle the request. Each peer keeps a constant eye on its other peers and automatically makes adjustments if one of them is unable to handle the request.


  • What is the underlying technology used to manage the data in the store? Some vendors rely on 100% .NET managed code. ScaleOut originally chose not to in order to avoid potential .NET garbage collection issues with extremely high volumes of objects in the store. ScaleOut is aptly named in that scalability is going to be far more about network cards, bandwidth, and server memory than it will be about their software crashing under heavy volume. Having used their product on numerous production web sites, I am keenly aware of how reliable it is. But, don't take my word for it. Have ScaleOut help you set up a performance test on your own equipment. This will immediately determine if your network, bandwidth, and server hardware can handle the projected load.


  • Does the vendor use any form of client-side caching to reduce unnecessary data transfers over the network and repeated object deserialization? The client-side caching actually works as promised and accurately evaluates whether objects need to be transferred or not.


  • What kind of global redundancy is available? There is a Geo Server add-on that enables you to replicate StateServer farms all over the world. So, if you think your volume will be comparable to big dogs like Amazon.com, you'll want to check this out.


  • Is a comprehensive set of management tools available? If your system admin people need a real-time view of the objects in the data store, ScaleOut offers a powerful Management Console and an object browser.


  • Can I use the store for more than just a simple in-memory cache? Aside from what has already been discussed, ScacleOut also offers a Grid Computing Edition that is pretty impressive and offers map/reduce computation.


  • Can we add new servers to the SOSS farm without changing our applications or involving programmers? Yes, just install SOSS on the new servers. Existing SOSS servers will instantly recognize the new servers and begin using them.


  • Do I need to buy separate server hardware or can I run SOSS on my existing web servers? No, you do not need to run SOSS on separate servers. I've run SOSS on production web servers without any issues at all.


  • Is the product reliable enough to be considered the store of record? In my opinion, SOSS is reliable enough to use as the store of record. Of course, you'd want to save the data permanently in a database but as long as the store is running on one server, that record will still be in memory.


  • Is customer retention a strong point? ScaleOut claims a maintenance agreement renewal rate of 87%. Based on my experience, I can believe that.


Is ScaleOut's technical support good?

They don't have an online forum like Telerik, Syncfusion, or ComponentOne do. In my experience with them, they prefer to establish a personal relationship with you and attempt to understand what it is you want to accomplish. From there, they'll give you detailed suggestions that often come with relevant code snippets to get you going.

What didn't I like about SOSS?

Distributed data grid technologies like SOSS have their own vocabulary and methodologies for doing things. What is commonplace to developers in this arena is a bit foreign to those of us who are not. So, it isn't always "obvious" which code samples are the right places for you to start or even which methods, events, and properties will get you the result you were looking for. As you get more accustomed to distributed data grid APIs, their terminology and techniques make much more sense. So, take my advice above and email their support team (especially if you are still in the trial phase) and ask for specific guidance of how best to approach an application with x,y, and z functionality. It will save you a ton of time and you'll be stunned at just how simple it was to wire up impressive functionality.

Does ScaleOut offer Microsoft MVPs a free license?

Yes, they do. You'll want to contact ScaleOut for details at scaleoutsoftware.com.

Code Sample

Here's the code sample I referred to above how simple sharing objects across applications can be. You can also download it here. The download includes 64 bit dlls as a reference. If you are running 32 bit, you'll need to swap out references to soss_namedcache.dll and soss_svcdotnet.dll in every project in the two solutions.

To run the sample, make sure the SOSS store is running (download trial). Then, start the console application first. It will show a command window with the line "Listening" when it is ready. Then, launch the ASP.NET solution. Notice what is written to the browser and to the console application's command window. From there, you can refresh the ASP.NET page to watch it process objects. I suggest putting breakpoints in each application to watch what happens under the hood.

If you need to use the DataContract attribute for Silverlight data object classes instead of Serializable, you can. You just have to use one of SOSS's custom serialization provider settings instead. Contact support for a small code snippet.

ShareSample.dll

namespace ScaleOut
{
    [Serializable]
    public class Customer
   {
       public int CustomerID { get; set; }
       public double CustomerTypeID { get; set; }
       public string Description { get; set; }
       public StateServerSetting StateServerSettings = new StateServerSetting();

      public string CacheKeyPrefix
     {
        get { return "Customer-CustomerID="; }
     }

     public string CacheKey
    {
      get
      {
       return CacheKeyPrefix + CustomerID.ToString();
      }
     }

     public Customer()
    {

   }

    public Customer(int customerID)
   {
      this.CustomerID = customerID;
  }

   public Customer(int customerID, double customerTypeID, string description)
   {
      this.CustomerID = customerID;
      this.CustomerTypeID = customerTypeID;
      this.Description = description;
   }
}
}

namespace ScaleOut
{
     [Serializable]
     public class StateServerSetting
    {

        public void SetServer(string server)
       {
          _server = server;
          _lastUpdateTime = DateTime.Now;
       }

       private string _server = string.Empty;
       public string Server
      {
        get
       {
          return _server;
        }
      }

      private DateTime _lastUpdateTime = DateTime.MinValue;
      public DateTime LastUpdateTime
      {
         get
        {
           return _lastUpdateTime;
        }
     }

    }
}

namespace ScaleOut
{
      public class CustomerBackingStoreAdapter : IBackingStore
     {
        public EventHandler Loaded;
        public EventHandler Updated;

        public object Load(CachedObjectId id)
       {
          return null;
       }

       public void Store(CachedObjectId id, object value)
      {
         if (Updated != null) Updated(value,EventArgs.Empty);
      }

      public void Erase(CachedObjectId id)
     {

     }

     public CreatePolicy GetCreatePolicy(CachedObjectId id)
    {
       return null;
    }
  }
}



Console Application (Listener)

class NamedCacheSample
{
     private static string _applicationName = "ConsoleListener";

     static void Main(string[] args)
    {

        var cache = CacheFactory.GetCache("CustomerCache");

        try
       {

           cache.DefaultCreatePolicy.BackingStoreMode = BackingStoreAsyncPolicy.WriteBehind;
           cache.DefaultCreatePolicy.BackingStoreInterval = TimeSpan.FromSeconds(1);

           var adapter = new CustomerBackingStoreAdapter();

           adapter.Updated += new EventHandler(OnCustomerUpdated);

           cache.SetBackingStoreAdapter(adapter, new BackingStorePolicy(true,false, false));

        }
       catch (Exception ex)
       {
           Console.WriteLine("Error reading object: " + ex.Message);
       }

      Console.WriteLine("Listening...");
      Console.ReadKey();

  }

   public static void OnCustomerUpdated(object sender, EventArgs e)
  {
     var customer = sender as Customer;
     if (customer == null) return;

     // Changes to the Customer object in the listener will fire
    // the event again. So, we track that the change came from this
    // application to avoid processing our own logic against again.

     if (customer.StateServerSettings.Server == _applicationName)
    {
        Console.WriteLine("Customer last updated by this server. No need to process again.");
        return;
   }

    Console.WriteLine("Updated: " + customer.CustomerID.ToString() + " " + customer.Description);

    var cache = CacheFactory.GetCache("CustomerCache");

    customer.Description = "Changed in console " + DateTime.Now.ToString();
    customer.StateServerSettings.SetServer(_applicationName);
    cache[customer.CacheKey] = customer;
  }

}

ASP.NET Application (Client)

namespace ScaleOut
{

    public partial class _default : System.Web.UI.Page
   {
       private string _applicationName = "ASP.NET Server";

       protected void Page_Load(object sender, EventArgs e)
      {

        var cache = CacheFactory.GetCache("CustomerCache");
        var customer = new Customer();

        try
       {

          cache.DefaultCreatePolicy.BackingStoreMode = BackingStoreAsyncPolicy.WriteBehind;
          cache.DefaultCreatePolicy.BackingStoreInterval = TimeSpan.FromSeconds(1);

          customer = cache[new Customer(1).CacheKey] as Customer; // See if this customer exists

          if (customer == null)
         {
             customer = new Customer(1, 1, "ASP.NET Loaded " + DateTime.Now.ToString());
             customer.StateServerSettings.SetServer(_applicationName);
             cache[customer.CacheKey] = customer;
             Response.Write(customer.Description + "<br/>");
          }
          else
         {
            Response.Write(customer.Description + "<br/>");
            customer.Description = "ASP.NET Changed " + DateTime.Now.ToString();
            customer.StateServerSettings.SetServer(_applicationName);
            cache[customer.CacheKey] = customer;
            Response.Write(customer.Description + "<br/>");
          }

      }
      catch (Exception ex)
     {
        Response.Write("<br/>Error reading object: " + ex.Message);
     }

   }
  }
}


By Robbe Morris   Popularity  (5632 Views)
Picture
Biography - Robbe Morris
Robbe has been a Microsoft MVP in C# since 2004. He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.  Robbe also loves to scuba dive and go deep sea fishing in the Florida Keys or off the coast of Daytona Beach. Microsoft MVP
Here's my most recent course on Pluralsight. I think it has some interesting insight on IT professional job interviews and using words in your resume to influence the questions you'll be asked. Resumes, Job Seeking, and Interviews in context.