Session concurrency and locking

I've been maintaining an application for the last 6 years. During those 6 years, we noticed what we came to call "the bad problem".

This application stores its sessions in a postgres database(so that we can load balance the web servers, and not use sticky sessions). During the busy season, we would notice db backends building up. Eventually, they could build up to the point where memory on the db server was exhausted, and new connections would be rejected. This was bad. For the longest time we couldn't find the source of our problem. We theorized it had to do with session locking, and we spent a lot of time building tools to track down how it happened.

short summary of tools created to debug:
startProfile('smarty','display');endProfile('smarty','display');
tracksession.php(a tool to show all the requests by a certain sessionId from the profile log)
set statement_timeout

Short summary of problem:
When a browser loses its internet connection unexpectedly, Apache can't tell that the connection is dead, so waits as long as its Timeout directive for a packet to be received from the client. This means that in PHP, an echo() call can hang as long as 120 seconds by default on newer apache installations. Any time your application will send data to the client, you should/must call session_write_close to avoid locking the session for the length of Apache's Timeout directive.

Long description of tools for tracking the problem:
startProfile('smarty','display');endProfile('smarty','display');
startProfile and endProfile are functions for logging performance of requests. By using the define('logprofile', 'print'); in your etc file, you can see what the measurements are, which is useful in development. define('logprofile', '/home/apps/log/profile'); will log the performance to a file(actually two files in the directory, profile.log and longest.log). Zoop already log's the total request time, and the time taken in sql queries, as well as the number of sql queries and the longest sql query. By adding startProfile('myapp', 'myfunc'); at the beginning of a function and endProfile('myapp','myfunc'); at the end, you can add profiling of your own functions. The first param, 'myapp' is the grouping used. All the calls to startProfile('myapp') will be grouped together and reported as a total time in the log. It will also log which 'myfunc' was the longest. A line from the profile.log can look like this:

"01/26/09 02:49:59.0779", "@ROOT/keepOpen/get", "1", "0.018956899642944", "0.0052809715270996", "query_497d24d71c653", "/home/apps/code/myapp/index.php @ line 51", "0.0052809715270996", "session", "1", "0.0051000118255615", "0.0051000118255615", "session_connect", "/home/apps/code/myapp/index.php @ line 51", "https://example.com/myapp/index.php/keepOpen", "3c928e7093a0da3ef8e91409a05fdf61"

First is the time the request started in UTC. Then the name of each zone in the request followed by the page in the request, then the method(get or post). zone_default is known as @ROOT. then it displays the number of sql queries, the total time of the request, the time spent in seql queries, a code to look up the longest query in longest.log, the location the longest query was called from, and the length of the longest query. Then it begins to list the other groupings you have specified. In this case the request called startProfile('session', 'session_connect');endProfile('session','session_connect'); and that was all. The first item after "session" is the number of calls in the "session" group, the total time, the longest call, the name of the longest call, the location of the longest call, then the url of the request and the session_id.

My favorite tool we created is tracksession.php. It uses the profile.log to see exactly what requests were made during a session from start to finish, and then we can diagnose any performance problems the user may see. It greps the profile.log file for a string that you give it, returning the rows that match. It's quite simple, but very helpful. It can be used to either track a session, or simply to see all the requests that are similar, or were made in the same minute.

Timeout values were masking our problem in a couple ways. PHP has "max_execution_time" that is set by default to 30 seconds in the php.ini. Obviously our requests were taking much longer than that. From a note at http://us2.php.net/set-time_limit

Note: The set_time_limit() function and the configuration directive max_execution_time only affect the execution time of the script itself. Any time spent on activity that happens outside the execution of the script such as system calls using system(), stream operations, database queries, etc. is not included when determining the maximum time that the script has been running.

