<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Lambda on Geoff Baskwill</title>
    <link>https://geoffbaskwill.ca/tags/lambda/</link>
    <description>Geoff Baskwill (Lambda)</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-ca</language>
    <lastBuildDate>Sat, 24 Apr 2021 00:00:00 +0000</lastBuildDate>
    
    <atom:link href="https://geoffbaskwill.ca/tags/lambda/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Using Step Functions to build CloudFormation custom resources</title>
      <link>https://geoffbaskwill.ca/posts/cloudformation-custom-resources-with-step-functions/</link>
      <pubDate>Sat, 24 Apr 2021 00:00:00 +0000</pubDate>
      
      <guid>https://geoffbaskwill.ca/posts/cloudformation-custom-resources-with-step-functions/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;images/serban-mestecaneanu-1Cwj7JowUHA-unsplash.jpg&#34; alt=&#34;Steps leading up&#34; title=&#34;Steps leading up&#34;&gt;
&lt;em&gt;Photo by
&lt;a href=&#34;https://unsplash.com/@meste?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&#34;&gt;Serban
Mestecaneanu&lt;/a&gt; on
&lt;a href=&#34;https://geoffbaskwill.ca/s/photos/steps?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&#34;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My team uses &lt;a href=&#34;https://aws.amazon.com/cloudformation/&#34;&gt;AWS CloudFormation&lt;/a&gt; to
provision our cloud infrastructure using code. Most of the time we can get what
we need with the set of resources that AWS provides.&lt;/p&gt;
&lt;p&gt;However, sometimes CloudFormation support for a service or a particular feature
takes a while to arrive, and we need to fill in the gap ourselves.
CloudFormation gives us the ability to fill these gaps by building
&lt;a href=&#34;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html&#34;&gt;&amp;ldquo;custom resources&amp;rdquo;&lt;/a&gt;
that can literally run any logic you need in
&lt;a href=&#34;https://aws.amazon.com/lambda/&#34;&gt;AWS Lambda&lt;/a&gt;, and we&amp;rsquo;ve used these when we
needed to.&lt;/p&gt;
&lt;h2&gt;Sometimes Lambda isn&amp;rsquo;t the right answer&lt;/h2&gt;&lt;p&gt;Sometimes even Lambda fails us, though, as some resources can potentially take a
long time to set up, and we don&amp;rsquo;t particularly want to have a Lambda function
sitting idle or potentially timing out halfway through the resource setup.&lt;/p&gt;
&lt;p&gt;The great thing about Lambda functions is that you only pay for them when
they&amp;rsquo;re running. This makes them great for places where you need to run a quick
task or handle an API request. You lose some of the benefits when your Lambda
function looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;start_something()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; not_done():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;finish()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;because you&amp;rsquo;re paying for the Lambda to run while you&amp;rsquo;re waiting for your
operation to complete. Worse, Lambda functions have a maximum lifetime of 15
minutes, so if your process takes longer than 15 minutes, you have to do weird
hacks to make it work with a pure Lambda solution.&lt;/p&gt;
&lt;p&gt;When we found out that
&lt;a href=&#34;https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/57&#34;&gt;CloudFormation didn&amp;rsquo;t have support for creating DynamodDB Global Tables&lt;/a&gt;,
our first thought was &amp;ldquo;we know how to do this, a Lambda function custom resource
can handle it.&amp;rdquo; However, as we dug into the details and tried it out, we quickly
learned that we could get into a scenario where creating the initial replica set
or updating the replicas could easily exceed the 15-minute Lambda timeout.&lt;/p&gt;
&lt;h2&gt;Step Functions to the rescue!&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s where &lt;a href=&#34;https://aws.amazon.com/step-functions/&#34;&gt;AWS Step Functions&lt;/a&gt; comes
in. Step Functions make the task of orchestrating processes easier. They have
built-in support for looping, waiting, and integrating with different functions
and services, which makes them perfect for this sort of thing.&lt;/p&gt;
&lt;p&gt;One of our team members put together this Step Function definition for creating
a DynamoDB global table. It starts out by checking the state of the table,
waiting until the table is ready for updates, then comparing the set of replicas
with the desired set. You can only add one replica at a time, so the step
function repeats the &lt;code&gt;UpdateReplicas&lt;/code&gt; step until the actual state matches the
desired state. Each step is very small and self-contained, usually only one or
two API calls, and all of the waiting is done by Step Functions instead of in
the Lambda function, so we&amp;rsquo;re not paying for idle time! Best of all, Step
Functions can run for up to a week, so we didn&amp;rsquo;t need to worry about the
15-minute timeout any more.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/replication-state-machine.png&#34; alt=&#34;Replication state machine&#34; title=&#34;Replication state machine&#34;&gt;&lt;/p&gt;
&lt;h2&gt;There&amp;rsquo;s a small catch&amp;hellip;&lt;/h2&gt;&lt;blockquote&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;I wish I could use Step Functions directly to build CloudFormation custom resources instead of having to have a Lambda function that triggers the Step Function. #awswishlist&lt;/p&gt;&amp;mdash; Geoff Baskwill (@geoff_baskwill) February 24, 2021&lt;/blockquote&gt;
&lt;p&gt;CloudFormation doesn&amp;rsquo;t support direct integration with Step Functions as a
custom resource provider yet, but we can use our old Lambda function trick to
trigger the Step Function execution, and send the response back to
CloudFormation when we get to an end state.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;images/step-function-overview.png&#34; alt=&#34;Invoking Step Functions as a custom resource using Lambda as a shim&#34; title=&#34;Invoking Step Functions as a custom resource using Lambda as a shim&#34;&gt;&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;&lt;p&gt;When you love infrastructure-as-code and need a custom resource for something
that CloudFormation doesn&amp;rsquo;t support, Lambda is usually a great solution. When
you need a bigger hammer for complex orchestration or operations with lots of
idle time, Step Functions can help get you there.&lt;/p&gt;
&lt;p&gt;The goal is to retire this particular resource soon, as AWS tells us that
they&amp;rsquo;ll have built-in support in CloudFormation for DynamoDB Global Tables
&lt;a href=&#34;https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/57#issuecomment-758200567&#34;&gt;in the very near future&lt;/a&gt;.
That said, my team is happy that we were able to deliver an initial
implementation with this workaround and provide value to our customers!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Behaviour of background goroutines in AWS Lambda</title>
      <link>https://geoffbaskwill.ca/posts/background-goroutines-in-lambda/</link>
      <pubDate>Mon, 02 Nov 2020 00:00:00 +0000</pubDate>
      
      <guid>https://geoffbaskwill.ca/posts/background-goroutines-in-lambda/</guid>
      <description>&lt;p&gt;I was curious what would happen with a &lt;code&gt;time.Ticker&lt;/code&gt; running in an AWS Lambda function.&lt;/p&gt;
