MisterRaindrop commented on issue #1850:
URL: https://github.com/apache/cloudberry/issues/1850#issuecomment-4989193044

   Thanks a lot for the detailed report, the screenshots, and for offering to 
send a PR — that context made this much easier to dig into. 🙏
   
   I tried to reproduce it and I think there's some good news: the 
vacuum/autovacuum logic itself looks correct here, and I believe the failing 
object is actually a *different* relation than the PXF foreign table. There's a 
really easy trap in how the file path maps back to a table (I walked straight 
into it myself while testing), so I wanted to share what I found in case it 
helps you get unblocked faster.
   
   ## Short version
   
   - `VACUUM`/autovacuum never process foreign tables — they're skipped by 
`relkind = 'f'` in every relevant code path (confirmed in code, and empirically 
with 12,000 PXF foreign tables present).
   - The `could not open file "base/17279/103349"` message names the file by 
its **`relfilenode`**, not by the table's **`oid`**. In Greenplum/Cloudberry 
those two come from independent counters, so for user relations `relfilenode != 
oid`.
   - That's the trap: looking up `WHERE oid = 103349` can land on a PXF foreign 
table purely by coincidence, while the file `.../103349` actually belongs to a 
*different* relation whose `relfilenode = 103349` — a regular 
(heap/AO/matview/toast) table whose data file is missing on disk.
   - So the underlying cause looks like a **catalog ↔ storage inconsistency** 
