<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Danny Reed</title>
    <description>The latest articles on DEV Community by Danny Reed (@trademark18).</description>
    <link>https://dev.to/trademark18</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F428725%2Fb86435eb-d6d0-47cd-a976-228b015d9066.jpg</url>
      <title>DEV Community: Danny Reed</title>
      <link>https://dev.to/trademark18</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trademark18"/>
    <language>en</language>
    <item>
      <title>Self-Provisioning Runtimes &amp; Serverless DX</title>
      <dc:creator>Danny Reed</dc:creator>
      <pubDate>Wed, 27 Apr 2022 14:43:00 +0000</pubDate>
      <link>https://dev.to/trademark18/self-provisioning-runtimes-serverless-dx-32o3</link>
      <guid>https://dev.to/trademark18/self-provisioning-runtimes-serverless-dx-32o3</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Developer experience (DX) has come a long way since the punch card days, but in the world of serverless development, we're actually approaching a significant regression in DX.&lt;/p&gt;

&lt;p&gt;Some of the practices and principles that unlock the power of serverless also make developer experience worse. Self-provisioning runtimes are the solution, and that's only the beginning of their value. &lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Using serverless best practices makes for a poor DX &lt;/li&gt;
&lt;li&gt;We should care about that because it costs us both money and happiness&lt;/li&gt;
&lt;li&gt;We should pursue self-provisioning runtimes for better DX (and many other reasons)&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Serverless best practices push us into bad DX
&lt;/h1&gt;

&lt;p&gt;In a very general way, I'm wrong.  Serverless affords us the luxury of spending very little time on "&lt;a href="https://www.factoftheday1.com/p/december-23-undifferentiated-heavy?s=r" rel="noopener noreferrer"&gt;undifferentiated heavy lifting&lt;/a&gt;" and more time writing the "special sauce" code that makes our projects distinct and competitive.  That, however, is just one metric that contributes to the overall DX.&lt;/p&gt;

&lt;p&gt;Consider these best practices that double as DX detractors:&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;IaC is critical to the successful management and evolution of serverless applications.  I think the benefits of IaC are generally well understood, so I'll forego that argument here.&lt;/p&gt;

&lt;p&gt;Let's consider CloudFormation.  Perhaps you've experienced the very long &lt;a href="https://www.getambassador.io/docs/telepresence/latest/concepts/devloop/#what-is-the-inner-dev-loop" rel="noopener noreferrer"&gt;dev loop&lt;/a&gt; involved with writing infrastructure templates.   You get an error, make a fix, push to &lt;code&gt;develop&lt;/code&gt;, wait, repeat.  I think it's fair to say that IaC is hard because of the &lt;strong&gt;long dev loop&lt;/strong&gt; and the challenges of "config languages" like YAML and JSON.&lt;/p&gt;

