kaxil commented on issue #67515:
URL: https://github.com/apache/airflow/issues/67515#issuecomment-4550501544

   Each task in Airflow 3 runs in its own `os.fork()` from the Task SDK 
supervisor. The fork parses the DAG file, runs the task, then `os._exit()`s. 
Memory is released to the kernel on exit; Python GC doesn't unload 
`sys.modules` entries.
   
   For a 2-worker x 16-process Celery cluster:
   
   | Process | Accumulates the heavy SDKs being discussed (neo4j / snowflake / 
google.cloud)? | Lazy-load benefit |
   |---|---|---|
   | Celery daemon + 32 prefork children | No -- they import the celery 
executor framework, not the third-party SDKs that #67515 targets | 0 |
   | Per-task fork (ephemeral) | Yes, but the fork dies after the task | ~60 MB 
peak per fork (Neo4j case), released on `os._exit()` |
   | **Dag-processor parsing process(es)** | **Yes, for the lifetime of the 
parsing process** | Bounded by `parsing_processes` x per-process saving |
   
   So in the 12-task mixed-DAG case, "11 of 12 tasks save 60 MB each" is 11 
transient peak reductions in ephemeral forks that die minutes later. Nothing 
persists in the worker tier.
   
   The only long-lived processes that benefit are the dag-processor parsing 
workers. The saving scales with `parsing_processes` x per-process saving, 
typically landing somewhere in the single-digit percent range of total worker 
RAM on a properly-sized cluster.
   
   For deployments that hit OOM in workers, the dominant consumers are data, 
not imports: XComs, intermediate task results, cursor rows, BigQuery result 
sets, pandas dataframes pulled into the worker.
   
   Frameworks that do lazy-load successfully do it architecturally, not by 
hand-moving imports. **boto3** imports the top-level package eagerly, but 
`Session.client('s3')` is a dispatch point that lazy-loads per-service models 
from JSON specs in `botocore.loaders` -- the laziness is in the service-model 
layer, not the import graph. **Django** populates an app registry at startup 
and uses `apps.get_model(label)` for string-based lookup after that, avoiding 
direct `from x.y import Model` ordering. Both are deliberate architectural 
patterns, not "move every heavy import into a method body." Inline imports for 
performance is not a pattern that scales -- it's a smell.
   
   And that's why Python 3.15's PEP 810 (`lazy import` keyword + 
`__lazy_modules__` polyfill) is the right shape of what we'd want here imo. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to