Thread: When are largobject records TOASTed into pg_toast_2613?

When are largobject records TOASTed into pg_toast_2613?

From
Thomas Boussekey
Date:
Hello all,

Working on a PostgreSQL 9.5 to 12 upgrade, I encounter problems on a PostgreSQL instance when I have an existing table `pg_toast_2613` into my application database.

The upgrade process fails with the following error:

```
No match found in new cluster for old relation with OID 16619 in database "mirakl_db": "pg_toast.pg_toast_2613" which is the TOAST table for "pg_catalog.pg_largeobject"
No match found in new cluster for old relation with OID 16621 in database "mirakl_db": "pg_toast.pg_toast_2613_index" which is an index on "pg_toast.pg_toast_2613" which is the TOAST table for "pg_catalog.pg_largeobject"
```

The `pg_upgrade` command fails when I have the table `pg_toast_2613` that exists, even if it is empty.
I read the PostgreSQL documentation, and I didn't find when the pg_largeobject table needs to be toasted. I thought it might be linked with records' size, but my queries below don't correlate that!

I tried to dig into the data and found the following elements:
* a records exists ONLY into one table (either the pg_largobject table or the pg_toast_2613, but not BOTH)
* The `chunk_id` present into the `pg_toast_2613` table doesn't represent real large objects (impossible to query their size)
* The `chunk_id` present into the `pg_toast_2613` table are not linked to existing documents into our applicative tables.

I had a look on my 200+ production & test environments:
* on half of these instances, the `pg_toast_2613` table doesn't exist
* on 10% of them, the `pg_toast_2613` table exists and is empty

Here are the points, I want to clarify:
- What is the aim of the `pg_toast_2613` table?
- Does it contain REAL large objects or other useful data?
- Is there a workaround to make the `pg_upgrade` successful?

Thanks in advance for your help,
Thomas


# Appendix

```sql
-- Getting the 30 first items of BOTH tables
# SELECT loid, count(*) from pg_largeobject group by loid order by 1 limit 30;
  loid  | count
--------+-------
  24567 |     1
  24588 |     1
  24608 |     1
  24635 |     1
  24648 |     1
  24699 |     1
  27505 |     1
  84454 |    32
  89483 |     1
 109676 |    34
 109753 |    34
 109821 |    34
 109855 |     2
 137150 |     6
 141236 |    29
 141265 |     1
 156978 |    29
 157036 |    29
 157065 |     2
 161835 |    29
 161864 |     1
 166275 |    29
 166333 |    29
 166404 |    29
 166439 |     2
 171487 |    29
 171516 |     1
 175825 |    29
 175854 |     1
 180171 |    29
(30 rows)

# SELECT chunk_id, count(*) from pg_toast.pg_toast_2613 group by chunk_id order by 1 limit 30;
 chunk_id | count
----------+-------
    84455 |     2
    84456 |     2
    84457 |     2
    84458 |     2
    84459 |     2
    84460 |     2
    84461 |     2
    84462 |     2
    84463 |     2
    84464 |     2
    84465 |     2
    84466 |     2
    84467 |     2
    84468 |     2
    84469 |     2
    84470 |     2
    84471 |     2
    84472 |     2
    84473 |     2
    84474 |     2
    84475 |     2
    84476 |     2
    84477 |     2
    84478 |     2
    84479 |     2
    84480 |     2
    84481 |     2
    84482 |     2
    84483 |     2
    84484 |     2
(30 rows)

-- Searching IDs 84454, 84455 into applicative table
# SELECT * from mirakl_lob where blob in (84454, 84455);
 mirakl_document_id | blob
--------------------+-------
               2859 | 84454

SELECT length(lo_get (84455));
ERROR:  large object 84455 does not exist

SELECT length(lo_get (84454));
 length
--------
  64080

```

Re: When are largobject records TOASTed into pg_toast_2613?

From
Thomas Boussekey
Date:


Le ven. 21 août 2020 à 14:00, Thomas Boussekey <thomas.boussekey@gmail.com> a écrit :
Hello all,

Working on a PostgreSQL 9.5 to 12 upgrade, I encounter problems on a PostgreSQL instance when I have an existing table `pg_toast_2613` into my application database.

The upgrade process fails with the following error:

```
No match found in new cluster for old relation with OID 16619 in database "mirakl_db": "pg_toast.pg_toast_2613" which is the TOAST table for "pg_catalog.pg_largeobject"
No match found in new cluster for old relation with OID 16621 in database "mirakl_db": "pg_toast.pg_toast_2613_index" which is an index on "pg_toast.pg_toast_2613" which is the TOAST table for "pg_catalog.pg_largeobject"
```

The `pg_upgrade` command fails when I have the table `pg_toast_2613` that exists, even if it is empty.
I read the PostgreSQL documentation, and I didn't find when the pg_largeobject table needs to be toasted. I thought it might be linked with records' size, but my queries below don't correlate that!

I tried to dig into the data and found the following elements:
* a records exists ONLY into one table (either the pg_largobject table or the pg_toast_2613, but not BOTH)
* The `chunk_id` present into the `pg_toast_2613` table doesn't represent real large objects (impossible to query their size)
* The `chunk_id` present into the `pg_toast_2613` table are not linked to existing documents into our applicative tables.

I had a look on my 200+ production & test environments:
* on half of these instances, the `pg_toast_2613` table doesn't exist
* on 10% of them, the `pg_toast_2613` table exists and is empty

Here are the points, I want to clarify:
- What is the aim of the `pg_toast_2613` table?
- Does it contain REAL large objects or other useful data?
- Is there a workaround to make the `pg_upgrade` successful?

Thanks in advance for your help,
Thomas


# Appendix

```sql
-- Getting the 30 first items of BOTH tables
# SELECT loid, count(*) from pg_largeobject group by loid order by 1 limit 30;
  loid  | count
--------+-------
  24567 |     1
  24588 |     1
  24608 |     1
  24635 |     1
  24648 |     1
  24699 |     1
  27505 |     1
  84454 |    32
  89483 |     1
 109676 |    34
 109753 |    34
 109821 |    34
 109855 |     2
 137150 |     6
 141236 |    29
 141265 |     1
 156978 |    29
 157036 |    29
 157065 |     2
 161835 |    29
 161864 |     1
 166275 |    29
 166333 |    29
 166404 |    29
 166439 |     2
 171487 |    29
 171516 |     1
 175825 |    29
 175854 |     1
 180171 |    29
(30 rows)

# SELECT chunk_id, count(*) from pg_toast.pg_toast_2613 group by chunk_id order by 1 limit 30;
 chunk_id | count
----------+-------
    84455 |     2
    84456 |     2
    84457 |     2
    84458 |     2
    84459 |     2
    84460 |     2
    84461 |     2
    84462 |     2
    84463 |     2
    84464 |     2
    84465 |     2
    84466 |     2
    84467 |     2
    84468 |     2
    84469 |     2
    84470 |     2
    84471 |     2
    84472 |     2
    84473 |     2
    84474 |     2
    84475 |     2
    84476 |     2
    84477 |     2
    84478 |     2
    84479 |     2
    84480 |     2
    84481 |     2
    84482 |     2
    84483 |     2
    84484 |     2
(30 rows)

-- Searching IDs 84454, 84455 into applicative table
# SELECT * from mirakl_lob where blob in (84454, 84455);
 mirakl_document_id | blob
--------------------+-------
               2859 | 84454

SELECT length(lo_get (84455));
ERROR:  large object 84455 does not exist

SELECT length(lo_get (84454));
 length
--------
  64080

```

Additional information,

I restored a basebackup for an instance containing the `pg_toast_2613` table.

At first glimpse, the TOAST table is 30 times the size of pg_largobject (see relpages in the first query below).
I tried to VACUUM FULL the `pg_largobject` table, and the rows into the `pg_toast_2613` table vanished!

Can it be a suitable workaround to apply the following logic in my migration process?

* If `pg_toast_2613` table exists
    - Perform `VACUUM FULL VERBOSE pg_largeobject`
    - If `SELECT COUNT(*) FROM pg_toast_2613;` = 0
        - unTOAST the `pg_largobject` table (if a procedure exists)

```sql
# SELECT oid, relname, relnamespace, relfilenode, reltoastrelid, relpages, reltuples
>from pg_class
>where relname like 'pg_toast_2613%'
>or relname like 'pg_largeobject%' order by relname;
  oid  |              relname              | relnamespace | relfilenode | reltoastrelid | relpages | reltuples
-------+-----------------------------------+--------------+-------------+---------------+----------+-----------
  2613 | pg_largeobject                    |           11 |     5349225 |         16637 |      263 |      5662
  2683 | pg_largeobject_loid_pn_index      |           11 |     5348991 |             0 |       90 |      5662
  2995 | pg_largeobject_metadata           |           11 |        2995 |             0 |      307 |       179
  2996 | pg_largeobject_metadata_oid_index |           11 |       27619 |             0 |      259 |       179
 16637 | pg_toast_2613                     |           99 |     5349226 |             0 |     6120 |     16027
 16639 | pg_toast_2613_index               |           99 |     5349227 |             0 |      251 |      4678
(6 rows)

# VACUUM FULL VERBOSE pg_largeobject;
INFO:  vacuuming "pg_catalog.pg_largeobject"
INFO:  "pg_largeobject": found 8 removable, 5770 nonremovable row versions in 263 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU 0.04s/0.11u sec elapsed 0.22 sec.
VACUUM
Time: 258.031 ms

# SELECT oid, relname, relnamespace, relfilenode, reltoastrelid, relpages, reltuples
from pg_class
where relname like 'pg_toast_2613%'
or relname like 'pg_largeobject%' order by relname;
  oid  |              relname              | relnamespace | relfilenode | reltoastrelid | relpages | reltuples
-------+-----------------------------------+--------------+-------------+---------------+----------+-----------
  2613 | pg_largeobject                    |           11 |     7819455 |         16637 |       67 |      5770
  2683 | pg_largeobject_loid_pn_index      |           11 |     7819461 |             0 |       18 |      5770
  2995 | pg_largeobject_metadata           |           11 |        2995 |             0 |      307 |       179
  2996 | pg_largeobject_metadata_oid_index |           11 |       27619 |             0 |      259 |       179
 16637 | pg_toast_2613                     |           99 |     7819458 |             0 |        0 |         0
 16639 | pg_toast_2613_index               |           99 |     7819460 |             0 |        1 |         0
(6 rows)

Time: 0.950 ms
``` 

Re: When are largobject records TOASTed into pg_toast_2613?

From
Laurenz Albe
Date:
On Fri, 2020-08-21 at 14:00 +0200, Thomas Boussekey wrote:
> Working on a PostgreSQL 9.5 to 12 upgrade, I encounter problems on a PostgreSQL instance when I have
>  an existing table `pg_toast_2613` into my application database.
> 
> The upgrade process fails with the following error:
> 
> ```
> No match found in new cluster for old relation with OID 16619 in database "mirakl_db": "pg_toast.pg_toast_2613" which
isthe TOAST table for "pg_catalog.pg_largeobject"
 
> No match found in new cluster for old relation with OID 16621 in database "mirakl_db": "pg_toast.pg_toast_2613_index"
whichis an index on "pg_toast.pg_toast_2613" which is the TOAST table for
 
> "pg_catalog.pg_largeobject"
> ```
> 
> The `pg_upgrade` command fails when I have the table `pg_toast_2613` that exists, even if it is empty.
> I read the PostgreSQL documentation, and I didn't find when the pg_largeobject table needs to be toasted.
>  I thought it might be linked with records' size, but my queries below don't correlate that!

Indeed, system tables have no TOAST tables in PostgreSQL, so I wonder
how your "pg_largeobject" table could have grown one.

Did you do any strange catalog modifications?

The safest way would be to upgrade with pg_dumpall/psql.
That should get rid of that data corruption.

Yours,
Laurenz Albe
-- 
Cybertec | https://www.cybertec-postgresql.com




Fwd: When are largobject records TOASTed into pg_toast_2613?

From
Thomas Boussekey
Date:


---------- Forwarded message ---------
De : Thomas Boussekey <thomas.boussekey@gmail.com>
Date: ven. 21 août 2020 à 15:37
Subject: Re: When are largobject records TOASTed into pg_toast_2613?
To: Laurenz Albe <laurenz.albe@cybertec.at>


Le ven. 21 août 2020 à 15:10, Laurenz Albe <laurenz.albe@cybertec.at> a écrit :
On Fri, 2020-08-21 at 14:00 +0200, Thomas Boussekey wrote:
> Working on a PostgreSQL 9.5 to 12 upgrade, I encounter problems on a PostgreSQL instance when I have
>  an existing table `pg_toast_2613` into my application database.
>
> The upgrade process fails with the following error:
>
> ```
> No match found in new cluster for old relation with OID 16619 in database "mirakl_db": "pg_toast.pg_toast_2613" which is the TOAST table for "pg_catalog.pg_largeobject"
> No match found in new cluster for old relation with OID 16621 in database "mirakl_db": "pg_toast.pg_toast_2613_index" which is an index on "pg_toast.pg_toast_2613" which is the TOAST table for
> "pg_catalog.pg_largeobject"
> ```
>
> The `pg_upgrade` command fails when I have the table `pg_toast_2613` that exists, even if it is empty.
> I read the PostgreSQL documentation, and I didn't find when the pg_largeobject table needs to be toasted.
>  I thought it might be linked with records' size, but my queries below don't correlate that!

Indeed, system tables have no TOAST tables in PostgreSQL, so I wonder
how your "pg_largeobject" table could have grown one.
Several years before I arrived in this company, the `pg_largeobject` table had been moved to a dedicated tablespace located on a low-IOPS mechanical disk.
One of my first projects when I started working in the company was to move the `pg_largeobject` table back to the default system tablespace.
This might be a side-effect of the migration.

Did you do any strange catalog modifications?

The safest way would be to upgrade with pg_dumpall/psql.
The `pg_dumpall` command will also copy the content and the existence of the `pg_toast_2613` table, isn't it?
It might generate errors at the execution on the new instance?
Moreover, it will generate a large downtime
That should get rid of that data corruption.

Yours,
Laurenz Albe
--
Cybertec | https://www.cybertec-postgresql.com

Re: Fwd: When are largobject records TOASTed into pg_toast_2613?

From
Tom Lane
Date:
Thomas Boussekey <thomas.boussekey@gmail.com> writes:
> Le ven. 21 août 2020 à 15:10, Laurenz Albe <laurenz.albe@cybertec.at> a
> écrit :
>> The safest way would be to upgrade with pg_dumpall/psql.

> The `pg_dumpall` command will also copy the content and the existence of
> the `pg_toast_2613` table, isn't it?

No.  pg_dumpall does not do anything with system catalogs per se.

> Moreover, it will generate a large downtime

Yeah.  It's a tradeoff of safety versus downtime.  If that toast table
is physically empty (I would not bet on it without checking), then you
could manually hack up pg_class to remove the toast table, but there's
a nontrivial risk of permanently hosing your database by messing that
up.

            regards, tom lane



Re: When are largobject records TOASTed into pg_toast_2613?

From
Laurenz Albe
Date:
On Fri, 2020-08-21 at 15:46 +0200, Thomas Boussekey wrote:
> Le ven. 21 août 2020 à 15:10, Laurenz Albe <laurenz.albe@cybertec.at> a écrit :
> > On Fri, 2020-08-21 at 14:00 +0200, Thomas Boussekey wrote:
> > > Working on a PostgreSQL 9.5 to 12 upgrade, I encounter problems on a PostgreSQL instance when I have
> > >  an existing table `pg_toast_2613` into my application database.
> > > 
> > > The upgrade process fails with the following error:
> > > 
> > > ```
> > > No match found in new cluster for old relation with OID 16619 in database "mirakl_db": "pg_toast.pg_toast_2613"
whichis the TOAST table for "pg_catalog.pg_largeobject"
 
> > > No match found in new cluster for old relation with OID 16621 in database "mirakl_db":
"pg_toast.pg_toast_2613_index"which is an index on "pg_toast.pg_toast_2613" which is the TOAST table for
 
> > > "pg_catalog.pg_largeobject"
> > > ```
> > > 
> > > The `pg_upgrade` command fails when I have the table `pg_toast_2613` that exists, even if it is empty.
> > > I read the PostgreSQL documentation, and I didn't find when the pg_largeobject table needs to be toasted.
> > >  I thought it might be linked with records' size, but my queries below don't correlate that!
> > 
> > Indeed, system tables have no TOAST tables in PostgreSQL, so I wonder
> > how your "pg_largeobject" table could have grown one.
> >
> > Did you do any strange catalog modifications?
> 
> Several years before I arrived in this company, the `pg_largeobject` table had been moved to a dedicated tablespace
locatedon a low-IOPS mechanical disk.
 
> One of my first projects when I started working in the company was to move the `pg_largeobject` table back to the
defaultsystem tablespace.
 
