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

remove slonik, insert postgres.js #564

Closed
wants to merge 10 commits into from
Closed

remove slonik, insert postgres.js #564

wants to merge 10 commits into from

Conversation

issa-tseng
Copy link
Member

No description provided.

* porsagres does not support multiple portals at once, so we have to
  serialize what were previously parallel streams.
  * we bottleneck at the csv => zip output anyway so it doesn't matter.
* using the callback interface and abusing the Promise lock to create
  a backpressure mechanism.
* the error gets passed all the way out now on .begin as opposed to
  .transaction before, so it's both a nice place to handle it as well as
  a necessary error to handle or the tests crash.
* see comments in tap.js for details.
@matthew-white
Copy link
Member

I'm surprised it's not a larger diff! It's nice that there are a few similarities between postgres.js and Slonik.

// including this file, may be copied, modified, propagated, or distributed
// except according to the terms contained in the LICENSE file.

// CRCRCR uhhhh confusing naming maybe idk
Copy link
Member Author

Choose a reason for hiding this comment

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

rm

// and p/postgres is happy to mix template literals across "connections".
const sql = _postgres(undefined, options);

module.exports = { postgres, sql, options };
Copy link
Member Author

Choose a reason for hiding this comment

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

rm options

return (v === null) ? null :
(v === undefined) ? null :
((typeof v === 'object') && (v.constructor === Object)) ? JSON.stringify(v) : // eslint-disable-line indent
(v instanceof Date) ? v.toISOString() : // eslint-disable-line indent
Copy link
Member Author

Choose a reason for hiding this comment

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

should i check for tz nastiness?

lib/util/db.js Outdated
//
// but also, they do seem a bit goofy and if anyone has a cleaner idea please give
// it a try and a pull request.
// CRCRCR: but actually maybe we can get rid of .map now and clean up the joiner call..
Copy link
Member Author

Choose a reason for hiding this comment

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

rm

obj.one = (s) => db.one(s);
obj.q = (s) => db`${s}`;

obj.one = (s) => db`${s}`.then(([ x ]) => x); // CRCRCR: slonik used to Enforce existence, do we care?
Copy link
Member Author

Choose a reason for hiding this comment

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

??

const testService = (test) => () =>
baseContainer.transacting((container) =>
test(augment(request(service(container))), container).then(rollback(resolve), rollback(reject))
).catch((f) => f());
Copy link
Member Author

Choose a reason for hiding this comment

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

call this g?

});
try {
return test(task._container).then(rollback(resolve), rollback(reject));
} catch(e) {
Copy link
Member Author

Choose a reason for hiding this comment

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

??

const _value = (x, string, parameters, types) => {
if (x instanceof Builder) return x.build(string, parameters, types, options);
else if (x instanceof Identifier) return x.value;
else if (x instanceof Query) return reduceFragment(x, types, parameters).string;
Copy link
Member Author

Choose a reason for hiding this comment

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

unbackwards?

test/util/sql.js Outdated
else if (x instanceof Identifier) return x.value;
else if (x instanceof Query) return reduceFragment(x, types, parameters).string;
else {
const value = (x?.value == null) ? x : x.value;
Copy link
Member Author

Choose a reason for hiding this comment

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

is this necessary?

test/util/sql.js Outdated

const options = { transform: { undefined: null, column: {} } };
const inferType = (x) =>
//x instanceof Parameter ? x.type :
Copy link
Member Author

Choose a reason for hiding this comment

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

?

@issa-tseng issa-tseng requested a review from ktuite August 19, 2022 22:00
@matthew-white
Copy link
Member

matthew-white commented Sep 12, 2022

I just followed the repro steps in getodk/central#482 in order to see whether getodk/central#604 fixed that issue. However, I forgot to checkout getodk/central#604 first, so I ended up running the repro steps against this PR. 😅 The result was interesting though, so I thought I'd note what I saw.

In good news, I'm no longer observing a database connection leak. 🎉 After I followed the repro steps, the pg_stat_activity count did not increase.

I ran the repro steps several times. Every time that I checked the number of successful requests, 8/10 requests were successful. That's interesting to me, because with Slonik today, it's 9/10. From a quick look, it seems like postgres.js defaults to allowing up to 10 database connections, the same as Slonik, so that doesn't seem to explain the difference.

I queried pg_stat_activity during the repro steps, running select count(*) from pg_stat_activity where state <> 'idle'. From what I can tell, the count spikes to 9, quickly decreases to 8, then continues to decrease. I'm surprised not to see a count of more like 10 or 11, which is what I'd expect if postgres.js were using 10 connections.

The requests that don't succeed return with a variety of errors. Usually it's a 500 or a 504, but I've also seen some 502s. When it's a 500, Backend is returning an error response whose message property starts with "Completely unhandled exception". All the 504s and 502s I checked were HTML responses from nginx.

The specific error details also varied. For 500s, those details showed up in the error response. In other cases, they showed up in Sentry. The two most common errors were:

  1. PostgresError: portal "" does not exist (Sentry [1][2][3])
  2. RangeError: The value of "offset" is out of range. … (Sentry [1][2])

I also saw these 1 or 2 times:

  1. TypeError: query.cursorFn is not a function (Sentry)
  2. TypeError: constructors[k] is not a function (Sentry)
  3. SyntaxError: Unexpected number in JSON at position 4 (Sentry)

(5) happened when I tried the repro steps with 20 concurrent requests instead of 10. I'm pretty sure that's true of (3) as well, and that might also be true of (4). Interestingly, when I tried 20 requests, there was also one time when I received a 404.1 Problem response ("Could not find the resource you were looking for."). There were also times when curl exited with an exit code of 6, displaying the message "Could not resolve host: test.getodk.cloud". That said, sending 20 concurrent requests resulted in more successful responses than sending 10 requests. (The count of successful requests seemed to vary. I saw 10 once.)

I have a theory about when a 500 is returned vs. a 504. Errors (1) and (2) above were thrown for stream, maybeOne, and oneFirst. I think I'm seeing a pattern that when maybeOne and oneFirst throw the error, it's a 500, while when it's stream, it's a 504.

@matthew-white
Copy link
Member

@lognaturel also noted on Slack:

For what it's worth, I was disrupted at least once right when Sentry got that portal error. I was attempting to publish a form draft in one tab and had another form’s submission page open in another. The publish request seemed to hang. I got a spinner on the modal. I got impatient and killed the window. When I came back the publish had succeeded.

I think this would be expected if the publish request was sent while all available database connections were in use by the stream requests. With Slonik, a connection attempt times out in 5 seconds, so you wouldn't expect the request to hang, but with postgres.js, it looks like the timeout is 30 seconds.

}
}
};
const postgres = (config) => _postgres(connectionString(config), options);
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure, but I think we'll need to do something more here to support SSL connections, which we currently support. From a quick glance, it looks like Slonik and postgres.js accept SSL options differently. For Slonik, we pass ?ssl=true as part of the connection string. However, the only mention of SSL that I see in the postgres.js readme has to do with an ssl option that's specified apart from the connection string.

Copy link
Member

Choose a reason for hiding this comment

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

#433 is about Slonik, but I think has some relevant discussion.

@matthew-white matthew-white added this to the v2023.1 milestone Nov 16, 2022
@matthew-white matthew-white removed this from the v2023.1 milestone Jan 24, 2023
@lognaturel lognaturel marked this pull request as draft February 27, 2023 17:45
@matthew-white
Copy link
Member

postgres.js branch that we could try to see if it fixes the issues we were seeing: https://github.com/porsager/postgres/tree/concurrent-cursors

@issa-tseng
Copy link
Member Author

issa-tseng commented Aug 16, 2023 via email

@lognaturel
Copy link
Member

Closing in favor of tracking issue at #1119

@lognaturel lognaturel closed this Mar 15, 2024
@matthew-white matthew-white deleted the issa/porsagres branch May 24, 2024 22:02
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.

4 participants