(a `pg_class` entry whose data file isn't there) — the kind of thing a 
migration/restore or a segment-recovery gap can leave behind. It doesn't seem 
related to PXF.
   
   For reference, I tested on a 3-segment `gpdemo` built from current `main`; 
the vacuum/foreign-table code paths are identical between the 
`2.1.0-incubating` tag and `main`, so this should apply to your 2.1.0.
   
   ## 1. Foreign tables aren't vacuumed
   
   A `gp_exttable_server` foreign table has no storage:
   
   ```
   relname=pxf_delta_hdfs_t_consent_audit | relkind=f | relam=0
   relfilenode=0 | relfrozenxid=0 | reltoastrelid=0 | age=2147483647
   ```
   
   The `relfrozenxid = 0` is what makes `age()` show `2147483647` (so it 
*looks* like it urgently needs a wraparound vacuum), but that value is inert — 
foreign tables are filtered out by `relkind` in all of: 
`get_all_vacuum_rels()`, `do_autovacuum()`, the `vacuum_rel()` guard, and 
`vac_update_datfrozenxid()` (so `datfrozenxid` isn't held back by them either). 
`RelationInitPhysicalAddr()` also returns early for storage-less relations.
   
   With **12,000** PXF foreign tables present:
   
   ```
   === bare VACUUM; with ~12k PXF foreign tables -> crash? ===
   VACUUM   >>> completed without error
   === VACUUM ANALYZE; ===
   VACUUM   >>> completed without error
   ```
   
   And on the autovacuum side: 0 of the 12,000 foreign tables even appear in 
`pg_stat_all_tables`, and with `log_autovacuum_min_duration=0` the logs only 
ever show regular tables being processed — never a foreign table.
   
   ## 2. Where the error actually comes from
   
   `could not open file "%s"` is raised in `mdopenfork()` 
(`src/backend/storage/smgr/md.c`), reached during vacuum via:
   
   ```
   vacuum_rel -> heap_vacuum_rel -> RelationGetNumberOfBlocks
     -> smgrnblocks -> mdnblocks -> mdopenfork(EXTENSION_FAIL)
     -> file missing -> ERROR: could not open file "base/<db>/<relfilenode>"
   ```
   
   This is only reachable for relations that *have* storage (`relkind` in `r / 
m / t / AO-aux`), so the failing object is a stored table with a missing data 
file — never a foreign table.
   
   ## 3. The `oid` vs `relfilenode` bit (the easy trap)
   
   The data file `base/<db>/<N>` is named by `relfilenode`, and in 
GPDB/Cloudberry `relfilenode` is assigned from a counter separate from `oid`:
   
   ```
   reg_heap: oid=16387 | relfilenode=16384 | filepath = base/5/16384
   ```
   
   Because the two counters draw from overlapping ranges, one relation's `oid` 
can equal a *different* relation's `relfilenode`. So `WHERE oid = <N>` doesn't 
necessarily identify the file `base/<db>/<N>` — the reliable lookup is `WHERE 
relfilenode = <N>`.
   
   ## 4. A reproduction that matches the report ("Every time")
   
   I created a few regular tables alongside the 12k foreign tables and 
simulated what a migration/restore gap leaves behind — a `pg_class` entry whose 
data file never landed on disk:
   
   ```
   mig_3: oid=60601  relfilenode=24585  file=base/5/24585  relkind=r
   
   STEP 1: remove the coordinator data file base/5/24585   (the "gap")
   
   STEP 2: bare VACUUM; three times
      #1: ERROR:  could not open file "base/5/24585": No such file or directory
      #2: ERROR:  could not open file "base/5/24585": No such file or directory
      #3: ERROR:  could not open file "base/5/24585": No such file or directory
   
   STEP 3: only the broken table fails
      VACUUM mig_1 (intact): OK
      VACUUM mig_3 (gap):    ERROR:  could not open file "base/5/24585"
   
   STEP 4: the same trap, reproduced (and it happened on its own)
      WHERE oid         = 24585 -> pxf_t_3 | relkind=f   (a PXF foreign table!)
      WHERE relfilenode = 24585 -> mig_3   | relkind=r   (the actual culprit)
   
   STEP 5: fixing the broken entry restores VACUUM
      DROP TABLE mig_3;  ->  VACUUM;  -> OK
   ```
   
   This lines up with what you saw: `VACUUM;` fails the same way every time, 
the number resolves to a foreign table by `oid` but to a broken regular table 
by `relfilenode`, and clearing the broken entry makes `VACUUM` work again. (I 
didn't engineer the `oid 24585 == foreign table` collision — it happened 
naturally once there were ~12k foreign tables around, which is probably exactly 
why the `oid` lookup was so convincing.)
   
   ## 5. Suggested next step on your side
   
   Could you try the `relfilenode` lookup on the affected DB? That should point 
at the real table:
   
   ```sql
   -- the real relation behind the file (note: relfilenode, not oid)
   SELECT oid, relname, relkind, relnamespace::regnamespace
   FROM pg_class WHERE relfilenode = 103349;
   
   -- check per segment (the file may be missing on only one)
   SELECT gp_segment_id, relname, relkind
   FROM gp_dist_random('pg_class') WHERE relfilenode = 103349
   UNION ALL
   SELECT -1, relname, relkind FROM pg_class WHERE relfilenode = 103349;
   
   -- when it was last touched
   SELECT * FROM pg_stat_last_operation
   WHERE objid = (SELECT oid FROM pg_class WHERE relfilenode = 103349);
   ```
   
   `gpcheckcat <db>` and checking whether `<datadir>/base/17279/103349` exists 
on each segment/coordinator would also help confirm it. Once identified, 
reloading that table from the source (or `DROP` + recreate from the source DDL) 
should clear the error. Happy to help interpret the output if you paste it here.
   
   ## 6. On the high CPU
   
   That part looks like a scale effect rather than foreign-table vacuuming: 
with ~12k relations, every autovacuum cycle still scans the large `pg_class`, 
and the PG14-era stats collector rewrites a `pgstat` file that grows with the 
total relation count (a known cost before the shared-memory stats rework). The 
foreign tables themselves aren't being processed.
   
   ## One idea worth discussing
   
   If it'd be useful, database-wide `VACUUM` could be made a bit more forgiving 
so that a single relation with a missing file logs a `WARNING` and is skipped, 
instead of aborting the whole command — that way one bad table doesn't block 
vacuuming everything else. Totally separate from the foreign-table question, 
but might be a nice small robustness improvement if you're still keen to send a 
PR. Happy to collaborate on it either way — thanks again for the great report!
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to