> This might be a side-effect of the migration.

I just tried that on v12, and it didn't create a TOAST table.

But obviously there is/was a bug somewhere.

> > The safest way would be to upgrade with pg_dumpall/psql.
> 
> The `pg_dumpall` command will also copy the content and the existence of the `pg_toast_2613` table, isn't it?
> It might generate errors at the execution on the new instance?
> Moreover, it will generate a large downtime

No, pg_dumpall will not duplicate that strange TOAST table.
It would be the only safe way to upgrade.

If you can ascertain that the TOAST table is empty and you
like to live dangerous, you can try:

UPDATE pg_class SET reltoastrelid = 0 WHERE oid = 2613;
UPDATE pg_class SET relkind = 'r' WHERE relname = 'pg_toast_2613';
DELETE FROM pg_depend WHERE classid = 1259 AND refclassid = 1259 AND refobjid = 2613 AND objid =
'pg_toast.pg_toast_2613'::regclass;
DROP TABLE pg_toast.pg_toast_2613;

But I won't guarantee that that won't break your database.

In particular, it is a no-go unless the TOAST table is empty.

Yours,
Laurenz Albe
-- 
Cybertec | https://www.cybertec-postgresql.com




Re: When are largobject records TOASTed into pg_toast_2613?

From
Thomas Boussekey
Date:


Le ven. 21 août 2020 à 16:45, Laurenz Albe <laurenz.albe@cybertec.at> a écrit :
On Fri, 2020-08-21 at 15:46 +0200, Thomas Boussekey wrote:
> Le ven. 21 août 2020 à 15:10, Laurenz Albe <laurenz.albe@cybertec.at> a écrit :
> > On Fri, 2020-08-21 at 14:00 +0200, Thomas Boussekey wrote:
> > > Working on a PostgreSQL 9.5 to 12 upgrade, I encounter problems on a PostgreSQL instance when I have
> > >  an existing table `pg_toast_2613` into my application database.
> > >
> > > The upgrade process fails with the following error:
> > >
> > > ```
> > > No match found in new cluster for old relation with OID 16619 in database "mirakl_db": "pg_toast.pg_toast_2613" which is the TOAST table for "pg_catalog.pg_largeobject"
> > > No match found in new cluster for old relation with OID 16621 in database "mirakl_db": "pg_toast.pg_toast_2613_index" which is an index on "pg_toast.pg_toast_2613" which is the TOAST table for
> > > "pg_catalog.pg_largeobject"
> > > ```
> > >
> > > The `pg_upgrade` command fails when I have the table `pg_toast_2613` that exists, even if it is empty.
> > > I read the PostgreSQL documentation, and I didn't find when the pg_largeobject table needs to be toasted.
> > >  I thought it might be linked with records' size, but my queries below don't correlate that!
> >
> > Indeed, system tables have no TOAST tables in PostgreSQL, so I wonder
> > how your "pg_largeobject" table could have grown one.
> >
> > Did you do any strange catalog modifications?
>
> Several years before I arrived in this company, the `pg_largeobject` table had been moved to a dedicated tablespace located on a low-IOPS mechanical disk.
> One of my first projects when I started working in the company was to move the `pg_largeobject` table back to the default system tablespace.
> This might be a side-effect of the migration.

I just tried that on v12, and it didn't create a TOAST table.

But obviously there is/was a bug somewhere.

> > The safest way would be to upgrade with pg_dumpall/psql.
>
> The `pg_dumpall` command will also copy the content and the existence of the `pg_toast_2613` table, isn't it?
> It might generate errors at the execution on the new instance?
> Moreover, it will generate a large downtime

No, pg_dumpall will not duplicate that strange TOAST table.
It would be the only safe way to upgrade.

If you can ascertain that the TOAST table is empty and you
like to live dangerous, you can try:

UPDATE pg_class SET reltoastrelid = 0 WHERE oid = 2613;
UPDATE pg_class SET relkind = 'r' WHERE relname = 'pg_toast_2613';
DELETE FROM pg_depend WHERE classid = 1259 AND refclassid = 1259 AND refobjid = 2613 AND objid = 'pg_toast.pg_toast_2613'::regclass;
DROP TABLE pg_toast.pg_toast_2613;
Thanks Laurenz & Tom for your precious information.

I wrote this BASH script to remove the TOAST table, if it may help anyone:

```sh
#!/usr/bin/env bash
#

set -euo pipefail

database_name="xxx"
postgresql_conf_file="/xxx/postgresql.conf"

# Step 1: check if table pg_toast_2613 exists
toast_count="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select count(*) from pg_class where relname = 'pg_toast_2613';")"
echo -e "TOAST exists ::${toast_count}"

if [[ "${toast_count}" == "1" ]]; then
  # Step 2: Check if table pg_toast_2613 has rows and pages
  toast_pages="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select relpages from pg_class where relname = 'pg_toast_2613';" )"
  toast_tuples="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select reltuples from pg_class where relname = 'pg_toast_2613';" )"
 
  echo -e "TOAST content pages ::${toast_pages}:: tuples ::${toast_tuples}"

  # Step 3 OPTIONAL: vacuum full pg_largobject if needed
  if [[ "${toast_tuples}" -gt "0" ]]; then
   
    echo -e "Start of vacuum"
    psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "VACUUM FULL VERBOSE pg_largobject;"
    echo -e "End of vacuum"
   
    ## After VACUUM post-check
    toast_pages="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select relpages from pg_class where relname = 'pg_toast_2613';" )"
    toast_tuples="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select reltuples from pg_class where relname = 'pg_toast_2613';" )"
   
    echo -e "TOAST content pages ::${toast_pages}:: tuples ::${toast_tuples}"
  fi

  # Step 4: Remove TOAST information for pg_largobject into pg_class
  echo -e "Remove TOAST on pg_largobject"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "UPDATE pg_class SET reltoastrelid = 0 WHERE oid = 2613;"

  # Step 5: Drop pg_toast_2613% objects
  echo -e "Change pg_toast_2613 type to relation"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "UPDATE pg_class SET relkind = 'r' WHERE relname = 'pg_toast_2613';"
 
  echo -e "Delete pg_depend for pg_toast_2613"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "DELETE FROM pg_depend WHERE classid = 1259 AND refclassid = 1259 AND refobjid = 2613 AND objid = 'pg_toast.pg_toast_2613'::regclass;"

  echo "allow_system_table_mods=on" >> "${postgresql_conf_file}"
  systemctl restart postgresql-9.5.service
 
  echo -e "Drop relation pg_toast_2613"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "DROP TABLE pg_toast.pg_toast_2613;"

  sed -i '/^postgres_enable_version:/d' ${postgresql_conf_file}
  systemctl restart postgresql-9.5.service
fi
```
My PostgreSQL instance is OK, and the migration to PostgreSQL12 is sucessful.
I continue testing the instance

Have a nice week-end,
Thomas 

But I won't guarantee that that won't break your database.

In particular, it is a no-go unless the TOAST table is empty.

Yours,
Laurenz Albe
--
Cybertec | https://www.cybertec-postgresql.com

Re: When are largobject records TOASTed into pg_toast_2613?

From
Michael Paquier
Date:
On Fri, Aug 21, 2020 at 03:10:30PM +0200, Laurenz Albe wrote:
> Indeed, system tables have no TOAST tables in PostgreSQL, so I wonder
> how your "pg_largeobject" table could have grown one.

FWIW, src/include/catalog/toasting.h is giving me a list of 28 catalog
tables with a toast relation as of HEAD.
--
Michael

Attachment

Re: When are largobject records TOASTed into pg_toast_2613?

From
Laurenz Albe
Date:
On Sat, 2020-08-22 at 10:47 +0900, Michael Paquier wrote:
> > Indeed, system tables have no TOAST tables in PostgreSQL, so I wonder
> > how your "pg_largeobject" table could have grown one.
> 
> FWIW, src/include/catalog/toasting.h is giving me a list of 28 catalog
> tables with a toast relation as of HEAD.

Yes, I was behind the times.

Catalog tables *do* have TOAST tables, but not all of them, and "pg_largeobject" is one that doesn't.

Yours,
Laurenz Albe
-- 
Cybertec | https://www.cybertec-postgresql.com




Re: When are largobject records TOASTed into pg_toast_2613?

From
Laurenz Albe
Date:
On Fri, 2020-08-21 at 18:59 +0200, Thomas Boussekey wrote:
> I wrote this BASH script to remove the TOAST table, if it may help anyone:
> 
> [...]
>   toast_pages="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select relpages from pg_class where
relname= 'pg_toast_2613';" )"
 
>   toast_tuples="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select reltuples from pg_class where
relname= 'pg_toast_2613';" )"
 
> [...]

That are just the estimates.
You need to ascertain that the table is *really* empty.

Yours,
Laurenz Albe
-- 
Cybertec | https://www.cybertec-postgresql.com




Re: When are largobject records TOASTed into pg_toast_2613?

From
Thomas Boussekey
Date:
Hello all,

You can find at the end of this email, a new version of the script that I use to remove the TOAST table on pg_largobject catalog table.
I fixed some typos and wrong synthaxes that I had typed too quickly in my first version.

Thanks to this script, I can migrate successfully the PostgreSQL instance.
Yet, the `pg_largobject` table is still considered TOASTed.

I have the following behaviour:

```sql
---Using the pg_largeobject_loid_pn_index is OK:
SELECT loid from pg_largeobject order by loid desc limit 5;
   loid  
----------
 47232219
 47232219
 47232219
 47232219
 47232219
(5 rows)

--- according to pg_class, pg_largobject is not TOASTed anymore:
SELECT oid, relname, relnamespace, relfilenode, reltoastrelid, relpages, (relpages*8/1024)::int as mb_size, reltuples::int, relkind
from pg_class
where relname like 'pg_toast_2613%'
or relname like 'pg_largeobject%' order by relname;
 oid  |              relname              | relnamespace | relfilenode | reltoastrelid | relpages | mb_size | reltuples | relkind
------+-----------------------------------+--------------+-------------+---------------+----------+---------+-----------+---------
 2613 | pg_largeobject                    |           11 |    47237561 |             0 |     8791 |      68 |    727520 | r
 2683 | pg_largeobject_loid_pn_index      |           11 |    47237567 |             0 |     1997 |      15 |    727520 | i
 2995 | pg_largeobject_metadata           |           11 |        2995 |             0 |      230 |       1 |      5071 | r
 2996 | pg_largeobject_metadata_oid_index |           11 |        2996 |             0 |     2320 |      18 |      5071 | i
(4 rows)

--- But the pg_largeobject table is not accessible:
SELECT * from pg_largeobject order by loid desc limit 5;
ERROR:  could not open relation with OID 16619

--- Same error when using largeobject functions:
SELECT lo_get(47232219);
ERROR:  could not open relation with OID 16619

--- No TOAST reference into pg_depend for pg_largobject
SELECT * from pg_depend where 2613 in (objsubid, refobjid);
 classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
---------+-------+----------+------------+----------+-------------+---------
       0 |     0 |        0 |       1259 |     2613 |           0 | p

--- As for OID 16619
SELECT * from pg_depend where 16619 in (objsubid, refobjid);
 classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
---------+-------+----------+------------+----------+-------------+---------
```

> Is there another catalog table where the TOAST reference can be located?

