morningman opened a new pull request, #68842:
URL: https://github.com/apache/doris/pull/68842

   ### What problem does this PR solve?
   
   Issue Number: None
   
   Related PR: #66717, #67726
   
   Problem Summary:
   
   **In short.** This ports four fixes from branch-4.1 #66717 (memory 
governance for external metadata caches) that master's re-implementation 
(#67726) does not have. Under a metadata cache weight limit, master still lets 
the Iceberg SDK keep its own unbounded manifest cache, fetches the whole 
snapshot history of REST `snapshot-loading-mode=refs` tables on every table 
cache miss, and under-weighs cached Paimon tables by 2-5x because they keep 
growing after admission. Catalogs without a weight limit are not affected.
   
   **Background.** #66717 bounds the estimated retained memory of external 
metadata caches with the FE-wide `external_meta_cache_max_weight`, the catalog 
property `meta.cache.max-weight`, and per-entry 
`meta.cache.<engine>.<entry>.max-weight`. A value is weighed once, when it is 
admitted; if it does not fit, it is returned to the query but not cached. 
master re-implemented this in #67726 on the connector cache framework, and 
#68839 ports the 4.x tests. Comparing the two implementations found the gaps 
below; branch-4.1 handles all of them.
   
   **The problem, and what it cost.**
   
   - **Iceberg SDK manifest cache outside the limit.** `IcebergCatalogFactory` 
derives `io.manifest.cache-enabled=true` whenever 
`meta.cache.iceberg.manifest.*` is enabled. That SDK cache keeps up to 
`io.manifest.cache.max-total-bytes` (100 MB by default) of manifest content per 
FileIO, outside every Doris limit, and Glue / S3 Tables catalogs have one 
FileIO per table. A user who sets a limit gets a second, unbounded manifest 
cache next to the bounded one.
   - **Remote IO while weighing REST refs-mode tables.** A weight-limited 
`IcebergTableCache` serializes the loaded metadata 
(`TableMetadataParser.toJson`) and weighs it. Both call 
`TableMetadata.snapshots()`, which in refs mode runs the catalog's supplier and 
fetches every snapshot. So each table cache miss pays one extra full-history 
REST load, exactly the cost refs mode exists to avoid, and the cached value 
then holds the whole history.
   - **Paimon tables grow after admission.** `paimon-table` is weighed from the 
schema, location and catalog environment. A cached `FileStoreTable` then grows: 
the first `latestSnapshot()` or scan builds its transient store (`RowType` 
copies of the schema, a copy of the options), and field lookups build the four 
lazy index maps of every `RowType`. Measured on Paimon 1.4.2, the grown graph 
is 2-5x the admitted weight, so the heap the cache really holds can exceed 
`MAX_WEIGHT` by that factor.
   - **Paimon REST data tokens are not charged.** With data tokens, 
`RESTCatalog` gives each table its own `RESTTokenFileIO`. The FileIO it 
delegates to lives in Paimon's process-wide cache, but the vended token it 
fetches on first data access (about 2 KB) belongs to the table and arrives 
after admission.
   
   **How this PR fixes it.**
   
   - `IcebergConnector#hasMetaCacheWeightLimit` decides whether any limit 
applies: the FE-global limit, the catalog limit, or the `max-weight` of one of 
the entries the connector reads (`table`, `partition`, `manifest`, 
`partition_view`, now one constant shared with the DDL validation). The three 
catalog option builders take that flag, and `appendManifestCacheProperties` 
does not derive `io.manifest.cache-enabled` under a limit. An explicit user 
value still wins, as in branch-4.1.
   - `IcebergCacheSizeEstimator.areSnapshotsLoaded` reads 
`TableMetadata.snapshotsLoaded` (Iceberg exposes no accessor for it). A 
weight-limited `TableOwner` checks it first and, if the list is still behind 
its supplier, declines retention with 
`incomplete_estimate:iceberg_snapshots_not_loaded` instead of serializing. The 
statement keeps the live table, whose supplier still serves historical 
snapshots on demand.
   - `PaimonCacheSizeEstimator` reserves the post-admission growth of every 
concrete `FileStoreTable` from its schema shape: a base, each field, each field 
nested in a row, each child type of an array / map / multiset / vector, each 
option and each key. The constants are calibrated against a retained-size 
oracle that walks the grown graph (new test helper 
`EstimatorCalibrationAssertions`). A table whose FileIO is a `RESTTokenFileIO` 
additionally reserves 4 KB for its token.
   
   **Results.**
   
   | Case | Before | After |
   |---|---|---|
   | `meta.cache.iceberg.manifest.enable=true` plus any weight limit | SDK 
manifest cache derived on, up to 100 MB per FileIO outside the limit | not 
derived; `io.manifest.cache-enabled=true` still turns it on |
   | Weight-limited REST catalog, `snapshot-loading-mode=refs`, table cache 
miss | one extra remote load of the full snapshot list, full history retained | 
no extra load; table not retained, statement reads the live table |
   | Paimon table with 301 INT columns, after one scan | 35 KB admitted, 114 KB 
retained | 134 KB admitted, 114 KB retained |
   | Paimon table with a 200-field ROW column, after field lookups | 25 KB 
admitted, 117 KB retained | 143 KB admitted, 117 KB retained |
   | Paimon REST data-token table | token (about 2 KB) not charged | 4 KB 
reserved |
   
   Across 11 Paimon fixtures (append and primary-key tables, wide schemas, many 
options, nested rows, arrays and maps of rows, multisets) the admitted weight 
is now 1.2-1.7x the fully grown graph.
   
   Not in this PR: wide schemas that exceed the 10,000-visit budget of 
`ReflectiveObjectSizeEstimator.estimateComplete` (for example HMS tables with 
about 1,000 primitive columns, or Paimon tables with about 3,400) are still 
never admitted under a limit. The same budget is shared by several connectors 
and the FE schema cache, so it needs its own change.
   
   **Classes, and how they call each other.**
   
   - `IcebergConnector`: `WEIGHTED_CACHE_ENTRIES`, `hasMetaCacheWeightLimit()`, 
and passes the flag at the three catalog builds.
   - `IcebergCatalogFactory`: `buildBaseCatalogProperties`, 
`buildCatalogProperties` and `buildS3TablesCatalogProperties` take 
`metaCacheWeightLimited` and stay pure.
   - `IcebergCatalogProperties`: validates `max-weight` keys against the shared 
entry list.
   - `IcebergCacheSizeEstimator` / `IcebergTableCache.TableOwner`: snapshot 
probe and the guard before serialization.
   - `PaimonCacheSizeEstimator`: `estimateStoreGrowth` and 
`estimateTableOwnedFileIO`.
   
   ```
   IcebergConnector.createCatalog / createS3TablesCatalog / createDlfCatalog
     └─ hasMetaCacheWeightLimit(): metaCache.hasEnclosingWeightLimit() || 
cacheSpec(entry).isWeightBounded()
     └─ IcebergCatalogFactory.build*CatalogProperties(..., 
metaCacheWeightLimited)
          └─ appendManifestCacheProperties: derive io.manifest.cache-enabled 
only when not limited
   
   IcebergTableCache.borrow ─ loader ─ new TableOwner(table, ..., weighted)
     └─ areSnapshotsLoaded(table) ? toJson + estimateTable : 
incomplete("iceberg_snapshots_not_loaded")
   
   PaimonMetaCacheCatalog "paimon-table" size estimator
     └─ PaimonCacheSizeEstimator.estimateTable ─ estimateFileStoreTable (each 
concrete table)
          ├─ estimateStoreGrowth(schema)
          └─ estimateTableOwnedFileIO(fileIO)
   ```
   
   Nothing changes for catalogs without a weight limit: the flag is false, and 
the probe and the reservations only run when an entry is weighed. Independent 
of #68839 (tests only); both add tests to `IcebergConnectorCacheTest` and 
`IcebergTableCacheTest`, in different places.
   
   Original author: @CalvinKirs
   
   ### Release note
   
   Under an external metadata cache weight limit, Iceberg catalogs no longer 
turn on the Iceberg SDK manifest content cache implicitly (set 
`io.manifest.cache-enabled=true` to keep it), weight-limited Iceberg REST 
catalogs using `snapshot-loading-mode=refs` no longer retain tables in the 
table cache, and Paimon table cache weights include what a cached table grows 
after admission.
   
   ### Check List (For Author)
   
   - Test <!-- At least one of them must be included. -->
       - [ ] Regression test
       - [x] Unit Test
           - `run-fe-ut.sh --run` on `IcebergCatalogFactoryTest`, 
`IcebergCatalogOptionsSnapshotTest`, `IcebergConnectorCacheTest`, 
`IcebergTableCacheTest`, `PaimonCacheSizeEstimatorTest` (new) and 
`PaimonMetaCacheCatalogTest`; reverting each production change turns exactly 
its new tests red
           - all unit tests of fe-connector-paimon (678) and 
fe-connector-iceberg (1485): 0 failures
           - `mvn checkstyle:check` on both modules: 0 violations
       - [ ] Manual test (add detailed scripts or steps below)
       - [ ] No need to test or manual test. Explain why:
   
   - Behavior changed:
       - [ ] No.
       - [x] Yes. <!-- Explain the behavior change -->
           - See the release note. All three changes apply only to catalogs 
with a metadata cache weight limit.
   
   - Does this need documentation?
       - [x] No.
       - [ ] Yes. <!-- Add document PR link here. eg: 
https://github.com/apache/doris-website/pull/1214 -->
   
   ### Check List (For Reviewer who merge this PR)
   
   - [ ] Confirm the release note
   - [ ] Confirm test cases
   - [ ] Confirm document
   - [ ] Add branch pick label <!-- Add branch pick label that this PR should 
merge into -->
   


-- 
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