<@ULVA73B9P> How does INPUTS task cache policy wor...
# marvin-ai
j
@Marvin How does INPUTS task cache policy work under the hood? Does it compute a hash of all the inputs of the task on the task runner and then use that to look up a value in the configured result store (e.g s3). Just checking as my task has very large parameters (e.g image bytes) and I want to make sure there isn't any performance issues.
m
🔄 Researching your answer... this may take a while 🔧 Using tool:
research_prefect_topic
📊 Tools used: •
research_prefect_topic
(1x)
j
rip
n
@Marvin sup
m
âś… thought for 7.9 seconds
Hey Nate! What can I help with today? Want to poke at task runners/parallelism or anything around EKS deployments—happy to drop concise examples or CLI steps.
n
@Marvin you dropped the ball on a question, wanna try again?
How does INPUTS task cache policy work under the hood? Does it compute a hash of all the inputs of the task on the task runner and then use that to look up a value in the configured result store (e.g s3). Just checking as my task has very large parameters (e.g image bytes) and I want to make sure there isn't any performance issues.
m
âś… thought for 270.6 seconds
Totally—here’s the straight answer for Prefect 3.x INPUTS caching, with code-verified links. What gets hashed - The task’s resolved input parameters (as a dict), minus anything you explicitly exclude with
INPUTS - 'param'
. - Prefect first tries JSON serialization with sorted keys; if that fails, it falls back to cloudpickle. - The serialized bytes are hashed with MD5 (
usedforsecurity=False
) to make the cache/transaction key. - Only task inputs are used; flow parameters are not unless you add
FLOW_PARAMETERS
to your policy. Where it’s computed - On the worker, when the task engine enters the transaction for that task run. - See compute_transaction_key in the task engine: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/task_engine.py#L260-L310 How the lookup works with result stores (e.g., S3) - The MD5 “transaction key” is used to check existence in the configured ResultStore (local, S3, etc.). - If present and not expired, the transaction reads the stored result and short-circuits task execution. - Under the hood, the transaction key points to metadata that includes a separate storage key for the actual result payload. - Transaction existence check: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/transactions.py#L299-L318 - Result store existence/read details: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/results.py#L590-L631 - Storage key generation (UUID by default): https://github.com/PrefectHQ/prefect/blob/main/src/prefect/results.py#L71-L74 Large parameters (e.g., image bytes) and performance - Prefect serializes the full inputs to bytes, then hashes them—there’s no truncation or streaming for the cache key. - For large binaries (tens/hundreds of MB+), expect noticeable CPU and memory overhead due to: - Building a serialized representation (JSON fails for
bytes
, so it’ll typically use cloudpickle) - MD5 over the full serialized byte string - Recommendation: exclude large payloads from the key and use a lightweight, deterministic surrogate instead (e.g., a content digest, file path, or S3 key). Practical patterns - Exclude the heavy param:
Copy code
from prefect import task
  from prefect.cache_policies import INPUTS

  @task(cache_policy=INPUTS - 'image_bytes')
  def process_image(image_bytes: bytes, model: str, resize: tuple[int, int]):
      ...
- Pass a small digest alongside the bytes, and only include the digest in the key:
Copy code
from prefect import task
  from prefect.cache_policies import INPUTS

  @task(cache_policy=INPUTS - 'image_bytes')
  def process_image(image_bytes: bytes, image_digest: str, model: str, resize: tuple[int, int]):
      # use image_bytes for work; caching keys off image_digest+model+resize
      ...
- Or define a custom key policy to control exactly what contributes to the key:
Copy code
from prefect import task
  from prefect.cache_policies import CacheKeyFnPolicy
  from prefect.utilities.hashing import stable_hash

  def key_fn(task_ctx, inputs, **_):
      return stable_hash(
          inputs['image_digest'],
          inputs.get('model', ''),
          str(inputs.get('resize', '')),
      )

  @task(cache_policy=CacheKeyFnPolicy(key_fn))
  def process_image(image_bytes: bytes, image_digest: str, model: str, resize: tuple[int, int]):
      ...
Notes and edge cases - Stable transforms: Prefect applies a stabilizing transform for pandas DataFrames (sorts columns) before hashing to keep keys deterministic. https://github.com/PrefectHQ/prefect/blob/main/src/prefect/cache_policies.py#L33-L46 - Non-serializable inputs (locks, file handles, etc.) will raise during hashing; exclude them or use a custom key fn. - Hashing utilities (JSON-then-pickle fallback and MD5): https://github.com/PrefectHQ/prefect/blob/main/src/prefect/utilities/hashing.py - INPUTS policy implementation: https://github.com/PrefectHQ/prefect/blob/main/src/prefect/cache_policies.py#L359-L412 Docs
- Caching concepts: https://docs.prefect.io/v3/concepts/caching - How to cache workflow steps: https://docs.prefect.io/v3/how-to-guides/workflows/cache-workflow-steps - Transactions: https://docs.prefect.io/v3/advanced/transactions If you want, I can sketch a tiny benchmark to show the overhead for, say, 50MB vs 500MB byte params, but the main takeaway is: don’t include raw image blobs in INPUTS; pass a small, deterministic fingerprint instead.