Thanks in advance for your help,
Have a nice Sunday,
Thomas

Latest version of the script:

```sql
#!/usr/bin/env bash
#

set -euo pipefail

database_name="xxx"
postgresql_conf_file="/yyy/postgresql.conf"

# Define log files
LOG_FOLDER="/zzz/log"
mkdir -p "${LOG_FOLDER}"
LOG_REMOVE="${LOG_FOLDER}/remove_operation.log"

# Step 1: check if table pg_toast_2613 exists
toast_count="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select count(*) from pg_class where relname = 'pg_toast_2613';")"
echo -e "TOAST exists ::${toast_count}" | tee -a "${LOG_REMOVE}"

if [[ "${toast_count}" == "1" ]]; then
  # Step 2: Check if table pg_toast_2613 has rows and pages
  toast_pages="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select relpages from pg_class where relname = 'pg_toast_2613';")"
  toast_tuples="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select reltuples::int from pg_class where relname = 'pg_toast_2613';")"

  echo -e "TOAST content pages ::${toast_pages}:: tuples ::${toast_tuples}" | tee -a "${LOG_REMOVE}"

  # Step 3 OPTIONAL: vacuum full pg_largeobject if needed
  if [[ "${toast_tuples}" -gt "0" ]]; then

    echo -e "Start of vacuum" | tee -a "${LOG_REMOVE}"
    psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "VACUUM FULL ANALYZE VERBOSE pg_largeobject;" 2>&1 | tee -a "${LOG_REMOVE}"
    echo -e "End of vacuum" | tee -a "${LOG_REMOVE}"

    ## After VACUUM post-check
    toast_pages="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select relpages from pg_class where relname = 'pg_toast_2613';")"
    toast_tuples="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select reltuples::int from pg_class where relname = 'pg_toast_2613';")"

    echo -e "TOAST content pages ::${toast_pages}:: tuples ::${toast_tuples}" | tee -a "${LOG_REMOVE}"
  fi

  # Step 4: Remove TOAST information for pg_largeobject into pg_class
  echo -e "Remove TOAST on pg_largeobject" | tee -a "${LOG_REMOVE}"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "UPDATE pg_class SET reltoastrelid = 0 WHERE oid = 2613;" | tee -a "${LOG_REMOVE}"

  # Step 5: Drop pg_toast_2613% objects
  echo -e "Change pg_toast_2613 type to relation" | tee -a "${LOG_REMOVE}"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "UPDATE pg_class SET relkind = 'r' WHERE relname = 'pg_toast_2613';" | tee -a "${LOG_REMOVE}"

  echo -e "Delete pg_depend link between pg_toast_2613 and pg_largeobject" | tee -a "${LOG_REMOVE}"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "DELETE FROM pg_depend WHERE classid = 1259 AND refclassid = 1259 AND refobjid = 2613 AND objid = 'pg_toast.pg_toast_2613'::regclass;" | tee -a "${LOG_REMOVE}"

  echo -e "Delete pg_depend link between pg_toast_2613 and ---MISSING OBJECT---" | tee -a "${LOG_REMOVE}"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "DELETE FROM pg_depend WHERE classid = 1259 AND refclassid = 1259 AND objid = 'pg_toast.pg_toast_2613'::regclass AND refobjsubid not in (select oid from pg_class);" | tee -a "${LOG_REMOVE}"

  echo "allow_system_table_mods=on" >>"${postgresql_conf_file}"
  systemctl restart postgresql-9.5.service

  echo -e "Drop relation pg_toast_2613" | tee -a "${LOG_REMOVE}"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "DROP TABLE pg_toast.pg_toast_2613;" | tee -a "${LOG_REMOVE}"

  sed -i '/^allow_system_table_mods=on/d' ${postgresql_conf_file}
  systemctl restart postgresql-9.5.service

  # Refresh value of variable toast_count
  toast_count="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select count(*) from pg_class where relname = 'pg_toast_2613';")"
fi

if [[ "${toast_count}" == "0" ]]; then
  echo -e "No TOAST table pg_toast_2613" | tee -a "${LOG_REMOVE}"
fi
```


Le ven. 21 août 2020 à 18:59, Thomas Boussekey <thomas.boussekey@gmail.com> a écrit :


Le ven. 21 août 2020 à 16:45, Laurenz Albe <laurenz.albe@cybertec.at> a écrit :
On Fri, 2020-08-21 at 15:46 +0200, Thomas Boussekey wrote:
> Le ven. 21 août 2020 à 15:10, Laurenz Albe <laurenz.albe@cybertec.at> a écrit :
> > On Fri, 2020-08-21 at 14:00 +0200, Thomas Boussekey wrote:
> > > Working on a PostgreSQL 9.5 to 12 upgrade, I encounter problems on a PostgreSQL instance when I have
> > >  an existing table `pg_toast_2613` into my application database.
> > >
> > > The upgrade process fails with the following error:
> > >
> > > ```
> > > No match found in new cluster for old relation with OID 16619 in database "mirakl_db": "pg_toast.pg_toast_2613" which is the TOAST table for "pg_catalog.pg_largeobject"
> > > No match found in new cluster for old relation with OID 16621 in database "mirakl_db": "pg_toast.pg_toast_2613_index" which is an index on "pg_toast.pg_toast_2613" which is the TOAST table for
> > > "pg_catalog.pg_largeobject"
> > > ```
> > >
> > > The `pg_upgrade` command fails when I have the table `pg_toast_2613` that exists, even if it is empty.
> > > I read the PostgreSQL documentation, and I didn't find when the pg_largeobject table needs to be toasted.
> > >  I thought it might be linked with records' size, but my queries below don't correlate that!
> >
> > Indeed, system tables have no TOAST tables in PostgreSQL, so I wonder
> > how your "pg_largeobject" table could have grown one.
> >
> > Did you do any strange catalog modifications?
>
> Several years before I arrived in this company, the `pg_largeobject` table had been moved to a dedicated tablespace located on a low-IOPS mechanical disk.
> One of my first projects when I started working in the company was to move the `pg_largeobject` table back to the default system tablespace.
> This might be a side-effect of the migration.

