Skip to content
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

fix: reduce request transaction lifetime #3299

Merged
merged 1 commit into from
Sep 27, 2022

Conversation

dnephin
Copy link
Contributor

@dnephin dnephin commented Sep 23, 2022

PostgreSQL row and table locks are held until the end of the transaction.

Previously we were using a single transaction for the entire lifetime of the request. One of the first things we did with that transaction was update the access key and user record associated with the request.

That update would block any other requests from that user until the first request finished. For short lived requests that was fine, but longer requests (anything making HTTP requests to an IDP, or blocking and waiting for updates) would prevent the user from making concurrent requests.

This commit fixes the problem by committing the "middleware" transaction first, then starting a new transaction for the request handler. The middleware transaction should be short lived, because it only does a few database operations and returns. It doesn't block or make external requests.

@@ -86,6 +92,7 @@ func authenticateRequest(c *gin.Context, tx *data.Transaction, srv *Server) erro
return internal.ErrBadRequest
}

// TODO: move to caller
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the transaction is separate, the only thing these two functions provide is the access.Authenticated struct. So I think we can return that directly instead of mutating the gin.Context.

I'm going to look to address these TODOs in a follow up PR. It requires a few changes to handleInfraDestinationHeader.

@@ -75,7 +75,13 @@ func handleInfraDestinationHeader(c *gin.Context) error {
// gin.Context.
// See validateRequestOrganization for a related function used for unauthenticated
// routes.
func authenticateRequest(c *gin.Context, tx *data.Transaction, srv *Server) error {
func authenticateRequest(c *gin.Context, srv *Server) error {
tx, err := srv.db.Begin(c.Request.Context())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will still work without explicitly starting a transaction, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I guess that is true. I haven't looked at all the queries, but it might be ok without an explicit transaction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think there is any advantage to not using a transaction? I thought generally we would want to put things into short lived transactions.

@@ -131,7 +142,13 @@ func validateOrgMatchesRequest(req *http.Request, tx data.GormTxn, accessKeyOrg
//
// validateRequestOrganization is also responsible for adding RequestContext to the
// gin.Context.
func validateRequestOrganization(c *gin.Context, tx *data.Transaction, srv *Server) error {
func validateRequestOrganization(c *gin.Context, srv *Server) error {
tx, err := srv.db.Begin(c.Request.Context())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also optional?

PostgreSQL row and table locks are held until the end of the
transaction.

Previously we were using a single transaction for the entire lifetime of
the request. One of the first things we did with that transaction was
update the access key and user record associated with the request.

That update would block any other requests from that user until the
first request finished. For short lived requests that was fine, but
longer requests (anything making HTTP requests to an IDP, or blocking
and waiting for updates) would prevent the user from making concurrent
requests.

This commit fixes the problem by committing the "middleware" transaction
first, then starting a new transaction for the request handler.
The middleware transaction should be short lived, because does only a
few database operations and returns. It doesn't block or make external
requests.
@dnephin dnephin force-pushed the dnephin/api-separate-transaction-for-middleware branch from b697a95 to ee92dec Compare September 27, 2022 15:20
@dnephin dnephin merged commit 5406d44 into main Sep 27, 2022
@dnephin dnephin deleted the dnephin/api-separate-transaction-for-middleware branch September 27, 2022 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants