Warm start¶
AWS Lambda reuses execution environments (“containers”) between invocations. Everything initialized outside the handler survives to the next call — and Processor initialization (config assembly, possibly a DynamoDB lookup, client construction) is exactly the kind of work you want to pay for once per container, not once per request.
sosw packages this pattern into two small pieces:
from sosw.app import LambdaGlobals, get_lambda_handler
class Processor(SoswProcessor):
...
global_vars = LambdaGlobals()
lambda_handler = get_lambda_handler(Processor, global_vars)
What the generated handler does¶
On every invocation, the lambda_handler returned by sosw.app.get_lambda_handler():
Optionally raises the logging level from the
logging_levelkey of the event.Resolves the effective
testflag from thetestkey of the event (an explicit flag always wins; otherwise derived from theSTAGEenvironment variable).Stores the fresh Lambda
contextinglobal_vars.lambda_context— the context is unique per invocation, even though the Processor is not.Constructs the Processor only if
global_vars.processorisNone— i.e. once per container. Warm invocations reuse the cached instance.Calls the Processor with the event and captures the result.
Logs
get_stats()and callsreset_stats(recursive=True)— exactly once.Returns the result.
The contract: one Processor per container¶
LambdaGlobals is a tiny namespace that survives for the lifetime of the Lambda container.
It holds two things:
global_vars.processor— the cached Processor instance;global_vars.lambda_context— the context of the current invocation (reset by the handler every call).
This gives you a simple set of rules to write correct warm-start code:
Per-invocation state belongs in
self.result(reset every call), local variables, or the event itself. Never leave request-scoped data in instance attributes: the next invocation of the warm container will see it.Container-lifetime state — clients, caches, compiled regexes, the assembled config — belongs on the Processor instance and is the whole point of the pattern.
Counters follow the stats / result contract:
self.statsvalues are folded intototal_*accumulators after every invocation.The Lambda context must always be read through
global_vars.lambda_context, never cached: each invocation brings a new one (remaining time, request id…).
Warning
LambdaGlobals instances proxy module-level globals of sosw.app: constructing a new
LambdaGlobals() resets the shared lambda_context placeholder. Create exactly one
instance, at the module level of your app.py, and pass it to get_lambda_handler.
The test flag¶
Both the Processor constructor and the generated handler resolve the effective test mode with the
same rule (sosw.app._derive_test_flag()):
an explicitly provided flag always wins —
test=Falsepassed in a test stage staysFalse,test=Truepassed in production staysTrue;when no flag is provided, it derives from the environment:
STAGEset totestorautotestmeans test mode.
Note
In the 0.7.x line an operator-precedence bug inverted this rule in both directions (an
explicit flag was ignored whenever the STAGE said otherwise). This is fixed — see the
migration guide.
Cold starts still exist¶
The first invocation of every container pays the full price: package import plus Processor construction. To keep it low:
keep your deployment package small — mount
soswfrom a shared Lambda Layer (Lambda Layer);skip the external config lookup when you do not need it (
disable_ddb_config— see Configuration);initialize only the clients you actually use (
init_clients), and use lazy factories likeget_ddbc()for the rest.