Description
I'm running in to a strange issue using this package with GCP Cloud SQL.
If I generate a lot of traffic I start to see timeout errors in places I really wouldn't expect to get timeout errors (eg: a simple insert statement). After running under high load for a while I then start getting this error:
Caught connect error, PostgreSQLSeverity.fatal 53300: remaining connection slots are reserved for non-replication superuser connections , #0 PostgreSQLConnection.open (package:postgres/src/connection.dart:133:7)
When I look at the pg_stat_activity I see many queries doing the insert with wait_event
= "ClientRead"
and state
= "idle in transaction"
. Not sure how I could fail to close a transaction?
I added a timeoutInSeconds
to both the queries in my file however now the connection pool is getting filled up with the delete queries, they show wait_event
= "tuple"
and state
= "active"
.
I think it sounds like I have a connection leak but I'm not sure how. I've included my code below.
/// Request should provide the AccountReplicateMessage object as it's body
Future<shelf.Response> accountReplicateService(shelf.Request request) async {
final requestString = await request.readAsString();
if (requestString.isEmpty) {
return shelf.Response(400,
body: '400: Bad Request', headers: {'Cache-Control': 'no-store'});
}
print('accountReplicateService request: $requestString');
final message = AccountNewMessage.fromJsonMap(json.decode(utf8.decode(base64
.decode(json.decode(requestString)['message']['data'].toString()))));
final db = GetIt.instance<DatabaseService>().db;
try {
await db.open();
} catch (e, s) {
print(['Caught connect error', e, s]);
return shelf.Response.internalServerError(
body: 'Failed to connect to Database.',
headers: {'Cache-Control': 'no-store'});
}
try {
final account = message.account;
var result = await db.transaction((PostgreSQLExecutionContext ctx) async {
await ctx.query("DELETE FROM public.profile where id = @idParam",
substitutionValues: {"idParam": account.uid}, timeoutInSeconds: 10);
await ctx.query("""
INSERT INTO public.profile (
id, dob, visible
) VALUES (
@idParam, @dobParam, @visibleParam,
)
""", substitutionValues: {
"idParam": account.uid,
"dobParam": account.dob,
"visibleParam": account.profile.visible,
}, timeoutInSeconds: 30);
// Calculate the bloom filter bits
final List<int> bitIndexes = BloomFilter.hashIndexesWithSize<String>(
ServiceConfig.bloomFilterBitCount,
ServiceConfig.bloomFilterExpectedItems,
account.uid);
final List<String> profileIds = await executeSearch(
ctx, bitIndexes.first, account.profile, account.uid);
final replicatedMessage = AccountReplicatedMessage((b) => b
..uid = account.uid
..bloomFilterBits = BuiltList<int>.from(bitIndexes).toBuilder()
..bloomFilterBitLastSearched = bitIndexes.first
..profiles = BuiltList<String>.from(profileIds).toBuilder());
// publish account_replicated message
final gapi = await GetIt.instance<GapiService>().gapiClient;
final PubsubApi pubsubApi =
GetIt.instance<PubSubService>().pubsubApi(gapi);
final response = await pubsubApi.projects.topics.publish(
PublishRequest.fromJson({
"messages": [
{
"attributes": <String, String>{
"uid": account.uid,
},
"data": base64Encode(
utf8.encode(json.encode(replicatedMessage.toJsonMap()))),
"publishTime": DateTime.now().toUtc().toIso8601String()
}
]
}),
'projects/onesceneapp/topics/account_replicated');
print('Inserted: ${account.uid}. x${profileIds.length} search results.');
});
await db.close();
return shelf.Response.ok('', headers: {'Cache-Control': 'no-store'});
} catch (e, s) {
print(e);
print(s);
await db.close();
return shelf.Response.internalServerError(
headers: {'Cache-Control': 'no-store'});
}
}