Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 2b8e527

Browse files
committed
Fix handling of shared statistics with dropped databases
Dropping a database while a connection is attempted on it was able to lead to the presence of valid database entries in shared statistics. The issue is that MyDatabaseId was getting set too early than it should, as, if the connection attempted on the dropped database fails when renamed or dropped, the shutdown callback of the shared statistics would finish by re-inserting a correct entry related to the database already dropped. As analyzed by the bug reporters, this issue could lead to phantom entries in the database list maintained by the autovacuum launcher (in rebuild_database_list()) if the database dropped was part of the database list when it was still valid. After the database was dropped, it would remain the highest on the list of databases to considered by the autovacuum worker as things to process. This would prevent autovacuum jobs to happen on all the other databases still present. The commit fixes this issue by delaying setting MyDatabaseId until the database existence has been re-checked with the second scan on pg_database after getting a shared lock on it, and by switching pgstat_update_dbstats() so as nothing happens if MyDatabaseId is not valid. Issue introduced by 5891c7a, so backpatch down to 15. Reported-by: Will Mortensen, Jacob Speidel Analyzed-by: Will Mortensen, Jacob Speidel Author: Andres Freund Discussion: https://postgr.es/m/17973-bca1f7d5c14f601e@postgresql.org Backpatch-through: 15
1 parent d0ec2dd commit 2b8e527

File tree

2 files changed

+74
-57
lines changed

2 files changed

+74
-57
lines changed

src/backend/utils/activity/pgstat_database.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ pgstat_update_dbstats(TimestampTz ts)
271271
{
272272
PgStat_StatDBEntry *dbentry;
273273

274+
/*
275+
* If not connected to a database yet, don't attribute time to "shared
276+
* state" (InvalidOid is used to track stats for shared relations, etc.).
277+
*/
278+
if (!OidIsValid(MyDatabaseId))
279+
return;
280+
274281
dbentry = pgstat_prep_database_pending(MyDatabaseId);
275282

276283
/*
@@ -327,6 +334,12 @@ pgstat_prep_database_pending(Oid dboid)
327334
{
328335
PgStat_EntryRef *entry_ref;
329336

337+
/*
338+
* This should not report stats on database objects before having
339+
* connected to a database.
340+
*/
341+
Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId));
342+
330343
entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid,
331344
NULL);
332345

src/backend/utils/init/postinit.c

