Skip to main content

Interacting with the Catalog Client

To interact with the Flyte Catalog for memoization, you use the catalog.Client or catalog.AsyncClient to manage task execution data. This allows Flyte to skip redundant computations by retrieving previously stored results based on a unique cache key.

Defining a Catalog Key

Every interaction with the catalog requires a catalog.Key. This structure uniquely identifies a task execution's inputs and configuration to determine if a cached result exists.

import (
"github.com/flyteorg/flyte/v2/flyteplugins/go/tasks/pluginmachinery/catalog"
corepb "github.com/flyteorg/flyteidl/gen/go/core"
)

// Constructing a key within a task execution context
key := catalog.Key{
Identifier: taskTemplate.GetId(),
CacheVersion: taskAction.Spec.CacheKey,
CacheIgnoreInputVars: metadata.GetCacheIgnoreInputVars(),
TypedInterface: taskTemplate.GetInterface(),
InputReader: tCtx.InputReader(),
}

The InputReader is used by the catalog client to fetch and hash the actual input values. You can exclude specific variables from this hash by adding them to CacheIgnoreInputVars.

Synchronous Cache Operations

The catalog.Client provides synchronous methods to check for hits and store new results. This is typically used within task controllers to determine if a task needs to run.

Retrieving Cached Results

Use Get to check for an existing entry. If found, you can restore the outputs directly to the task's output writer.

// From executor/pkg/controller/taskaction_cache.go
entry, err := r.Catalog.Get(ctx, key)
if err == nil {
// Cache Hit: Restore outputs
if err := tCtx.OutputWriter().Put(ctx, entry.GetOutputs()); err != nil {
return pluginsCore.UnknownTransition, false, fmt.Errorf("persisting cached outputs: %w", err)
}
return pluginsCore.DoTransition(pluginsCore.PhaseInfoSuccess(info)), true, nil
}

if catalog.IsNotFound(err) {
// Cache Miss: Proceed with execution
}

Storing New Results

After a task completes successfully, use Put to store the outputs in the catalog.

// From executor/pkg/controller/taskaction_cache.go
func (r *TaskActionReconciler) writeTaskOutputsToCache(ctx context.Context, tCtx pluginsCore.TaskExecutionContext, key catalog.Key) error {
outputPaths := ioutils.NewReadOnlyOutputFilePaths(ctx, r.DataStore, tCtx.OutputWriter().GetOutputPrefixPath())
outputReader := ioutils.NewRemoteFileOutputReader(ctx, r.DataStore, outputPaths, 0)

metadata := catalog.Metadata{
WorkflowExecutionIdentifier: tCtx.TaskExecutionMetadata().GetTaskExecutionID().GetID().GetNodeExecutionId().GetExecutionId(),
CreatedAt: timestamppb.Now(),
}

_, err := r.Catalog.Put(ctx, key, outputReader, metadata)
return err
}

Managing Serializable Cache Reservations

When CacheSerializable is enabled, Flyte uses a reservation system to ensure that only one execution attempts to populate the cache for a specific key at a time. This prevents multiple concurrent executions from performing the same expensive computation.

// From executor/pkg/controller/taskaction_cache.go
reservation, err := r.Catalog.GetOrExtendReservation(ctx, key, ownerID, heartbeatInterval)
if err != nil {
return pluginsCore.UnknownTransition, false, fmt.Errorf("acquiring cache reservation: %w", err)
}

if reservation.GetOwnerId() == ownerID {
// Current execution owns the reservation; proceed to run the task and populate cache
return pluginsCore.UnknownTransition, false, nil
}

// Another execution owns the reservation; wait for them to finish
info := cacheTaskInfo(corepb.CatalogCacheStatus_CACHE_MISS, "waiting for serialized cache owner")

Always call ReleaseReservation if the execution fails or completes without populating the cache to allow other executions to proceed.

Asynchronous Catalog Interactions

For high-throughput scenarios or plugins handling many sub-tasks (like array tasks), use AsyncClient. It offloads catalog requests to internal workqueues and returns futures.

// From flyteplugins/go/tasks/pluginmachinery/catalog/async_client_impl_test.go
asyncClient := catalog.AsyncClientImpl{
Reader: readerWorkqueue,
Writer: writerWorkqueue,
}

// Request an asynchronous download
request := catalog.DownloadRequest{
Key: key,
Target: outputWriter,
}

future, err := asyncClient.Download(ctx, request)
if err != nil {
return err
}

// Check status later in the execution loop
if future.GetResponseStatus() == catalog.ResponseStatusReady {
resp, err := future.GetResponse()
// Process response
}

Configuration

The behavior of the catalog client, especially the asynchronous workqueues, is controlled via the catalogCache configuration section.

ParameterDefaultDescription
reader.workers10Number of parallel workers for catalog reads.
writer.workers10Number of parallel workers for catalog writes.
reader.maxRetries3Maximum retries for a failed catalog read.
reader.indexCacheMaxItems10000Size of the internal index for the reader workqueue.
maxCacheAge0Maximum age of a cache entry before it is considered a miss (0 = never expires).

Troubleshooting

Distinguishing Misses from Failures

Use catalog.IsNotFound(err) to identify a legitimate cache miss. Other errors (network timeouts, permission issues) should generally be logged but may allow the task to continue execution without memoization to avoid blocking the workflow.

Input Hashing Gotchas

If you experience unexpected cache misses, verify that the InputReader provided to the Key has access to the same data across executions. If certain inputs (like timestamps or random seeds) should not trigger a cache miss, ensure they are included in CacheIgnoreInputVars.