May 14, 2004
@ 08:10 AM

Welcome back ;-)

On one of those flights last week I read a short article about Enterprise Services in a developer magazine (name withheld to protect the innocent). The “teaser” introduction of the article said: “Enterprise Services serviced components are scalable, because they are stateless.” That statement is of course an echo of the same claim found in many other articles about the topic and also in many write-ups about Web services. So, is it true? Unfortunately, it’s not.

public class AmIStateless
{
       public int MyMethod()
       {
              // do some work
              return 0;
       }
}

 “Stateless” in the sense that it is being used in that article and many others describes the static structure of a class. Unfortunately that does not help us much to figure out how well instances of that class help us to scale by limiting the amount of server resources they consume. More precisely: If you look at a component and find that it doesn’t have any fields to hold data across calls (see the code snippet) and does furthermore not hold any data across calls in some secondary store (such as a “session object”), the component can be thought of as being stateless with regards to its callers, but how is the relationship with components and services that are called from it?

But before I continue: Why do we say that “stateless” scales well?

A component (or service) that does not hold any state across invocations has many benefits with regards to scalability. First, it lends itself well to load balancing. If you run the same component/service on a cluster of several machines and the client needs to make several calls, the client can walk up to any of the cluster machines in any order. That way, you can add any number of machines to the cluster and scale linearly. Second, components that don’t hold state across invocations can be discarded by the server at will or can be pooled and reused for any other client. This saves activation (construction) cost if you choose to pool and limits the amount of resources (memory, etc.) that instances consume on the server-end if you choose to discard components after each call. Pooling saves CPU cycles. Discarding saves memory and other resources. Both choices allow the server to serve more clients.

However, looking at the “edge” of a service isn’t enough and that’s where the problem lies.

The AmIStateless service that I am illustrating here does not stand alone. And even though it doesn’t keep any instance state in fields as you can see from the code snippet, it is absolutely not stateless. In fact, it may be a horrible resource hog. When the client makes a call to a method of the service (or otherwise sends a message to it), the service does its work by employing the components X and Y. Y in turn delegates work to an external service named ILiveElsewhere. All of a sudden, the oh-so-stateless AmIStateless service might turn into a significant resource consumer and limit scalability.

First observation: While no state is held in fields, the service does hold state on the stack while it runs. All local variables that are kept in on the call stack in the invoked service method, in X and in Y are consuming resources and depending on what you do that may not be little. Also, that memory will remain consumed until the next garbage collector run.

Second observation: If any of the secondary components takes a long time for processing (especially ILiveElsewhere), the service consumes and blocks a thread for a long time. Depending on how you invoke ILiveElsewhere you might indeed consume more than just the thread you run on.

Third observation:  If AmIStateless is the root of a transaction, you consume significant resources (locks) in all backend resource managers until the transaction completes – which may be much later than when the call returns. If you happen to run into an unfortunate situation, the transaction may take a significant time (minutes) to resolve.

Conclusion:  Since the whole purpose of what we usually do is data processing and we need to pass that data on between components, nothing is ever stateless while it runs. “Stateless” is a purely static view on code and only describes the immediate relationship between one provider and one consumer with regards to how much information is kept across calls. “Stateless” says nothing about what happens during a call.

Consequence: The scalability recipe isn’t to try achieving static statelessness by avoiding holding state across calls. Using this as a pattern certainly helps the naïve, but the actual goal is rather to keep sessions (interaction sequence duration) as short as possible and therefore limit the resource consumption of a single activity. A component that holds state across calls but for which the call sequence takes only a very short time or which does not block a lot of resources during the sequence may turn out to aid scalability much more than a component that seems “stateless” when you look at it, but which takes a long time for processing or consumes a lot of resources while processing the call. One way to get there is to avoid accumulating state on call stacks. How? Stay tuned.

Saturday, May 15, 2004 3:17:06 AM UTC
Hi.

Good point, thanks for sharing it with us.

Being quite new to the .NET, I have a small question though - I hope it will not be too silly. You wrote:

First observation:
... All local variables that are kept in on the call stack in the invoked service method,
... Also, that memory will remain consumed until the next garbage collector run.

I got the impresion, that the garbage collection is only operating on the managed heap. In this way, I would expect that the stack dedicated to local varibles is not subject to garbage collection... Of course, if the "stateless" service creates new objects, then the consumed memory will not be freed until the next garbage collector run.
Can you tell me if my reasoning is correct and if not - explain where I made the mistake.

