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

Immediately destroy stream created in rejected Promise.all() #634

Closed
matthew-white opened this issue Oct 4, 2022 · 1 comment
Closed
Assignees

Comments

@matthew-white
Copy link
Member

matthew-white commented Oct 4, 2022

We saw in #482 that the following sequence is possible:

  • A stream is created within a Promise.all().
  • The stream is to be processed after the Promise.all() is fulfilled.
  • However, another promise in the Promise.all() is rejected.
  • As a result, the Promise.all() as a whole is rejected.
  • As a result, the stream is never processed and isn't destroyed.

We fixed #482 by destroying any stream that hasn't been read from in 2 minutes (#604). That's a general fix that also addresses other problem scenarios. However, in the sequence above, it would be great if we could destroy the stream more quickly than 2 minutes. I think this comes up in three places, all involving submissions:

return Promise.all([
(options.deletedFields === true) ? Forms.getMergedFields(form.id) : Forms.getFields(form.def.id),
Submissions.streamForExport(form.id, draft, keys, options),
(options.splitSelectMultiples !== true) ? null : Submissions.getSelectMultipleValuesForExport(form.id, draft, options),
SubmissionAttachments.streamForExport(form.id, draft, keys, options),
ClientAudits.streamForExport(form.id, draft, keys, options),
draft ? null : Audits.log(auth.actor, 'form.submission.export', form)
]).then(([fields, rows, selectValues, attachments, clientAudits]) => {

return Promise.all([
(options.deletedFields === true) ? Forms.getMergedFields(form.id) : Forms.getFields(form.def.id),
Submissions.streamForExport(form.id, draft, Object.keys(passphrases), options),
(options.splitSelectMultiples !== true) ? null : Submissions.getSelectMultipleValuesForExport(form.id, draft, options),
Keys.getDecryptor(passphrases),
draft ? null : Audits.log(auth.actor, 'form.submission.export', form)
])

return Promise.all([
Forms.getFields(form.def.id).then(selectFields(query, params.table)),
Submissions.streamForExport(form.id, draft, undefined, options),
((params.table === 'Submissions') && options.hasPaging())
? Submissions.countByFormId(form.id, draft, options) : resolve(null)
])

I believe that #564 would fix the first two cases by moving the stream(s) out of the Promise.all(). We'd still have to fix the third case. #608 takes a similar approach to #564 and fixes the third case as well, but it didn't seem to perform well when we benchmarked it. We also have #609, which takes a different approach to fix the three cases.

We're planning to return to this issue after finishing our work with #564.

@matthew-white matthew-white modified the milestones: v2023.1, v2023.2 Nov 16, 2022
@matthew-white matthew-white moved this to 🕒 backlog in ODK Central Dec 20, 2022
@matthew-white matthew-white removed the status in ODK Central Dec 20, 2022
@matthew-white matthew-white moved this to 🕒 backlog in ODK Central Dec 21, 2022
@matthew-white matthew-white removed this from the v2023.2 milestone Jan 3, 2023
@matthew-white matthew-white self-assigned this Jan 13, 2023
@matthew-white matthew-white moved this from 🕒 backlog to ✏️ in progress in ODK Central Mar 10, 2023
@matthew-white
Copy link
Member Author

We've decided to close this issue. We know that there are other, more likely situations in which a stream won't be destroyed for 2 minutes. The main example that comes to mind is premature closes, which are pretty common and can lead to a stream lingering for 2 minutes. In contrast, the case described in this issue should be pretty uncommon. The main time that we've seen it is when there is already other pressure on the database connection pool, resulting in a Backend request acquiring a connection for the stream but not the next query after the stream. So if there is already pressure on the connection pool, this issue could exacerbate it. But we don't expect such pressure on the connection pool in normal usage: now that pm2 is working, Backend has access to up to 40 database connections (10 connections × 4 pm2 instances). Even if the connection pool does become full, Backend will destroy the stream after 2 minutes, reclaiming the connection. The main concern I have with this issue is that it could impede load testing, during which we expect there to be pressure on the connection pool. However, it doesn't seem like that's been the case in the load testing that we've done so far.

@github-project-automation github-project-automation bot moved this from ✏️ in progress to ✅ done in ODK Central Mar 22, 2023
@matthew-white matthew-white moved this from ✅ done to ✏️ in progress in ODK Central Mar 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant