Merge lp:~stub/launchpad/bug-490239-session-tuning into lp:launchpad

Proposed by Stuart Bishop
Status: Merged
Merge reported by: Stuart Bishop
Merged at revision: not available
Proposed branch: lp:~stub/launchpad/bug-490239-session-tuning
Merge into: lp:launchpad
Diff against target: 120 lines (+2/-80)
2 files modified
lib/canonical/launchpad/webapp/pgsession.py (+2/-19)
lib/canonical/launchpad/webapp/tests/test_pgsession.py (+0/-61)
To merge this branch: bzr merge lp:~stub/launchpad/bug-490239-session-tuning
Reviewer Review Type Date Requested Status
Guilherme Salgado (community) release-critical Approve
Graham Binns (community) code Approve
Review via email: mp+15403@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Stuart Bishop (stub) wrote :

Currently, our session machinery prunes old session information during a request. This is silly because it might take a while, and under high load, might contribute to a death spiral of the production systems and deletion requests time out and are retried by subsequent requests. Although this is still just a theory, removing this code still seems a good idea and batch processes to prune the old data are already in place.

Revision history for this message
Graham Binns (gmb) :
review: Approve (code)
Revision history for this message
Guilherme Salgado (salgado) :
review: Approve (release-critical)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/webapp/pgsession.py'
--- lib/canonical/launchpad/webapp/pgsession.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/launchpad/webapp/pgsession.py 2009-11-30 09:55:24 +0000
@@ -54,10 +54,11 @@
54 pickle bytea NOT NULL,54 pickle bytea NOT NULL,
55 CONSTRAINT sessiondata_key UNIQUE (client_id, product_id, key)55 CONSTRAINT sessiondata_key UNIQUE (client_id, product_id, key)
56 );56 );
57
58 Removing expired data needs to be done out of band.
57 """59 """
58 implements(ISessionDataContainer)60 implements(ISessionDataContainer)
5961
60 timeout = 60 * DAYS
61 # If we have a low enough resolution, we can determine active users62 # If we have a low enough resolution, we can determine active users
62 # using the session data.63 # using the session data.
63 resolution = 9 * MINUTES64 resolution = 9 * MINUTES
@@ -67,7 +68,6 @@
6768
68 def __getitem__(self, client_id):69 def __getitem__(self, client_id):
69 """See zope.session.interfaces.ISessionDataContainer"""70 """See zope.session.interfaces.ISessionDataContainer"""
70 self._sweep()
71 return PGSessionData(self, client_id)71 return PGSessionData(self, client_id)
7272
73 def __setitem__(self, client_id, session_data):73 def __setitem__(self, client_id, session_data):
@@ -76,23 +76,6 @@
76 # themselves.76 # themselves.
77 pass77 pass
7878
79 _last_sweep = datetime.utcnow()
80 fuzz = 10 # Our sweeps may occur +- this many seconds to minimize races.
81
82 def _sweep(self):
83 interval = timedelta(
84 seconds=self.resolution - self.fuzz + 2 * self.fuzz * random()
85 )
86 now = datetime.utcnow()
87 if self._last_sweep + interval > now:
88 return
89 self._last_sweep = now
90 query = """
91 DELETE FROM SessionData WHERE last_accessed
92 < CURRENT_TIMESTAMP AT TIME ZONE 'UTC' - '%d seconds'::interval
93 """ % self.timeout
94 self.store.execute(query, noresult=True)
95
9679
97class PGSessionData(PGSessionBase):80class PGSessionData(PGSessionBase):
98 implements(ISessionData)81 implements(ISessionData)
9982
=== modified file 'lib/canonical/launchpad/webapp/tests/test_pgsession.py'
--- lib/canonical/launchpad/webapp/tests/test_pgsession.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/launchpad/webapp/tests/test_pgsession.py 2009-11-30 09:55:24 +0000
@@ -62,67 +62,6 @@
62 self.failUnless(isinstance(session_data, PGSessionData))62 self.failUnless(isinstance(session_data, PGSessionData))
63 self.failUnless(ISessionData.providedBy(session_data))63 self.failUnless(ISessionData.providedBy(session_data))
6464
65 def test_sweep(self):
66 product_id = 'Product Id'
67 client_id1 = 'Client Id #1'
68 client_id2 = 'Client Id #2'
69
70 store = self.sdc.store
71
72 # Create a session
73 session1 = self.sdc[client_id1]
74 session2 = self.sdc[client_id2]
75
76 # Store some session data to ensure we can clean up sessions
77 # with data.
78 spd = self.sdc[client_id1][product_id]
79 spd['key'] = 'value'
80
81 # Add and delete some data on the second client ID to ensure
82 # that it exists in the database.
83 session2._ensureClientId()
84
85 # Do a quick sanity check. Nothing has been stored for the
86 # third client ID.
87 result = store.execute(
88 "SELECT client_id FROM SessionData ORDER BY client_id")
89 client_ids = [row[0] for row in result]
90 self.failUnlessEqual(client_ids, [client_id1, client_id2])
91 result = store.execute("SELECT COUNT(*) FROM SessionPkgData")
92 self.failUnlessEqual(result.get_one()[0], 1)
93
94 # Push the session into the past. There is fuzzyness involved
95 # in when the sweeping actually happens (to minimize concurrency
96 # issues), so we just push it into the far past for testing.
97 store.execute("""
98 UPDATE SessionData
99 SET last_accessed = last_accessed - '1 year'::interval
100 WHERE client_id = ?
101 """, (client_id1,), noresult=True)
102
103 # Make the SessionDataContainer think it hasn't swept in a while
104 self.sdc._last_sweep = self.sdc._last_sweep - timedelta(days=365)
105
106 # Sweep happens automatically in __getitem__
107 ignored_result = self.sdc[client_id2][product_id]
108
109 # So the client_id1 session should now have been removed.
110 result = store.execute(
111 "SELECT client_id FROM SessionData ORDER BY client_id")
112 client_ids = [row[0] for row in result]
113 self.failUnlessEqual(client_ids, [client_id2])
114
115 # __getitem__ does not cause a sweep though if sweep has been
116 # done recently, to minimize database queries.
117 store.execute("""
118 UPDATE SessionData
119 SET last_accessed = last_accessed - '1 year'::interval
120 """, noresult=True)
121 session1._ensureClientId()
122 session1 = self.sdc[client_id1]
123 result = store.execute("SELECT COUNT(*) FROM SessionData")
124 self.failUnlessEqual(result.get_one()[0], 2)
125
126 def test_storage(self):65 def test_storage(self):
127 client_id1 = 'Client Id #1'66 client_id1 = 'Client Id #1'
128 client_id2 = 'Client Id #2'67 client_id2 = 'Client Id #2'