I just tried that on v12, and it didn't create a TOAST table.

But obviously there is/was a bug somewhere.

> > The safest way would be to upgrade with pg_dumpall/psql.
>
> The `pg_dumpall` command will also copy the content and the existence of the `pg_toast_2613` table, isn't it?
> It might generate errors at the execution on the new instance?
> Moreover, it will generate a large downtime

No, pg_dumpall will not duplicate that strange TOAST table.
It would be the only safe way to upgrade.

If you can ascertain that the TOAST table is empty and you
like to live dangerous, you can try:

UPDATE pg_class SET reltoastrelid = 0 WHERE oid = 2613;
UPDATE pg_class SET relkind = 'r' WHERE relname = 'pg_toast_2613';
DELETE FROM pg_depend WHERE classid = 1259 AND refclassid = 1259 AND refobjid = 2613 AND objid = 'pg_toast.pg_toast_2613'::regclass;
DROP TABLE pg_toast.pg_toast_2613;
Thanks Laurenz & Tom for your precious information.

I wrote this BASH script to remove the TOAST table, if it may help anyone:

```sh
#!/usr/bin/env bash
#

set -euo pipefail

database_name="xxx"
postgresql_conf_file="/xxx/postgresql.conf"

# Step 1: check if table pg_toast_2613 exists
toast_count="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select count(*) from pg_class where relname = 'pg_toast_2613';")"
echo -e "TOAST exists ::${toast_count}"

if [[ "${toast_count}" == "1" ]]; then
  # Step 2: Check if table pg_toast_2613 has rows and pages
  toast_pages="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select relpages from pg_class where relname = 'pg_toast_2613';" )"
  toast_tuples="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select reltuples from pg_class where relname = 'pg_toast_2613';" )"
 
  echo -e "TOAST content pages ::${toast_pages}:: tuples ::${toast_tuples}"

  # Step 3 OPTIONAL: vacuum full pg_largobject if needed
  if [[ "${toast_tuples}" -gt "0" ]]; then
   
    echo -e "Start of vacuum"
    psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "VACUUM FULL VERBOSE pg_largobject;"
    echo -e "End of vacuum"
   
    ## After VACUUM post-check
    toast_pages="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select relpages from pg_class where relname = 'pg_toast_2613';" )"
    toast_tuples="$(psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "select reltuples from pg_class where relname = 'pg_toast_2613';" )"
   
    echo -e "TOAST content pages ::${toast_pages}:: tuples ::${toast_tuples}"
  fi

  # Step 4: Remove TOAST information for pg_largobject into pg_class
  echo -e "Remove TOAST on pg_largobject"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "UPDATE pg_class SET reltoastrelid = 0 WHERE oid = 2613;"

  # Step 5: Drop pg_toast_2613% objects
  echo -e "Change pg_toast_2613 type to relation"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "UPDATE pg_class SET relkind = 'r' WHERE relname = 'pg_toast_2613';"
 
  echo -e "Delete pg_depend for pg_toast_2613"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "DELETE FROM pg_depend WHERE classid = 1259 AND refclassid = 1259 AND refobjid = 2613 AND objid = 'pg_toast.pg_toast_2613'::regclass;"

  echo "allow_system_table_mods=on" >> "${postgresql_conf_file}"
  systemctl restart postgresql-9.5.service
 
  echo -e "Drop relation pg_toast_2613"
  psql -U postgres --dbname=${database_name} -At --no-psqlrc -c "DROP TABLE pg_toast.pg_toast_2613;"

  sed -i '/^postgres_enable_version:/d' ${postgresql_conf_file}
  systemctl restart postgresql-9.5.service
fi
```
My PostgreSQL instance is OK, and the migration to PostgreSQL12 is sucessful.
I continue testing the instance

Have a nice week-end,
Thomas 

But I won't guarantee that that won't break your database.

In particular, it is a no-go unless the TOAST table is empty.

Yours,
Laurenz Albe
--
Cybertec | https://www.cybertec-postgresql.com

Re: When are largobject records TOASTed into pg_toast_2613?

From
Laurenz Albe
Date:
On Sat, 2020-08-29 at 21:18 +0200, Thomas Boussekey wrote:
> You can find at the end of this email, a new version of the script that I use to remove the TOAST table on
pg_largobjectcatalog table.
 