Lines changed: 61 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
10061006
*/
10071007
if (bootstrap)
10081008
{
1009-
MyDatabaseId = Template1DbOid;
1009+
dboid = Template1DbOid;
10101010
MyDatabaseTableSpace = DEFAULTTABLESPACE_OID;
10111011
}
10121012
else if (in_dbname != NULL)
@@ -1020,32 +1020,9 @@ InitPostgres(const char *in_dbname, Oid dboid,
10201020
(errcode(ERRCODE_UNDEFINED_DATABASE),
10211021
errmsg("database \"%s\" does not exist", in_dbname)));
10221022
dbform = (Form_pg_database) GETSTRUCT(tuple);
1023-
MyDatabaseId = dbform->oid;
1024-
MyDatabaseTableSpace = dbform->dattablespace;
1025-
/* take database name from the caller, just for paranoia */
1026-
strlcpy(dbname, in_dbname, sizeof(dbname));
1023+
dboid = dbform->oid;
10271024
}
1028-
else if (OidIsValid(dboid))
1029-
{
1030-
/* caller specified database by OID */
1031-
HeapTuple tuple;
1032-
Form_pg_database dbform;
1033-
1034-
tuple = GetDatabaseTupleByOid(dboid);
1035-
if (!HeapTupleIsValid(tuple))
1036-
ereport(FATAL,
1037-
(errcode(ERRCODE_UNDEFINED_DATABASE),
1038-
errmsg("database %u does not exist", dboid)));
1039-
dbform = (Form_pg_database) GETSTRUCT(tuple);
1040-
MyDatabaseId = dbform->oid;
1041-
MyDatabaseTableSpace = dbform->dattablespace;
1042-
Assert(MyDatabaseId == dboid);
1043-
strlcpy(dbname, NameStr(dbform->datname), sizeof(dbname));
1044-
/* pass the database name back to the caller */
1045-
if (out_dbname)
1046-
strcpy(out_dbname, dbname);
1047-
}
1048-
else
1025+
else if (!OidIsValid(dboid))
10491026
{
10501027
/*
10511028
* If this is a background worker not bound to any particular
@@ -1083,8 +1060,64 @@ InitPostgres(const char *in_dbname, Oid dboid,
10831060
* CREATE DATABASE.
10841061
*/
10851062
if (!bootstrap)
1086-
LockSharedObject(DatabaseRelationId, MyDatabaseId, 0,
1087-
RowExclusiveLock);
1063+
LockSharedObject(DatabaseRelationId, dboid, 0, RowExclusiveLock);
1064+
1065+
/*
1066+
* Recheck pg_database to make sure the target database hasn't gone away.
1067+
* If there was a concurrent DROP DATABASE, this ensures we will die
1068+
* cleanly without creating a mess.
1069+
*/
1070+
if (!bootstrap)
1071+
{
1072+
HeapTuple tuple;
1073+
Form_pg_database datform;
1074+
1075+
tuple = GetDatabaseTupleByOid(dboid);
1076+
if (HeapTupleIsValid(tuple))
1077+
datform = (Form_pg_database) GETSTRUCT(tuple);
1078+
1079+
if (!HeapTupleIsValid(tuple) ||
1080+
(in_dbname && namestrcmp(&datform->datname, in_dbname)))
1081+
{
1082+
if (in_dbname)
1083+
ereport(FATAL,
1084+
(errcode(ERRCODE_UNDEFINED_DATABASE),
1085+
errmsg("database \"%s\" does not exist", in_dbname),
1086+
errdetail("It seems to have just been dropped or renamed.")));
1087+
else
1088+
ereport(FATAL,
1089+
(errcode(ERRCODE_UNDEFINED_DATABASE),
1090+
errmsg("database %u does not exist", dboid)));
1091+
}
1092+
1093+
strlcpy(dbname, NameStr(datform->datname), sizeof(dbname));
1094+
1095+
if (database_is_invalid_form(datform))
1096+
{
1097+
ereport(FATAL,
1098+
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1099+
errmsg("cannot connect to invalid database \"%s\"", dbname),
1100+
errhint("Use DROP DATABASE to drop invalid databases."));
1101+
}
1102+
1103+
MyDatabaseTableSpace = datform->dattablespace;
1104+
/* pass the database name back to the caller */
1105+
if (out_dbname)
1106+
strcpy(out_dbname, dbname);
1107+
}
1108+
1109+
/*
1110+
* Now that we rechecked, we are certain to be connected to a database and
1111+
* thus can set MyDatabaseId.
1112+
*
1113+
* It is important that MyDatabaseId only be set once we are sure that the
1114+
* target database can no longer be concurrently dropped or renamed. For
1115+
* example, without this guarantee, pgstat_update_dbstats() could create
1116+
* entries for databases that were just dropped in the pgstat shutdown
1117+
* callback, which could confuse other code paths like the autovacuum
1118+
* scheduler.
1119+
*/
1120+
MyDatabaseId = dboid;
10881121

10891122
/*
10901123
* Now we can mark our PGPROC entry with the database ID.
@@ -1108,35 +1141,6 @@ InitPostgres(const char *in_dbname, Oid dboid,
11081141
*/
11091142
InvalidateCatalogSnapshot();
11101143

1111-
/*
1112-
* Recheck pg_database to make sure the target database hasn't gone away.
1113-
* If there was a concurrent DROP DATABASE, this ensures we will die
1114-
* cleanly without creating a mess.
1115-
*/
1116-
if (!bootstrap)
1117-
{
1118-
HeapTuple tuple;
1119-
Form_pg_database datform;
1120-
1121-
tuple = GetDatabaseTuple(dbname);
1122-
if (!HeapTupleIsValid(tuple) ||
1123-
MyDatabaseId != ((Form_pg_database) GETSTRUCT(tuple))->oid ||
1124-
MyDatabaseTableSpace != ((Form_pg_database) GETSTRUCT(tuple))->dattablespace)
1125-
ereport(FATAL,
1126-
(errcode(ERRCODE_UNDEFINED_DATABASE),
1127-
errmsg("database \"%s\" does not exist", dbname),
1128-
errdetail("It seems to have just been dropped or renamed.")));
1129-
1130-
datform = (Form_pg_database) GETSTRUCT(tuple);
1131-
if (database_is_invalid_form(datform))
1132-
{
1133-
ereport(FATAL,
1134-
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1135-
errmsg("cannot connect to invalid database \"%s\"", dbname),
1136-
errhint("Use DROP DATABASE to drop invalid databases."));
1137-
}
1138-
}
1139-
11401144
/*
11411145
* Now we should be able to access the database directory safely. Verify
11421146
* it's there and looks reasonable.

0 commit comments

Comments
 (0)