&lt;p&gt;IaC use cases demand a lot from configuration-oriented languages. We aren't just &lt;em&gt;configuring&lt;/em&gt;, we are also &lt;em&gt;defining&lt;/em&gt;.  We want maps, string manipulation, &lt;code&gt;!Ref&lt;/code&gt;-ing, and lots of other things that are a stretch for "config."  Sometimes even simple things like concatenation can wind up being tricky to read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="kt"&gt;!Join&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;AWS::Partition&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:s3:::elasticbeanstalk-*-'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;AWS::AccountId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IaC templates are slow and difficult to develop, but because we believe in the benefits of IaC, we dutifully shoulder that burden.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;(Don't worry, CDK fans.  We'll consider CDK in Appendix A.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration Over Code
&lt;/h2&gt;

&lt;p&gt;"Configuration over code" falls in line with the maxim, "&lt;a href="https://somehowmanage.com/2020/10/17/code-is-a-liability-not-an-asset/" rel="noopener noreferrer"&gt;code is a liability&lt;/a&gt;" and that we should prefer to &lt;strong&gt;configure&lt;/strong&gt; a general-purpose service over writing custom code to maintain.&lt;/p&gt;

&lt;p&gt;One example of this is using Step Functions instead of Lambda.  Based on our practice of "configuration over code" we should (generally) use Lambda only when Step Functions can't reasonably be used instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The depressing ramification of "configuration over code" is that now business logic is infrastructure, too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If YAML was rough for infrastructure, imagine how bad it is at business logic?  Whether you use YAML, JSON, or a domain-specific language (DSL), defining business logic as config is a nightmare:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No debugger&lt;/li&gt;
&lt;li&gt;Almost no use of IDE tools like IntelliSense&lt;/li&gt;
&lt;li&gt;The available linters aren't great&lt;/li&gt;
&lt;li&gt;IaC-defined business logic doesn't read at all like a program&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;We've reset the DX clock by decades.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look at this (silly) number classifier written in Amazon States Language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"States"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ClassifyNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Choice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Choices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Variable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"NumericEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IsSmallNumber"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Variable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"NumericGreaterThan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IsLargeNumber"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"IsSmallNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SuccessState"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"IsLargeNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pass"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SuccessState"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SuccessState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Succeed"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"StartAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ClassifyNumber"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unless you're an ASL guru, I trust you found that difficult to read and quite verbose.  Now compare to this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;classifyNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Small&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I admit that's not a realistic program, but we can see the extra layer of developer-effort that has to transform simple logical statements into a highly-specialized domain-specific language (DSL).  &lt;/p&gt;

&lt;p&gt;(Don't worry Workflow Studio fans, I'll address that in Appendix B)&lt;/p&gt;

&lt;h1&gt;
  
  
  The Costs of Bad DX
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Gap-Closing
&lt;/h2&gt;

&lt;p&gt;We might think very highly of ourselves for accepting these tremendous burdens in order to achieve a philosophically-consistent outcome.  This sort of digital Asceticism isn't doing us favors.&lt;/p&gt;

&lt;p&gt;One metric that contributes to DX is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much distance is there between the developer's intent and their code?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another way to phrase it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How different is the pseudocode from the final code?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think it's safe to say that nobody has ever pseudocoded something that looked remotely similar to the ASL definition we saw above.  So there's distance there -- there's distance between intent and code, and the developer has to close that gap.  Doing so is not free:&lt;/p&gt;

&lt;h3&gt;
  
  
  Gap-Closing Costs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Bugs introduced while transforming intent into final code&lt;/li&gt;
&lt;li&gt;Higher maintenance costs due to unintuitive/unreadable codebase&lt;/li&gt;
&lt;li&gt;Developer time&lt;/li&gt;
&lt;li&gt;Training/ramp-up costs (especially with DSL's)&lt;/li&gt;
&lt;li&gt;Developer morale, retention, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Is "developer morale" a stretch?  Consider this: I personally dislike JavaScript for various reasons, but it's one of my go-to languages.  Why?  I'm productive with it.  We developers crave productivity -- it boosts our work-satisfaction and perceived effectiveness.  When we have to use technology with poor DX, we feel less productive/effective/satisfied and that is a cost we shouldn't ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recruiting
&lt;/h2&gt;

&lt;p&gt;You will pay more to &lt;strong&gt;find&lt;/strong&gt; and &lt;strong&gt;retain&lt;/strong&gt; a developer who is thoroughly-versed in an obscure DSL than you will pay to hire a developer skilled in general-purpose languages like TypeScript, C#, or Python.  &lt;/p&gt;

&lt;p&gt;If you want to make life easier when it comes to recruiting, don't make your stack special.  Make it normal.&lt;/p&gt;

&lt;h1&gt;
  
  
  Have Your Cake, Eat it Too: Self-Provisioning Runtimes
&lt;/h1&gt;

&lt;p&gt;I first came across the term "Self-Provisioning Runtimes" on &lt;a href="https://www.serverlesschats.com/124/" rel="noopener noreferrer"&gt;this episode&lt;/a&gt; of the excellent Serverless Chats podcast by Jeremy Daily.  In this episode he interviews &lt;a href="https://www.swyx.io/" rel="noopener noreferrer"&gt;Sean (swyx) Wang&lt;/a&gt; and a good portion of their discussion centers on the topic.  Not knowing what self-provisioning runtimes (let's call them SPR's, OK?) were, I listened as they began to describe how, essentially, &lt;strong&gt;you just write your business logic, and then the system looks at your code, figures out what infrastructure you need, and then runs it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Swyx poses the idea this way:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the Platonic ideal of Developer Experience is a world where you ”Just Write Business Logic”, the logical endgame is a language+infrastructure combination that figures out everything else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jeremy amusingly described his reaction to reading this sentence for the first time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I put my arms out like this, lights light up, music starts playing, doves fly out from behind me. I'm like, "Yes! Yes! That. Why do more people not get that?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had a pretty similar reaction.&lt;/p&gt;

&lt;p&gt;This is fake code that doesn't work with any SPR, but it gives you the idea of what we're going for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Upload handler&lt;/span&gt;
&lt;span class="nx"&gt;uploadEndpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;spr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Years&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's imagine that this snippet gets processed by the SPR, and it spits out an API Gateway, a Step Functions state machine, and an S3 bucket with some lifecycle rules.&lt;/p&gt;

&lt;p&gt;The distance between intent and code is extremely short.  There's no accompanying infrastructure template to write.  We just write business logic, and the SPR figures out how to architect that.&lt;/p&gt;

&lt;h2&gt;
  
  
  All Your Practices Are Belong to the SPR
&lt;/h2&gt;

&lt;p&gt;Developers no longer need to subject themselves to the pain of writing business logic in config languages, deal with the quirks of YAML, or learn obscure DSL's.  The SPR knows all the best practices and will create the best-fit architecture for the business logic at hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  DX Regained
&lt;/h2&gt;

&lt;p&gt;Now that we're back to writing business logic in programming languages like TypeScript, C#, Python, or whatever you want, you can use your debugger, your IDE tools, your language skills, etc.!  This is the DX we want, and it's the DX we should push for.  &lt;/p&gt;

&lt;p&gt;It's not because we can't handle the pain, but because we are actually worse and more expensive developers when we are in pain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rainbows and Unicorns
&lt;/h2&gt;

&lt;p&gt;You're saying, "great, but it's made up, right?"  Yes, kind of.  Why doesn't this exist?  Swyx does a pretty good round-up on the contenders out there in his &lt;a href="https://www.swyx.io/self-provisioning-runtime" rel="noopener noreferrer"&gt;article&lt;/a&gt;.  The most mature one is &lt;a href="https://www.serverless.com/cloud" rel="noopener noreferrer"&gt;Serverless Cloud&lt;/a&gt;, and you should definitely check them out.&lt;/p&gt;

&lt;p&gt;I think we need to see more attempts, loftier goals, and more folks throwing energy at this.  I think Serverless Cloud is awesome, but I have a mercilessly-ambitious wishlist, about which I'll share in a future article.&lt;/p&gt;

&lt;h1&gt;
  
  
  Appendix A: CDK
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;I think of CDK as a stepping stone on the way to where we want to go.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The CDK is great.  I found it long before I heard of SPR's and I thought it was the most epic thing I had ever seen.  The reasons I liked it basically boiled down to DX pain-relief.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No more YAML/JSON&lt;/li&gt;
&lt;li&gt;Constructs let me reuse stuff, leverage abstractions&lt;/li&gt;
&lt;li&gt;IntelliSense works&lt;/li&gt;
&lt;li&gt;I can catch many errors before running my long pipeline&lt;/li&gt;
&lt;li&gt;Easy infrastructure diffs&lt;/li&gt;
&lt;li&gt;Productivity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ben Kehoe and others have raised concerns with CDK for various valid reasons, but I was so hungry for a better DX I was disposed to try to defend it against all the arguments, no matter how good they seemed.&lt;/p&gt;

&lt;p&gt;The CDK does help with DX, and that's good, but it's not solving the whole problem, and whatever &lt;strong&gt;does&lt;/strong&gt; solve the whole problem will make CDK-like tools easy to abandon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is CDK just a stepping stone?
&lt;/h3&gt;

&lt;p&gt;For one thing, you still have to write infrastructure separately from your code, so we haven't achieved swyx's "Platonic ideal".  Look at this Lambda definition.  It clearly points to where the business logic lives, over in &lt;code&gt;lib/lambda/myLambdaFunction&lt;/code&gt;.  This is an improvement over doing things in YAML/JSON, but it's just a way to do the same thing with less pain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myLambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-lambda-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// See? your business logic lives separately!&lt;/span&gt;
    &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib/lambda/myLambdaFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_14_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;architecture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Architecture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARM_64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-lambda-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What about CDK-defined step functions?
&lt;/h3&gt;

&lt;p&gt;Let's look at a CDK-defined state machine that I created recently.  It runs an Athena query, then uploads the results to an external web service using Lambda.  &lt;/p&gt;

&lt;p&gt;Here, we did the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Used a programming language&lt;/li&gt;
&lt;li&gt;Practiced "infrasructure as code"&lt;/li&gt;
&lt;li&gt;Practiced "config over code"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Home run?  Almost.  Here's why it's not quite there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We aren't describing business logic; we are still describing infrastructure.&lt;/li&gt;
&lt;li&gt;The developer still has the burden of translating business logic into "state machine form" and then defining the state machine with CDK.  We haven't closed the "intent to code" gap yet.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artifactStateMachine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;stepFn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`StateMachine-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Step 1: Start the Athena query&lt;/span&gt;
  &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AthenaStartQueryExecution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Run Athena Query: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queryString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resultConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;outputLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dataPipelineBucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;objectKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewName&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;queryExecutionContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;databaseName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyAthenaDB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;workGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyAthenaWorkGroup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;integrationPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IntegrationPattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RUN_JOB&lt;/span&gt;
    &lt;span class="c1"&gt;// Step 2: Invoke uploader Lambda&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Upload to destination: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;artifact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lambdaFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myLambda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.QueryExecution.ResultConfiguration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stateMachineRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an example of what we might do in an ideal world, with an SPR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;viewName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;externalService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SPR would "compile" that down into something like what we saw in the CDK example above.&lt;/p&gt;

&lt;p&gt;Notice that the developer doesn't need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Know what a state machine is or how to define one.&lt;/li&gt;
&lt;li&gt;Know if a state machine is the best choice for this workload.&lt;/li&gt;
&lt;li&gt;Write the Lambda code separately&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Appendix B: Step Functions Workflow Studio
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;AWS went to the trouble to make us a really nice Workflow Studio for Step Functions, so why doesn't that count as a great DX?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fair point.  Even better when you use the YAML/JSON export features to create your ASL files, and then reference them using serverless transforms in your CloudFormation template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyStepFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::StateMachine&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DefinitionUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aslDef.yml&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;-- behold, the generated ASL file&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# [omitted for brevity]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an improvement on writing those things by hand, but here are my challenges with this arrangement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can use the Workflow Studio to make your ASL file, but you still have to dig into it manually when it comes to merge conflicts or other version control-related tasks.&lt;/li&gt;
&lt;li&gt;You can't "read" the logic of the state machine easily in ASL format, so you have to copy/paste it into the Workflow Studio to see what it really does.  This means as a developer you can't really understand your code by looking at the code in the repo any more.&lt;/li&gt;
&lt;li&gt;Copying and pasting YAML back and forth from the browser to the IDE seems error-prone at worst, and clunky at best.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Feedback
&lt;/h1&gt;

&lt;p&gt;Thank you for considering my lengthy thoughts on this topic.  I'm very excited to see what can be done in the wonderland of SPR's, and I'm hoping to push out some more articles that focus on defining what an ideal SPR looks like, what characteristics it needs to have, and what obstacles are in the way.&lt;/p&gt;

&lt;p&gt;Please share your thoughts on the topic, relevant resources, or ideas for where to go next in my exploration of SPR's.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>cloud</category>
      <category>dx</category>
    </item>
    <item>
      <title>Debounce in Event-Driven Serverless</title>
      <dc:creator>Danny Reed</dc:creator>
      <pubDate>Thu, 31 Mar 2022 12:58:07 +0000</pubDate>
      <link>https://dev.to/trademark18/debounce-in-event-driven-serverless-nio</link>
      <guid>https://dev.to/trademark18/debounce-in-event-driven-serverless-nio</guid>
      <description>&lt;h1&gt;
  
  
  Debounce
&lt;/h1&gt;

&lt;p&gt;Debounce is a technique that seeks to consolidate several near-simultaneous events into one.  It's related to fan-in, but debounce usually has a time component.  One way to say it is, "If we get X related inputs within N seconds, only trigger one output."  My favorite way to think of it is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2Fproxy%2FGqbKuDkpvAvUgN27vv82ZMdt0245f5sbOCobxRpMLv_8Wl7FMFISCOUKvoxuJ4msAOcDsID24UzD_4-F414bvZNjJjyCedGzakVDhjefIo203XAgv6Zpv157EG3cc4rnklun98LMvGBDysMeN_LL1TcsSGIqM4-Dpd0" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh3.googleusercontent.com%2Fproxy%2FGqbKuDkpvAvUgN27vv82ZMdt0245f5sbOCobxRpMLv_8Wl7FMFISCOUKvoxuJ4msAOcDsID24UzD_4-F414bvZNjJjyCedGzakVDhjefIo203XAgv6Zpv157EG3cc4rnklun98LMvGBDysMeN_LL1TcsSGIqM4-Dpd0" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Use Case
&lt;/h1&gt;

&lt;p&gt;In my case, I have several sensors that submit events when certain conditions occur.  Each event will trigger some computations, but if several sensors submit an event within the same second, I just want to trigger one computation.  Having N separate computations outputting N separate results and resulting in N separate rows in a data store can mean some rows are obsolete almost immediately after being created.  It also means I have to store lots of extraneous data.&lt;/p&gt;

&lt;p&gt;What I need is to debounce those events so that if N events come in "at once" then we just make one new row in the data store.&lt;/p&gt;

&lt;h1&gt;
  
  
  Challenges
&lt;/h1&gt;

&lt;p&gt;This is a hard problem, especially in serverless.  One reason it's hard is that in order to implement debounce, we need to find mechanisms to do two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deduplicate&lt;/li&gt;
&lt;li&gt;Delay&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Approach
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Deduplication: DynamoDB
&lt;/h3&gt;

&lt;p&gt;DynamoDB will let us &lt;code&gt;PutItem&lt;/code&gt; &lt;em&gt;conditionally&lt;/em&gt;.  This is important because we'll use a Condition Expression to prevent overwriting an existing record.  Once we put an Item with a given key, we won't overwrite it.  &lt;strong&gt;This means when we hook up a DynamoDB Stream to this table, it will only fire one event even if we send several &lt;code&gt;PutItem&lt;/code&gt; calls down to DynamoDB at nearly the same time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This snippet shows writing the record conditionally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PutItemCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;marshall&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;ConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;attribute_not_exists(pk)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;marshall&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disappearTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;ReturnConsumedCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TOTAL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the call to PutItem would overwrite an existing record, the call will throw a &lt;code&gt;ConditionalCheckFailedException&lt;/code&gt; which you should catch (verify the exception name) and ignore as valid behavior.&lt;/p&gt;

&lt;p&gt;It's worth noting that deduplication is a helpful mechanism by itself.  If you just want deduplication and not a full debounce solution, you can just use this part and then hook up your Lambda target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delay: SQS
&lt;/h3&gt;

&lt;p&gt;This part of the solution isn't without its drawbacks.  SQS is one of the only serverless offerings that allows us to delay a message (something we usually avoid in event-driven architectures).  Part of the nature of most event-driven services is that they &lt;em&gt;don't hold onto stuff&lt;/em&gt; for more time than necessary to process it and pass it on.  We actually &lt;em&gt;want&lt;/em&gt; to put the brakes on this message for a bit in order to allow any "at the same time" messages to finish coming in.  SQS facilitates that.&lt;/p&gt;

&lt;p&gt;To configure a delay, we'll just set &lt;code&gt;DelaySeconds&lt;/code&gt; to whatever value you feel suits your use case.  I chose 1 second for mine.  Here's my CloudFormation snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AppDelayQueue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SQS::Queue&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;DelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;QueueName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AppDelayQueue&lt;/span&gt;
    &lt;span class="na"&gt;KmsMasterKeyId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alias/aws/sqs&lt;/span&gt; &lt;span class="c1"&gt;# Enable encryption&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting it Up: DynamoDB Stream
&lt;/h3&gt;

&lt;p&gt;We'll pipe our PutItem events to a Lambda using a DynamoDB Stream, and then the Lambda will use the SDK to enqueue the message in a delay queue.  The code is very simple, and frankly I wish DynamoDB Streams would let you send messages directly to SQS, but alas, the options are currently Lambda and Kinesis Data Streams.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edit: Nowadays, there's also EventBridge Pipes which can hook up to a DynamoDB Stream and then pipe your data into a wide variety of targets, including SQS Queues.  That would be the more "managed" way to do things at this point.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's the simple code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;SQSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SendMessageCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-sqs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sqs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SQSClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;QUEUE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queueUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Parse out the relevant data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deviceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Send message to SQS delay queue&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SendMessageCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;MessageBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;QueueUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QUEUE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Diagram
&lt;/h3&gt;

&lt;p&gt;All together now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4i3a0bx2mjs5b33o1dt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4i3a0bx2mjs5b33o1dt.png" alt=" " width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Tips
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The nature of debounce involves setting a threshold for what should "count as one" so you'll always have messages falling just outside or just inside the threshold you set.  Your app should be designed (downstream) so that getting a repeat event, say, 1.1 seconds after the first doesn't break stuff or corrupt data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You may want to disable retries on the DynamoDB Stream depending on your use case.  Leaving this on will cause failed messages to get recycled forever, which in this case wasn't helpful.  This can't be done through the AWS Console, weirdly.  You'll need to do it via the CLI (or through IaC).  Read about it &lt;a href="https://aws.amazon.com/about-aws/whats-new/2019/11/aws-lambda-supports-failure-handling-features-for-kinesis-and-dynamodb-event-sources/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you want to measure how many records are getting debounced, you can add some code where you're PUTting to DynamoDB inside the &lt;code&gt;catch&lt;/code&gt;.  After you've confirmed that the exception name is &lt;code&gt;ConditionalCheckFailedException&lt;/code&gt; you can either log a message or PUT to a CloudWatch metric for bonus points :)  Just be careful with the latter option if you're dealing with tons of messages since CloudWatch metrics can be expensive!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  FIFO Delay Queues with Deduplication?
&lt;/h1&gt;

&lt;p&gt;Why don't you just use a FIFO queue from SQS with a delay?  Using a deduplication ID (or content-based deduplication) you can achieve deduplication out-of-the-box.  You can also build the delay right in.  Honestly that's a really good option for most use cases.&lt;/p&gt;

&lt;p&gt;The one really frustrating problem with it is that the deduplication feature doesn't just deduplicate within messages currently in the queue, but &lt;strong&gt;it deduplicates against all messages sent in the past 5 minutes.&lt;/strong&gt;  You currently cannot configure that to be shorter.  In my use case, I actually need to send messages with the same deduplication ID within 5 minutes.  I just want it deduplicated against what's &lt;em&gt;actually in the queue&lt;/em&gt; at the time.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Event-driven architectures are very powerful, but debounce is hard to achieve.  Thanks to the combination of DynamoDB and SQS, we can achieve this behavior and minimize extraneous data processing and storage.&lt;/p&gt;

&lt;p&gt;Thoughts and criticisms welcome!  Let me know how you're solving these challenges in your shop!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>eventdriven</category>
      <category>serverless</category>
    </item>
    <item>
      <title>An attempt was made to access a socket...</title>
      <dc:creator>Danny Reed</dc:creator>
      <pubDate>Wed, 12 Jan 2022 01:34:56 +0000</pubDate>
      <link>https://dev.to/trademark18/an-attempt-was-made-to-access-a-socket-4nh9</link>
      <guid>https://dev.to/trademark18/an-attempt-was-made-to-access-a-socket-4nh9</guid>
      <description>&lt;h2&gt;
  
  
  The Error
&lt;/h2&gt;

&lt;p&gt;Today I tried to fire up a project I haven't run in a long time.  It's a web app, and it happens to launch on a specific port: 50617.&lt;/p&gt;

&lt;p&gt;I was frustrated to see this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An attempt was made to access a socket in a way forbidden by its access permissions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Reason
&lt;/h2&gt;

&lt;p&gt;I use Docker on Windows with the WSL2 engine, which involves Hyper-V.  Some quick googling revealed that this error usually comes up when you're trying to access a port that has been reserved by something else.  Hyper-V reserves a bunch of ephemeral ports, so it's a common culprit.&lt;/p&gt;

&lt;p&gt;To confirm your port has already been reserved, open an admin command prompt and run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;netsh interface ipv4 show excludedportrange protocol=tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will show you the ranges that have been reserved.  My port was in one of the ranges. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Soltuion
&lt;/h2&gt;

&lt;p&gt;I found the solution on this &lt;a href="https://github.com/docker/for-win/issues/3171#issuecomment-873970535" rel="noopener noreferrer"&gt;GitHub comment&lt;/a&gt;, so full props to the author for finding the solution.  I'm just writing this post to help make this easier to find than digging through long GitHub comment chains :)&lt;/p&gt;

&lt;p&gt;The gist of it is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open an Administrator command prompt&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stop the Windows NAT Driver service&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net stop winnat
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a manual reservation for the specific port you're trying to use (50617 in my case).&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;netsh int ipv4 add excludedportrange protocol=tcp startport=50617 numberofports=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start the Windows NAT Driver service&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net start winnat
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run your project!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This should get your port reserved and let you get your project running.  If you have additional tips related to this issue, leave a comment!&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>webdev</category>
    </item>
    <item>
      <title>2 Important Optimizations for Athena with S3</title>
      <dc:creator>Danny Reed</dc:creator>
      <pubDate>Wed, 01 Dec 2021 14:33:09 +0000</pubDate>
      <link>https://dev.to/trademark18/2-important-optimizations-for-athena-with-s3-52g0</link>
      <guid>https://dev.to/trademark18/2-important-optimizations-for-athena-with-s3-52g0</guid>
      <description>&lt;p&gt;Do you use Athena to query data in S3?  Coupled with the use of the Parquet file format, this is a really powerful combination.  S3 is very cheap, Athena is very cheap, and wait a second....my bill is steadily going up?  What happened?&lt;/p&gt;

&lt;p&gt;That's what happened to me, and it took a while to identify two key configuration mistakes that could have been avoided easily.  I will share them both here so you can do these right the first time and save yourself one major headache and one minor one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimization 1: Athena Query Results
&lt;/h2&gt;

&lt;p&gt;Did you know that by default, Athena will save a CSV of the output of every query you run?  It stashes it in S3.  If your system runs Athena queries periodically, this can add up (especially in the Standard storage tier).  Honestly these serve no purpose for us, so this wound up creating terabytes of unnecessary data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;One fix is to create a lifecycle rule on the "folder" in which Athena stores these results.  For our purposes, it's sufficient to keep just a few days worth of results for debugging purposes.  You'll need to identify where those results are stored, then create the appropriate lifecycle rule to delete old files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can also create a rule to at least move these to a cheaper-than-Standard storage tier to reduce costs without deleting any data.  This should at least minimize the costs if you truly have a reason to keep all your query results.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Optimization 2: KMS
&lt;/h2&gt;

&lt;p&gt;This one was sneaky.  It took me quite a while to figure out why my KMS costs were going through the roof.  It turns out that when Athena pulls out my .parquet files from S3, it makes &lt;code&gt;kms:decrypt&lt;/code&gt; calls for &lt;em&gt;every file&lt;/em&gt; it retrieves.  When you're retrieving a hundred thousand files dozens of times per day, this gets &lt;em&gt;really&lt;/em&gt; expensive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root Cause
&lt;/h3&gt;

&lt;p&gt;The issue was that I had selected S3 Encryption for the bucket in which my .parquet files are stored.  This works fine for many use cases, but when frequently retrieving many files, it's expensive, and you should prefer Bucket Keys with &lt;code&gt;SSE-KMS&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;p&gt;Use a bucket key.  I was originally using &lt;code&gt;SSE-S3&lt;/code&gt; encryption, but to use a bucket key, you'll need to move to &lt;code&gt;SSE-KMS&lt;/code&gt; &lt;em&gt;with&lt;/em&gt; Bucket Key enabled.  The use of a bucket key is really the crux of the solution here.&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html" rel="noopener noreferrer"&gt;AWS docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Amazon S3 Bucket Keys reduce the cost of Amazon S3 server-side encryption using AWS Key Management Service (SSE-KMS). This new bucket-level key for SSE can reduce AWS KMS request costs by up to 99 percent by decreasing the request traffic from Amazon S3 to AWS KMS. With a few clicks in the AWS Management Console, and without any changes to your client applications, you can configure your bucket to use an S3 Bucket Key for AWS KMS-based encryption on new objects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Note that at the end of the quote it says, "on new objects."&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;This is a depressing detail if you've already stored data &lt;em&gt;without&lt;/em&gt; bucket key enabled.  You'll have to do what I did and follow this process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable the bucket key for new objects&lt;/li&gt;
&lt;li&gt;Enable daily manifests on your bucket&lt;/li&gt;
&lt;li&gt;Wait a day or two for manifests to be generated&lt;/li&gt;
&lt;li&gt;Make a backup of your data (some will be encrypted with bucket key, most will not)&lt;/li&gt;
&lt;li&gt;Create a "Batch Operation" job in S3 to &lt;em&gt;COPY&lt;/em&gt; your files over top of themselves, and have the job configure bucket key encryption as it overwrites.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process is scary because you're essentially overwriting your production data.  I recommend thorough testing in a test environment and good study of the related documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outcome
&lt;/h2&gt;

&lt;p&gt;We have seen our S3 storage costs go down by close to 70%, and our KMS costs go down by 83% for an overall cost savings of hundreds of dollars per month.&lt;/p&gt;

&lt;p&gt;These small details are easy to overlook since they don't impact the functionality of your application.  Until these two pieces are configured correctly, however, you'll miss out on the cost effectiveness that these powerful services can offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-set-lifecycle-configuration-intro.html" rel="noopener noreferrer"&gt;AWS Docs: S3 Lifecycle Rules&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/athena/latest/ug/querying.html" rel="noopener noreferrer"&gt;AWS Docs: Athena Query Results&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html" rel="noopener noreferrer"&gt;AWS Docs: Bucket Keys&lt;/a&gt;&lt;br&gt;
&lt;a href="https://aws.amazon.com/blogs/storage/encrypting-objects-with-amazon-s3-batch-operations/" rel="noopener noreferrer"&gt;AWS Docs: S3 Batch Operation to Re-Encrypt&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Kalman Filtering in SQL</title>
      <dc:creator>Danny Reed</dc:creator>
      <pubDate>Tue, 20 Apr 2021 19:51:10 +0000</pubDate>
      <link>https://dev.to/trademark18/kalman-filtering-in-sql-2f20</link>
      <guid>https://dev.to/trademark18/kalman-filtering-in-sql-2f20</guid>
      <description>&lt;h2&gt;
  
  
  Kalman who?
&lt;/h2&gt;

&lt;p&gt;Have you ever used a Kalman filter?  My first experience with Kalman filtering was in smoothing motion sensor readings for a homemade Segway I built back in college.  Many years ago it was used in the Apollo missions (ok, they win on coolness...).  It's job is simple -- take noisy data and smooth it out a bit. &lt;/p&gt;

&lt;p&gt;At work, I needed to smooth some latitude/longitude values to help make navigation paths be smoother.  Normally, I'd just &lt;code&gt;npm i kalmanjs&lt;/code&gt; and be off to the races, but this particular data pipeline presented a challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge
&lt;/h2&gt;

&lt;p&gt;I apply several data-processing operations to the data I'm working with, and several of those operations happen in the database because  SQL is a great place to do set-based operations.  These operations must take effect in a specific order in order for things to come out right, and I needed to stick the Kalman filtering operation right between two specific steps in my process.  Both of those steps were done in SQL.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1
&lt;/h3&gt;

&lt;p&gt;Do the first few operations in SQL, then return results to the API layer (Lambda) to do the Kalman filtering (using Kalmanjs) &lt;em&gt;and&lt;/em&gt; the remaining operations.  This requires porting the rest of the steps from SQL to JS and doing them in the Lambda.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantage&lt;/strong&gt;: Puts some CPU load on the Lambda instead of the DB, which is a good thing in my case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantage&lt;/strong&gt;: I have to translate several operations from SQL to JS, and some of them are set-based, which means they're &lt;em&gt;better&lt;/em&gt; to do in SQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2
&lt;/h3&gt;

&lt;p&gt;Try to implement Kalman filtering in SQL.  This option is tricky because there's practically no information out there on Kalman filtering in SQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantage&lt;/strong&gt;: Preserve data pipeline and existing SQL implementations for subsequent operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantage&lt;/strong&gt;: Requires writing a Kalman filter in SQL, which has some specific challenges we'll discuss later. Also, I don't do much fancy math, so understanding the way Kalman filtering actually works would be a challenge.&lt;/p&gt;

&lt;p&gt;I chose option 2 because I just can't justify doing several operations in the "wrong" place just because that's where the Kalman implementations are available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translation
&lt;/h2&gt;

&lt;p&gt;Before attempting the implementation, I took a good look at the code in KalmanJS and tried to understand it.  Honestly the code looked pretty simple, and all I really had to do was change syntax around a bit.  Here's a snippet of the translation:&lt;/p&gt;

&lt;p&gt;Original JS implementation (&lt;a href="https://github.com/wouterbulten/kalmanjs/blob/81da4ed3909d9135f23359389b4e7a4dc1ed094f/src/kalman.js#L34-L62" rel="noopener noreferrer"&gt;KalmanJS&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
  * Filter a new value
  * @param  {Number} z Measurement
  * @param  {Number} u Control
  * @return {Number}
  */&lt;/span&gt;
  &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="c1"&gt;// Compute prediction&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;predX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;predCov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uncertainty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="c1"&gt;// Kalman gain&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;predCov&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;predCov&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

      &lt;span class="c1"&gt;// Correction&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;predX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;predX&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;predCov&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;predCov&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essentially this just does basic ifs, and some math.  In fact, I found it so clear that I decided I could probably just translate the code without even having to dive into understanding the math behind Kalman filtering.&lt;/p&gt;

&lt;p&gt;SQL Translation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="n"&gt;PRINT&lt;/span&gt; &lt;span class="s1"&gt;'No existing state; proceeding without it'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="k"&gt;ELSE&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predCov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predCov&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predCov&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predCov&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;C&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predCov&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did choose to change &lt;code&gt;uncertainty()&lt;/code&gt; and &lt;code&gt;predict()&lt;/code&gt; functions to inline formulas, just to minimize the need to create function declarations in SQL.  Other than that, you can tell they're pretty much the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed Bump
&lt;/h2&gt;

&lt;p&gt;This is where I hit a snag with the translation.  Kalman works by looking at statistical properties the &lt;em&gt;previous&lt;/em&gt; values in order to predict and smooth the &lt;em&gt;next&lt;/em&gt; value.  &lt;strong&gt;This means that Kalman needs to keep track of the state of the filter.&lt;/strong&gt;  In OOP you can just keep an instance of a class around in memory and use instance properties to keep track of state.  In SQL, once the invocation completes, nothing is stored in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Since the only obvious way to store things in SQL is in a table, I decided to create a &lt;code&gt;KalmanState&lt;/code&gt; table in my database.  I read through the JS code again to determine which values were actually stored and used in the next calculation.  From what I gathered, it appeared that all it needs to store are these variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x, cov, predX, predCov, K
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it needs to store one set of these variables for &lt;em&gt;each instance&lt;/em&gt; of the filter, so I made a simple table that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id          x                      cov                    predX                  predCov                K                      identifier
----------- ---------------------- ---------------------- ---------------------- ---------------------- ---------------------- ----------------
43          26.9861111111111       0.618055555555556      26.9636363636364       1.61818181818182       0.618055555555555      testingLat
44          21.0416666666667       0.618055555555556      21.1090909090909       1.61818181818182       0.618055555555555      testingLon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last column is "identifier" which is just a string that you can use to name the instance.  In my case, I'm using one filter for each of several MAC addresses of devices I'm using.  So for me, I can just pass in the MAC address of the device as the identifier.  This helps the SPROC retrieve the proper state when it's time to filter a new value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Here's how I integrated this into my query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;LatIdentifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;DeviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-lat'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;LonIdentifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;DeviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-lon'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rawLat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Latitude&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rawLon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Longitude&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="k"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sp_kalman&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;LatIdentifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rawLat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predictedLat&lt;/span&gt; &lt;span class="k"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sp_kalman&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;LonIdentifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;rawLon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predictedLon&lt;/span&gt; &lt;span class="k"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Overwrite the unfiltered lat/lon values in the temp table&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="k"&gt;temp&lt;/span&gt; 
    &lt;span class="k"&gt;SET&lt;/span&gt;
        &lt;span class="n"&gt;Latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predictedLat&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;predictedLon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that this is part of a much larger query, there were contextual reasons to use the temp table for this purpose, and that there is only ever one row in that table when this is executed.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems/Caveats
&lt;/h2&gt;

&lt;p&gt;I'm not writing this to say that I've come up with some perfect solution...in fact this solution has some serious challenges.  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I was forced to make it a &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/stored-procedures/stored-procedures-database-engine?view=sql-server-ver15" rel="noopener noreferrer"&gt;SPROC&lt;/a&gt;&lt;/strong&gt;. I attempted to turn my code into a &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/user-defined-functions?view=sql-server-ver15" rel="noopener noreferrer"&gt;UDF&lt;/a&gt;.  Functions are nice because you can use them right in a &lt;code&gt;SELECT&lt;/code&gt; or even a &lt;code&gt;JOIN&lt;/code&gt;.  Sadly (but sensibly) functions are not allowed to alter data.  As we discussed earlier, the Kalman filter must alter data in order to save its state.  Now to integrate it with my query, I can't just flow it right in with the &lt;code&gt;SELECT&lt;/code&gt;s but I have to &lt;code&gt;EXEC&lt;/code&gt; the SPROC with parameters and then retrieve the output into a variable.  This is just a clunky experience that makes for uglier and less readable code, in my opinion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You can't use it with &lt;code&gt;ISOLATION LEVEL SNAPSHOT&lt;/code&gt;&lt;/strong&gt;. This is true for the same reason as #1.  You can't alter data in a &lt;code&gt;SNAPSHOT&lt;/code&gt;-isolated transaction.  This makes sense too, but it's annoying!  I had to change up how my transaction worked in order to exclude the &lt;code&gt;EXEC&lt;/code&gt; calls from it.  In my case it was possible to side-step this limitation, but in other cases it may not be!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You have to clean up after yourself&lt;/strong&gt; By this I mean that there's no mechanism whereby this will ever &lt;code&gt;DELETE&lt;/code&gt; records from the &lt;code&gt;KalmanState&lt;/code&gt; table, which could allow for uncontrolled growth dependent on the application.  In my case, I had to modify my API layer to perform cleanup when a filter is no longer useful to the system.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Should you use this?
&lt;/h2&gt;

&lt;p&gt;It seems to me that the vast majority of use cases for Kalman filtering can be accomplished without doing the math in the DB.  It's just easier all around :)  &lt;/p&gt;

&lt;p&gt;If you find yourself in a position like mine, where doing it in the DB really was the best option, then I hope this gives you a leg up on accomplishing that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;The author of the KalmanJS library (who does some cool stuff -- &lt;a href="https://github.com/wouterbulten" rel="noopener noreferrer"&gt;check out his work&lt;/a&gt;) has a contrib filter where he invites translations to other languages.  You can find this implementation &lt;a href="https://github.com/wouterbulten/kalmanjs/tree/master/contrib" rel="noopener noreferrer"&gt;there&lt;/a&gt; now.  &lt;/p&gt;

&lt;p&gt;Also check out his &lt;a href="https://www.wouterbulten.nl/blog/tech/lightweight-javascript-library-for-noise-filtering/" rel="noopener noreferrer"&gt;original blog post&lt;/a&gt; about the development of KalmanJS here.&lt;/p&gt;

&lt;p&gt;If you can think of improvements, please feel free to have a go at it!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Cover image sourced from &lt;a href="https://www.wouterbulten.nl/blog/tech/lightweight-javascript-library-for-noise-filtering/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and used with permission)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
    </item>
    <item>
      <title>Serverless - A Primer</title>
      <dc:creator>Danny Reed</dc:creator>
      <pubDate>Tue, 20 Apr 2021 02:39:28 +0000</pubDate>
      <link>https://dev.to/trademark18/serverless-a-primer-1nkf</link>
      <guid>https://dev.to/trademark18/serverless-a-primer-1nkf</guid>
      <description>&lt;h1&gt;
  
  
  What is "serverless"
&lt;/h1&gt;

&lt;p&gt;I know serverless isn't exactly a new word any more.  The first truly abstract serverless offerings came around more than 10 years ago, which is an eternity in the tech world.  The reason I want to write about it is that even though it has been around a while, many folks (especially large, established corporations) who could benefit from it suffer from inertia of mindset, commitment to previous investments, and a lack of serverless-ready skillsets.  Perhaps accurately understanding what it is will help set a course for progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Serverless is more than just a buzzword applied to a product by the marketing team.  It's an architecture paradigm, a mindset, a pricing model, and a commitment to an additional layer of abstraction that keeps your time focused on what makes your project special.  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Adding to the confusion, there's also a company called &lt;a href="https://www.serverless.com/open-source/" rel="noopener noreferrer"&gt;Serverless Inc&lt;/a&gt;, offering tools to help you build...serverless apps.  We won't dive into that today.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless: An Architecture Paradigm
&lt;/h2&gt;

&lt;p&gt;When designing an architecture for a serverless system, one has a different set of building blocks at her/his disposal.  Instead of thinking of CPU and RAM capacities, one thinks in more abstract terms like services, protocols, and data pipelines.&lt;/p&gt;

&lt;p&gt;A serverless architecture is one that can scale with little or no intervention because it is built on "elastic" services that scale horizontally as the load increases.&lt;/p&gt;

&lt;p&gt;A serverless architecture will avoid stateful transactions where possible.  Statelessness lends itself to horizontal scaling, scale-to-zero services that shut down entirely when not needed, and typically result in information being stored in nonvolatile, highly durable places instead of memory.&lt;/p&gt;

&lt;p&gt;A serverless architecture will decouple wherever possible.  Technically decoupling is a useful feature in any type of architecture, but it's especially beneficial in serverless.  Cloud services designed for use in serverless architectures are built to make decoupling easier than ever before.&lt;/p&gt;

&lt;p&gt;Here's &lt;a href="https://www.jeremydaly.com/serverless-reference-architectures/" rel="noopener noreferrer"&gt;a great set of serverless reference architectures&lt;/a&gt; complete with diagrams that I found very useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless: A Mindset
&lt;/h2&gt;

&lt;p&gt;I like to think of serverless as a mindset.  Some projects I've worked on start with someone asking something like,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Ok, so how big of a server do we want and where should we host it."  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Someone with a serverless mindset would first ask, &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can this be done without a server?"  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That seems like a simple change, but the payoff can be impressive.  I haven't researched the economics of it, but it seems to me that cloud providers charge quite a bit for a VM.  Moving to cloud VM's has benefits, but it's very expensive, too!  &lt;/p&gt;

&lt;p&gt;Ben Kehoe is one of the top serverless gurus out there, and during an interview on the &lt;em&gt;Real World Serverless with theburningmonk&lt;/em&gt; podcast (#15), he mentioned the serverless mindset:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Interviewer: What advice would you give to people just looking to start their serverless journey today?  Anything that would help them get started quickly and avoid common mistakes?&lt;/p&gt;

&lt;p&gt;Ben Keohe: The most important bit is adopting the serverless mindset, which is something you can do even before you can use the technologies that we refer to as serverless.  It's about wanting to own less technology to deliver customer value better.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Serverless: A Pricing Model
&lt;/h2&gt;

&lt;p&gt;When you're looking at pricing information for serverless services, you'll probably see prices listed in hundredths or thousandths of a penny.  Most serverless offerings have a per-invocation or per-use pricing model.  This means that if nobody is using your app, the cost to operate it goes down.  If you have a big surge in load, you'll pay for exactly enough horsepower to tackle it.&lt;/p&gt;

&lt;p&gt;This pricing model helps you pay for the things that are actually giving you value.  A beefy server with no workload applied to it is giving you negative value.  It's costing you money without performing any tasks.  A serverless function, by comparison, will only wake up when needed, and then evaporate when it's done.  You get charged for the milliseconds/seconds that it was awake.&lt;/p&gt;

&lt;p&gt;This is a big benefit for startups who need to keep costs low during development and testing, but need to scale quickly as their apps gain traction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless: A Commitment to Abstraction
&lt;/h2&gt;

&lt;p&gt;One great way to focus on the important things is to alleviate the need to give your attention to &lt;em&gt;other&lt;/em&gt; things.  This is an unsung strength of serverless!  While you may be &lt;em&gt;capable&lt;/em&gt; of managing your own servers, that's not the mission of your app!  Having spent an hour managing server OS patches will not result in your app being fundamentally better at what it does.  Having that hour to start development of that community-suggested feature on your backlog WILL make your app fundamentally better.&lt;/p&gt;

&lt;p&gt;In economics this dynamic is called "opportunity cost" and we should be careful to add this to the equation when evaluating the server-based vs. serverless decision.&lt;/p&gt;

&lt;p&gt;Technology has long been a game of abstraction.  Serverless computing is the next major step in getting us further away from 1's and 0's and closer to the ideas and concepts that we use to build amazing things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downsides?
&lt;/h2&gt;

&lt;p&gt;While serverless is awesome, it's not quite a silver bullet.  Here are a couple challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Portability&lt;/strong&gt;:  If you built your system in GCP and then one day are required to port it to Azure, it's not just going to be spinning up a new server and copying over your configs.  It's going to involve understanding the nuanced differences between GCP's services and Azure's services, and you'll likely spend quite a bit of time adapting your code and architecture to work well with the new cloud provider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Control&lt;/strong&gt;: When using a service, you're at the mercy of the owner of that service.  While many times a cloud provider will make positive changes to their services to keep the it modern and secure, sometimes your schedule can be clobbered by a notice that says, "you have a resource using [protocol/runtime/service version] that will be retired on [date]." In my experience cloud providers are judicious about the kinds of changes they make and allow for backwards compatibility where reasonable.  Notice is typically given well in advance of anything changing.  The bottom line remains, however, that they are in control of their service, and your system must be kept in compatibility with it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Serverless is important because it presents new opportunities for us to be effective, efficient, and excellent in our work.  The impact of serverless can be seen in the stories of many successful businesses, but some of the most dramatic impacts are not yet realized.&lt;/p&gt;

&lt;p&gt;I hope this fairly informal post has helped shape your perception of serverless systems and their characteristics, and that you'll be able to use this exciting set of tools to build something amazing!&lt;/p&gt;

</description>
      <category>serverless</category>
    </item>
  </channel>
</rss>
