-
-
Notifications
You must be signed in to change notification settings - Fork 7.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sessions: Migrate to custom indexed session model.
Fixes #19490 and has other performance benefits. Create RealmSession model with fields: realm, user and ip_address, with combined index on (realm, user) and an index on ip_address. Migrate code to use the new model, zerver/lib/sessions' has most impact. Create data_migration to copy over data from django_session to zerver_realmsession table.
- Loading branch information
1 parent
db86027
commit efa5c75
Showing
18 changed files
with
331 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Generated by Django 4.2.11 on 2024-04-17 04:28 | ||
|
||
import django.db.models.deletion | ||
from django.conf import settings | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("zerver", "0509_fix_emoji_metadata"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="RealmSession", | ||
fields=[ | ||
( | ||
"session_key", | ||
models.CharField( | ||
max_length=40, primary_key=True, serialize=False, verbose_name="session key" | ||
), | ||
), | ||
("session_data", models.TextField(verbose_name="session data")), | ||
("expire_date", models.DateTimeField(db_index=True, verbose_name="expire date")), | ||
("ip_address", models.GenericIPAddressField(null=True)), | ||
( | ||
"realm", | ||
models.ForeignKey( | ||
null=True, on_delete=django.db.models.deletion.CASCADE, to="zerver.realm" | ||
), | ||
), | ||
( | ||
"user", | ||
models.ForeignKey( | ||
null=True, | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to=settings.AUTH_USER_MODEL, | ||
), | ||
), | ||
], | ||
options={ | ||
"indexes": [ | ||
models.Index( | ||
fields=["realm", "user"], name="zerver_realmsession_realm_user_idx" | ||
), | ||
models.Index(fields=["ip_address"], name="zerver_realmsession_ip_address_idx"), | ||
], | ||
}, | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# Generated by Django 4.2.11 on 2024-04-17 04:30 | ||
|
||
from django.db import connection, migrations, transaction | ||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||
from django.db.migrations.state import StateApps | ||
from psycopg2.sql import SQL, Composable, Identifier | ||
|
||
# This migration copies over data from existing sessions table "django_session" | ||
# to the custom session table "zerver_realmsession" which is introduced in previous migration. | ||
|
||
# Since django_session is a large table we want to do the insertion as performantly as possible, | ||
# approach is pretty simple: drop all indexes, copy data, create back the indexes, | ||
# because creating an index on pre-existing data is quicker than updating it incrementally as each row is loaded. | ||
|
||
# copy and drop operations are done in one transaction (because we can't write to postgres then update its scheme (i.e. create the indexes) in the same transaction), | ||
# create indexes is done in a separate transaction | ||
|
||
|
||
def migrate_data_from_django_session_to_realmsession( | ||
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor | ||
) -> None: | ||
session_key_index_name = "" | ||
with transaction.atomic(): | ||
with connection.cursor() as cursor: | ||
# Fetch indexes names | ||
cursor.execute( | ||
SQL("""SELECT indexname FROM pg_indexes WHERE tablename = 'zerver_realmsession' """) | ||
) | ||
|
||
indexes_tuples = cursor.fetchall() | ||
# Drop all indexes | ||
for index in indexes_tuples: | ||
if index[0] == "zerver_realmsession_pkey": | ||
query: Composable = SQL( | ||
"""ALTER TABLE zerver_realmsession drop constraint zerver_realmsession_pkey""" | ||
) | ||
else: | ||
if "session_key" in index[0]: | ||
session_key_index_name = index[0] | ||
|
||
query = SQL("""DROP INDEX {index_name}""").format( | ||
index_name=Identifier(index[0]) | ||
) | ||
|
||
cursor.execute(query) | ||
|
||
# Copy over data | ||
cursor.execute(SQL("INSERT INTO zerver_realmsession (SELECT * FROM django_session)")) | ||
|
||
# Create back the indexes | ||
with transaction.atomic(): | ||
with connection.cursor() as cursor: | ||
# Primary key creates an index by default | ||
cursor.execute( | ||
SQL( | ||
"""ALTER TABLE zerver_realmsession ADD CONSTRAINT zerver_realmsession_pkey PRIMARY KEY (session_key)""" | ||
) | ||
) | ||
|
||
# That index was auto generated, we create the index with the same name, but if a custom name won't be a problem then it would be simpler to add our custom name. | ||
cursor.execute( | ||
SQL( | ||
"""CREATE INDEX IF NOT EXISTS {index_name} ON zerver_realmsession (session_key varchar_pattern_ops)""" | ||
).format(index_name=Identifier(session_key_index_name)) | ||
) | ||
|
||
cursor.execute( | ||
SQL( | ||
"""CREATE INDEX IF NOT EXISTS zerver_realmsession_expire_date_idx ON zerver_realmsession (expire_date)""" | ||
) | ||
) | ||
|
||
cursor.execute( | ||
SQL( | ||
"""CREATE INDEX IF NOT EXISTS zerver_realmsession_realm_idx ON zerver_realmsession (realm_id)""" | ||
) | ||
) | ||
|
||
cursor.execute( | ||
SQL( | ||
"""CREATE INDEX IF NOT EXISTS zerver_realmsession_user_idx ON zerver_realmsession (user_id)""" | ||
) | ||
) | ||
|
||
cursor.execute( | ||
SQL( | ||
"""CREATE INDEX IF NOT EXISTS zerver_realmsession_realm_user_idx ON zerver_realmsession (realm_id, user_id)""" | ||
) | ||
) | ||
|
||
cursor.execute( | ||
SQL( | ||
"""CREATE INDEX IF NOT EXISTS zerver_realmsession_ip_address_idx ON zerver_realmsession (ip_address)""" | ||
) | ||
) | ||
|
||
# To do | ||
# if above queries are successful, Should we delete data from django_session table ? | ||
# TRUNCATE TABLE django_session. | ||
|
||
|
||
class Migration(migrations.Migration): | ||
atomic = False | ||
|
||
dependencies = [ | ||
("zerver", "0510_sessions_create_custom_model"), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython(migrate_data_from_django_session_to_realmsession), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from typing import Dict, Type, Union | ||
|
||
from django.contrib.sessions.backends.db import SessionStore as DbSessionStore | ||
from typing_extensions import override | ||
|
||
from zerver.models import RealmSession | ||
from zerver.models.sessions import create_realm_session_instance | ||
|
||
|
||
# Used in tests. | ||
class SessionStore(DbSessionStore): | ||
@classmethod | ||
@override | ||
def get_model_class(cls) -> Type[RealmSession]: | ||
return RealmSession | ||
|
||
@override | ||
def create_model_instance(self, data: Dict[str, Union[int, str]]) -> RealmSession: | ||
session_object: RealmSession = super().create_model_instance(data) # type: ignore[assignment] # https://github.com/typeddjango/django-stubs/issues/2056 | ||
return create_realm_session_instance(session_object, data) |
Oops, something went wrong.