> I fixed some typos and wrong synthaxes that I had typed too quickly in my first version.
> 
> Thanks to this script, I can migrate successfully the PostgreSQL instance.
> Yet, the `pg_largobject` table is still considered TOASTed.
> 
> I have the following behaviour:
> 
> [...]
> 
> --- But the pg_largeobject table is not accessible:
> SELECT * from pg_largeobject order by loid desc limit 5;
> ERROR:  could not open relation with OID 16619
> 
> --- Same error when using largeobject functions:
> SELECT lo_get(47232219);
> ERROR:  could not open relation with OID 16619
> 
> --- No TOAST reference into pg_depend for pg_largobject
> SELECT * from pg_depend where 2613 in (objsubid, refobjid);
>  classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype 
> ---------+-------+----------+------------+----------+-------------+---------
>        0 |     0 |        0 |       1259 |     2613 |           0 | p
> 
> --- As for OID 16619
> SELECT * from pg_depend where 16619 in (objsubid, refobjid);
>  classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype 
> ---------+-------+----------+------------+----------+-------------+---------
> ```
> 
> > Is there another catalog table where the TOAST reference can be located?

Yes, in the table itself.  It seems like some values in pg_largeobject
were stored in the TOAST table after all.

I told you it was dangerous...

I guess you'll have to migrate with dump/restore.

Yours,
Laurenz Albe
-- 
Cybertec | https://www.cybertec-postgresql.com




Re: When are largobject records TOASTed into pg_toast_2613?

From
Thomas Boussekey
Date:
Thanks Laurenz for your email

Le lun. 31 août 2020 à 09:42, Laurenz Albe <laurenz.albe@cybertec.at> a écrit :
On Sat, 2020-08-29 at 21:18 +0200, Thomas Boussekey wrote:
> You can find at the end of this email, a new version of the script that I use to remove the TOAST table on pg_largobject catalog table.
> I fixed some typos and wrong synthaxes that I had typed too quickly in my first version.
>
> Thanks to this script, I can migrate successfully the PostgreSQL instance.
> Yet, the `pg_largobject` table is still considered TOASTed.
>
> I have the following behaviour:
>
> [...]
>
> --- But the pg_largeobject table is not accessible:
> SELECT * from pg_largeobject order by loid desc limit 5;
> ERROR:  could not open relation with OID 16619
>
> --- Same error when using largeobject functions:
> SELECT lo_get(47232219);
> ERROR:  could not open relation with OID 16619
>
> --- No TOAST reference into pg_depend for pg_largobject
> SELECT * from pg_depend where 2613 in (objsubid, refobjid);
>  classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
> ---------+-------+----------+------------+----------+-------------+---------
>        0 |     0 |        0 |       1259 |     2613 |           0 | p
>
> --- As for OID 16619
> SELECT * from pg_depend where 16619 in (objsubid, refobjid);
>  classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
> ---------+-------+----------+------------+----------+-------------+---------
> ```
>
> > Is there another catalog table where the TOAST reference can be located?

Yes, in the table itself.  It seems like some values in pg_largeobject
were stored in the TOAST table after all.
I can empty the TOAST without altering the access to the data (through vacuum full).

```
-- Check before VACUUM
WITH last_loid(loid) AS (SELECT distinct loid FROM pg_largeobject order by loid desc limit 5)
select loid, length(lo_get(loid)) from last_loid;
  loid  | length
--------+--------
 361314 |    672
 361294 |  40672
 359321 |    672
 359301 |  40672
 355170 |    672

-- VACUUM the pg_largeobject table:
VACUUM FULL ANALYZE VERBOSE pg_largeobject;
INFO:  vacuuming "pg_catalog.pg_largeobject"
INFO:  "pg_largeobject": found 0 removable, 12393 nonremovable row versions in 120 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU 0.10s/0.29u sec elapsed 0.61 sec.
INFO:  analyzing "pg_catalog.pg_largeobject"
INFO:  "pg_largeobject": scanned 114 of 114 pages, containing 12393 live rows and 0 dead rows; 12393 rows in sample, 12393 estimated total rows
VACUUM
Time: 675.114 ms

-- TOAST is now empty (0 tuples)
SELECT oid, relname, relnamespace, relfilenode, reltoastrelid, relpages, (relpages*8/1024)::int as mb_size, reltuples::int, relkind
from pg_class
where relname like 'pg_toast_2613%'
or relname like 'pg_largeobject%' order by relname;

  oid  |              relname              | relnamespace | relfilenode | reltoastrelid | relpages | mb_size | reltuples | relkind
-------+-----------------------------------+--------------+-------------+---------------+----------+---------+-----------+---------
  2613 | pg_largeobject                    |           11 |      369726 |         18172 |      114 |       0 |     12393 | r
  2683 | pg_largeobject_loid_pn_index      |           11 |      369732 |             0 |       36 |       0 |     12393 | i
  2995 | pg_largeobject_metadata           |           11 |        2995 |             0 |        2 |       0 |       181 | r
  2996 | pg_largeobject_metadata_oid_index |           11 |        2996 |             0 |        2 |       0 |       181 | i
 18172 | pg_toast_2613                     |           99 |      369729 |             0 |        0 |       0 |         0 | t
 18174 | pg_toast_2613_index               |           99 |      369731 |             0 |        1 |       0 |         0 | i
(6 rows)

-- The 5 last largeObjects are still available
WITH last_loid(loid) AS (SELECT distinct loid FROM pg_largeobject order by loid desc limit 5)
>select loid, length(lo_get(loid)) from last_loid;
  loid  | length
--------+--------
 361314 |    672
 361294 |  40672
 359321 |    672
 359301 |  40672
 355170 |    672
```

None of the LOID in the pg_largeobject table have a corresponding chunk_id into the pg_toast table.

```
-- ID existing in the 2 tables (HEAP & TOAST)
WITH
  plo(id,count_rows) AS (SELECT loid, count(*) FROM pg_largeobject GROUP BY loid),
  pt2(id,count_rows) AS (SELECT chunk_id, count(*) FROM pg_toast.pg_toast_2613 GROUP BY chunk_id)
SELECT count(*)
FROM plo
  INNER JOIN pt2
    ON plo.id = pt2.id;
 count
-------
     0
```

I have no record of the TOAST link in the `pg_depend` table:

```
 # DELETE FROM pg_depend WHERE classid = 1259 AND refclassid = 1259 AND refobjid = 2613 AND objid = 'pg_toast.pg_toast_2613'::regclass;
 DELETE 0
```


I told you it was dangerous...

I guess you'll have to migrate with dump/restore.
It seems the only possible option, I would have liked that another way could be possible

Yours,
Laurenz Albe
--
Cybertec | https://www.cybertec-postgresql.com

Thomas