“Nobody expects the Spanish Inquisition!” With that Cardinal Ximinez spun on his well-shined heel and pulled the heavy, copper strapped cell door closed. Laughter echoed in the tunnel as the meager light drifted away with the torch smoke.
I didn’t set out to commit heresy, I suppose noone really does.
Sprawled on a bug infested pallet, locked in a cell deep beneath the Abbey, remnants from my former life draped in ragged tatters from my bruised limbs, I wondered if my simple act of heresy was worth the heavy price I am doomed to pay. I hold scant hope for my redemption, but you gentle reader, you have a chance to learn from my descent into dissent.
Back in April…
…after resolving the last couple of issues with transparent persistence, I turned towards the scalability and performance of Seaside and GLASS. For focus I set a hypothetical goal of 100,000 requests/second – if you’re going to aim for something you might as well aim high.
Since GLASS performs one commit per HTTP request I started out by tuning GemStone/S for maximum commit performance. Before long I realized that I was barking up the wrong tree – if our commercial customers can do say 5,000 commits/second, then I’d need to come up with a 20x performance boost for commit processing to reach 100,000 requests(commits)/second – an admirable goal but not very realistic – we’ve spent 20 years tuning commit performance.
For a 20x performance improvement, I’d just have to pack more bang into each commit or somehow avoid state changes altogether. I’d already explored the ‘more bang per commit’ option way back when I first ported Seaside to GemStone, so I knew that there wasn’t going to be much gained by that route. With regards to avoiding commits, the theory is that avoiding commits for 95% of the pages could result in a 20x gain in performance. The more commits avoided the bigger the multiplier. Now we’re talking!
In May, I started exploring the reuse of session state. By the end of July, I had a proof of concept demonstrating the reuse of 99% of the session state, but I wasn’t real happy with the extent of changes I had to make to Seaside. Nor could I eliminate the decoration chain update used with #call:/#answer: the primary navigation scheme for Seaside. We aren’t going to avoid commits on 95% of the pages without being able to navigate statelessly. I need a navigation scheme that doesn’t require Seaside session state.
Last week (end of July) James Foster and I tossed around some ideas for doing navigation in Seaside without using #call:/#answer: and without using Seaside session state. After playing around with a couple of approaches I settled on the following style of URL, which I’ll call a navigation URL – it’s sorta RESTful, but not too RESTful, it is bookmarkable and it was pretty easy write a component to render pages (the most important – Ha, Ha):
http://example.com/sushi?_n&pg=Home
http://example.com/sushi?_n&pg=Sale
http://example.com/sushi?_n&pg=Catalog
http://example.com/sushi?_n&pg=CartPage
http://example.com/sushi?_n&pg=Item&id=26
I wrote an example Seaside application (Sushi-Example) using the navigation URLs. It is available on GemSource in the GemStone Examples project. As of this writing Sushi-Example-dkh.25 is the version that should be used. The Sushi-Example package can be loaded into either GLASS or Squeak Seaside. There is no styling in the example so don’t expect anything pretty. I encourage you to take a look at the example and give us feedback.
Last Sunday morning I was lying in bed mulling over the problem when it occured to me that if I dropped the ‘_k’ off a navigation URL and figured out how to render the page, I wouldn’t need to save a continuation for each page hit and most importantly I’d avoid a commit for each of those pages as well! After a little more thought, I figured it would be possible to avoid saving sesssions making it possible to drop the ‘_s’, too. By Monday afternoon, I had a prototype running.
I spent Tuesday doing some benchmarks to see if there were noticeable improvements in performance. I knew that by avoiding commits I would greatly improve the scalability of Seaside, but I was also hoping to see some performance gains, too.
I have to admit that advocating the use of navigation URLs and proposing the ‘_k’ and ‘_s’ be optional URL parameters makes me feel like a Seaside heretic…
(JARRING CHORD. The door flies open. In come three evil types in red robes.)
A Case to make ‘_k’ and ‘_s’ optional using Navigation URLs
… but here goes, the rack be damned.
I have glossed over a couple of details in the following section, so you’ll want to look at the code for the real skinny:)
In a normal Seaside application URLs are generated for links that look something like this: http://example.com/sushi?_s=68pqfS&_k=SW7A&_n. To decode this URL Seaside uses the ‘_s=68pqfS‘ to lookup a session, the ‘_k=SW7A‘ to look up a continuation in the session and the rootComponent associated with the continuation is rendered. The ‘_n’ indicates to Seaside that no redirect is needed for the rendering pass.
Consider a navigation URL that looks like this: http://example.com/sushi?pg=Item&id=26. To decode the navigation URL, Seaside creates a default session (no ‘_s’ present) and renders the rootComponent associated with a default rendering continuation (no ‘_k’ present). The rootComponent uses the ‘pg=Item&id=26′ to lookup and render the appropriate item.
When a navigation URL is used to reach a page, Seaside creates and saves a new instance of the session class. If a customer is window shopping (i.e., navigating around the site without logging in or adding an item to a cart), the session state is uninteresting and doesn’t really need to be saved. If a request URL has no ‘_s’ and no state is changed while processing the URL, the ‘_s’ could be dropped from generated navigation URLs with no loss of information. There’d be a definite advantage for web sites with lots of window shoppers as only the interesting sessions would be saved.
If a customer logs in or adds an item to a cart, then the session becomes interesting and it makes sense to propogate the ‘_s’ to all generated navigation URLs. Such an URL looks like this: http://example.com/sushi?pg=Item&id=26&_s=68pqfS&_n. To decode this URL, I would expect Seaside to use the ‘_s=68pqfS‘ to lookup a session and render the ‘pg=Item&id=26′ item. The ‘pg=Item&id=26′ is retained in the URL to make it possible to lookup and render the target page. Essentially the ‘pg=Item&id=26′ replaces the ‘_k’, which ends up serving two useful purposes:
- In a window shopping scenario, the customer will continue to navigate around the site. The interesting session state is encoded in the ‘_s’ and without a ‘_k’ there is no need to create and save session state for each page rendered.
- such a navigation URL is natively bookmarkable. If the session expires before the bookmarked URL is used, it still contains enough information to allow a valid navigation to the bookmarked page.
With navigation URLs, a ‘_k’ is needed only when a #callback: or #call: is associated with a rendered anchor or form.
Old Lady : I don’t understand what I’m accused of.
Ximinez : Ha! Then we shall MAKE you understand! Biggles! Fetch… …THE CUSHIONS!
The Benchmark
In the Sushi Example, you can use either the standard WASession or the SushiSession. When you use WASession, you’ll get ‘_s’ and ‘_k’ parameters generated for every link (along with the associated session state), while using the SushiSession, the ‘_s’ and ‘_k’ parameters are only included in the URL when needed. Since I wanted to measure the effect of the accumulation of session state (or lack thereof), I decided to run the tests for 20 minutes (double the default sessionExpory). With a 20 minute test we’ll generate a full complement of session state and presumably settle into a steady state as aged sessions expire.
For all of the tests (except Run5), I used the following siege invocation:
siege -b -d 0 -c 10 -t 20M http://example.com/seaside/examples/sushi
The arguments tell siege to hit the URL as hard as possible simulating 10 concurrent users for 20 minutes. For more on siege, see my post Scaling Seaside with GemStone/S.
For Run5, I had to increase the -c argument in order consume both CPUs on the box. Run5 was made with 300 concurrent sessions.
The Benchmark Results
The following table summarizes the results:
Run | Req/Sec | Core | Gem | VM | Machine | Session Class |
1 | 11 | 1 | 1 | S | laptop | WASession |
2 | 14 | 1 | 3 | G | Foos | WASession |
3 | 67 | 1 | 1 | S | laptop | SushiSession |
4 | 129 | 1 | 3 | G | Foos | SushiSession |
5 | 335 | 2 | 3 | G | Foos | SushiSession |
Run1 (Squeak) and Run2 (GemStone) are baseline runs and if you look at the results from my benchmarks last fall, you’ll see that the figures are comparable (previous Squeak results and previous GemStone results).
Things get interesting when you look at Run3 (Squeak). Not saving session state results in a 6x improvement in performance. Since both runs (Run1 and Run3) were made against the same Sushi-Example the difference in performance can be attributed to the reduced load on the memory management system. While I didn’t measure the size of the VM, I am sure that it is a lot smaller in Run3.
For GemStone Run4 the improvement is even more dramatic – a 9x improvement. For GemStone/S, the improvement is due to the fact that without saving session state, no commit processing overhead is incurred. I should note that on the machine Foos, no effort was made to use raw tranlogs (see Scaling Seaside with GemStone/S and GemStone 101: Transactions for info about raw tranlogs and performance) so the improvement is more dramatic than you’d see on a system that was tuned for commit performance.
For Run5 (GemStone) I added an adidtional CPU allowing the stone and 3 gems to utilize both CPUs available on the machine. This time the improvement was 2.5x. You’d expect a 2x, so the extra margin of performance is probably due to the fact that the there was very little context switching going on with 2 CPUs.
Conclusions
The quest for 100,000 pages/second doesn’t look quite as quixotic now as it looked a week ago. I think that using navigation URLs with Seaside is a valid technique especially if you are interested in a highly scalable Seaside application.
If you are using the Web Edition of GLASS, using navigation URLs will definitely make it possible to sustain rates in excess of 15 pages/second, since you will be able to avoid saving uninteresing session state in the repository.
For low traffic sites, I think navigation URLs hold promise for making it possible to get the most bang for your resource buck.
The prototype for making the ‘_k’ and ‘_s’ optional parameters is less than a week old and I know that there are some weak spots in the implementation, but I’m sure that they can be resolved:
I don’t like how navigationOnly is used in SushiSession. I’m sure that navigationOnly can be eliminated, but it might be necessary to change the logic for generating Form html, since I added navigationOnly so that Forms would work correctly.
I would like to be able to create Forms where an URL could be used as an alternative to using a #callback:. I know I risk be boiled in oil for this, but I would have liked to include a search field on nearly every page, but that would have required a #callback:.
After spending bits and pieces of 3 months working part time towards the reuse of session state, it is pleasing to see how little code it took to make the ‘_k’ and ‘_s’ parameters optional.
18 comments
Comments feed for this article
August 7, 2008 at 5:29 pm
Julian
Posted a reply to seaside-dev…
August 7, 2008 at 6:28 pm
Ramon Leon
“If a customer is window shopping (i.e., navigating around the site without logging in or adding an item to a cart), the session state is uninteresting and doesn’t really need to be saved.”
It’s interesting, but I gotta disagree about that, that will only work if the pages basically don’t have any stateful user interface controls on them, and stateful reusable controls like tabs and trees, components really, are what make Seaside so much different from page based frameworks.
You’d have to put yourself in a pretty constraining box and avoid writing anything too interesting to be able to not create state. It’d certainly be useful as an option for those parts of a site that are very static, but callbacks and components are why I use Seaside in the first place.
August 7, 2008 at 7:32 pm
Dale Henrichs
Ramon,
I hope I didn’t imply that by making _s and _k optional, you wouldn’t be able to use stateful controls.
I wanted to point out that in Seaside you pay the same cost for a page of navigation links as you do for a page of stateful controls. If you aren’t using state, then you shouldn’t pay the cost.
August 7, 2008 at 7:51 pm
Ramon Leon
Oh I know, but since you’re playing the heretic I thought I’d object and make you feel more so!
August 7, 2008 at 9:32 pm
Dale Henrichs
HaHa! When I left work tonight, I wore dark glasses and took a different route home:)
August 8, 2008 at 2:19 am
Bruce Badger
As you know, we run our web apps in GemStone. In fact, all OpenSkills apps are web apps and they all have run in GemStone for many a year. We don’t persist any session state to the database at all, ever. All session state is encoded in the pages so any gem receiving any request can work out what was going on. A get never needs to commit. Posts do, of course.
So given this, do you think we should be able to manage 100,000 tps? :-)
August 8, 2008 at 9:21 am
kentreis
What a cool optimization — only saving session state when you have to. Are you just using the standard Seaside bookmarkable URL mechanisms like updateUrl:/initialRequest: ?
August 8, 2008 at 11:02 am
Dale Henrichs
@Bruce, I’m sure that we could … just make sure to leave some tps for the REST of us:)
August 8, 2008 at 11:19 am
Dale Henrichs
@ken, I didn’t use updateUrl:/initialRequest:, I encoded the information in the query fields (‘pg’ and ‘id’). If I had used updateUrl:/initialRequest:, I would have had to stash the info in the component and end up with a commit, so I used the fields instead. There are ways to use updateUrl:/initialRequest: without changing persistent objects, but it would have complicated the example.
When I constructed an anchor I added the appropriate fields to the brush. During rendering, the component extracted the field values from the currentRequest, did a lookup and then rendered the target object.
August 11, 2008 at 2:48 am
Ryan Simmons
I was wondering how much time is used generating session state vs commiting it.
Would it be possible to not comit session state to the stone and use something like Ramons current post on scalling ( http://onsmalltalk.com/programming/smalltalk/seaside/scaling-seaside-more-advanced-load-balancing-and-publishing/ ) to redirect users to the same gem.
Any chance of being able to run some benchmarks on a configuration like that?
August 11, 2008 at 11:21 am
Dale Henrichs
Ryan,
In both runs, the session state was created, in one variant it was saved and committed and in the other it was dropped on the floor for GC to take care of – the generation of session state isn’t the issue. The issue is saving versus not saving.
The commit cost is a function of the fact that a commit is in essence a single threaded operation. A commit token is passed from stone to gem and back with a certain amount of processing performed by each process while it has the token. Over the years we’ve minimized the amount of processing that needs to be done while a process has the token and we continue to make incremental improvements, but in the final analysis there will always be a limit on commit speed that is a function of the cpu processing speed.
So, the trick to get real big scalability gains is to avoid the ‘commit bottleneck.’ The ‘share nothing’ architecture is based on this principle and came into wide use because the commit performance of RDBMs are even more limited and cpu intensive than GemStone.
As for keeping session-state in a vm and using ‘session affinity’ to route requests to the same vm ala Ramon’s post – very early in the port effort, I did take the approach where session state was not committed. At the time I was seeing performance in the range of 200 requests/second which is approaching the performance of the ‘avoid commit’ technique. However, because GemStone vms are transactional, only a single request can be handled by a vm at any one time (see the post https://gemstonesoup.wordpress.com/2007/06/29/unlimited-gemstone-vms-in-every-garage-and-a-stone-in-every-pot/ for more information). For sites that see traffic in the range of several hundred requests/second or lower, the cpu speed of commodity hardware is sufficient to supprt the commit/request model. In the final analysis you will get better overall performance using multiple GemStone vms and sharing session state across those vms with a commit per request.
Only when you start looking looking to scale into the 1,000s of requests/second, do you need to start thinking in terms of avoiding commits (or buying fancier hardware). Of course at these rates, new and interesting problems crop up no matter what route you take:).
August 18, 2008 at 3:51 pm
Bob
Bravo!
Is the following a sensible way to think about this work? Exploration along these lines might let us work entirely in Seaside, with any combination of (1) no session info at all, just blindly dish out “static” navigable pages generated **dynamically* from smalltalk objects (2) most basic session info such as who the user is with something like _u and nothing more, dishing out user-specific views, (3) stateful session-specific components .
Thanks
August 18, 2008 at 4:09 pm
Dale Henrichs
@Bob,
Yes that’s exactly the intent. With the added notion that it be possible to move back and forth between the 3 different options depending upon circumstances:
– from 1) to 2) and back to 1) after a ‘logout’
– from 2) to 3) and back to 2) if hitting a page without stateful components.
August 19, 2008 at 12:33 pm
1 Session per VM: Another Scaling Alternative « (gem)Stone Soup
[…] blame Ryan Simmons. In a comment to my previous post, he innocently asked the question (emphasis mine): Would it be possible to not […]
December 15, 2008 at 5:25 pm
y22icom
شات
September 10, 2009 at 7:46 am
sandrar
Hi! I was surfing and found your blog post… nice! I love your blog. :) Cheers! Sandra. R.
April 16, 2010 at 7:10 pm
2wer
دردشة الغلا
August 10, 2010 at 11:38 am
black celebs
Sign: wdpad Hello!!! twvsa and 7713cgrfcspxkf and 7625 : I love your site. :) Love design!!! I just came across your blog and wanted to say that Ive really enjoyed browsing your blog posts.