import%20marimo%0A%0A__generated_with%20%3D%20%220.17.7%22%0Aapp%20%3D%20marimo.App(width%3D%22medium%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Lambda%20Retries%0A%0A%20%20%20%20When%20a%20function%20invocation%20fails%2C%20does%20the%20Lambda%20service%20retry%20it%3F%0A%20%20%20%20How%20often%2C%20and%20with%20what%20backoff%3F%0A%20%20%20%20Does%20it%20depend%20on%20the%20invocation%20type%20(synchronous%20or%20asynchronous)%20or%20the%20failure%20reason%20(timeout%2C%20exception%2C%20throttling)%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Stack%0A%0A%20%20%20%20A%20bunch%20of%20functions%3A%0A%0A%20%20%20%20-%20%60async_handler_raises_exception%60%0A%20%20%20%20-%20%60sync_handler_raises_exception%60%0A%20%20%20%20-%20%60async_invocation_times_out%60%0A%20%20%20%20-%20%60sync_invocation_times_out%60%0A%20%20%20%20-%20%60async_throttled%60%0A%20%20%20%20-%20%60sync_throttled%60%0A%0A%20%20%20%20each%20doing%20what%20its%20name%20suggests%2C%0A%20%20%20%20and%20a%20log%20group%20for%20each.%0A%0A%20%20%20%20The%20%60async*%60%20and%20%60sync*%60%20prefixes%20just%20indicate%20how%20I'll%20call%20the%20functions%2C%0A%20%20%20%20nothing%20about%20the%20functions%20themselves.%0A%0A%20%20%20%20I%20have%20%60async%60%20and%20%60sync%60%20versions%0A%20%20%20%20so%20I%20can%20call%20them%20concurrently%20without%20getting%20confused.%0A%0A%20%20%20%20By%20default%20functions%20sends%20logs%20to%20an%20automatically%20created%20log%20group.%0A%20%20%20%20But%20that%20log%20group%20is%20not%20configurable%0A%20%20%20%20(e.g.%20you%20should%20not%20set%20its%20removal%20policy).%0A%20%20%20%20So%20the%20docs%20recommend%20creating%20your%20own%20and%20passing%20it%20in%2C%0A%20%20%20%20which%20is%20what%20I've%20done.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Investigation%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Set%20up%20boto3%20clients%0A%0A%20%20%20%20We'll%20use%20boto3%20to%20invoke%20and%20monitor%20the%20functions.%0A%0A%20%20%20%20But%20by%20default%20boto3%20automatically%20retries%20for%20certain%20errors%2C%20such%20as%20%60ThrottlingException%60%20%5B1%5D.%0A%20%20%20%20We%20don't%20want%20boto3%20to%20retry%3A%20it%20would%20be%20confusing.%0A%20%20%20%20(Was%20this%20function%20invoked%20twice%20because%20boto3%20retried%20or%20because%20Lambda%20retried%3F)%0A%20%20%20%20So%20let's%20disable.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20boto3%0A%20%20%20%20import%20botocore%0A%20%20%20%20return%20boto3%2C%20botocore%0A%0A%0A%40app.cell%0Adef%20_(boto3%2C%20botocore)%3A%0A%20%20%20%20config%20%3D%20botocore.config.Config(retries%3D%7B%22total_max_attempts%22%3A%201%7D)%0A%20%20%20%20logs%20%3D%20boto3.client(%22logs%22%2C%20config%3Dconfig)%0A%20%20%20%20lambda_%20%3D%20boto3.client(%22lambda%22%2C%20config%3Dconfig)%0A%20%20%20%20cloudwatch%20%3D%20boto3.client(%22cloudwatch%22%2C%20config%3Dconfig)%0A%20%20%20%20return%20cloudwatch%2C%20lambda_%2C%20logs%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20%60retries%60%20dictionary%20accepts%0A%20%20%20%20a%20%60total_max_attempts%60%20key%2C%20which%20_includes_%20the%20initial%20attempt%2C%0A%20%20%20%20or%20a%20%60max_attempts%60%20key%2C%20which%20_excludes_%20it.%0A%20%20%20%20Confusing!%0A%0A%20%20%20%20Using%20%22retries%22%20is%20always%20confusing.%0A%20%20%20%20Does%20%22third%20retry%22%20mean%20third%20try%20or%20fourth%20try%3F%0A%20%20%20%20Throw%200%20vs%201-indexing%20into%20the%20mix%0A%20%20%20%20(say%20you%20store%20an%20array%20with%20an%20item%20for%20each%20try)%0A%20%20%20%20and%20you're%20asking%20for%20trouble.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Throttle%0A%0A%20%20%20%20Two%20of%20the%20lambdas%20are%20for%20checking%20retries%20given%20throttling.%0A%20%20%20%20So%20let's%20throttle%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(lambda_)%3A%0A%20%20%20%20lambda_.put_function_concurrency(%0A%20%20%20%20%20%20%20%20FunctionName%3D%22async_throttled%22%2C%0A%20%20%20%20%20%20%20%20ReservedConcurrentExecutions%3D0%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(lambda_)%3A%0A%20%20%20%20lambda_.put_function_concurrency(%0A%20%20%20%20%20%20%20%20FunctionName%3D%22sync_throttled%22%2C%0A%20%20%20%20%20%20%20%20ReservedConcurrentExecutions%3D0%2C%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20Invoke%20the%20functions%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20datetime%0A%20%20%20%20from%20pprint%20import%20pprint%0A%20%20%20%20return%20datetime%2C%20pprint%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20call_times%20%3D%20%7B%7D%0A%20%20%20%20return%20(call_times%2C)%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20datetime%2C%20lambda_)%3A%0A%20%20%20%20call_times%5B%22async_handler_raises_exception%22%5D%20%3D%20datetime.datetime.now(datetime.UTC)%0A%20%20%20%20lambda_.invoke(FunctionName%3D%22async_handler_raises_exception%22%2C%20InvocationType%3D%22Event%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20datetime%2C%20lambda_)%3A%0A%20%20%20%20call_times%5B%22async_invocation_times_out%22%5D%20%3D%20datetime.datetime.now(datetime.UTC)%0A%20%20%20%20lambda_.invoke(FunctionName%3D%22async_invocation_times_out%22%2C%20InvocationType%3D%22Event%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20datetime%2C%20lambda_)%3A%0A%20%20%20%20call_times%5B%22async_throttled%22%5D%20%3D%20datetime.datetime.now(datetime.UTC)%0A%20%20%20%20%23%20async%20invocation%20so%20we%20don't%20get%20TooManyRequestsException%0A%20%20%20%20lambda_.invoke(FunctionName%3D%22async_throttled%22%2C%20InvocationType%3D%22Event%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20datetime%2C%20lambda_)%3A%0A%20%20%20%20call_times%5B%22sync_handler_raises_exception%22%5D%20%3D%20datetime.datetime.now(datetime.UTC)%0A%20%20%20%20lambda_.invoke(FunctionName%3D%22sync_handler_raises_exception%22%2C%20InvocationType%3D%22RequestResponse%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20datetime%2C%20lambda_)%3A%0A%20%20%20%20call_times%5B%22sync_invocation_times_out%22%5D%20%3D%20datetime.datetime.now(datetime.UTC)%0A%20%20%20%20lambda_.invoke(FunctionName%3D%22sync_invocation_times_out%22%2C%20InvocationType%3D%22RequestResponse%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(botocore%2C%20call_times%2C%20datetime%2C%20lambda_%2C%20pprint)%3A%0A%20%20%20%20call_times%5B%22sync_throttled%22%5D%20%3D%20datetime.datetime.now(datetime.UTC)%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20lambda_.invoke(FunctionName%3D%22sync_throttled%22%2C%20InvocationType%3D%22RequestResponse%22)%0A%20%20%20%20except%20botocore.exceptions.ClientError%20as%20err%3A%0A%20%20%20%20%20%20%20%20if%20err.response%5B%22Error%22%5D%5B%22Code%22%5D%20%3D%3D%20%22TooManyRequestsException%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20sync%20invocation%20of%20throttled%20lambda%2C%20so%20we%20expect%20to%20get%20TooManyRequestsException%0A%20%20%20%20%20%20%20%20%20%20%20%20pprint(err.response)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20err%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20sync%20calls%20get%20200%20OK.%0A%0A%20%20%20%20Except%20for%20%60sync_throttled%60%2C%0A%20%20%20%20which%20throws%20a%20%60TooManyRequestsException%60%0A%20%20%20%20with%20underlying%20%60'HTTPStatusCode'%3A%20429%60%20(TooManyRequests).%0A%0A%20%20%20%20The%20async%20calls%20get%20202%20Accepted%3A%0A%0A%20%20%20%20%3E%20The%20request%20has%20been%20received%20but%20not%20yet%20acted%20upon.%0A%20%20%20%20%3E%20It%20is%20noncommittal%2C%20since%20there%20is%20no%20way%20in%20HTTP%20to%20later%20send%20an%20asynchronous%20response%20indicating%20the%20outcome%20of%20the%20request.%0A%20%20%20%20%3E%20It%20is%20intended%20for%20cases%20where%20another%20process%20or%20server%20handles%20the%20request%2C%20or%20for%20batch%20processing.%20%5B5%5D%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20pprint)%3A%0A%20%20%20%20pprint(call_times)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Let's%20wait%20a%20while%20to%20give%20Lambda%20a%20chance%20to%20retry.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20from%20time%20import%20sleep%0A%20%20%20%20return%20(sleep%2C)%0A%0A%0A%40app.cell%0Adef%20_(sleep)%3A%0A%20%20%20%20sleep(5%20*%2060)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20I'll%20be%20using%20the%20logs%20to%20find%20function%20invocations%2C%0A%20%20%20%20since%20logs%20have%20high%20resolution%20timestamps.%0A%0A%20%20%20%20I'll%20write%20a%20helper%20for%20that.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20datetime%2C%20logs)%3A%0A%20%20%20%20def%20get_invocations_from_logs(function_name%3A%20str)%20-%3E%20list%5Bfloat%5D%3A%0A%20%20%20%20%20%20%20%20call_timestamp_in_ms%20%3D%20int(call_times%5Bfunction_name%5D.timestamp())%20*%201000%0A%0A%20%20%20%20%20%20%20%20response%20%3D%20logs.filter_log_events(%0A%20%20%20%20%20%20%20%20%20%20%20%20logGroupName%3Df%22%2Faws%2Flambda%2F%7Bfunction_name%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20startTime%3Dcall_timestamp_in_ms%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20invocations%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20datetime.datetime.fromtimestamp(event%5B%22timestamp%22%5D%20%2F%201000.0%2C%20tz%3Ddatetime.UTC)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20event%20in%20response%5B%22events%22%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20event%5B%22message%22%5D.startswith(%22START%22)%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20invocations%20%3D%20sorted(invocations)%0A%0A%20%20%20%20%20%20%20%20return%20invocations%0A%20%20%20%20return%20(get_invocations_from_logs%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Now%20let's%20check%20retries.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20asynchronous%2C%20errors%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20get_invocations_from_logs%2C%20pprint)%3A%0A%20%20%20%20print(%22call%20time%3A%22%2C%20call_times%5B%22async_handler_raises_exception%22%5D)%0A%20%20%20%20pprint(get_invocations_from_logs(%22async_handler_raises_exception%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20first%20invocation%20was%20a%20moment%20after%20the%20boto3%20call.%0A%20%20%20%20That%20makes%20sense%3A%20the%20function%20has%20to%20init%20first.%0A%20%20%20%20The%20second%20invocation%20was%20about%201%20minute%20later.%0A%20%20%20%20The%20third%20was%20about%202%20minutes%20after%20that.%0A%0A%20%20%20%20This%20matches%20the%20docs%20%5B4%5D%3A%0A%0A%20%20%20%20%3E%20If%20the%20function%20returns%20an%20error%2C%0A%20%20%20%20%3E%20by%20default%20Lambda%20attempts%20to%20run%20it%20two%20more%20times%2C%0A%20%20%20%20%3E%20with%20a%20one-minute%20wait%20between%20the%20first%20two%20attempts%2C%0A%20%20%20%20%3E%20and%20two%20minutes%20between%20the%20second%20and%20third%20attempts%0A%0A%20%20%20%20except%20the%20waits%20are%20only%20_approximately_%20one%20and%20two%20minutes.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20async%2C%20times%20out%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20get_invocations_from_logs%2C%20pprint)%3A%0A%20%20%20%20print(%22call%20time%3A%22%2C%20call_times%5B%22async_invocation_times_out%22%5D)%0A%20%20%20%20pprint(get_invocations_from_logs(%22async_invocation_times_out%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Same%20as%20for%20error%3A%0A%20%20%20%20try%2C%0A%20%20%20%20about%201%20minute%20wait%2C%0A%20%20%20%20try%2C%0A%20%20%20%20about%202%20minute%20wait%2C%0A%20%20%20%20try.%0A%0A%20%20%20%20This%20matches%20the%20docs%20too%3A%0A%0A%20%20%20%20%3E%20Function%20errors%20include%20errors%20returned%20by%20the%20function's%20code%20and%20errors%20returned%20by%20the%20function's%20runtime%2C%20such%20as%20timeouts.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20async%2C%20throttled%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20get_invocations_from_logs%2C%20pprint)%3A%0A%20%20%20%20print(%22call%20time%3A%22%2C%20call_times%5B%22async_throttled%22%5D)%0A%20%20%20%20pprint(get_invocations_from_logs(%22async_throttled%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20That%20makes%20sense%3A%20it's%20throttled%20so%20no%20invocations%20so%20no%20logs.%0A%0A%20%20%20%20Can%20we%20use%20the%20%60Throttles%60%20metric%20instead%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20cloudwatch%2C%20datetime)%3A%0A%20%20%20%20def%20get_metric_sum(function_name%3A%20str%2C%20metric_name%3A%20str)%20-%3E%20list%5Btuple%5D%3A%0A%20%20%20%20%20%20%20%20response%20%3D%20cloudwatch.get_metric_statistics(%0A%20%20%20%20%20%20%20%20%20%20%20%20Namespace%3D%22AWS%2FLambda%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20MetricName%3Dmetric_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Dimensions%3D%5B%7B%22Name%22%3A%20%22FunctionName%22%2C%20%22Value%22%3A%20function_name%7D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20StartTime%3Dcall_times%5Bfunction_name%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20EndTime%3Ddatetime.datetime.now(datetime.UTC)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Period%3D60%2C%20%20%23%20lowest%20possible%20for%20non-custom%20metrics%0A%20%20%20%20%20%20%20%20%20%20%20%20Statistics%3D%5B%22Sum%22%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20sum_and_timestamps%20%3D%20%5B(datapoint%5B%22Sum%22%5D%2C%20datapoint%5B%22Timestamp%22%5D)%20for%20datapoint%20in%20response%5B%22Datapoints%22%5D%5D%0A%20%20%20%20%20%20%20%20sum_and_timestamps%20%3D%20sorted(sum_and_timestamps%2C%20key%3Dlambda%20x%3A%20x%5B1%5D)%0A%0A%20%20%20%20%20%20%20%20return%20sum_and_timestamps%0A%20%20%20%20return%20(get_metric_sum%2C)%0A%0A%0A%40app.cell%0Adef%20_(get_metric_sum%2C%20pprint)%3A%0A%20%20%20%20pprint(get_metric_sum(%22async_throttled%22%2C%20%22Throttles%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Nope.%0A%0A%20%20%20%20It%20seems%20that%20for%20throttling%20due%20to%20zero%20reserved%20concurrency%2C%0A%20%20%20%20nothing%20is%20published%20to%20the%20%60Throttles%60%20metric.%0A%20%20%20%20So%20we%20can't%20use%20it%20to%20check%20retries.%0A%0A%20%20%20%20What%20to%20do%3F%0A%20%20%20%20I%20found%20a%20helpful%20aws%20blog%20post%20%5B2%5D.%0A%20%20%20%20It%20describes%20async-specific%20metrics%2C%20including%3A%0A%20%20%20%20%20%20-%20%60AsyncEventsReceived%60%2C%20which%20counts%20the%20number%20of%20events%20for%20this%20function%20which%20arrived%20in%20Lambda's%20internal%20queue%2C%20and%0A%20%20%20%20%20%20-%20%60AsyncEventsDropped%60%2C%20which%20counts%20the%20number%20of%20events%20dropped%20because%20of%20processing%20failures.%0A%0A%20%20%20%20Let's%20try%20them.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(get_metric_sum%2C%20pprint)%3A%0A%20%20%20%20async_events_received%20%3D%20get_metric_sum(%22async_throttled%22%2C%20%22AsyncEventsReceived%22)%0A%20%20%20%20pprint(async_events_received)%0A%0A%20%20%20%20async_events_dropped%20%3D%20get_metric_sum(%22async_throttled%22%2C%20%22AsyncEventsDropped%22)%0A%20%20%20%20pprint(async_events_dropped)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20So%20the%20event%20was%20received%20and%20immediately%20dropped.%0A%0A%20%20%20%20Conclusion%3A%20no%20retries%20for%20functions%20with%20zero%20reserved%20concurrency.%0A%0A%20%20%20%20That%20is%20mentioned%20in%20the%20blog%20post%2C%20though%20I%20haven't%20found%20it%20in%20the%20main%20aws%20docs.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20But%20what%20about%20retries%20for%20_genuine_%20throttles%3F%0A%20%20%20%20That's%20what%20we%20really%20care%20about.%0A%20%20%20%20How%20to%20investigate%20that%3F%0A%0A%20%20%20%20Strategy%3A%0A%20%20%20%20Set%20the%20reserved%20concurrency%20to%201%2C%0A%20%20%20%20then%20invoke%20the%20function%20twice%20in%20quick%20succession.%0A%20%20%20%20The%20first%20invocation%20will%20succeed.%0A%20%20%20%20The%20handler%20will%20sleep%20for%2060s.%0A%20%20%20%20The%20second%20invocation%20will%20be%20throttled%2C%0A%20%20%20%20as%20will%20its%20retries%2C%20for%2060s%2C%0A%20%20%20%20and%20we%20can%20check%20retries%20in%20that%20window.%0A%0A%20%20%20%20It%20won't%20show%20us%20retries%20outside%2060s%2C%20but%20it'll%20be%20something.%0A%0A%20%20%20%20Here%20we%20go.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(lambda_)%3A%0A%20%20%20%20lambda_.put_function_concurrency(FunctionName%3D%22async_throttled%22%2C%20ReservedConcurrentExecutions%3D1)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20The%20first%20time%20I%20tried%20this%2C%0A%20%20%20%20I%20got%20an%20%60InvalidParameterValueException%60.%0A%20%20%20%20The%20message%20complained%20that%20the%20operation%20was%20forbidden%0A%20%20%20%20because%20it%20would%20reduce%20my%20account's%20%60UnreservedConcurrentExecution%60%20below%20the%20minimum%20value%20of%20100.%0A%20%20%20%20Does%20that%20mean%20my%20account%20concurrency%20was%20101%3F%0A%20%20%20%20Isn't%20the%20default%201000%3F%0A%0A%20%20%20%20I%20found%20a%20blog%20post%20explaining%20the%20situation%20%5B3%5D.%0A%20%20%20%20It%20turns%20out%20that%20for%20newish%20accounts%2C%20like%20mine%2C%20the%20default%20account%20concurrency%20is%2010.%0A%20%20%20%20Yes%2C%2010.%0A%20%20%20%20But%20aws%20only%20lets%20you%20set%20a%20function's%20reserved%20concurrency%20if%0A%20%20%20%20you're%20setting%20it%20to%200%20(like%20we%20did%20above)%0A%20%20%20%20or%20the%20leftover%20concurrency%20would%20be%20at%20least%20100.%0A%0A%20%20%20%20What%20to%20do%3F%0A%20%20%20%20I%20could%20have%3A%0A%20%20%20%20Unset%20reserved%20concurrency.%0A%20%20%20%20Then%20invoked%20the%20function%2011%20times%20in%20quick%20succession.%0A%20%20%20%20The%20first%2010%20invocations%20would%20succeed.%0A%20%20%20%20The%20last%20would%20be%20throttled%20and%20we%20could%20check%20retries%20within%2060s.%0A%0A%20%20%20%20But%20I%20opted%20for%20a%20simpler%20approach%3A%0A%20%20%20%20I%20asked%20to%20up%20my%20account%20concurrency%20to%201000%2C%0A%20%20%20%20and%20waited%20a%20few%20hours%20for%20aws%20to%20approve%20it.%0A%0A%20%20%20%20Now%20we%20can%20try%20again.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20datetime%2C%20lambda_)%3A%0A%20%20%20%20call_times%5B%22async_throttled%22%5D%20%3D%20datetime.datetime.now(datetime.UTC)%0A%20%20%20%20for%20_%20in%20range(2)%3A%0A%20%20%20%20%20%20%20%20lambda_.invoke(FunctionName%3D%22async_throttled%22%2C%20InvocationType%3D%22Event%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We'll%20wait%20a%20bit%20to%20give%20the%20metrics%20a%20chance%20to%20update%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(sleep)%3A%0A%20%20%20%20sleep(5%20*%2060)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20then%20check%20throttles%20again.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(get_metric_sum%2C%20pprint)%3A%0A%20%20%20%20pprint(get_metric_sum(%22async_throttled%22%2C%20%22Throttles%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Nice!%20We%20can%20see%20that%20there%20were%20a%20bunch%20of%20retries.%0A%0A%20%20%20%20But%20metrics%20only%20have%20a%20one-minute%20resolution.%0A%20%20%20%20So%20we%20can't%20see%20in%20much%20detail%20when%20the%20retries%20happened.%0A%0A%20%20%20%20Maybe%20%60AsyncEventsReceived%60%20gives%20a%20richer%20picture%3F%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(get_metric_sum%2C%20pprint)%3A%0A%20%20%20%20pprint(get_metric_sum(%22async_throttled%22%2C%20%22AsyncEventsReceived%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20No.%0A%20%20%20%20%60AsyncEventsReceived%60%20only%20counts%20an%20event%20once%2C%20no%20matter%20how%20many%20retries.%0A%20%20%20%20So%20it%20just%20counted%20two%20events%2C%20corresponding%20to%20our%20two%20invocations.%0A%0A%20%20%20%20However%2C%20the%20blog%20post%20describes%20another%20async-specific%20metric%20we%20can%20use%3A%0A%0A%20%20%20%20%3E%20The%20%60AsyncEventAge%60%20metric%20is%20a%20measure%20of%20the%20difference%20between%20the%20time%20that%20an%20event%20is%20first%20enqueued%0A%20%20%20%20in%20the%20internal%20queue%20and%20the%20time%20the%20Lambda%20service%20invokes%20the%20function.%0A%20%20%20%20With%20retries%2C%20Lambda%20emits%20this%20metric%20every%20time%20it%20attempts%20to%20invoke%20the%20function%20with%20the%20event.%0A%20%20%20%20An%20increasing%20value%20shows%20retries%20because%20of%20error%20or%20throttles.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20cloudwatch%2C%20datetime)%3A%0A%20%20%20%20def%20get_async_event_age_metric(function_name%3A%20str)%20-%3E%20list%3A%0A%20%20%20%20%20%20%20%20response%20%3D%20cloudwatch.get_metric_statistics(%0A%20%20%20%20%20%20%20%20%20%20%20%20Namespace%3D%22AWS%2FLambda%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20MetricName%3D%22AsyncEventAge%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Dimensions%3D%5B%7B%22Name%22%3A%20%22FunctionName%22%2C%20%22Value%22%3A%20function_name%7D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20StartTime%3Dcall_times%5Bfunction_name%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20EndTime%3Ddatetime.datetime.now(datetime.UTC)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Period%3D60%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20Statistics%3D%5B%22SampleCount%22%2C%20%22Minimum%22%2C%20%22Average%22%2C%20%22Maximum%22%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20return%20sorted(response%5B%22Datapoints%22%5D%2C%20key%3Dlambda%20datapoint%3A%20datapoint%5B%22Timestamp%22%5D)%0A%20%20%20%20return%20(get_async_event_age_metric%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20(Why%20those%204%20statistics%3F%20We'll%20see%20shortly.)%0A%0A%20%20%20%20Fingers%20crossed.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(get_async_event_age_metric%2C%20pprint)%3A%0A%20%20%20%20pprint(get_async_event_age_metric(%22async_throttled%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Ok%2C%20that's%20looking%20better.%0A%0A%20%20%20%20We%20can%20see%20that%20Lambda%20is%20backing%20off%20and%20retrying.%0A%0A%20%20%20%20Let's%20also%20get%20the%20call%20time%20and%20the%20start%20times%20from%20the%20logs%2C%20then%20think%20about%20what%20the%20numbers%20are%20telling%20us.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20get_invocations_from_logs)%3A%0A%20%20%20%20print(%22boto3%20call%20time%3A%22)%0A%20%20%20%20print(f%22%5Ct%7Bcall_times%5B'async_throttled'%5D%7D%22)%0A%20%20%20%20print(%22function%20invocation%20times%3A%22)%0A%20%20%20%20for%20invocation_time%20in%20get_invocations_from_logs(%22async_throttled%22)%3A%0A%20%20%20%20%20%20%20%20print(f%22%5Ct%7Binvocation_time%7D%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20We%20asynchronously%20invoked%20the%20function%20twice%20at%20about%2016%3A29%3A33.%0A%20%20%20%20So%20two%20events%20got%20enqueued%20about%20then.%0A%0A%20%20%20%20Focus%20on%20the%20first%20%60AsyncEventAge%60%20datapoint.%0A%20%20%20%20It%20shows%20Lambda%20first%20tried%20to%20invoke%20the%20function%2033ms%20after%20enqueueing%20(%60'Minimum'%3A%2033%60).%0A%20%20%20%20That%20try%20was%20successful%2C%20the%20event%20was%20removed%20from%20the%20queue%2C%20and%20the%20function%20ran%20for%2060s.%0A%20%20%20%20The%20datapoint%20shows%205%20additional%20tries%20(%60'SampleCount'%3A%206%60)%2C%0A%20%20%20%20all%20for%20the%20second%20event%2C%0A%20%20%20%20the%20last%20of%20which%20was%20about%2017s%20after%20enqueueing%20(%60'Maximum%60%3A%2016645%60).%0A%20%20%20%20We%20can't%20tell%20exactly%20when%20the%20intermediate%20tries%20were%2C%0A%20%20%20%20but%20the%20numbers%20(%60'Average'%3A%204791%60)%20suggest%20expanding%20waits%20between%20tries.%0A%20%20%20%20(This%20is%20why%20we%20fetched%20all%20four%20metrics.)%0A%20%20%20%20Those%20tries%20all%20failed.%0A%0A%20%20%20%20The%20second%20datapoint%20shows%20Lambda%20tried%20again%20to%20invoke%20the%20function%0A%20%20%20%20about%2033s%20after%20enqeueing%20(%60'Minimum'%3A%2032790%60).%0A%20%20%20%20That%20try%20failed%20too.%0A%20%20%20%20It%20tried%20yet%20again%20about%2068s%20after%20enqueueing%20(%60'Maximum'%3A%2067932%60).%0A%20%20%20%20That%20try%20was%20successful%2C%0A%20%20%20%20because%20the%20first%20run%20had%20completed%20by%20then%2C%0A%20%20%20%20and%20the%20function%20re-started%20at%20about%2016%3A30%3A42.%0A%20%20%20%20So%20no%20more%20datapoints.%0A%0A%20%20%20%20In%20short%3A%0A%20%20%20%20for%20the%20throttled%20invocation%0A%20%20%20%20there%20were%206%20failed%20tries%20in%20the%2060s%20window%0A%20%20%20%20followed%20by%201%20successful%20try%20a%20bit%20later.%0A%0A%20%20%20%20This%20matches%20the%20docs%3A%0A%0A%20%20%20%20%3E%20For%20throttling%20errors%20(429)%20and%20system%20errors%20(500-series)%2C%0A%20%20%20%20%3E%20Lambda%20returns%20the%20event%20to%20the%20queue%20and%20attempts%20to%20run%20the%20function%20again%20for%20up%20to%206%20hours%20by%20default.%0A%20%20%20%20%3E%20The%20retry%20interval%20increases%20exponentially%20from%201%20second%20after%20the%20first%20attempt%20to%20a%20maximum%20of%205%20minutes.%0A%0A%20%20%20%20except%20the%20the%20docs%20don't%20explicitly%20say%20what's%20plain%20from%20the%20numbers%3A%0A%20%20%20%20that%20Lambda%20uses%20_jittered_%20backoff.%0A%0A%20%20%20%20Lambda%20could%20have%20successfully%20tried%20earlier%20than%2068s%20after%20enqueuing.%0A%20%20%20%20The%20concurrency%20was%20only%20used%20up%20for%2060s.%0A%20%20%20%20But%20Lambda%20couldn't%20know%20that.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20synchronous%2C%20exception%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Lambda%20does%20not%20retry%20failed%20synchronous%20invocations%2C%0A%20%20%20%20whether%20it%20fails%20from%20throttling%2C%20timeout%2C%20exception%2C%20whatever.%0A%20%20%20%20How%20could%20it%3F%0A%20%20%20%20For%20a%20synchronous%20invocation%2C%0A%20%20%20%20the%20requester%20gets%20the%20function's%20return%20value%20or%20error%20in%20the%20response%2C%0A%20%20%20%20and%20the%20connection%20is%20closed.%0A%20%20%20%20So%20if%20a%20failed%20invocation%20were%20retried%2C%20it's%20too%20late%20to%20respond.%0A%0A%20%20%20%20The%20_caller_%20may%20retry%0A%20%20%20%20(whether%20that's%20you%2C%20or%20an%20aws%20service%2C%20or%20whatever).%0A%20%20%20%20But%20Lambda%20itself%20does%20not%20%E2%80%94%20could%20not%20sensibly%20%E2%80%94%20retry.%0A%0A%20%20%20%20Still%2C%20for%20completeness%2C%20let's%20check.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(get_invocations_from_logs%2C%20pprint)%3A%0A%20%20%20%20pprint(get_invocations_from_logs(%22sync_handler_raises_exception%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20One%20invocation%20only%2C%20as%20expected.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20synchronous%2C%20timeout%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(get_invocations_from_logs%2C%20pprint)%3A%0A%20%20%20%20pprint(get_invocations_from_logs(%22sync_invocation_times_out%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Ditto.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%23%20synchronous%2C%20throttled%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(get_invocations_from_logs%2C%20pprint)%3A%0A%20%20%20%20pprint(get_invocations_from_logs(%22sync_throttled%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20No%20retries.%0A%0A%20%20%20%20We%20saw%20the%20same%20with%20the%20asynchronous%20invocation.%0A%20%20%20%20But%20in%20that%20case%2C%20there%20_were_%20retries%20for%20genuine%20throttles%0A%20%20%20%20(rather%20than%20throttles%20because%20of%20zero%20reserved%20concurrency).%0A%20%20%20%20Is%20it%20the%20same%20here%3F%0A%0A%20%20%20%20Surely%20not%2C%20for%20reasons%20explained%20above.%0A%20%20%20%20But%20let's%20confirm%20anyway.%0A%0A%20%20%20%20We'll%20set%20reserved%20concurrency%20to%201%0A%20%20%20%20then%20synchronously%20invoke%20the%20function%20twice%2C%20like%20before.%0A%0A%20%20%20%20For%20a%20synchronous%20invocation%2C%20the%20boto3%20call%20blocks%20until%20the%20function%20returns.%0A%20%20%20%20So%20invoking%20the%20function%20twice%20in%20a%20loop%20won't%20tell%20us%20anything%3A%0A%20%20%20%20the%20second%20invocation%20will%20only%20happen%20after%20the%20first%20has%20completed%2C%0A%20%20%20%20and%20both%20will%20succeed.%0A%0A%20%20%20%20What%20we%20can%20do%20instead%20is%20to%20invoke%20concurrently%2C%20in%20two%20threads.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(lambda_)%3A%0A%20%20%20%20lambda_.put_function_concurrency(FunctionName%3D%22sync_throttled%22%2C%20ReservedConcurrentExecutions%3D1)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(botocore%2C%20lambda_)%3A%0A%20%20%20%20def%20invoke_and_suppress_throttling_exception()%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20lambda_.invoke(FunctionName%3D%22sync_throttled%22%2C%20InvocationType%3D%22RequestResponse%22)%0A%20%20%20%20%20%20%20%20except%20botocore.exceptions.ClientError%20as%20err%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20err.response%5B%22Error%22%5D%5B%22Code%22%5D%20!%3D%20%22TooManyRequestsException%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20err%0A%20%20%20%20return%20(invoke_and_suppress_throttling_exception%2C)%0A%0A%0A%40app.cell%0Adef%20_(call_times%2C%20datetime%2C%20invoke_and_suppress_throttling_exception)%3A%0A%20%20%20%20%23%20this%20will%20run%20for%20at%20least%20as%20long%20as%20the%20function%20runs%0A%20%20%20%20from%20threading%20import%20Thread%0A%0A%20%20%20%20call_times%5B%22sync_throttled%22%5D%20%3D%20datetime.datetime.now(datetime.UTC)%0A%20%20%20%20thread1%20%3D%20Thread(target%3Dinvoke_and_suppress_throttling_exception)%0A%20%20%20%20thread2%20%3D%20Thread(target%3Dinvoke_and_suppress_throttling_exception)%0A%20%20%20%20thread1.start()%0A%20%20%20%20thread2.start()%0A%20%20%20%20thread1.join()%0A%20%20%20%20thread2.join()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(sleep)%3A%0A%20%20%20%20sleep(2%20*%2060)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(get_metric_sum%2C%20pprint)%3A%0A%20%20%20%20pprint(get_metric_sum(%22sync_throttled%22%2C%20%22Throttles%22))%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20As%20expected%3A%201%20throttle%2C%20meaning%200%20retries.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Summary%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%7C%20%20%20%20%20%20%20%20%20%20%20%7C%20**exception%20or%20timeout**%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%20**throttled%20because%20zero%20provisioned%20concurrency**%20%7C%20**genuine%20throttle**%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%0A%20%20%20%20%7C-----------%7C---------------------------------------%7C----------------------------------------------------%7C--------------------------------------------------------------------------------------%7C%0A%20%20%20%20%7C%20**async**%20%7C%20try%2C%20~1m%20wait%2C%20retry%2C%20~2m%20wait%2C%20retry%20%7C%20no%20retry%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%20capped%20jittered%20exponential%20backoff%20until%20timeout%3Cbr%3E(typically%3A%20cap%205m%2C%20timeout%206h)%20%7C%0A%20%20%20%20%7C%20**sync**%20%20%7C%20no%20retry%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%20no%20retry%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%20no%20retry%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7C%0A%20%20%20%20%22%22%22)%23%20noqa%3A%20E501%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20References%0A%0A%20%20%20%20%5B1%5D%20https%3A%2F%2Fboto3.amazonaws.com%2Fv1%2Fdocumentation%2Fapi%2Flatest%2Fguide%2Fretries.html%0A%0A%20%20%20%20%5B2%5D%20https%3A%2F%2Faws.amazon.com%2Fblogs%2Fcompute%2Fintroducing-new-asynchronous-invocation-metrics-for-aws-lambda%2F%0A%0A%20%20%20%20%5B3%5D%20https%3A%2F%2Fbenellis.cloud%2Fmy-lambda-concurrency-applied-quota-is-only-10-but-why%0A%0A%20%20%20%20%5B4%5D%20https%3A%2F%2Fdocs.aws.amazon.com%2Flambda%2Flatest%2Fdg%2Finvocation-async-error-handling.html%0A%0A%20%20%20%20%5B5%5D%20https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FHTTP%2FReference%2FStatus%23successful_responses%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
bcf6e61957883c32b51ab130be6d9d28