-
Notifications
You must be signed in to change notification settings - Fork 263
Connections and transactions meta issue #456
Description
Hi all, we've been experiencing some issues with Databases regarding connection and transaction management, so I've spent some time reading through the different issues and PRs and understanding the code. There are couple cross cutting problems that I think need discussion as to how to best tackle them. I'll do my best to outline the past and pressent issues and their causes.
- Databases makes use of ContextVars to store the Connection objects across tasks. When a query method (execute, fetch, etc) is awaited the
connection
method is called which checks for the presence of a connection in the context, if there is one it is returned, otherwise it creates a new connection and sets it in the context (https://github.com/encode/databases/blob/master/databases/core.py#L190-L202). - However, as outlined in Using ContextVar to hold connections does not guarantee they are always unique to tasks. #230, once a connection is created all coroutines/tasks inherit the context from the parent, which effectively means the same connection will be returned. It's unclear to me if this was an intentional decision or an oversight, but it's the cause of issues regarding transactions opened in concurrent tasks, reported in PostgreSQL: Transaction rollback errors with "another operation is in progress" #340 and Async execution of transactions results in deadlock #327.
- A PR was opened and merged addressing this issue which essentially moves away from the previous behaviour of one connection being shared to each time
transaction
is called a new connection is created. This change was part of the 0.5.0 release. However, this change led to issues where transactions are not rolled back correctly (Database transaction is not rollbacked #403, Clarification on transaction isolation and management #424, Transaction rollback seems to be unsuccessful #425), although it's not entirely clear to me why (any clarification on this respect would be appreciated).
In summary, there's two valid use cases, concurrent transaction management and predictable non-concurrent transaction rollback, that seem at odds with each other in the current implementation. Ideally we'd find a solution for #327 that does not require creating a new connection per transaction.
I also think it might be worth clarifying the connection management model, the docs suggest the connections are handled transparently but it's unclear when actual backend connections are created, from the code it seems they're only created once on Connection.__aenter__
and since the first created connection is set in the context and returned at all times, there's effectively a single backend connection acquired at any point in time.
Hopefully this summary sparks a conversation around possible fixes for these problems, please do let me know if I misunderstood anything.