&lt;p&gt;It was pretty obvious that the ticker wouldn’t tick while the container was hibernating, but what about when it woke up? Would a bunch of ticks happen if the container had been hibernating for a long time? Would you get a tick at all?&lt;/p&gt;
&lt;h2&gt;tl;dr&lt;/h2&gt;&lt;p&gt;It looks like you get one tick when the function comes out of hibernation (assuming the ticker duration has passed at least once), and if the function never hibernates the ticks happen on time, which for my purposes is pretty much perfect.&lt;/p&gt;
&lt;h2&gt;The code&lt;/h2&gt;&lt;p&gt;I set up a small AWS Lambda function that responds to API Gateway requests and has a goroutine running with a &lt;code&gt;time.Ticker&lt;/code&gt; that ticks every minute:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;package main

import (
  &amp;#34;context&amp;#34;
  &amp;#34;fmt&amp;#34;
  &amp;#34;net/http&amp;#34;
  &amp;#34;sync&amp;#34;
  &amp;#34;time&amp;#34;

  &amp;#34;github.com/aws/aws-lambda-go/events&amp;#34;
  &amp;#34;github.com/aws/aws-lambda-go/lambda&amp;#34;
)

func main() {
  wg := &amp;amp;sync.WaitGroup{}

  ticker := time.NewTicker(time.Second * 60)
  go func() {
    for t := range ticker.C {
      // let the WaitGroup know that we&amp;#39;re working
      wg.Add(1)

      // tell the observer that the ticker actually ticked
      fmt.Println(&amp;#34;tick at&amp;#34;, t)
      
      // simulate a background process that takes a bit to complete
      time.Sleep(time.Second)
      
      // let the WaitGroup know that we&amp;#39;re done
      wg.Done()
    }
  }()

  lambda.Start(func(ctx context.Context, req events.APIGatewayProxyRequest)(events.APIGatewayProxyResponse, error) {
    resp, err := realHandler(ctx, req)
    
    // If the ticker is running, wait for it.
    wg.Wait()

    return resp, err
  })
}

func realHandler(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
  return events.APIGatewayProxyResponse{
    StatusCode: http.StatusPaymentRequired,
  }, nil
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You’ll see how I’ve split out the real handler and the ticker-waiting “middleware” &amp;hellip; this keeps the handler logic separated from the bit that waits for the background goroutine to complete what it’s doing before returning.&lt;/p&gt;
&lt;h2&gt;The test&lt;/h2&gt;&lt;p&gt;I called the function a couple of times, waited a few minutes, and called the function again. The logs told me what happened:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;2020-11-02T14:11:51.061-05:00 START RequestId: f2604bb1-8bc2-445a-b7b2-3893d8d3416d Version: $LATEST
2020-11-02T14:11:51.063-05:00 END RequestId: f2604bb1-8bc2-445a-b7b2-3893d8d3416d
2020-11-02T14:11:51.063-05:00 REPORT RequestId: f2604bb1-8bc2-445a-b7b2-3893d8d3416d Duration: 1.39 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 35 MB Init Duration: 66.79 ms XRAY TraceId: 1-5fa059f6-10e9a1715cb581111721b9a8 SegmentId: 0467a00a5d719145 Sampled: true
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;2020-11-02T14:11:54.171-05:00 START RequestId: 7a39872e-4b18-42da-b2ff-315a6590a1b6 Version: $LATEST
2020-11-02T14:11:54.174-05:00 END RequestId: 7a39872e-4b18-42da-b2ff-315a6590a1b6
2020-11-02T14:11:54.174-05:00 REPORT RequestId: 7a39872e-4b18-42da-b2ff-315a6590a1b6 Duration: 0.68 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 42 MB XRAY TraceId: 1-5fa059fa-0be52e2845702dd0365eeb74 SegmentId: 5d5f37f527b7de1a Sampled: true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first two log blocks show the first two calls, at &lt;code&gt;14:11:51-05:00&lt;/code&gt; and at &lt;code&gt;14:11:54-05:00&lt;/code&gt;. We wouldn’t expect to see a tick log here, and as expected we do not.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;2020-11-02T14:16:28.076-05:00 START RequestId: 488123cd-43bc-4567-9970-00c35bfc91b8 Version: $LATEST
2020-11-02T14:16:28.076-05:00 tick at 2020-11-02 19:16:28.076072523 +0000 UTC m=+277.052607202
2020-11-02T14:16:28.078-05:00 END RequestId: 488123cd-43bc-4567-9970-00c35bfc91b8
2020-11-02T14:16:28.078-05:00 REPORT RequestId: 488123cd-43bc-4567-9970-00c35bfc91b8 Duration: 0.59 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 42 MB XRAY TraceId: 1-5fa05b0c-35afecef72dc3fb25cf4eeef SegmentId: 045243422ccaea72 Sampled: true
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;2020-11-02T14:20:15.133-05:00 START RequestId: 2dc415ef-f53f-4e35-865d-21b89cd9147e Version: $LATEST
2020-11-02T14:20:15.133-05:00 tick at 2020-11-02 19:20:15.133194003 +0000 UTC m=+504.107026404
2020-11-02T14:20:15.137-05:00 END RequestId: 2dc415ef-f53f-4e35-865d-21b89cd9147e
2020-11-02T14:20:15.137-05:00 REPORT RequestId: 2dc415ef-f53f-4e35-865d-21b89cd9147e Duration: 0.62 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 43 MB XRAY TraceId: 1-5fa05bef-5e8453ea22ff3fa4454e0af6 SegmentId: 183982d42c0b3848 Sampled: true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The next two log blocks show calls at &lt;code&gt;14:16:28-05:00&lt;/code&gt; and &lt;code&gt;14:20:15-05:00&lt;/code&gt; and one tick happening each time the code is invoked. This would seem to indicate that even if multiple ticks would normally have happened in the time period, they are consolidated into one.&lt;/p&gt;
&lt;p&gt;Neat!&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
