<@ULVA73B9P> The problem: We are running server ...
# ask-community
s
@Marvin The problem: We are running server side prefect with k8s oficial helm charts. the problem we are trying to solve is the following: • Our flows are simple, they start, perform http request (can take long to respond) and stop. • We are running kubernetes workers, so we create a new pod for every execution. Every pod consumes arround 300MB of RAM • In other words, we are currently using prefect to run background jobs that we could run with Celery • It's quite expensive to us if every job, takes 300MB of RAM. What we have looked into: We've looked at alternatives, like using prefect background tasks: https://docs.prefect.io/v3/advanced/background-tasks But we are missing primitives to tell concurrency and so on to background tasks. Our new proposal: Instead of using a kubernetes worker, just use a process worker for this workload. This worker will run flows as subprocesses, we can scale the worker by CPU/RAM if that's a problem. We can benefit from deployment definitions, concurrency, and all the things. I have the impression this is a better solution for our specific case. In the future, if we have a different workload that we want to execute one flow in one kubernetes pod, we can create a new kubernetes worker for it. Is this solution an antipattern? are we doing something wrong? It seems process worker pools are meant for development or try things in local
m
thought for 331.6 seconds
Short answer: your proposal is not an antipattern. For your workload, a process work pool with one or more process workers is a perfectly valid (and common) production setup in Prefect 3.x—especially for lightweight, I/O-bound flows where a pod-per-run is cost-prohibitive. Why a process worker can be a good fit - Minimal overhead: A single worker pod can execute many flow runs as subprocesses instead of creating a Pod per run. This typically reduces memory/CPU overhead vs k8s worker jobs (which incur ~hundreds of MB per pod in your case). - Full orchestration: You still get deployments, scheduling, retries, concurrency limits, tags, and automations. - Simple scaling: Scale by adding more worker replicas or adjusting pool/queue concurrency. Trade-offs to be aware of - Failure/Isolation: A process worker is a single pod; if it restarts, you lose all runs it was executing. You don’t get per-run container isolation. If you need strict isolation or specific per-run dependencies, Kubernetes/Docker workers are better. - Dependency conflicts: Runs share the same base environment. If your flows have divergent dependencies, containerized workers are safer. - Resource caps per worker: Put reasonable concurrency limits in place to avoid OOM on the worker pod. What to use for concurrency and rate-limiting (Prefect 3.x) - Work pool concurrency limit: Caps concurrent flow runs started from a pool.
Copy code
prefect work-pool create "io-process-pool" --type process
  prefect work-pool set-concurrency-limit "io-process-pool" 20
- Work queue concurrency limit: Optional per-queue cap if you segment runs by queue.
Copy code
prefect work-queue create "http-queue" --pool "io-process-pool"
  prefect work-queue set-concurrency-limit "http-queue" 10
- Tag-based concurrency limits (task-level): Create a limit for a tag, then tag tasks to gate concurrent executions across all flows.
Copy code
prefect concurrency-limit create external-api 10
Then tag tasks or use the concurrency contexts below. - Code-based concurrency gates and rate limits: Use Prefect’s concurrency contexts to enforce global limits/rates from code (sync or asyncio):
Copy code
from prefect.concurrency.sync import concurrency
  from prefect import flow, task
  import httpx

  @task(tags=["external-api"])  # optional if you also want tag-based visibility/controls
  def call_api(url):
      with concurrency("external-api", occupy=1, timeout_seconds=30):
          return httpx.get(url, timeout=60).json()

  @flow
  def run_many(urls):
      return [call_api.submit(u) for u in urls]
Or rate-limit (async example):
Copy code
from prefect.concurrency.asyncio import rate_limit

  async with rate_limit("external-api-qps", occupy=1):
      # make an async HTTP call here
      ...
About background tasks - Background tasks are useful for lightweight concurrent work inside one flow run, but they do not give you per-run orchestration controls (scheduling, retries across runs, etc.). If you need cross-run concurrency control and operational guarantees, stick with “normal” flows + workers and use the concurrency primitives above. - You can still use concurrency contexts inside background tasks if you go that route, but most teams prefer regular flows for the kind of job orchestration you described. Starting a process worker - Run a process worker that polls your pool (use a Deployment/StatefulSet in your cluster so it’s supervised):
Copy code
prefect worker start -p "io-process-pool" --name io-worker-1
- Scale by: - Increasing the work pool/queue concurrency limit(s) - Running multiple worker replicas - Adjusting the pod’s CPU/RAM requests/limits When to prefer Kubernetes workers - You need one flow per isolated container (e.g., strict per-run dep isolation, different base images, GPU, job-specific node scheduling, etc.) - You’re okay paying for the per-run pod overhead for those isolation benefits.
Given your description (simple flows that make long HTTP calls), a process pool is a great way to reduce cost and keep the orchestration benefits. Many users run process workers in production for exactly this pattern. If you want, share how you’re deploying workers today (via the Prefect Helm chart or your own Deployment YAML), and your target concurrency (e.g., total concurrent HTTP requests, QPS per API, memory budget per worker). I can suggest concrete values.yaml changes or a Deployment manifest that matches your goals. Reference you already found: https://docs.prefect.io/v3/advanced/background-tasks
j
We just switched from spinning up Azure Container Instances for each flow to using process workers and it works great and is way faster. We just deploy a docker container with the flows as well as prefect installed (obviously). It works great.
s
Great @Jordan Jones We are deploying the same to production. We've deployed in k8s a background-jobs-worker that is a prefect worker of type process. For the image, at the moment we have one like:
Copy code
FROM prefecthq/prefect:3.4.6-python3.11-kubernetes

COPY requirements
RUN pip install requirements

ADD all our flows we want to execute as "background jobs"
We are planning to deploy another worker of type kubernetes for some workflows that we want to be executed in their own pod