Edgewall Software

Ticket #3067: session.patch

File session.patch, 13.5 kB (added by jonas, 2 years ago)

A more optimized session schema (updated).

  • trac/db_default.py

     
    1717from trac.db import Table, Column, Index 
    1818 
    1919# Database version identifier. Used for automatic upgrades. 
    20 db_version = 17 
     20db_version = 18 
    2121 
    2222def __mkreports(reports): 
    2323    """Utility function used to create report data in same syntax as the 
     
    4848        Column('time', type='int')], 
    4949    Table('session', key=('sid', 'authenticated', 'var_name'))[ 
    5050        Column('sid'), 
     51        Column('time', type='int'), 
    5152        Column('authenticated', type='int'), 
    5253        Column('var_name'), 
    53         Column('var_value')], 
     54        Column('var_value'), 
     55        Index(['sid']), 
     56        Index(['time']), 
     57        Index(['authenticated'])], 
    5458 
    5559    # Attachments 
    5660    Table('attachment', key=('type', 'id', 'filename'))[ 
     
    119123        Column('field'), 
    120124        Column('oldvalue'), 
    121125        Column('newvalue'), 
    122         Index(['ticket', 'time'])], 
     126        Index(['ticket']), 
     127        Index(['time'])], 
    123128    Table('ticket_custom', key=('ticket', 'name'))[ 
    124129        Column('ticket', type='int'), 
    125130        Column('name'), 
  • trac/tests/env.py

     
    2727        """Testing env.get_known_users""" 
    2828        cursor = self.db.cursor() 
    2929        cursor.execute("INSERT INTO session " 
    30                        "VALUES ('123',0,'email','a@example.com')") 
    31         cursor.executemany("INSERT INTO session VALUES (%s,1,%s,%s)", 
     30                       "VALUES ('123',0,0,'email','a@example.com')") 
     31        cursor.executemany("INSERT INTO session VALUES (%s,0,1,%s,%s)", 
    3232                           [('tom', 'name', 'Tom'), 
    3333                            ('tom', 'email', 'tom@example.com'), 
    3434                            ('joe', 'email', 'joe@example.com'), 
  • trac/upgrades/db18.py

     
     1from trac.db import Table, Column, Index, DatabaseManager 
     2 
     3def do_upgrade(env, ver, cursor): 
     4    cursor.execute("CREATE TEMP TABLE session_old AS SELECT * FROM session") 
     5    cursor.execute("DROP TABLE session") 
     6    cursor.execute("CREATE TEMP TABLE ticket_change_old AS SELECT * FROM ticket_change") 
     7    cursor.execute("DROP TABLE ticket_change") 
     8 
     9    # A slightly more denormalized session schema where the 'last_visit' values are 
     10    # stored in a column for performance reasons 
     11    session = Table('session', key=('sid', 'authenticated', 'var_name'))[ 
     12        Column('sid'), 
     13        Column('time', type='int'), 
     14        Column('authenticated', type='int'), 
     15        Column('var_name'), 
     16        Column('var_value'), 
     17        Index(['sid']), 
     18        Index(['time']), 
     19        Index(['authenticated'])] 
     20 
     21    # The old ticket_change table had a composite index on (ticket, time), this however 
     22    # does not help us when we need to order changes by time (for the timeline). 
     23    # 
     24    # Unfortunately this index is called "ticket_change_idx" on old environments 
     25    # and "ticket_change_ticket_time_idx" on newer ones. This makes it easiest to 
     26    # simply recreate the entire table. 
     27    ticket_change = Table('ticket_change', key=('ticket', 'time', 'field'))[ 
     28        Column('ticket', type='int'), 
     29        Column('time', type='int'), 
     30        Column('author'), 
     31        Column('field'), 
     32        Column('oldvalue'), 
     33        Column('newvalue'), 
     34        Index(['ticket']), 
     35        Index(['time'])] 
     36     
     37    db_connector, _ = DatabaseManager(env)._get_connector() 
     38    for stmt in db_connector.to_sql(session): 
     39        cursor.execute(stmt) 
     40    for stmt in db_connector.to_sql(ticket_change): 
     41        cursor.execute(stmt) 
     42 
     43    # Add an index to the temporary table to speed up the conversion 
     44    cursor.execute("CREATE INDEX session_old_sid_idx ON session_old(sid)") 
     45    # Insert the sessions into the new table 
     46    cursor.execute("INSERT INTO session (sid, time, authenticated, " 
     47                   "var_name, var_value) SELECT " 
     48                   "s.sid, s2.var_value, s.authenticated, s.var_name, s.var_value " 
     49                   "FROM session_old s, session_old s2 " 
     50                   "WHERE s.sid=s2.sid AND s2.var_name='last_visit' AND " 
     51                   "s.var_name <> 'last_visit'") 
     52 
     53    # Insert ticket change data into the new table 
     54    cursor.execute("INSERT INTO ticket_change " 
     55                   "(ticket, time, author, field, oldvalue, newvalue) " 
     56                   "SELECT ticket, time, author, field, oldvalue, newvalue " 
     57                   "FROM ticket_change_old") 
     58 
     59 
  • trac/web/tests/session.py

     
    6565        authenticated session when the user logs in. 
    6666        """ 
    6767        cursor = self.db.cursor() 
    68         cursor.execute("INSERT INTO session VALUES ('123456', 0, 'foo', 'bar')") 
     68        cursor.execute("INSERT INTO session VALUES ('123456', 0, 0, 'foo', 'bar')") 
    6969 
    7070        incookie = Cookie() 
    7171        incookie['trac_session'] = '123456' 
     
    103103        accordingly for an anonymous session. 
    104104        """ 
    105105        cursor = self.db.cursor() 
    106         cursor.execute("INSERT INTO session VALUES ('123456', 0, 'foo', 'bar')") 
     106        cursor.execute("INSERT INTO session VALUES ('123456', 0, 0, 'foo', 'bar')") 
    107107 
    108108        incookie = Cookie() 
    109109        incookie['trac_session'] = '123456' 
     
    123123        for an anonymous session. 
    124124        """ 
    125125        cursor = self.db.cursor() 
    126         cursor.execute("INSERT INTO session VALUES ('123456', 0, 'foo', 'bar')") 
     126        cursor.execute("INSERT INTO session VALUES ('123456', 0, 0, 'foo', 'bar')") 
    127127 
    128128        incookie = Cookie() 
    129129        incookie['trac_session'] = '123456' 
     
    143143        """ 
    144144        cursor = self.db.cursor() 
    145145        cursor.execute("INSERT INTO session " 
    146                        "VALUES ('987654', 0, 'last_visit', %s)", 
     146                       "VALUES ('987654', %s, 0, 'foo', 'bar')", 
    147147                       (time.time() - PURGE_AGE - 3600,)) 
    148148         
    149149        # We need to modify a different session to trigger the purging 
     
    169169        # Make sure the session has data so that it doesn't get dropped 
    170170        cursor = self.db.cursor() 
    171171        cursor.execute("INSERT INTO session " 
    172                        "VALUES ('123456', 0, 'last_visit', %s)", 
     172                       "VALUES ('123456', %s, 0, 'foo', 'bar')", 
    173173                       (int(now - UPDATE_INTERVAL - 3600),)) 
    174174 
    175175        incookie = Cookie() 
     
    177177        req = Mock(authname='anonymous', base_path='/', incookie=incookie, 
    178178                   outcookie=Cookie()) 
    179179        session = Session(self.env, req) 
     180        del session['foo'] 
    180181        session.save() 
    181182 
    182183        cursor.execute("SELECT COUNT(*) FROM session WHERE sid='123456' AND " 
     
    203204        accordingly for an authenticated session. 
    204205        """ 
    205206        cursor = self.db.cursor() 
    206         cursor.execute("INSERT INTO session VALUES ('john', 1, 'foo', 'bar')") 
     207        cursor.execute("INSERT INTO session VALUES ('john', 0, 1, 'foo', 'bar')") 
    207208 
    208209        req = Mock(authname='john', base_path='/', incookie=Cookie()) 
    209210        session = Session(self.env, req) 
     
    220221        for an authenticated session. 
    221222        """ 
    222223        cursor = self.db.cursor() 
    223         cursor.execute("INSERT INTO session VALUES ('john', 1, 'foo', 'bar')") 
     224        cursor.execute("INSERT INTO session VALUES ('john', 0, 1, 'foo', 'bar')") 
    224225 
    225226        req = Mock(authname='john', base_path='/', incookie=Cookie()) 
    226227        session = Session(self.env, req) 
     
    240241 
    241242        # Make sure the session has data so that it doesn't get dropped 
    242243        cursor = self.db.cursor() 
    243         cursor.executemany("INSERT INTO session VALUES ('123456', 0, %s, %s)", 
    244                            [('last_visit', int(now - UPDATE_INTERVAL - 3600)), 
    245                             ('foo', 'bar')]) 
     244        cursor.execute("INSERT INTO session VALUES ('123456', %s, 0, %s, %s)", 
     245                           (int(now - UPDATE_INTERVAL - 3600), 'foo', 'bar')) 
    246246 
    247247        incookie = Cookie() 
    248248        incookie['trac_session'] = '123456' 
     
    254254 
    255255        self.assertEqual(PURGE_AGE, outcookie['trac_session']['expires']) 
    256256 
    257         cursor.execute("SELECT var_value FROM session WHERE sid='123456' AND " 
    258                        "authenticated=0 AND var_name='last_visit'") 
     257        cursor.execute("SELECT time FROM session WHERE sid='123456' AND " 
     258                       "authenticated=0") 
    259259        self.assertAlmostEqual(now, int(cursor.fetchone()[0]), -1) 
    260260 
    261261 
  • trac/web/session.py

     
    3434        self.env = env 
    3535        self.req = req 
    3636        self.sid = None 
     37        self.last_visit = 0 
    3738        self._old = {} 
    3839        if req.authname == 'anonymous': 
    3940            if not req.incookie.has_key(COOKIE_KEY): 
     
    5859        db = self.env.get_db_cnx() 
    5960        cursor = db.cursor() 
    6061        self.sid = sid 
    61         cursor.execute("SELECT var_name,var_value FROM session " 
     62        cursor.execute("SELECT var_name,var_value, time FROM session " 
    6263                       "WHERE sid=%s AND authenticated=%s", 
    6364                       (sid, int(authenticated))) 
    64         for name, value in cursor: 
     65        for name, value, time_ in cursor: 
    6566            self[name] = value 
     67            self.last_visit = int(time_) 
    6668        self._old.update(self) 
    6769 
    6870        # Refresh the session cookie if this is the first visit since over a day 
    69         if not authenticated and self.has_key('last_visit'): 
    70             if time.time() - int(self['last_visit']) > UPDATE_INTERVAL: 
     71        if not authenticated and self.last_visit: 
     72            if time.time() - self.last_visit > UPDATE_INTERVAL: 
    7173                self.bake_cookie() 
     74        #self.last_visit = time.time() 
    7275 
    7376    def change_sid(self, new_sid): 
    7477        assert self.req.authname == 'anonymous', \ 
     
    127130            # persist it 
    128131            return 
    129132 
    130         changed = False 
    131         now = int(time.time()) 
    132  
    133         if self.req.authname == 'anonymous': 
    134             # Update the session last visit time if it is over an hour old, 
    135             # so that session doesn't get purged 
    136             last_visit = int(self.get('last_visit', 0)) 
    137             if now - last_visit > UPDATE_INTERVAL: 
    138                 self.env.log.info("Refreshing session %s" % self.sid) 
    139                 self['last_visit'] = now 
    140  
    141             # If the only data in the session is the last_visit time, it makes 
    142             # no sense to keep the session around 
    143             if len(self.items()) == 1: 
    144                 del self['last_visit'] 
    145  
    146133        db = self.env.get_db_cnx() 
    147134        cursor = db.cursor() 
    148135        authenticated = int(self.req.authname != 'anonymous') 
     
    154141                self.env.log.debug('Adding variable %s with value "%s" to ' 
    155142                                   'session %s' % (k, v, 
    156143                                   self.sid or self.req.authname)) 
    157                 cursor.execute("INSERT INTO session VALUES(%s,%s,%s,%s)", 
    158                                (self.sid, authenticated, k, v)) 
    159                 changed = True 
     144                cursor.execute("INSERT INTO session " 
     145                               "(sid,time,authenticated,var_name,var_value) " 
     146                               "VALUES(%s,%s,%s,%s,%s)", 
     147                               (self.sid, self.last_visit, authenticated, k, v)) 
    160148            elif v != self._old[k]: 
    161149                self.env.log.debug('Changing variable %s from "%s" to "%s" in ' 
    162150                                   'session %s' % (k, self._old[k], v, 
     
    165153                               "WHERE sid=%s AND authenticated=%s " 
    166154                               "AND var_name=%s", (v, self.sid, authenticated, 
    167155                               k)) 
    168                 changed = True 
    169156 
    170157        # Find all variables that have been deleted and also remove them from 
    171158        # the database 
     
    175162            cursor.execute("DELETE FROM session WHERE sid=%s AND " 
    176163                           "authenticated=%s AND var_name=%s", 
    177164                           (self.sid, authenticated, k)) 
    178             changed = True 
    179165 
    180         if changed: 
     166        now = int(time.time()) 
     167        # Update the session last visit time if it is over an hour old, 
     168        # so that session doesn't get purged 
     169        if now - self.last_visit > UPDATE_INTERVAL: 
     170            self.last_visit = now 
     171            self.env.log.info("Refreshing session %s" % self.sid) 
     172            cursor.execute('UPDATE session SET time=%s WHERE sid=%s', 
     173                           (self.last_visit, self.sid)) 
     174             
    181175            # Purge expired sessions. We do this only when the session was 
    182176            # changed as to minimize the purging. 
    183177            mintime = now - PURGE_AGE 
    184178            self.env.log.debug('Purging old, expired, sessions.') 
    185179            cursor.execute("DELETE FROM session WHERE authenticated=0 AND " 
    186                            "sid IN (SELECT sid FROM session WHERE " 
    187                            "var_name='last_visit' AND var_value < %s)", 
    188                            (mintime,)) 
    189  
    190             db.commit() 
     180                           "time < %s", (mintime,)) 
     181        db.commit()