So our database calls were not being terminated after 30 seconds, but waiting indefinitely for the lock. And the original request that hung was not being terminated after 30 seconds, but waiting for apache to timeout the connection. Using sql_query("SET statement_timeout 1000");, we are able to control at least the script waiting for the lock. It will now wait for a second at a time. If you then allow the query to be retried 10 times, you can give a script 10 seconds to get the lock, then trigger_error and terminate the script, minimizing the buildup of db processes.

I'll be committing some of these tools to zoop. I hope you enjoy them!

Session and Concurrency

SQL Server allows multiple clients to use the same database concurrently. As such, it needs to control concurrent access to shared data, to ensure data integrity - when multiple clients update the same data, or clients attempt to read data that is in the process of being changed by another client. SQL Server provides two modes of concurrency control: pessimistic concurrency and optimistic concurrency. When pessimistic concurrency control is being used, SQL Server controls concurrent access by using locks. Locks can be either shared or exclusive. Exclusive lock grants the user exclusive access to the data - no other user can access the data as long as the lock is held. Shared locks are used when some data is being read - multiple users can read from data locked with a shared lock, but not acquire an exclusive lock. The latter would have to wait for all shared locks to be released. Locks can be applied on different levels of granularity - on entire tables, pages, or even on a per-row basis on tables. For indexes, it can either be on the entire index or on index leaves. The level of granularity to be used is defined on a per-database basis by the database administrator. While a fine grained locking system allows more users to use the table or index simultaneously, it requires more resources. So it does not automatically turn into higher performing solution. SQL Server also includes two more lightweight mutual exclusion solutions - latches and spinlocks - which are less robust than locks but are less resource intensive. SQL Server uses them for DMVs and other resources that are usually not busy. SQL Server also monitors all worker threads that acquire locks to ensure that they do not end up in deadlocks - in case they do, SQL Server takes remedial measures, which in many cases is to kill one of the threads entangled in a deadlock and rollback the transaction it started.[19] To implement locking, SQL Server contains the Lock Manager. The Lock Manager maintains an in-memory table that manages the database objects and locks, if any, on them along with other metadata about the lock. Access to any shared object is mediated by the lock manager, which either grants access to the resource or blocks it.

SQL Server also provides the optimistic concurrency control mechanism, which is similar to the multiversion concurrency control used in other databases. The mechanism allows a new version of a row to be created whenever the row is updated, as opposed to overwriting the row, i.e., a row is additionally identified by the ID of the transaction that created the version of the row. Both the old as well as the new versions of the row are stored and maintained, though the old versions are moved out of the database into a system database identified as Tempdb. When a row is in the process of being updated, any other requests are not blocked (unlike locking) but are executed on the older version of the row. If the other request is an update statement, it will result in two different versions of the rows - both of them will be stored by the database, identified by their respective transaction IDs.
-----------
Show your incredibility in ccnp exams to conquer the world of IT!

Matt John 15 Oct 2009

Load Balancing With Proxies

I am curious with the presence of MYSQL proxy and Apache or Squid proxy does the application still need to know anything about the load balancing which you had written into the application? I know that MYSQL proxy did not exist until recently and you had written the software starting 6 years ago, but taking that into account would you still need to load balance within the application and not use the servers?

jmorant@cloud9l... 16 Oct 2009

Apache reverse proxy load balancing

A late reply :). With the right configuration, the application doesn't need to know anything about the load balancing. With "ProxyPreserveHost On", zoop will correctly build SCRIPT_URL, VIRTUAL_URL and BASE_HREF. I don't know much about Mysql Proxy, we use Postgresql, and currently just have the one server in use by the application.

john 08 Jul 2010

sessions

I certainly appreciate the concurrency control mechanisms included in SQL servers, which really streamlines things for my wordpress sites. The tracksessions.php sounds really interesting too, maybe a quick fix, sort of like tanning beds, hahaa.

JerryD 23 Jul 2010

i agree with you Jerry that

i agree with you Jerry that it is really streamlines things for my wordpress sites.a good post!

email extractor

meggga 08 Aug 2010