-
Notifications
You must be signed in to change notification settings - Fork 20
INTPYTHON-355 Add transaction support #313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
django_mongodb_backend/features.py
Outdated
@@ -97,6 +95,22 @@ class DatabaseFeatures(BaseDatabaseFeatures): | |||
"expressions.tests.ExpressionOperatorTests.test_lefthand_transformed_field_bitwise_or", | |||
} | |||
|
|||
@cached_property | |||
def supports_transactions(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of this property? Is it just for the test suite or do apps rely on it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not just for testing.
In this case, rather than declare False
or True
for supports_transactions
we dynamically set True
or False
based on what type of MongoDB we are connected to.
Then DatabaseFeatures
is passed to DatabaseWrapper
IIRC and the runtime behavior of Django changes accordingly.
Based on this: https://github.com/django/django/blob/main/django/db/backends/base/features.py#L419-L431
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It depends on what we want to implement. If we want to dynamically enable transactions based on whether or not the database supports it, we could use it internally. Otherwise, we'll have to introduce a separate setting the user sets to indicate whether or not they want to use transactions, and raise an error if it's not supported.
It's also used by Django in a couple places. For example, to determine whether or not to use transactions to speed up tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is that there are certain storage engines where a replica set or sharded cluster does not actually support transactions so this check isn't 100% accurate. That said, those should be rare so I'm fine with the current "hello" based approach. We can always revisit later if it causes an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in c8f8881 (assuming wired tiger storage engine is correct or we could also check to make sure it is not mmapv1)
django_mongodb_backend/features.py
Outdated
@@ -97,6 +95,22 @@ class DatabaseFeatures(BaseDatabaseFeatures): | |||
"expressions.tests.ExpressionOperatorTests.test_lefthand_transformed_field_bitwise_or", | |||
} | |||
|
|||
@cached_property | |||
def supports_transactions(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is that there are certain storage engines where a replica set or sharded cluster does not actually support transactions so this check isn't 100% accurate. That said, those should be rare so I'm fine with the current "hello" based approach. We can always revisit later if it causes an issue.
This comment was marked as resolved.
This comment was marked as resolved.
e68f8c9
to
4a8209a
Compare
8205175
to
1c784eb
Compare
@@ -140,6 +142,10 @@ def _isnull_operator(a, b): | |||
ops_class = DatabaseOperations | |||
validation_class = DatabaseValidation | |||
|
|||
def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS): | |||
super().__init__(settings_dict, alias=alias) | |||
self.session = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about thread safety here?
For example:
# Thread 1
with atomic():
....
# Thread 2
with atomic():
....
Won't the session property be mistakenly shared between threads?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean if the session property were to be mistakenly shared between threads that would be a Django bug … there's nothing we can do within the framework they provide (_commit
, _rollback
, etc) to manage threads as far as I know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Django, database connections are thread-local, so the use of atomic()
in two separate threads will use two separate connections (and thus two separation sessions). It's no different from any other state stored on DatabaseWrapper
[My understanding of thread locals, etc. isn't 100% rock solid, particularly since pymongo operates a bit differently than the PEP 249-conforming drivers for Django's other database backends, but I think what I've stated is correct. Let me know if you have a particular concern.]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, since each thread gets it's own instance of our DatabaseWrapper I agree this is not an issue. I have a feeling this is not the first nor the last time I will ask this same question so bare with me, Django uses a different model than I'm used to.
Referring to tests defined in |
is_sharded_cluster = hello_response.get("msg") == "isdbgrid" | ||
if is_replica_set or is_sharded_cluster: | ||
engine = client.command("serverStatus").get("storageEngine", {}) | ||
return engine.get("name") == "wiredTiger" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this less readable than the logical grouping, but not enough to protest more than adding this comment!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I nested it to avoid the call to command("serverStatus")
when unnecessary, since Shane mentioned it's expensive.
todo: