1818import com .google .common .base .Splitter .MapSplitter ;
1919import com .google .common .base .Suppliers ;
2020import com .google .common .base .VerifyException ;
21- import com .google .common .cache .Cache ;
2221import com .google .common .collect .ImmutableList ;
2322import com .google .common .collect .ImmutableMap ;
2423import com .google .common .collect .ImmutableSet ;
2524import com .google .common .collect .Iterables ;
2625import com .google .common .collect .Lists ;
2726import com .google .common .collect .Sets ;
2827import com .google .common .collect .Streams ;
29- import com .google .common .util .concurrent .UncheckedExecutionException ;
3028import io .airlift .concurrent .MoreFutures ;
3129import io .airlift .json .JsonCodec ;
3230import io .airlift .log .Logger ;
3331import io .airlift .slice .Slice ;
3432import io .airlift .slice .Slices ;
3533import io .airlift .units .DataSize ;
3634import io .airlift .units .Duration ;
37- import io .trino .cache .EvictableCacheBuilder ;
3835import io .trino .filesystem .FileEntry ;
3936import io .trino .filesystem .FileIterator ;
4037import io .trino .filesystem .Location ;
5552import io .trino .plugin .iceberg .delete .DeletionVectorWriter ;
5653import io .trino .plugin .iceberg .delete .DeletionVectorWriter .DeletionVectorInfo ;
5754import io .trino .plugin .iceberg .functions .IcebergFunctionProvider ;
58- import io .trino .plugin .iceberg .functions .tablechanges .TableChangesFunctionHandle ;
5955import io .trino .plugin .iceberg .procedure .IcebergAddFilesFromTableHandle ;
6056import io .trino .plugin .iceberg .procedure .IcebergAddFilesHandle ;
6157import io .trino .plugin .iceberg .procedure .IcebergDropExtendedStatsHandle ;
268264
269265import static com .google .common .base .Preconditions .checkArgument ;
270266import static com .google .common .base .Preconditions .checkState ;
271- import static com .google .common .base .Throwables .throwIfUnchecked ;
272267import static com .google .common .base .Verify .verify ;
273268import static com .google .common .base .Verify .verifyNotNull ;
274269import static com .google .common .collect .ImmutableList .toImmutableList ;
279274import static com .google .common .collect .Maps .transformValues ;
280275import static com .google .common .collect .Sets .difference ;
281276import static io .airlift .units .Duration .ZERO ;
282- import static io .trino .cache .CacheUtils .uncheckedCacheGet ;
283277import static io .trino .filesystem .Locations .isS3Tables ;
284278import static io .trino .plugin .base .filter .UtcConstraintExtractor .extractTupleDomain ;
285279import static io .trino .plugin .base .projection .ApplyProjectionUtil .extractSupportedProjectedColumns ;
359353import static io .trino .plugin .iceberg .IcebergTableProperties .getPartitioning ;
360354import static io .trino .plugin .iceberg .IcebergTableProperties .getTableLocation ;
361355import static io .trino .plugin .iceberg .IcebergTableProperties .validateCompression ;
362- import static io .trino .plugin .iceberg .IcebergUtil .COORDINATOR_CREDENTIAL_PREFETCH ;
363- import static io .trino .plugin .iceberg .IcebergUtil .CREDENTIAL_CACHE_TTL ;
364356import static io .trino .plugin .iceberg .IcebergUtil .buildPath ;
365357import static io .trino .plugin .iceberg .IcebergUtil .canEnforceColumnConstraintInSpecs ;
366358import static io .trino .plugin .iceberg .IcebergUtil .checkFormatForProperty ;
@@ -538,7 +530,8 @@ public class IcebergMetadata
538530 private final int materializedViewRefreshMaxSnapshotsToExpire ;
539531 private final Duration materializedViewRefreshSnapshotRetentionPeriod ;
540532 private final Map <IcebergTableHandle , AtomicReference <TableStatistics >> tableStatisticsCache = new ConcurrentHashMap <>();
541- private final Cache <SchemaTableName , IcebergTableCredentials > tableCredentialsCache ;
533+ private final ConcurrentHashMap <SchemaTableName , IcebergTableCredentials > tableCredentialsCache = new ConcurrentHashMap <>();
534+ private final Duration credentialCacheTtl ;
542535 private final DeletionVectorWriter deletionVectorWriter ;
543536
544537 private Transaction transaction ;
@@ -560,7 +553,8 @@ public IcebergMetadata(
560553 ExecutorService icebergPlanningExecutor ,
561554 ExecutorService icebergFileDeleteExecutor ,
562555 int materializedViewRefreshMaxSnapshotsToExpire ,
563- Duration materializedViewRefreshSnapshotRetentionPeriod )
556+ Duration materializedViewRefreshSnapshotRetentionPeriod ,
557+ Duration credentialCacheTtl )
564558 {
565559 this .typeManager = requireNonNull (typeManager , "typeManager is null" );
566560 this .commitTaskCodec = requireNonNull (commitTaskCodec , "commitTaskCodec is null" );
@@ -578,15 +572,7 @@ public IcebergMetadata(
578572 this .deletionVectorWriter = requireNonNull (deletionVectorWriter , "deletionVectorWriter is null" );
579573 this .materializedViewRefreshMaxSnapshotsToExpire = materializedViewRefreshMaxSnapshotsToExpire ;
580574 this .materializedViewRefreshSnapshotRetentionPeriod = materializedViewRefreshSnapshotRetentionPeriod ;
581- // Credentials are cached with a TTL so that the coordinator will re-fetch fresh credentials
582- // when getTableCredentials() is called for new tasks in long-running queries.
583- // The cache evicts COORDINATOR_CREDENTIAL_PREFETCH (2 min) before the token expires,
584- // ensuring the next getTableCredentials() call re-fetches while the old token is still valid.
585- // EvictableCache guarantees that invalidate() is immediately visible to subsequent get() calls.
586- this .tableCredentialsCache = EvictableCacheBuilder .newBuilder ()
587- .expireAfterWrite (CREDENTIAL_CACHE_TTL .minus (COORDINATOR_CREDENTIAL_PREFETCH ))
588- .shareNothingWhenDisabled ()
589- .build ();
575+ this .credentialCacheTtl = requireNonNull (credentialCacheTtl , "credentialCacheTtl is null" );
590576 }
591577
592578 @ Override
@@ -604,20 +590,31 @@ public Optional<ConnectorTableCredentials> getTableCredentials(ConnectorSession
604590 private Optional <ConnectorTableCredentials > getOrLoadTableCredentials (ConnectorSession session , SchemaTableName schemaTableName )
605591 {
606592 try {
607- return Optional .of (uncheckedCacheGet (
608- tableCredentialsCache ,
609- schemaTableName ,
610- () -> {
611- BaseTable baseTable = catalog .loadTable (session , schemaTableName );
612- return IcebergTableCredentials .forFileIO (baseTable .io ());
613- }));
593+ // Check if existing cached entry is still fresh (per-entry expiry).
594+ // Each IcebergTableCredentials carries its own expiresAt, so entries with
595+ // different token lifetimes are handled correctly without a cache-wide static TTL.
596+ IcebergTableCredentials cached = tableCredentialsCache .get (schemaTableName );
597+ if (cached != null && !cached .shouldRefresh ()) {
598+ return Optional .of (cached );
599+ }
600+ // Entry is missing or approaching expiry — reload from catalog
601+ IcebergTableCredentials fresh = loadTableCredentials (session , schemaTableName );
602+ tableCredentialsCache .put (schemaTableName , fresh );
603+ return Optional .of (fresh );
614604 }
615- catch (UncheckedExecutionException e ) {
616- throwIfUnchecked (e .getCause ());
617- throw e ;
605+ catch (TableNotFoundException e ) {
606+ // Table may not exist yet (e.g. during CREATE TABLE AS SELECT);
607+ // no credentials to vend in that case.
608+ return Optional .empty ();
618609 }
619610 }
620611
612+ private IcebergTableCredentials loadTableCredentials (ConnectorSession session , SchemaTableName schemaTableName )
613+ {
614+ BaseTable baseTable = catalog .loadTable (session , schemaTableName );
615+ return IcebergTableCredentials .forFileIO (baseTable .io (), credentialCacheTtl .toMillis ());
616+ }
617+
621618 private static SchemaTableName getSchemaTableName (ConnectorTableHandle tableHandle )
622619 {
623620 if (tableHandle instanceof IcebergTableHandle handle ) {
@@ -1621,10 +1618,9 @@ private List<String> getChildNamespaces(ConnectorSession session, String parentN
16211618
16221619 private IcebergWritableTableHandle newWritableTableHandle (SchemaTableName name , Table table )
16231620 {
1624- // Invalidate so the next getOrLoadTableCredentials() call re-loads fresh credentials
1625- // from the catalog. EvictableCache does not support put(); invalidate + lazy reload is
1626- // equivalent and avoids races with concurrent get() calls.
1627- tableCredentialsCache .invalidate (name );
1621+ // Remove so the next getOrLoadTableCredentials() call re-loads fresh credentials
1622+ // from the catalog for the newly created writable table handle.
1623+ tableCredentialsCache .remove (name );
16281624 SortFieldInfo sortInfo = getSupportedSortFields (table .schema (), table .sortOrder ());
16291625 return new IcebergWritableTableHandle (
16301626 name ,
0 commit comments