Liliya Angelova
12/03/2025, 12:03 PMMarvin
12/03/2025, 12:03 PMMarvin
12/03/2025, 12:06 PM@task(cache_policy=NO_CACHE) disables caching and result persistence for that task. Prefect won’t compute a cache key, won’t read any prior result, and won’t write the task’s result to storage—so the task function runs every time.
What that means for performance
- Slower if the task is expensive: because you cannot reuse a previous result, every run (and every repeated call within a run) recomputes the work.
- Slightly faster per call if the task is cheap: you avoid cache-key computation and result persistence I/O.
- No deduping under concurrency: if multiple runs/threads hit the same task simultaneously, they’ll all execute instead of one producing a result that others reuse.
- No cache expiration/refresh effects: cache_expiration and refresh_cache don’t apply when using NO_CACHE.
When to use it
- Non-deterministic or side-effecting tasks (e.g., calling external APIs, writing to DBs, using timestamps/randomness).
- Debugging or when you explicitly want to force re-execution.
- When result persistence is unnecessary overhead.
Notes
- This is task-scoped; other tasks in the flow are unaffected.
- In Prefect 3.x, the default cache policy is a composition (DEFAULT = INPUTS + TASK_SOURCE + RUN_ID), which primarily helps within a single flow run and for concurrency control. NO_CACHE disables even that behavior.
- Setting persist_result=False will also effectively disable caching for that task because there’s nothing to store or retrieve.
Docs and reference
- Caching concepts: Prefect 3.x caching concepts
- How-to and patterns: Cache workflow steps
- API reference: prefect.cache_policies (see NO_CACHE)
If you share what the task does and where you’re seeing bottlenecks, I can suggest a cache policy (or isolation level) that balances performance and correctness for your case.Liliya Angelova
12/03/2025, 12:23 PM@flow(task_runner=ThreadPoolTaskRunner())
def timocom_search_flow(
search_parameters: dict,
pg_secret_block: str,
timocom_secret_block: str,
rabbitmq_secret_block: str,
ors_server_variable: str,
price_calculation_server_variable: str,
predictive_scoring_server_variable: str,
here_maps_api_key_variable: str,
):
logger = get_run_logger()
logger.warning(f"Starting Timocom search with parameters: {search_parameters} at {datetime.now()}")
try:
sanitized_search_parameters = sanitize_and_validate_search_parameters(search_parameters)
pg_utils = build_pg_utils_from_block(pg_secret_block)
# Perform Timocom search
# Timocom credentials are stored as "tenant_id": {"user": "username", "pass": "password"}
creds = Secret.load(timocom_secret_block).get()
tenant_ref = sanitized_search_parameters["tenant_ref"]
logger.warning(f"Using Timocom credentials for tenant: {tenant_ref}")
tenant_creds = creds.get(tenant_ref)
if not tenant_creds:
raise ValueError(f"No Timocom credentials found for tenant {tenant_ref}")
user = tenant_creds["user"]
password = tenant_creds["pass"]
# Initialize Timocom instance
timocom_instance = Timocom(pg_utils, user, password)
# Create search tasks
search_tasks = timocom_instance.create_search_tasks(sanitized_search_parameters)
search_futures = timocom_instance.search.map(search_tasks)
logger.warning("Timocom search process completed.")
config = get_config(
ors_server_variable,
price_calculation_server_variable,
predictive_scoring_server_variable,
here_maps_api_key_variable,
)
# for future in search_futures:
for finished_future in as_completed(search_futures):
res = finished_future.result()
# res = future.result()
if not res:
logger.warning("No results found for this search task.")
continue
for batch in chunked(res, 100):
logger.warning(f"Processing batch of {len(batch)} results starting at {datetime.now()}.")
distance_mapping = get_distance_mapping(batch, config)
proccessed_results = []
for i in range(len(batch)):
proccessed_results.append(process_single(config, batch[i], distance_mapping[i]))
logger.warning(f"Post-processing of all freight requests completed at {datetime.now()}.")
save_requests_pg(proccessed_results, pg_secret_block)
logger.warning("Requests data persistence completed.")
logger.warning(f"Publish message: {datetime.now()}")
publish_refresh_message(
rabbitmq_secret_block,
search_parameters["tab_id"],
search_parameters["job_id"],
"REFRESH",
)
except Exception as e:
logger.error(f"Error during Timocom search initiation: {e}")
publish_refresh_message(
rabbitmq_secret_block, search_parameters["tab_id"], search_parameters["job_id"], "FAILED"
)
raiseMarvin
12/03/2025, 12:23 PMMarvin
12/03/2025, 12:27 PMas_completed with Prefect futures from a mapped task. A few common reasons it “doesn’t work”:
Most common pitfalls
1) Wrong import
- If you imported as_completed from concurrent.futures, it won’t work with `PrefectFuture`s.
- Fix:
from prefect.futures import as_completed
Docs: prefect.futures API
2) Not actually mapping a Prefect task
- timocom_instance.search must be a Prefect task (i.e., created with @task). If it’s a plain Python method, .map(...) here isn’t Prefect mapping and won’t return `PrefectFuture`s.
- Quick check:
logger.warning(type(search_futures[0]))
# should be <class 'prefect.futures.PrefectTaskRunFuture'> (or similar)
3) Heavy, blocking work in the flow thread
- Your post-processing (chunking, distance mapping, DB writes, publish messages) runs in the flow thread. That’s okay, but if it’s very heavy it can make it seem like nothing is “arriving.” Tasks still run concurrently in background threads, but the flow only returns to the as_completed loop after your processing finishes.
- If that’s the case, consider moving the heavy steps into tasks as well so the flow thread stays responsive and you keep concurrency high.
4) Shared state/thread-safety
- If Timocom or any client is shared across threads, it may serialize access internally and you won’t see true concurrency. Prefer constructing the client inside a task per call, or otherwise ensure the client is thread-safe.
A minimal working pattern
from prefect import flow, task
from prefect.futures import as_completed
from prefect.task_runners import ThreadPoolTaskRunner
@task
def search_one(item):
# do the work
return item
@flow(task_runner=ThreadPoolTaskRunner())
def my_flow(items):
futures = search_one.map(items)
for fut in as_completed(futures):
# optionally: fut.result(raise_on_failure=False) if you want to keep going on failures
res = fut.result()
# process this result immediately
print("got:", res)
Apply that to your code
- Ensure the import:
from prefect.futures import as_completed
- Ensure timocom_instance.search is a Prefect task, or wrap it:
@task
def search_task(task_input, user, password):
t = Timocom(build_pg_utils_from_block(...), user, password)
return t.search(task_input)
search_futures = search_task.map(search_tasks, unmapped(user), unmapped(password))
for fut in as_completed(search_futures):
res = fut.result(raise_on_failure=False)
...
If it’s still not working, can you share:
- How you imported as_completed
- The type of search_futures[0]
- Whether timocom_instance.search is a Prefect @task
- What “doesn’t work” means (blocks until all complete? throws an error? yields nothing?) and any traceback
References
- Run work concurrently
- prefect.futures