You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/10/02 22:53:18 UTC
(datafusion-comet) branch asf-site updated: Publish built docs triggered by 97ea38f838aa705e3c9465e24734d6df0ffc976b
This is an automated email from the ASF dual-hosted git repository.
github-actions[bot] pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git
The following commit(s) were added to refs/heads/asf-site by this push:
new d2452bf53e Publish built docs triggered by 97ea38f838aa705e3c9465e24734d6df0ffc976b
d2452bf53e is described below
commit d2452bf53e3a50f47dd1f6e2a09a9eef42dfe26a
Author: github-actions[bot] <gi...@users.noreply.github.com>
AuthorDate: Fri Oct 2 22:53:13 2026 +0000
Publish built docs triggered by 97ea38f838aa705e3c9465e24734d6df0ffc976b
---
...id-f5b40459e5fe9b892b845c9198fe1c9420e05d14.svg | 2 +-
.../s3-credential-provider-design.md.txt | 22 +++++++++++++++++----
.../latest/s3-credential-providers.md.txt | 14 +++++++------
.../s3-credential-provider-design.html | 23 ++++++++++++++++++----
searchindex.js | 2 +-
user-guide/latest/s3-credential-providers.html | 13 ++++++------
6 files changed, 54 insertions(+), 22 deletions(-)
diff --git a/_images/mermaid-f5b40459e5fe9b892b845c9198fe1c9420e05d14.svg b/_images/mermaid-f5b40459e5fe9b892b845c9198fe1c9420e05d14.svg
index 63865bb4de..87390a2caa 100644
--- a/_images/mermaid-f5b40459e5fe9b892b845c9198fe1c9420e05d14.svg
+++ b/_images/mermaid-f5b40459e5fe9b892b845c9198fe1c9420e05d14.svg
@@ -1 +1 @@
-<svg id="my-svg" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 511.73px; background-color: transparent;" viewBox="0 0 511.7299499511719 966" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg [...]
\ No newline at end of file
+<svg id="my-svg" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 511.73px; background-color: transparent;" viewBox="0 0 511.7299499511719 966" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg [...]
\ No newline at end of file
diff --git a/_sources/contributor-guide/s3-credential-provider-design.md.txt b/_sources/contributor-guide/s3-credential-provider-design.md.txt
index ab5aa04251..d2f00c0c41 100644
--- a/_sources/contributor-guide/s3-credential-provider-design.md.txt
+++ b/_sources/contributor-guide/s3-credential-provider-design.md.txt
@@ -95,13 +95,27 @@ The locations come from asking for the bucket's whole list rather than which pre
The store itself caches no credentials. Each location's bridge reuses its own credential until shortly before its expiry, as [Credential reuse, bounded by the reported expiry](#credential-reuse-bounded-by-the-reported-expiry) describes. What it keeps is the provider's location list and a store for each location that has been read, so it grows with the vendor's policy list, not with the paths read. The list is only fetched again after a 403 or a credential failure, so a change that causes [...]
-The dispatcher returns `null` for a provider that does not implement the interface without calling it, and Comet builds the same plain store as before. The Iceberg path does not use locations. Operations other than reads route by path without the retry, since Comet only reads through these stores.
+The dispatcher returns `null` for a provider that does not implement the interface without calling it, and Comet builds the same plain store as before. The Iceberg path routes by the same locations; see the next section. Operations other than reads route by path without the retry, since Comet only reads through these stores.
+
+## Location-scoped credentials on the Iceberg path
+
+iceberg-rust's OpenDAL S3 storage attaches one credential loader to the operator it builds for every file, and Comet builds that loader from one reference path: the table's metadata location for a scan, its data location for a write. A base provider therefore gets one credential per table, requested with that path. That is wrong for a table whose files span locations with different policies: data outside the table location (`write.data.path`, the object-storage layout, files added by `ad [...]
+
+For a location-scoped provider, `storage_factory_for` returns a `LocationScopedS3StorageFactory` (`execution/operators/iceberg_location_scoped.rs`) instead of `OpenDalStorageFactory::S3`, following the pattern of `BlobHostPromotingS3StorageFactory`. Its storage implements iceberg-rust's `Storage`, whose every method receives the path it operates on:
+
+- Each call is routed by its path to the longest location covering it in that path's bucket, using the same `LocationIndex` as the Parquet store (`cloud/s3/policy_locations.rs`), and delegated to an OpenDAL S3 storage whose loader is a bridge bound to that location. Bucket snapshots and location storages are built on first use and belong to a `SharedLocations` that every `FileIO` of the provider registration shares; see [Executor `FileIO` cache on the Iceberg path](#executor-fileio-cache [...]
+- The key a call is routed by is the key the OpenDAL storage asks S3 for: the rest of its path after `{scheme}://{bucket}/`, normalized as opendal normalizes every path, which trims whitespace and drops leading and empty segments. The Parquet store percent-decodes a request's URI to get its key, but an Iceberg path is not a URI: a partition value's escape, such as the `%3A` in `ts=2024-01-01T00%3A00`, is part of the key. Routing stops at the first segment no location can have, such as `. [...]
+- The reference bucket's locations are fetched when the registration's first `FileIO` is built, on the thread that loads it, as `create_store` does on the Parquet path. A later `FileIO` whose reference bucket is new to the registration fetches that bucket's on its own loading thread. Another bucket's are fetched when a call first names it, inside an async storage call, so the JVM call runs in `tokio::task::block_in_place`. Bridges for other locations and buckets are derived from the refe [...]
+- `new_input` and `new_output` return files bound to this storage rather than to a location's, so every later read of a file is routed and can retry. `reader` returns a `FileRead` that routes each range read at the current snapshot, as a fresh request is routed, and reopens the file when that snapshot routes it elsewhere. So a refresh by another request moves an open reader too, and a failed range read shares only a refresh attempted after it was routed, which keeps a burst of failures o [...]
+- The refresh semantics are the Parquet store's, and `PolicyLocations` shares the per-generation bookkeeping between the two: a request that gets a 403, or that fails because the provider could not produce its location's credential, fetches the bucket's locations again, once per snapshot generation, and retries once if its path now routes elsewhere. The second trigger looks different here. reqsign's credential chain logs a provider exception and reports no credential (see [the error mess [...]
+- For an s3-compliant alias scheme, `BlobHostPromotingS3Storage` wraps this storage, so routing sees the promoted host as the bucket. A hostless reference path, such as `blob:///bucket/...`, is not covered: `build_s3_access` finds no bucket in it and returns `S3Access::Loader(None)` before it looks up the provider, so the table uses opendal's default chain, as it does with a base provider.
+- A provider whose `getPolicyLocations` throws when the factory is built fails the read or write, as on the Parquet path, rather than falling back to one credential for every file.
## The IRSA web-identity provider keeps its own cache
The bridge's reuse above is bounded by the expiry a vendor reports. The EKS/IRSA web-identity provider in `native/core/src/cloud/s3/web_identity.rs` is Comet's own and keeps a fuller cache. It is not a vendor path -- there is no JVM SPI involved -- so the reasoning above does not apply.
-It exists because the implicit IRSA credential path on the **Iceberg scan** mishandles STS throttling. `AssumeRoleWithWebIdentity` is resolved per reader thread; a concurrent startup burst throttles STS; opendal's default reqsign chain does not retry the throttle and falls through to the EKS node instance role, which lacks bucket access, turning a transient throttle into a hard `403` (this is the reported failure). The raw-Parquet path is intentionally left alone: its AWS SDK default cha [...]
+It exists because the implicit IRSA credential path on the **Iceberg scan** mishandles STS throttling. `AssumeRoleWithWebIdentity` is resolved per reader thread; a concurrent startup burst throttles STS; opendal's default reqsign chain does not retry the throttle and falls through to the EKS node instance role, which lacks bucket access, turning a transient throttle into a hard `403` (this is the reported failure). The raw-Parquet path is intentionally left alone: its AWS SDK default cha [...]
- builds its STS client from the AWS SDK's fully-resolved `SdkConfig` (`aws_config::defaults(...).load()`) with a raised `RetryConfig`, and calls `AssumeRoleWithWebIdentity` on it; because the client comes from the resolved config rather than a hand-assembled one, it honors region, `use_fips`, `use_dual_stack` and any profile/custom STS endpoint with no per-setting copying to keep in sync,
- reports the credential's real STS expiry (not an early deadline). reqsign's signer refuses to sign within ~10s of the reported expiry and reloads within 120s of it, so an early deadline would open a signing dead zone before every refresh; instead our own cache margin (`min_ttl`, floored to 120s) refreshes at or before the signer's reload point, and a still-valid credential keeps being served if a refresh is throttled,
@@ -134,7 +148,7 @@ The full unfiltered FileIO property bag crosses JNI as `catalog_properties`. The
## Executor `FileIO` cache on the Iceberg path
-`load_file_io` in `iceberg_common.rs` serves clones from a per-executor cache instead of building a `FileIO` per task. An entry is keyed by access mode, catalog name, the full reference path and the whole catalog property bag, and it holds the `FileIO` together with the bridge it was built with, so a bridge and its dispatcher handle live as long as the entry. The cache holds 64 entries, evicts the least recently used one, and is drained with the Tokio runtime. The reference path has to s [...]
+`load_file_io` in `iceberg_common.rs` serves clones from a per-executor cache instead of building a `FileIO` per task. An entry is keyed by access mode, catalog name, the full reference path and the whole catalog property bag, and it holds the `FileIO` together with the bridge it was built with, so a bridge and its dispatcher handle live as long as the entry. The cache holds 64 entries, evicts the least recently used one, and is drained with the Tokio runtime. The reference path has to s [...]
The cache holds the `FileIO` and its bridge. The bridge reuses its credential until shortly before its expiry, as [Credential reuse, bounded by the reported expiry](#credential-reuse-bounded-by-the-reported-expiry) describes, so `provide_credential` reaches the vendor about once per credential rather than once per storage call.
@@ -161,4 +175,4 @@ The SPI returns a `CometS3Credentials` or throws. There is no sentinel "I do not
## Iceberg path: error message fidelity caveat
-When the bridge is wired into `iceberg-rust`, the outer `reqsign-core::ProvideCredentialChain` currently swallows thrown exceptions into "no credential" before the request reaches `opendal`. The credential is still not issued and the request still fails, but the message is degraded to an opaque anonymous-request failure. No Comet change fixes this; it is resolved upstream when `iceberg-rust` stops wrapping custom loaders in its outer chain or moves its S3 backend to `object_store`.
+When the bridge is wired into `iceberg-rust`, the outer `reqsign-core::ProvideCredentialChain` currently swallows thrown exceptions into "no credential" before the request reaches `opendal`. The credential is still not issued and the request still fails, but the message is degraded to reqsign's generic `failed to load signing credential`, and the request is never sent. No Comet change fixes this; it is resolved upstream when `iceberg-rust` stops wrapping custom loaders in its outer chain [...]
diff --git a/_sources/user-guide/latest/s3-credential-providers.md.txt b/_sources/user-guide/latest/s3-credential-providers.md.txt
index 7f0dea4c7b..4a35f477ed 100644
--- a/_sources/user-guide/latest/s3-credential-providers.md.txt
+++ b/_sources/user-guide/latest/s3-credential-providers.md.txt
@@ -99,6 +99,8 @@ For Iceberg (the `opendal` path), set the per-catalog property under the `s3.` n
spark.sql.catalog.<catalog>.s3.comet.credential.provider.class=com.vendor.MyCometCredentialProvider
```
+On the Iceberg path Comet builds a table's bridge from the bucket in the table's metadata location, or its data location for a write. A location with no host, such as the `blob:///bucket/...` form of an s3-compliant alias scheme, names no bucket there, so Comet does not use the configured provider for that table at all: opendal's default credential chain signs every request, and a location-scoped provider's locations are not used.
+
Add the vendor JAR to your Spark executor classpath:
```sh
@@ -284,7 +286,7 @@ public CometS3Credentials getCredentialsForPath(CometS3CredentialContext ctx) th
### Credentials per location
-On the Parquet path, a `CometS3CredentialProvider` gets one credential per bucket: Comet requests it with the path of the first file it reads from the bucket and uses it for every file there. If your policies differ by location within a bucket, for example one policy for `warehouse/sales` and another for `warehouse/finance`, implement `CometS3LocationScopedCredentialProvider` and tell Comet where those locations are:
+On the Parquet path, a `CometS3CredentialProvider` gets one credential per bucket: Comet requests it with the path of the first file it reads from the bucket and uses it for every file there. On the Iceberg path it gets one per table: Comet requests it with the path of the table's metadata file (its data location, for a write) and uses it for every file the table reads or writes. If your policies differ by location within a bucket, for example one policy for `warehouse/sales` and another [...]
```java
public final class MyLocationProvider implements CometS3LocationScopedCredentialProvider {
@@ -301,15 +303,15 @@ public final class MyLocationProvider implements CometS3LocationScopedCredential
}
```
-Comet serves each request with the credential of the longest location that covers its path. A location covers a path when the path is the location itself or lies below it, compared one `/`-separated segment at a time, so `warehouse/sales` covers `warehouse/sales/part-0.parquet` but not `warehouse/sales_eu/part-0.parquet`. The bucket root covers every path that no returned location covers, and an empty list serves the whole bucket with the root's credential. Write locations the way `Comet [...]
+Comet serves each request with the credential of the longest location that covers its path. A location covers a path when the path is the location itself or lies below it, compared one `/`-separated segment at a time, so `warehouse/sales` covers `warehouse/sales/part-0.parquet` but not `warehouse/sales_eu/part-0.parquet`. The bucket root covers every path that no returned location covers, and an empty list serves the whole bucket with the root's credential. Write locations the way `Comet [...]
-Comet requests a location's credential by calling `getCredentialsForPath` with the location as the path, as you returned it but with a leading slash. Every request under a location shares that credential, so it must authorize every path the location is the longest match for, and your cache can key on the location. Locations apply to Comet's native Parquet reads only; Iceberg reads call `getCredentialsForPath` as they do for any provider.
+Comet requests a location's credential by calling `getCredentialsForPath` with the location as the path, as you returned it but with a leading slash. Every request under a location shares that credential, so it must authorize every path the location is the longest match for, and your cache can key on the location. Locations apply to Comet's native Parquet reads and to its native Iceberg reads and writes. On the Iceberg path each data and delete file is routed by its own path, in its own [...]
-**When Comet asks.** Comet calls `getPolicyLocations` when it creates the store for a bucket on an executor and keeps the answer for later reads of that bucket with the same S3 configuration. Reads that start at the same moment may each create a store and call it. If a read then fails with 403, or because `getCredentialsForPath` threw for the location Comet sent it to, Comet asks again, once for all the reads that failed on the same answer, and retries each read once if its path now fall [...]
+**When Comet asks.** Comet calls `getPolicyLocations` when it creates the store for a bucket on an executor and keeps the answer for later reads of that bucket with the same S3 configuration. On the Iceberg path it asks once per set of catalog properties and access mode on an executor, for the bucket of the first table's metadata or data location, and again when a table or file in another bucket is first used. Tables share the answers when their catalog properties match, and Comet keeps [...]
**Failures.** If `getPolicyLocations` throws or returns `null`, or returns a location that is `null` or invalid, the read fails. A location is invalid if, once decoded, it is not valid UTF-8 or has a segment that is empty, `.`, `..`, or contains a control character, so a URI such as `s3://bucket/a` is invalid too. Comet does not fall back to a broader credential.
-**Backward compatibility.** Providers that implement only `CometS3CredentialProvider` are unaffected. Comet calls nothing new on them and keeps one credential per bucket, as before.
+**Backward compatibility.** Providers that implement only `CometS3CredentialProvider` are unaffected. Comet calls nothing new on them and keeps one credential per bucket on the Parquet path and one per table on the Iceberg path, as before.
### Composing multiple credential backends
@@ -405,4 +407,4 @@ Vendor implementations need the Comet SPI classes at compile time only. Use `pro
### Iceberg path: error message fidelity
-When the bridge is wired into `iceberg-rust`, the outer `reqsign-core::ProvideCredentialChain` currently swallows thrown exceptions into "no credential" before the request reaches `opendal`. The credential is still not issued and the request still fails, only the message is degraded to an opaque anonymous-request failure. No Comet change fixes this; it is resolved upstream if `iceberg-rust` stops wrapping custom loaders in its outer chain or moves its S3 backend to `object_store`.
+When the bridge is wired into `iceberg-rust`, the outer `reqsign-core::ProvideCredentialChain` currently swallows thrown exceptions into "no credential" before the request reaches `opendal`. The credential is still not issued and the request still fails, only the message is degraded to reqsign's generic `failed to load signing credential`. No Comet change fixes this; it is resolved upstream if `iceberg-rust` stops wrapping custom loaders in its outer chain or moves its S3 backend to `obj [...]
diff --git a/contributor-guide/s3-credential-provider-design.html b/contributor-guide/s3-credential-provider-design.html
index 6b75236a92..18fe6a76ff 100644
--- a/contributor-guide/s3-credential-provider-design.html
+++ b/contributor-guide/s3-credential-provider-design.html
@@ -622,12 +622,26 @@ under the License.
</ul>
<p>The locations come from asking for the bucket’s whole list rather than which prefixes one session covers. Asking per session leaves Comet to discover the other scopes from 403s, which needs mutable per-store state and cannot route a partition that spans scopes. With the whole list up front, routing is a function of the path.</p>
<p>The store itself caches no credentials. Each location’s bridge reuses its own credential until shortly before its expiry, as <a class="reference internal" href="#credential-reuse-bounded-by-the-reported-expiry">Credential reuse, bounded by the reported expiry</a> describes. What it keeps is the provider’s location list and a store for each location that has been read, so it grows with the vendor’s policy list, not with the paths read. The list is only fetched again after a 403 or a cr [...]
-<p>The dispatcher returns <code class="docutils literal notranslate"><span class="pre">null</span></code> for a provider that does not implement the interface without calling it, and Comet builds the same plain store as before. The Iceberg path does not use locations. Operations other than reads route by path without the retry, since Comet only reads through these stores.</p>
+<p>The dispatcher returns <code class="docutils literal notranslate"><span class="pre">null</span></code> for a provider that does not implement the interface without calling it, and Comet builds the same plain store as before. The Iceberg path routes by the same locations; see the next section. Operations other than reads route by path without the retry, since Comet only reads through these stores.</p>
+</section>
+<section id="location-scoped-credentials-on-the-iceberg-path">
+<h2>Location-scoped credentials on the Iceberg path<a class="headerlink" href="#location-scoped-credentials-on-the-iceberg-path" title="Link to this heading">#</a></h2>
+<p>iceberg-rust’s OpenDAL S3 storage attaches one credential loader to the operator it builds for every file, and Comet builds that loader from one reference path: the table’s metadata location for a scan, its data location for a write. A base provider therefore gets one credential per table, requested with that path. That is wrong for a table whose files span locations with different policies: data outside the table location (<code class="docutils literal notranslate"><span class="pre"> [...]
+<p>For a location-scoped provider, <code class="docutils literal notranslate"><span class="pre">storage_factory_for</span></code> returns a <code class="docutils literal notranslate"><span class="pre">LocationScopedS3StorageFactory</span></code> (<code class="docutils literal notranslate"><span class="pre">execution/operators/iceberg_location_scoped.rs</span></code>) instead of <code class="docutils literal notranslate"><span class="pre">OpenDalStorageFactory::S3</span></code>, following [...]
+<ul class="simple">
+<li><p>Each call is routed by its path to the longest location covering it in that path’s bucket, using the same <code class="docutils literal notranslate"><span class="pre">LocationIndex</span></code> as the Parquet store (<code class="docutils literal notranslate"><span class="pre">cloud/s3/policy_locations.rs</span></code>), and delegated to an OpenDAL S3 storage whose loader is a bridge bound to that location. Bucket snapshots and location storages are built on first use and belong t [...]
+<li><p>The key a call is routed by is the key the OpenDAL storage asks S3 for: the rest of its path after <code class="docutils literal notranslate"><span class="pre">{scheme}://{bucket}/</span></code>, normalized as opendal normalizes every path, which trims whitespace and drops leading and empty segments. The Parquet store percent-decodes a request’s URI to get its key, but an Iceberg path is not a URI: a partition value’s escape, such as the <code class="docutils literal notranslate"> [...]
+<li><p>The reference bucket’s locations are fetched when the registration’s first <code class="docutils literal notranslate"><span class="pre">FileIO</span></code> is built, on the thread that loads it, as <code class="docutils literal notranslate"><span class="pre">create_store</span></code> does on the Parquet path. A later <code class="docutils literal notranslate"><span class="pre">FileIO</span></code> whose reference bucket is new to the registration fetches that bucket’s on its own [...]
+<li><p><code class="docutils literal notranslate"><span class="pre">new_input</span></code> and <code class="docutils literal notranslate"><span class="pre">new_output</span></code> return files bound to this storage rather than to a location’s, so every later read of a file is routed and can retry. <code class="docutils literal notranslate"><span class="pre">reader</span></code> returns a <code class="docutils literal notranslate"><span class="pre">FileRead</span></code> that routes eac [...]
+<li><p>The refresh semantics are the Parquet store’s, and <code class="docutils literal notranslate"><span class="pre">PolicyLocations</span></code> shares the per-generation bookkeeping between the two: a request that gets a 403, or that fails because the provider could not produce its location’s credential, fetches the bucket’s locations again, once per snapshot generation, and retries once if its path now routes elsewhere. The second trigger looks different here. reqsign’s credential [...]
+<li><p>For an s3-compliant alias scheme, <code class="docutils literal notranslate"><span class="pre">BlobHostPromotingS3Storage</span></code> wraps this storage, so routing sees the promoted host as the bucket. A hostless reference path, such as <code class="docutils literal notranslate"><span class="pre">blob:///bucket/...</span></code>, is not covered: <code class="docutils literal notranslate"><span class="pre">build_s3_access</span></code> finds no bucket in it and returns <code cla [...]
+<li><p>A provider whose <code class="docutils literal notranslate"><span class="pre">getPolicyLocations</span></code> throws when the factory is built fails the read or write, as on the Parquet path, rather than falling back to one credential for every file.</p></li>
+</ul>
</section>
<section id="the-irsa-web-identity-provider-keeps-its-own-cache">
<h2>The IRSA web-identity provider keeps its own cache<a class="headerlink" href="#the-irsa-web-identity-provider-keeps-its-own-cache" title="Link to this heading">#</a></h2>
<p>The bridge’s reuse above is bounded by the expiry a vendor reports. The EKS/IRSA web-identity provider in <code class="docutils literal notranslate"><span class="pre">native/core/src/cloud/s3/web_identity.rs</span></code> is Comet’s own and keeps a fuller cache. It is not a vendor path – there is no JVM SPI involved – so the reasoning above does not apply.</p>
-<p>It exists because the implicit IRSA credential path on the <strong>Iceberg scan</strong> mishandles STS throttling. <code class="docutils literal notranslate"><span class="pre">AssumeRoleWithWebIdentity</span></code> is resolved per reader thread; a concurrent startup burst throttles STS; opendal’s default reqsign chain does not retry the throttle and falls through to the EKS node instance role, which lacks bucket access, turning a transient throttle into a hard <code class="docutils [...]
+<p>It exists because the implicit IRSA credential path on the <strong>Iceberg scan</strong> mishandles STS throttling. <code class="docutils literal notranslate"><span class="pre">AssumeRoleWithWebIdentity</span></code> is resolved per reader thread; a concurrent startup burst throttles STS; opendal’s default reqsign chain does not retry the throttle and falls through to the EKS node instance role, which lacks bucket access, turning a transient throttle into a hard <code class="docutils [...]
<ul class="simple">
<li><p>builds its STS client from the AWS SDK’s fully-resolved <code class="docutils literal notranslate"><span class="pre">SdkConfig</span></code> (<code class="docutils literal notranslate"><span class="pre">aws_config::defaults(...).load()</span></code>) with a raised <code class="docutils literal notranslate"><span class="pre">RetryConfig</span></code>, and calls <code class="docutils literal notranslate"><span class="pre">AssumeRoleWithWebIdentity</span></code> on it; because the cl [...]
<li><p>reports the credential’s real STS expiry (not an early deadline). reqsign’s signer refuses to sign within ~10s of the reported expiry and reloads within 120s of it, so an early deadline would open a signing dead zone before every refresh; instead our own cache margin (<code class="docutils literal notranslate"><span class="pre">min_ttl</span></code>, floored to 120s) refreshes at or before the signer’s reload point, and a still-valid credential keeps being served if a refresh is t [...]
@@ -680,7 +694,7 @@ under the License.
</section>
<section id="executor-fileio-cache-on-the-iceberg-path">
<h2>Executor <code class="docutils literal notranslate"><span class="pre">FileIO</span></code> cache on the Iceberg path<a class="headerlink" href="#executor-fileio-cache-on-the-iceberg-path" title="Link to this heading">#</a></h2>
-<p><code class="docutils literal notranslate"><span class="pre">load_file_io</span></code> in <code class="docutils literal notranslate"><span class="pre">iceberg_common.rs</span></code> serves clones from a per-executor cache instead of building a <code class="docutils literal notranslate"><span class="pre">FileIO</span></code> per task. An entry is keyed by access mode, catalog name, the full reference path and the whole catalog property bag, and it holds the <code class="docutils lite [...]
+<p><code class="docutils literal notranslate"><span class="pre">load_file_io</span></code> in <code class="docutils literal notranslate"><span class="pre">iceberg_common.rs</span></code> serves clones from a per-executor cache instead of building a <code class="docutils literal notranslate"><span class="pre">FileIO</span></code> per task. An entry is keyed by access mode, catalog name, the full reference path and the whole catalog property bag, and it holds the <code class="docutils lite [...]
<p>The cache holds the <code class="docutils literal notranslate"><span class="pre">FileIO</span></code> and its bridge. The bridge reuses its credential until shortly before its expiry, as <a class="reference internal" href="#credential-reuse-bounded-by-the-reported-expiry">Credential reuse, bounded by the reported expiry</a> describes, so <code class="docutils literal notranslate"><span class="pre">provide_credential</span></code> reaches the vendor about once per credential rather tha [...]
</section>
<section id="property-bag-handling-on-the-parquet-path">
@@ -706,7 +720,7 @@ under the License.
</section>
<section id="iceberg-path-error-message-fidelity-caveat">
<h2>Iceberg path: error message fidelity caveat<a class="headerlink" href="#iceberg-path-error-message-fidelity-caveat" title="Link to this heading">#</a></h2>
-<p>When the bridge is wired into <code class="docutils literal notranslate"><span class="pre">iceberg-rust</span></code>, the outer <code class="docutils literal notranslate"><span class="pre">reqsign-core::ProvideCredentialChain</span></code> currently swallows thrown exceptions into “no credential” before the request reaches <code class="docutils literal notranslate"><span class="pre">opendal</span></code>. The credential is still not issued and the request still fails, but the message [...]
+<p>When the bridge is wired into <code class="docutils literal notranslate"><span class="pre">iceberg-rust</span></code>, the outer <code class="docutils literal notranslate"><span class="pre">reqsign-core::ProvideCredentialChain</span></code> currently swallows thrown exceptions into “no credential” before the request reaches <code class="docutils literal notranslate"><span class="pre">opendal</span></code>. The credential is still not issued and the request still fails, but the message [...]
</section>
</section>
@@ -763,6 +777,7 @@ under the License.
<li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#why-fresh-construction-in-initialize-not-probing-a-jvm-wide-static">Why fresh construction in <code class="docutils literal notranslate"><span class="pre">initialize</span></code>, not probing a JVM-wide static</a></li>
<li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#credential-reuse-bounded-by-the-reported-expiry">Credential reuse, bounded by the reported expiry</a></li>
<li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#location-scoped-credentials-on-the-parquet-path">Location-scoped credentials on the Parquet path</a></li>
+<li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#location-scoped-credentials-on-the-iceberg-path">Location-scoped credentials on the Iceberg path</a></li>
<li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#the-irsa-web-identity-provider-keeps-its-own-cache">The IRSA web-identity provider keeps its own cache</a></li>
<li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#path-specific-behavior">Path-specific behavior</a></li>
<li class="toc-h2 nav-item toc-entry"><a class="reference internal nav-link" href="#property-bag-handling-on-the-iceberg-path">Property-bag handling on the Iceberg path</a></li>
diff --git a/searchindex.js b/searchindex.js
index 503d03768c..a19b9cd977 100644
--- a/searchindex.js
+++ b/searchindex.js
@@ -1 +1 @@
-Search.setIndex({"alltitles": {"!": [[57, "id1"]], "%": [[55, "id1"]], "&": [[45, "id1"]], "*": [[55, "id2"]], "+": [[55, "id3"]], "-": [[55, "id4"]], "/": [[55, "id5"]], "1. Format Your Code": [[42, "format-your-code"]], "1. Install Comet": [[63, "install-comet"], [75, "install-comet"]], "1. Native Operators (nativeExecs map)": [[28, "native-operators-nativeexecs-map"]], "2. Build and Verify": [[42, "build-and-verify"]], "2. Clone Iceberg and Apply Diff": [[63, "clone-iceberg-and-apply- [...]
\ No newline at end of file
+Search.setIndex({"alltitles": {"!": [[57, "id1"]], "%": [[55, "id1"]], "&": [[45, "id1"]], "*": [[55, "id2"]], "+": [[55, "id3"]], "-": [[55, "id4"]], "/": [[55, "id5"]], "1. Format Your Code": [[42, "format-your-code"]], "1. Install Comet": [[63, "install-comet"], [75, "install-comet"]], "1. Native Operators (nativeExecs map)": [[28, "native-operators-nativeexecs-map"]], "2. Build and Verify": [[42, "build-and-verify"]], "2. Clone Iceberg and Apply Diff": [[63, "clone-iceberg-and-apply- [...]
\ No newline at end of file
diff --git a/user-guide/latest/s3-credential-providers.html b/user-guide/latest/s3-credential-providers.html
index 590cbff822..f923dcc580 100644
--- a/user-guide/latest/s3-credential-providers.html
+++ b/user-guide/latest/s3-credential-providers.html
@@ -866,6 +866,7 @@ under the License.
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">spark</span><span class="o">.</span><span class="n">sql</span><span class="o">.</span><span class="n">catalog</span><span class="o">.<</span><span class="n">catalog</span><span class="o">>.</span><span class="n">s3</span><span class="o">.</span><span class="n">comet</span><span class="o">.</span><span class="n">credential</span><span class="o">.</span><span class="n">provider</span>< [...]
</pre></div>
</div>
+<p>On the Iceberg path Comet builds a table’s bridge from the bucket in the table’s metadata location, or its data location for a write. A location with no host, such as the <code class="docutils literal notranslate"><span class="pre">blob:///bucket/...</span></code> form of an s3-compliant alias scheme, names no bucket there, so Comet does not use the configured provider for that table at all: opendal’s default credential chain signs every request, and a location-scoped provider’s locat [...]
<p>Add the vendor JAR to your Spark executor classpath:</p>
<div class="highlight-sh notranslate"><div class="highlight"><pre><span></span>spark-submit<span class="w"> </span>--jars<span class="w"> </span>vendor-comet-bridge.jar<span class="w"> </span>...
</pre></div>
@@ -1056,7 +1057,7 @@ under the License.
</section>
<section id="credentials-per-location">
<h3>Credentials per location<a class="headerlink" href="#credentials-per-location" title="Link to this heading">#</a></h3>
-<p>On the Parquet path, a <code class="docutils literal notranslate"><span class="pre">CometS3CredentialProvider</span></code> gets one credential per bucket: Comet requests it with the path of the first file it reads from the bucket and uses it for every file there. If your policies differ by location within a bucket, for example one policy for <code class="docutils literal notranslate"><span class="pre">warehouse/sales</span></code> and another for <code class="docutils literal notrans [...]
+<p>On the Parquet path, a <code class="docutils literal notranslate"><span class="pre">CometS3CredentialProvider</span></code> gets one credential per bucket: Comet requests it with the path of the first file it reads from the bucket and uses it for every file there. On the Iceberg path it gets one per table: Comet requests it with the path of the table’s metadata file (its data location, for a write) and uses it for every file the table reads or writes. If your policies differ by locati [...]
<div class="highlight-java notranslate"><div class="highlight"><pre><span></span><span class="kd">public</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="kd">class</span> <span class="nc">MyLocationProvider</span><span class="w"> </span><span class="kd">implements</span><span class="w"> </span><span class="n">CometS3LocationScopedCredentialProvider</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nd">@Override</span>
<span class="w"> </span><span class="kd">public</span><span class="w"> </span><span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span><span class="w"> </span><span class="nf">getPolicyLocations</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">bucket</span><span class="p">)</span><span class="w"> </span><span class="kd">throws</span><span class="w"> </span><span class="n">Exception</ [...]
@@ -1071,11 +1072,11 @@ under the License.
<span class="p">}</span>
</pre></div>
</div>
-<p>Comet serves each request with the credential of the longest location that covers its path. A location covers a path when the path is the location itself or lies below it, compared one <code class="docutils literal notranslate"><span class="pre">/</span></code>-separated segment at a time, so <code class="docutils literal notranslate"><span class="pre">warehouse/sales</span></code> covers <code class="docutils literal notranslate"><span class="pre">warehouse/sales/part-0.parquet</span [...]
-<p>Comet requests a location’s credential by calling <code class="docutils literal notranslate"><span class="pre">getCredentialsForPath</span></code> with the location as the path, as you returned it but with a leading slash. Every request under a location shares that credential, so it must authorize every path the location is the longest match for, and your cache can key on the location. Locations apply to Comet’s native Parquet reads only; Iceberg reads call <code class="docutils liter [...]
-<p><strong>When Comet asks.</strong> Comet calls <code class="docutils literal notranslate"><span class="pre">getPolicyLocations</span></code> when it creates the store for a bucket on an executor and keeps the answer for later reads of that bucket with the same S3 configuration. Reads that start at the same moment may each create a store and call it. If a read then fails with 403, or because <code class="docutils literal notranslate"><span class="pre">getCredentialsForPath</span></code> [...]
+<p>Comet serves each request with the credential of the longest location that covers its path. A location covers a path when the path is the location itself or lies below it, compared one <code class="docutils literal notranslate"><span class="pre">/</span></code>-separated segment at a time, so <code class="docutils literal notranslate"><span class="pre">warehouse/sales</span></code> covers <code class="docutils literal notranslate"><span class="pre">warehouse/sales/part-0.parquet</span [...]
+<p>Comet requests a location’s credential by calling <code class="docutils literal notranslate"><span class="pre">getCredentialsForPath</span></code> with the location as the path, as you returned it but with a leading slash. Every request under a location shares that credential, so it must authorize every path the location is the longest match for, and your cache can key on the location. Locations apply to Comet’s native Parquet reads and to its native Iceberg reads and writes. On the I [...]
+<p><strong>When Comet asks.</strong> Comet calls <code class="docutils literal notranslate"><span class="pre">getPolicyLocations</span></code> when it creates the store for a bucket on an executor and keeps the answer for later reads of that bucket with the same S3 configuration. On the Iceberg path it asks once per set of catalog properties and access mode on an executor, for the bucket of the first table’s metadata or data location, and again when a table or file in another bucket is f [...]
<p><strong>Failures.</strong> If <code class="docutils literal notranslate"><span class="pre">getPolicyLocations</span></code> throws or returns <code class="docutils literal notranslate"><span class="pre">null</span></code>, or returns a location that is <code class="docutils literal notranslate"><span class="pre">null</span></code> or invalid, the read fails. A location is invalid if, once decoded, it is not valid UTF-8 or has a segment that is empty, <code class="docutils literal notr [...]
-<p><strong>Backward compatibility.</strong> Providers that implement only <code class="docutils literal notranslate"><span class="pre">CometS3CredentialProvider</span></code> are unaffected. Comet calls nothing new on them and keeps one credential per bucket, as before.</p>
+<p><strong>Backward compatibility.</strong> Providers that implement only <code class="docutils literal notranslate"><span class="pre">CometS3CredentialProvider</span></code> are unaffected. Comet calls nothing new on them and keeps one credential per bucket on the Parquet path and one per table on the Iceberg path, as before.</p>
</section>
<section id="composing-multiple-credential-backends">
<h3>Composing multiple credential backends<a class="headerlink" href="#composing-multiple-credential-backends" title="Link to this heading">#</a></h3>
@@ -1175,7 +1176,7 @@ under the License.
</section>
<section id="iceberg-path-error-message-fidelity">
<h3>Iceberg path: error message fidelity<a class="headerlink" href="#iceberg-path-error-message-fidelity" title="Link to this heading">#</a></h3>
-<p>When the bridge is wired into <code class="docutils literal notranslate"><span class="pre">iceberg-rust</span></code>, the outer <code class="docutils literal notranslate"><span class="pre">reqsign-core::ProvideCredentialChain</span></code> currently swallows thrown exceptions into “no credential” before the request reaches <code class="docutils literal notranslate"><span class="pre">opendal</span></code>. The credential is still not issued and the request still fails, only the messag [...]
+<p>When the bridge is wired into <code class="docutils literal notranslate"><span class="pre">iceberg-rust</span></code>, the outer <code class="docutils literal notranslate"><span class="pre">reqsign-core::ProvideCredentialChain</span></code> currently swallows thrown exceptions into “no credential” before the request reaches <code class="docutils literal notranslate"><span class="pre">opendal</span></code>. The credential is still not issued and the request still fails, only the messag [...]
</section>
</section>
</section>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org