pithecuse527 opened a new issue, #12856:
URL: https://github.com/apache/gravitino/issues/12856

   ### What would you like to be improved?
   
   When there is an operation related to GVFS, there are a lot of duplicated 
and unnecessary REST communications between GVFS client and the Gravitino 
server.
   
   ## Details
   
   Every `GravitinoVirtualFileSystem` operation issues **11 REST calls** to 
obtain **5 distinct pieces
   of information**. Measured on current `main` against 
`DefaultGVFSOperations`, with the metadata
   cache at its default (disabled), counting the requests the server actually 
received:
   
   | Operation | total | loadCatalog | loadFileset | loadSchema | getSecrets | 
getFileLocation |
   |---|---|---|---|---|---|---|
   | `open` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `create` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `append` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `delete` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `mkdirs` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `getFileStatus` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `listStatus` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `getDefaultBlockSize` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `getDefaultReplication` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `setWorkingDirectory` | 11 | **4** | **2** | 1 | **3** | 1 |
   | `rename` | 13 | **5** | **2** | 1 | **3** | 2 |
   
   The same catalog is loaded four times and the same fileset twice to serve 
one operation. Where they
   come from:
   
   ```
   DefaultGVFSOperations.open(path)
   │
   ├── getActualFileSystem(path)  ->  getActualFileSystemByLocationName(...)
   │   │
   │   ├── getFileset(ident)
   │   │   ├── getFilesetCatalog(...)                       [1]  loadCatalog
   │   │   └── loadFileset(...)                             [2]  loadFileset
   │   │
   │   ├── getAllProperties(ident)
   │   │   ├── getGravitinoClient().loadCatalog(...)        [3]  loadCatalog   
<- already at [1]
   │   │   ├── catalog.supportsSecrets().getSecrets()       [4]  getSecrets
   │   │   ├── catalog.asSchemas().loadSchema(...)          [5]  loadSchema
   │   │   ├── schema.supportsSecrets().getSecrets()        [6]  getSecrets
   │   │   ├── catalog.asFilesetCatalog().loadFileset(...)  [7]  loadFileset   
<- already at [2]
   │   │   └── fileset.supportsSecrets().getSecrets()       [8]  getSecrets
   │   │
   │   └── createFilesetLocationIfNeed(ident, fs, path)
   │       └── getFilesetCatalog(...)                       [9]  loadCatalog   
<- already at [1]
   │
   └── getActualFilePath(path)
       ├── getFilesetCatalog(...)                           [10] loadCatalog   
<- already at [1]
       └── getFileLocation(...)                             [11] getFileLocation
   ```
   
   Three separate causes:
   
   1. **The two halves of a path resolution do not share a lookup.** Every 
operation calls
      `getActualFileSystem` and `getActualFilePath` back to back, and each 
resolves the catalog from
      scratch, although both need the same one.
   
   2. **`getAllProperties` re-fetches what its caller already holds.** It 
reloads the catalog and the
      fileset the caller just resolved, and it reaches `getGravitinoClient()` 
directly rather than
      going through the cache-aware `getFilesetCatalog()` / `getFileset()` / 
`getSchema()`. That last
      part matters: **these calls are not eliminated even when
      `fs.gravitino.filesetMetadataCache.cache.enable` is turned on.**
   
   3. **Properties are built eagerly for a cache that almost always hits.** 
Building the property map
      costs a schema load plus three `getSecrets()` calls — and `getSecrets()` 
is a REST call per
      metadata object that no cache absorbs. But the map is only read when the 
`FileSystem` cache
      misses, which happens once per scheme/authority/user per JVM. Every 
operation after the first
      pays four REST calls for a map it immediately discards.
   
   The cost scales with the number of filesystem operations, not the amount of 
metadata.
   Reading a fileset that holds many files re-resolves the same unchanged 
catalog once per file
   (we have seen more than 10,000 REST calls for a single pass over the data).
   
   ### How should we improve?
   
   Resolve each piece of information once per operation and pass it down.
   
   | Operation | AS-IS | TO-BE (cache off) | TO-BE (cache on) |
   |---|---|---|---|
   | `open` / `create` / `delete` / `listStatus` / ... | 11 | **3** | **1** |
   | `rename` | 13 | **5** | **2** |
   
   ```
   DefaultGVFSOperations.open(path)
   │
   └── resolvePath(path)                      one catalog lookup, shared by 
both halves
       ├── getFilesetCatalog(...)                           [1]  loadCatalog
       ├── getFileset(ident, catalog)                       [2]  loadFileset
       │
       ├── buildFileSystem(ident, catalog, fileset, ...)    reuses [1] and [2]
       │   └── on FileSystem cache miss only:
       │       loadSchema, getSecrets x3, credentials
       │
       └── buildActualFilePath(ident, catalog, ...)         reuses [1]
           └── getFileLocation(...)                         [3]  getFileLocation
   ```
   
   Concretely:
   
   - Add a single `resolvePath()` that returns both the `FileSystem` and the 
resolved storage path,
     so the catalog is resolved once per operation instead of once per consumer.
   - Have `getAllProperties`, `getSchema`, `createFilesetLocationIfNeed` and 
`getCredentialProperties`
     accept the already-resolved `Catalog` and `Fileset` instead of fetching 
them again, and route
     through the cache-aware accessors so enabling the metadata cache actually 
removes them.
   - Split the property map in two: the small set that forms the `FileSystem` 
cache key, built from
     values already in hand, and the full set — schema properties and all 
secrets — deferred behind a
     `Supplier` that only runs when a `FileSystem` is genuinely constructed. 
This removes the
     per-operation `loadSchema` and all three `getSecrets()` calls.
   
   Secrets are deliberately kept out of the cache-key half: no 
`FileSystemProvider` derives an
   authority from a secret (`getFullAuthority` reads only the principal and 
impersonation keys), and
   each `getSecrets()` is a round trip.
   
   Property precedence is unchanged (catalog → schema → fileset → filesystem 
conf →
   `fs.path.config.*`), and no configuration is added. `getFileLocation` 
remains one call per
   operation; reducing *that* is a separate concern, since it also carries the 
per-file audit event.
   
   While making this change we also found that `getSchema()` reads the 
`filesetMetadataCache` field
   directly rather than the lazy `getFilesetMetadataCache()` accessor that 
every other lookup uses.
   The field is null until first access, so `getSchema()` throws an NPE on any 
path that reaches it
   before the cache is initialized. Currently nothing reaches it, because 
`getAllProperties` loads the
   schema itself; routing through `getSchema()` exposes it. One-line fix, 
included.
   
   A regression test asserts the catalog is loaded exactly once per operation — 
it fails with 4 on
   current `main`.
   
   I would be happy to open a PR for this if the approach looks reasonable.


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

Reply via email to