Regards and keep the good work,
Tadej

Tadej Mali
Saturday, May 15, 2004 2:06:26 PM UTC
Immediate stack-resident elements are cleaned up as you leave the frame, but in the CLR world this is only true for value types. By average, a CLR stack frame contains more heap references than a C++ stack frame.
Clemens Vasters
Saturday, May 15, 2004 5:44:07 PM UTC
Of course scalability may be increased by having a stateful service in certain scenarios. What if the service had millions of requests per minute, but only had a very few unique clients making those requests? You could save bandwidth by storing state on a per-client basis. For example - instead of sending a large amount of stateful data back to the client on every round trip, it could be stored in memory on in a state store - and the amount of data saved in every round trip is multiplied by the millions of requests per minute to get a large bandwidth saving.
RichB
Wednesday, May 19, 2004 3:09:44 PM UTC
If X and Y objects are stateless, or their state does not matter for some reason in particular case, then internally IamStateless service could keep them in its instance state, so it don't need to recreated X a Y object every time when its method called. IamService instances could be pooled, so number its instances would always be equal to number of client request processed simultaneously. This way client could always have an instance of IamService available and at the same time the number of resources used by the service would be under control.
Otherwise if X and Y objects are state full then you need to recreate those for each call, or keep the same set of objects in client session state so you could reuse them in next call from the same client to the same method.
I wonder if you would suggested to use messaging as solution of this problem. Since messages are de-queued by the limited number of processes you could control amount of consumed resources by making de-queuing process to own all resources required for message processing.
For example we have message queue specific for IamStateless service served by 4 processes, each process requires one X and Y object and one virtual connection to the ILiveElsewhere service.
But I’m not so familiar to Indigo; I don’t know how it works.
Armancho
Tuesday, May 25, 2004 3:49:49 PM UTC
You highlighted a very good point. Normally when we say a component is stateless it is in regards to its clients, and that means the component does not keep state between its method calls. For example assume we have a Customer object, which has got two methods: GetDetails and SendEmail. As an stateless object, Customer does not have any properties/fields to keep the EmailAddress when the client first calls GetDetails and then SendEmail. So in other terms, when clients are accessing a stateless resource, they should know that they shouldn't expect the server to remember them every time.

You mentioned that even if a service is stateless, it is using local resources and also it may be using other expensive resources. That is correct, but I don't see how it prevents us from achieving the expected scalability. Consider the following solution:

We have got web-based application, consisting of four layers:

Presentation: Browser + ASP.NET
Service Interface: Web Services
Business: .NET Components
Data: .NET Components

In the service interface layer if we don't enable session state, then we get the scalability and we can add more hardware and create a web-farm to address higher demand.
Now we are concerned that our business layer is doing a complex processing or accessing an expensive resource. We can address this by adding more hardware, each of which hosts the service interface, business and data layers (I am assuming that all of the bottom 3 layers are deployed on the same machine).

We are still holding the state on the stack while each method of the Service Interface is running and we are performing expensive processes, but since it is stateless, we can scale out at the very same layer. So we get n times stack space and processing power. If that layer was not scalable, then we couldn't scale out.

Technically all of the layers can be made stateless, and deployed to multiple machines to get the highest level of scalability (assume a farm for web servers, another one for business components ...) but scalability can be an enemy for performance. If our components are spanning 4/5 different machines, then the overhead of activating objects on those machines could be very high. Also as RichB mentioned, it makes sense to keep the state between method calls if we are working with large amounts of data. We really don't want to send large amounts of data back and forth each time. We can populate a business entity (e.g. an Order with 1000 order lines) and keep it on the stack while the calling component is doing its job. We are loosing scalability here but we are achieving the scalability at a higher level, so we can have multiple copies of that object on the same machine or scale out and use additional nodes.

The other problem with creating all of the components stateless is that in many cases we can't scale because the technology doesn't support it (or we don't want to use that technology!). For example I don't like the idea of deploying my business layer components (which are .NET components) onto many computers and then use Windows load balancing to do it for me. This is because I don't like Windows load balancing. But I like doing DNS load balancing because it is cheap, easy to setup and maintain. So what I normally do is to make the service interface stateless, host it on the web server and then use DNS load balancing.

As you mentioned, when we are architecting our solution, we should try to find the bottlenecks and make sure we have a solution for it. For example the whole idea of being stateless and doing load-balancing doesn't make sense if we are using an Access database!
Comments are closed.