<?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: Gurudev Prasad Teketi</title>
    <description>The latest articles on DEV Community by Gurudev Prasad Teketi (@gurudev_prasadteketi).</description>
    <link>https://dev.to/gurudev_prasadteketi</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%2F1503509%2F7dbf9b23-6220-4c83-8451-15739a8aabe9.png</url>
      <title>DEV Community: Gurudev Prasad Teketi</title>
      <link>https://dev.to/gurudev_prasadteketi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gurudev_prasadteketi"/>
    <language>en</language>
    <item>
      <title>Day 4: Talking About Work, Hobbies, and Daily Activities</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Mon, 08 Jun 2026 13:01:49 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/day-4-talking-about-work-hobbies-and-daily-activities-513p</link>
      <guid>https://dev.to/gurudev_prasadteketi/day-4-talking-about-work-hobbies-and-daily-activities-513p</guid>
      <description>&lt;p&gt;¡Hola!&lt;/p&gt;

&lt;p&gt;This class focused on expanding vocabulary around daily activities, work, free time, and practicing some of the most useful sentence patterns in Spanish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Four Sentence Patterns We Keep Using&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing I’m noticing is that Spanish becomes much easier when I master a few sentence structures and reuse them in different situations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tengo que… (I have to…)&lt;/strong&gt;&lt;br&gt;
Used for obligations and responsibilities.&lt;/p&gt;

&lt;p&gt;Examples: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tengo que trabajar.&lt;/li&gt;
&lt;li&gt;Tengo que reparar mi carro.&lt;/li&gt;
&lt;li&gt;Tengo que asistir a muchas reuniones.&lt;/li&gt;
&lt;li&gt;Tengo que ir al parque con mi hijo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Voy a… (I’m going to…)&lt;/strong&gt;&lt;br&gt;
Used for future plans.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Voy a limpiar mi casa.&lt;/li&gt;
&lt;li&gt;Voy a cocinar comida india.&lt;/li&gt;
&lt;li&gt;Voy a viajar.&lt;/li&gt;
&lt;li&gt;Voy a estudiar español.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quiero… (I want…)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Used to express desires and goals.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quiero hablar español.&lt;/li&gt;
&lt;li&gt;Quiero viajar a México.&lt;/li&gt;
&lt;li&gt;Quiero aprender más vocabulario.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Me gusta… (I like…)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the most useful expressions for conversations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Me gusta cocinar.&lt;/li&gt;
&lt;li&gt;Me gusta correr.&lt;/li&gt;
&lt;li&gt;Me gusta meditar.&lt;/li&gt;
&lt;li&gt;Me gusta viajar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vocabulary: Activities at Home&lt;/p&gt;

&lt;p&gt;We learned several verbs commonly used around the house.&lt;/p&gt;

&lt;h2&gt;
  
  
  Activities at Home
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Trabajar — To work&lt;/li&gt;
&lt;li&gt;Limpiar — To clean&lt;/li&gt;
&lt;li&gt;Leer — To read&lt;/li&gt;
&lt;li&gt;Hacer — To do / make&lt;/li&gt;
&lt;li&gt;Comer — To eat&lt;/li&gt;
&lt;li&gt;Dormir — To sleep&lt;/li&gt;
&lt;li&gt;Ver — To watch&lt;/li&gt;
&lt;li&gt;Hablar — To speak&lt;/li&gt;
&lt;li&gt;Cocinar — To cook&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Activities at Work
&lt;/h2&gt;

&lt;p&gt;This section felt especially relevant because many of these activities describe a typical workday.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asistir a reuniones — To attend meetings&lt;/li&gt;
&lt;li&gt;Asignar trabajos — To assign tasks&lt;/li&gt;
&lt;li&gt;Dibujar diagramas arquitectónicos — To draw architectural diagrams&lt;/li&gt;
&lt;li&gt;Planear proyectos — To plan projects&lt;/li&gt;
&lt;li&gt;Solucionar problemas — To solve problems&lt;/li&gt;
&lt;li&gt;Documentar proyectos — To document projects&lt;/li&gt;
&lt;li&gt;Hablar con mi jefe — To talk with my boss&lt;/li&gt;
&lt;li&gt;Discutir con mi equipo — To discuss with my team&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Free Time Activities
&lt;/h2&gt;

&lt;p&gt;We also covered hobbies and leisure activities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jugar — To play&lt;/li&gt;
&lt;li&gt;Viajar — To travel&lt;/li&gt;
&lt;li&gt;Pasar tiempo — To spend time&lt;/li&gt;
&lt;li&gt;Ir de compras — To go shopping&lt;/li&gt;
&lt;li&gt;Correr — To run&lt;/li&gt;
&lt;li&gt;Meditar — To meditate&lt;/li&gt;
&lt;li&gt;Estudiar — To study&lt;/li&gt;
&lt;li&gt;Tocar la guitarra — To play the guitar&lt;/li&gt;
&lt;li&gt;Ver deportes — To watch sports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A Useful Grammar Observation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spanish verbs are grouped into three major infinitive endings:&lt;/p&gt;

&lt;p&gt;-ar&lt;br&gt;
-er&lt;br&gt;
-ir&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trabajar&lt;/li&gt;
&lt;li&gt;Comer&lt;/li&gt;
&lt;li&gt;Dormir&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This may seem like a small detail now, but it becomes very important once verb conjugations start getting more advanced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Expressions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Me gusta... — I like...&lt;/li&gt;
&lt;li&gt;Tengo que... — I have to...&lt;/li&gt;
&lt;li&gt;Voy a... — I am going to...&lt;/li&gt;
&lt;li&gt;Quiero... — I want...&lt;/li&gt;
&lt;li&gt;¿Te gusta...? — Do you like...?&lt;/li&gt;
&lt;li&gt;¿Qué te gusta? — What do you like?&lt;/li&gt;
&lt;li&gt;Sí, me gusta. — Yes, I like it.&lt;/li&gt;
&lt;li&gt;No, no me gusta. — No, I don't like it.&lt;/li&gt;
&lt;li&gt;Dime... — Tell me...&lt;/li&gt;
&lt;li&gt;¿Cómo se dice...? — How do you say...?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Nouns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;La reunión — The meeting&lt;/li&gt;
&lt;li&gt;Las reuniones — The meetings&lt;/li&gt;
&lt;li&gt;El proyecto — The project&lt;/li&gt;
&lt;li&gt;Los proyectos — The projects&lt;/li&gt;
&lt;li&gt;El jefe — The boss&lt;/li&gt;
&lt;li&gt;El equipo — The team&lt;/li&gt;
&lt;li&gt;La casa — The house&lt;/li&gt;
&lt;li&gt;El trabajo — The work / job&lt;/li&gt;
&lt;li&gt;El problema — The problem&lt;/li&gt;
&lt;li&gt;Los problemas — The problems&lt;/li&gt;
&lt;li&gt;El día — The day&lt;/li&gt;
&lt;li&gt;La clase de Español — The Spanish class&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verb Endings (Infinitives)
&lt;/h2&gt;

&lt;p&gt;Spanish verbs are grouped into three categories based on their endings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;-AR&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trabajar — To work&lt;/li&gt;
&lt;li&gt;Limpiar — To clean&lt;/li&gt;
&lt;li&gt;Hablar — To speak&lt;/li&gt;
&lt;li&gt;Cocinar — To cook&lt;/li&gt;
&lt;li&gt;Viajar — To travel&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;-ER&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comer — To eat&lt;/li&gt;
&lt;li&gt;Beber — To drink&lt;/li&gt;
&lt;li&gt;Leer — To read&lt;/li&gt;
&lt;li&gt;Correr — To run&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;-IR&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dormir — To sleep&lt;/li&gt;
&lt;li&gt;Asistir — To attend&lt;/li&gt;
&lt;li&gt;Discutir — To discuss&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>beginners</category>
      <category>learning</category>
      <category>spanish</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Day 3: Talking About Days, Months, and Daily Expressions</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Mon, 08 Jun 2026 02:06:29 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/day-3-talking-about-days-months-and-daily-expressions-43gp</link>
      <guid>https://dev.to/gurudev_prasadteketi/day-3-talking-about-days-months-and-daily-expressions-43gp</guid>
      <description>&lt;p&gt;Hola, ¿cómo estás?&lt;/p&gt;

&lt;p&gt;Today we focused on daily expressions, days of the week, months of the year, and Spanish articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  📅 Days of the Week
&lt;/h2&gt;

&lt;p&gt;One interesting thing I learned is that days of the week are not capitalized in Spanish.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spanish   English

lunes     Monday

martes    Tuesday

miércoles Wednesday

jueves    Thursday

viernes   Friday

sábado    Saturday

domingo   Sunday
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples: &lt;/p&gt;

&lt;p&gt;Hoy es miércoles.&lt;br&gt;
(Today is Wednesday.)&lt;/p&gt;

&lt;p&gt;Mañana es jueves.&lt;br&gt;
(Tomorrow is Thursday.)&lt;/p&gt;

&lt;p&gt;Ayer fue martes.&lt;br&gt;
(Yesterday was Tuesday.)&lt;/p&gt;

&lt;p&gt;I also learned how to talk about recurring activities:&lt;/p&gt;

&lt;p&gt;Tengo clase de Español los lunes y los miércoles.&lt;br&gt;
(I have Spanish class on Mondays and Wednesdays.)&lt;/p&gt;

&lt;p&gt;No trabajo los domingos.&lt;br&gt;
(I don't work on Sundays.)&lt;/p&gt;
&lt;h2&gt;
  
  
  📆 Months of the Year
&lt;/h2&gt;

&lt;p&gt;Just like the days of the week, months are written in lowercase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spanish     English

enero       January

febrero     February

marzo       March

abril       April

mayo        May

junio       June

julio       July

agosto      August

septiembre  September

octubre     October

noviembre   November

diciembre   December
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples: &lt;/p&gt;

&lt;p&gt;Tengo mi clase de Español el 8 de junio.&lt;br&gt;
(I have my Spanish class on June 8.)&lt;/p&gt;

&lt;p&gt;Mi cumpleaños es el 19 de agosto.&lt;br&gt;
(My birthday is August 19.)&lt;/p&gt;
&lt;h2&gt;
  
  
  Everyday Expressions
&lt;/h2&gt;

&lt;p&gt;These four expressions came up repeatedly throughout the lesson:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me gusta…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Used to say what you like.&lt;/p&gt;

&lt;p&gt;Me gusta viajar con mi familia.&lt;br&gt;
(I like traveling with my family.)&lt;/p&gt;

&lt;p&gt;Me gusta beber té con mi esposa.&lt;br&gt;
(I like drinking tea with my wife.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voy a…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Used for future plans (“I’m going to…”).&lt;/p&gt;

&lt;p&gt;Voy a practicar español.&lt;br&gt;
(I am going to practice Spanish.)&lt;/p&gt;

&lt;p&gt;Voy a leer un libro.&lt;br&gt;
(I am going to read a book.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tengo que…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Used when you have to do something.&lt;/p&gt;

&lt;p&gt;Tengo que trabajar hoy.&lt;br&gt;
(I have to work today.)&lt;/p&gt;

&lt;p&gt;Tengo que estudiar Español.&lt;br&gt;
(I have to study Spanish.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quiero…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Used to express what you want.&lt;/p&gt;

&lt;p&gt;Quiero viajar a Japón.&lt;br&gt;
(I want to travel to Japan.)&lt;/p&gt;

&lt;p&gt;Quiero hablar en español.&lt;br&gt;
(I want to speak in Spanish.)&lt;/p&gt;
&lt;h2&gt;
  
  
  📖 Learning About Articles
&lt;/h2&gt;

&lt;p&gt;Spanish articles change depending on gender and number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Definite Articles (“The”)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Singular               Plural

El                      Los

La                      Las
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;/p&gt;

&lt;p&gt;El café&lt;br&gt;
Los cafés&lt;/p&gt;

&lt;p&gt;La casa&lt;br&gt;
Las casas&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indefinite Articles (“A” / “An”)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Singular

Un

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

&lt;/div&gt;



&lt;p&gt;Examples:&lt;/p&gt;

&lt;p&gt;Un día&lt;br&gt;
Una clase de Español&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>learning</category>
      <category>spanish</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Dia 2 : Tu Identidad (Your Identity)</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Mon, 08 Jun 2026 01:54:21 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/dia-2-tu-identidad-your-identity-53a7</link>
      <guid>https://dev.to/gurudev_prasadteketi/dia-2-tu-identidad-your-identity-53a7</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Vocaubulario

Convivir = to live together / to coexist / to get along with others
demás = others / the rest
Saludar = to greet / to say hello
y = and
despedirse = to say goodbye / to take leave
presentarse = to introduce oneself
otras = other
personas = people/persons
llamarse = to be called / to name oneself
identidad = identity
edad = age
pais = country
busca = look for / search for 
deporte = sport
equipo = team
nombre = first name
apellido = last name/surname
siguientes = following
Mire = this
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;¿Cómo se llama tu profesor o profesora?&lt;br&gt;
(What is your teacher’s name?)&lt;/p&gt;

&lt;p&gt;¿Cómo se escribe su nombre y apellido?&lt;br&gt;
(How do you spell their first and last name?)&lt;/p&gt;

&lt;p&gt;¿Cómo te llamas tú?&lt;br&gt;
(What is your name?)&lt;/p&gt;

&lt;p&gt;Mire, este es mi novio, Lucas.&lt;br&gt;
(Look, this is my boyfriend, Lucas.)&lt;/p&gt;

&lt;p&gt;En México se habla español.&lt;br&gt;
(Spanish is spoken in Mexico.)&lt;/p&gt;

&lt;p&gt;Frida Kahlo es una pintora mexicana conocida en todo el mundo.&lt;br&gt;
(Frida Kahlo is a Mexican painter known all over the world.)&lt;/p&gt;

&lt;p&gt;Los mejores tenistas de la historia:&lt;br&gt;
(he best tennis players in history)&lt;br&gt;
Rafa Nadal es español,&lt;br&gt;
(Rafa Nadal is Spanish)&lt;br&gt;
Roger Federer es suizo&lt;br&gt;
(Roger Federer is Swiss.)&lt;br&gt;
y Novak Djokovic es serbio.&lt;br&gt;
(Novak Djokovic is Serbian.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Nationality Adjective Patterns
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Masculine Singular Feminine Singular Masculine Plural Feminine Plural

italiano          italiana           italianos          italianas

japonés           japonesa           japoneses          japonesas

canadiense        canadiense         canadienses        canadienses

marroquí          marroquí           marroquíes          marroquíes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>beginners</category>
      <category>learning</category>
      <category>spanish</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Dia 1 : Las palabras importantes, verbos en presente</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Mon, 01 Jun 2026 02:41:06 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/dia-1-las-palabras-importantes-verbos-en-presente-283b</link>
      <guid>https://dev.to/gurudev_prasadteketi/dia-1-las-palabras-importantes-verbos-en-presente-283b</guid>
      <description>&lt;h2&gt;
  
  
  Hola.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  ¿Cómo estás?
&lt;/h2&gt;

&lt;p&gt;I’ve been practicing Spanish on Duolingo for more than 350 days and currently have a 340-day streak. But even with all that consistency, I realized I still lacked confidence when speaking with real people.&lt;/p&gt;

&lt;p&gt;So I decided to take the next step and joined an online Spanish course with live instructors to practice real conversations and improve my fluency.&lt;/p&gt;

&lt;p&gt;Here’s what I learned on my very first day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Las palabras importantes de la clase
&lt;/h2&gt;

&lt;p&gt;(The important words of the class)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- ¿Qué significa ...? = what does mean?
- Significa... = it means
- ¿Cómo se dice...? = how do you say ...
- Se dice... = we say
- Tengo una pregunta = I have a question
- ¿Tienes preguntas? = You have a question?
- No tengo preguntas = I do not have questions
- Dime = tell me
- ¿Entiendes? = Understood?
- Entiendo =  I understand
- No entiendo = No I do not understand
- Repite, por favor = Repeat, Please 
- ¿Puedes repetir? = Can you repeat
- Vamos a... = we are going to / lets...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Vocaubulario

- Tiempo = time
- Hace = ago
- Empleado/a = employees
- De = of
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Verbos

- Hablar = to talk
- Ayudar = to help
- Enseñar = to teach
- Entender = to understand
- Ver = to see
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Presentacion

- Yo soy Gurudev
  (I am Gurudev)

- Me dicen Guru
  (They call me Guru)

- Soy de India
  (I am from India)

- Soy ingeniero de Software
  (I am a software Engineer)

- Vivo en Austin, Tx
  (I live in Texas)

- Me gusta jugar Badminton y pasar tiempo con mi familia
  (I like to play badminton and spend time with my family)

- ¿Cuánto tiempo hace que vives en Austin?
  (How long you live in Austin)

- Hace 5 años que vivo en Austin
  (I have been living in Austin for 5 years)

- ¿Por qué quieres estudiar español?
  (Why do you want to learn spanish)

- Porque tengo empleados en mi casa que hablan español
  (because I have employees in my house who speaks spanish)

- Para hablar con mis empleados
  (to talk to my employees)

- Para ayudar a otros ingenieros de software
  (To help other software engineers)

- Para enseñar
  (To teach)

- ¿Cuál o cuáles son tus lenguas madre?
  (what is your mother tongue(language))

- Mi lengua madre es Telugu
  (My mother tounge is Telugu)

- ¿El inglés es tu segunda lengua?
   (Is English your second language?)

- No, el hindi es mi segunda lengua y el inglés es mi tercera
  (No Hindi is my second language and Englihs is my third)

- ¿Hablas otras lenguas?
  (How many languages do you speak)
- Sí. (yes)

- ¿qué significa "enseñar"?
  (What do you mean by "enseñar)

- "Enseñar" significa "to teach"
   ("Enseñar" means "to teach")

- '¿Cómo se dice "house" en español?
   (How do you say "house" in spanish)

- se dice casa
  (we say "casa")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>beginners</category>
      <category>learning</category>
      <category>spanish</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>AWS Cost Reporting: Why Parquet Data Doesn't Match Your Console (And How to Fix It)</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Mon, 13 Oct 2025 20:46:18 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/aws-cost-reporting-why-parquet-data-doesnt-match-your-console-and-how-to-fix-it-5dlh</link>
      <guid>https://dev.to/gurudev_prasadteketi/aws-cost-reporting-why-parquet-data-doesnt-match-your-console-and-how-to-fix-it-5dlh</guid>
      <description>&lt;h2&gt;
  
  
  1. The Problem
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real production issue: Lambda showing 10x higher costs than console&lt;/li&gt;
&lt;li&gt;User expectation vs reality ($1,262 console vs $12,918 Lambda)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Initial Investigation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Athena/parquet approach seemed logical&lt;/li&gt;
&lt;li&gt;Adjustment factors didn't solve service-level discrepancies&lt;/li&gt;
&lt;li&gt;Individual service ratios were inconsistent (4.0x to 5.7x)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Research &amp;amp; Discovery
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS documentation deep-dive&lt;/li&gt;
&lt;li&gt;Key differences between Cost Explorer API vs Cost and Usage Reports&lt;/li&gt;
&lt;li&gt;Data freshness, calculation methods, service groupings&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. The Solution
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cost Explorer API implementation&lt;/li&gt;
&lt;li&gt;Exact console matching with proper error handling&lt;/li&gt;
&lt;li&gt;99.999% accuracy achieved&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When to use CUR vs Cost Explorer API&lt;/li&gt;
&lt;li&gt;AWS billing reconciliation best practices&lt;/li&gt;
&lt;li&gt;Real-world cost considerations ($0.30/month vs engineering time)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Value:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Practical code examples in Python/boto3&lt;/li&gt;
&lt;li&gt;AWS architecture decisions with reasoning&lt;/li&gt;
&lt;li&gt;Performance optimization and cost trade-offs&lt;/li&gt;
&lt;li&gt;Enterprise-grade error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This would be valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DevOps/FinOps teams building cost monitoring&lt;/li&gt;
&lt;li&gt;AWS architects choosing between cost data sources&lt;/li&gt;
&lt;li&gt;Developers debugging billing discrepancies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;99.999% console accuracy using Cost Explorer API&lt;/li&gt;
&lt;li&gt;Enterprise error handling with proper logging&lt;/li&gt;
&lt;li&gt;Cost-effective: ~$0.30/month for daily reports&lt;/li&gt;
&lt;li&gt;No adjustment factors needed&lt;/li&gt;
&lt;li&gt;Production-ready with comprehensive validation
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
  import boto3
  import logging
  import os
  from datetime import datetime, timedelta
  from typing import Dict, List
  import urllib3

  logger = logging.getLogger()
  logger.setLevel(os.getenv('LOG_LEVEL', 'INFO'))

  def lambda_handler(event, context):

      try:
          logger.info("Starting daily cost usage report processing with Cost Explorer API")

          # Initialize Cost Explorer client (must be us-east-1)
          ce_client = boto3.client('ce', region_name='us-east-1')

          # Get environment variables
          slack_webhook_url = os.getenv('SLACK_WEBHOOK_URL')

          if not slack_webhook_url:
              raise ValueError("SLACK_WEBHOOK_URL environment variable is required")

          # Get current month date range for Cost Explorer API
          today = datetime.now()
          month_start = datetime(today.year, today.month, 1)

          # Format dates for Cost Explorer API (YYYY-MM-DD format)
          start_date = month_start.strftime('%Y-%m-%d')
          end_date = (today + timedelta(days=1)).strftime('%Y-%m-%d')  # API end date is exclusive

          logger.info(f"Processing costs from {start_date} to {end_date} using Cost Explorer API")

          # Get cost data using Cost Explorer API
          cost_data = get_cost_data_from_cost_explorer(
              ce_client,
              start_date,
              end_date
          )

          # Format and send Slack message
          slack_message = format_slack_message(cost_data, month_start, today)
          send_slack_message(slack_webhook_url, slack_message)

          logger.info("Successfully completed daily cost usage report")

          return {
              'statusCode': 200,
              'body': json.dumps({
                  'message': 'Cost report sent successfully',
                  'total_cost': cost_data['total_cost'],
                  'services_count': len(cost_data['services'])
              })
          }

      except Exception as e:
          logger.error(f"Error processing cost report: {str(e)}")
          return {
              'statusCode': 500,
              'body': json.dumps({
                  'error': str(e)
              })
          }
  def get_cost_data_from_cost_explorer(ce_client, start_date: str, end_date: str) -&amp;gt; Dict:
      logger.info(f"Querying Cost Explorer API from {start_date} to {end_date}")

      try:
          # Cost Explorer API call - matches exactly what AWS console uses
          response = ce_client.get_cost_and_usage(
              TimePeriod={
                  'Start': start_date,
                  'End': end_date
              },
              Granularity='MONTHLY',
              Metrics=['UnblendedCost'],
              GroupBy=[
                  {
                      'Type': 'DIMENSION',
                      'Key': 'SERVICE'
                  }
              ]
          )

          # Parse Cost Explorer API response
          services = []
          total_cost = 0.0

          # Validate response structure
          if not response.get('ResultsByTime') or len(response['ResultsByTime']) == 0:
              logger.warning("No cost data in Cost Explorer API response")
              return {'services': [], 'total_cost': 0.0, 'currency': 'USD'}

          # Get the first (and only) time period result
          result = response['ResultsByTime'][0]

          # Process each service group
          for group in result.get('Groups', []):
              try:
                  service_name = group['Keys'][0] if group.get('Keys') else 'Unknown'

                  # Get unblended cost amount
                  cost_data = group.get('Metrics', {}).get('UnblendedCost', {})
                  cost_amount_str = cost_data.get('Amount', '0')
                  currency = cost_data.get('Unit', 'USD')

                  # Safely parse cost amount
                  try:
                      cost_amount = float(cost_amount_str)
                  except (ValueError, TypeError):
                      logger.warning(f"Invalid cost amount for service {service_name}: {cost_amount_str}")
                      continue

                  if cost_amount &amp;gt; 0.01:  # Only include services with meaningful cost
                      services.append({
                          'name': service_name,
                          'cost': round(cost_amount, 2),
                          'currency': currency
                      })
                      total_cost += cost_amount

              except Exception as service_error:
                  logger.warning(f"Error processing service group: {str(service_error)}")
                  continue

          # Sort by cost (highest first)
          services.sort(key=lambda x: x['cost'], reverse=True)

          logger.info(f"Retrieved {len(services)} services with total cost: ${total_cost:.2f}")

          return {
              'services': services,
              'total_cost': round(total_cost, 2),
              'currency': 'USD'
          }

      except Exception as e:
          logger.error(f"Error calling Cost Explorer API: {str(e)}")
          raise Exception(f"Cost Explorer API call failed: {str(e)}")

  def format_slack_message(cost_data: Dict, start_date: datetime, end_date: datetime) -&amp;gt; Dict:

      total_cost = cost_data['total_cost']
      services = cost_data['services']
      currency = cost_data.get('currency', 'USD')

      # Header
      date_range = f"{start_date.strftime('%b %d')} - {end_date.strftime('%b %d, %Y')}"
      header = f"💰 *AWS Cost Report: {date_range}*\n"
      header += f"💳 *Total Cost: ${total_cost:,.2f} {currency}*\n\n"

      # Services table
      if services:
          services_text = "```

\n"
          services_text += f"{'AWS Service':&amp;lt;30} {'Cost':&amp;lt;12}\n"
          services_text += f"{'-' * 30} {'-' * 12}\n"

          # Show top 15 services
          for service in services[:15]:
              cost = service['cost']
              name = service['name']

              # Format service name for readability
              display_name = name.replace('Amazon', '').replace('AWS', '').strip()
              if not display_name:
                  display_name = name

              # Truncate long service names
              if len(display_name) &amp;gt; 29:
                  display_name = display_name[:26] + "..."

              services_text += f"{display_name:&amp;lt;30} ${cost:&amp;gt;11,.2f}\n"

          services_text += f"{'-' * 30} {'-' * 12}\n"
          services_text += f"{'TOTAL':&amp;lt;30} ${total_cost:&amp;gt;11,.2f}\n"
          services_text += "

```"

          if len(services) &amp;gt; 15:
              remaining = len(services) - 15
              services_text += f"\n_+ {remaining} more services with smaller costs_"
      else:
          services_text = "No cost data available for this period."

      message_text = header + services_text

      return {
          "text": f"AWS Daily Cost Report - ${total_cost:,.2f}",
          "blocks": [
              {
                  "type": "section",
                  "text": {
                      "type": "mrkdwn",
                      "text": message_text
                  }
              }
          ]
      }

  def send_slack_message(webhook_url: str, message: Dict):

      http = urllib3.PoolManager()

      logger.info("Sending message to Slack channel")

      response = http.request(
          'POST',
          webhook_url,
          body=json.dumps(message),
          headers={'Content-Type': 'application/json'}
      )

      if response.status == 200:
          logger.info("Successfully sent message to Slack")
      else:
          logger.error(f"Failed to send Slack message: {response.status}")
          raise Exception(f"Slack webhook failed with status {response.status}")

  Required IAM Policy:

  {
      "Version": "2012-10-17",
      "Statement": [
          {
              "Effect": "Allow",
              "Action": [
                  "ce:GetCostAndUsage",
                  "ce:GetUsageReport"
              ],
              "Resource": "*"
          }
      ]
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment Variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SLACK_WEBHOOK_URL: Your Slack webhook URL&lt;/li&gt;
&lt;li&gt;LOG_LEVEL: INFO (optional)`&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>aws</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Building a True Dual-Destination Analytics Pipeline: Real-Time Streaming with S3 Backup and Recovery</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Fri, 10 Oct 2025 19:26:36 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/building-a-true-dual-destination-analytics-pipeline-real-time-streaming-with-s3-backup-and-recovery-php</link>
      <guid>https://dev.to/gurudev_prasadteketi/building-a-true-dual-destination-analytics-pipeline-real-time-streaming-with-s3-backup-and-recovery-php</guid>
      <description>&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;p&gt;This article details the implementation of a sophisticated dual-destination analytics pipeline that combines real-time data streaming to AWS Redshift Serverless with robust S3 backup and recovery capabilities. The architecture provides the best of both worlds: immediate analytics insights with comprehensive data protection and quality assurance mechanisms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Objective
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Primary Goal&lt;/strong&gt;: Design and implement a resilient dual-destination data pipeline that delivers real-time analytics capabilities while maintaining comprehensive data backup, recovery, and quality assurance processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Requirements&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time analytics&lt;/strong&gt; with sub-5-minute data availability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive data backup&lt;/strong&gt; in S3 for recovery and compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero data loss&lt;/strong&gt; guarantee during failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data quality assurance&lt;/strong&gt; with validation and reconciliation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-effective&lt;/strong&gt; architecture balancing performance and expenses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt; solution supporting growing data volumes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational simplicity&lt;/strong&gt; with automated monitoring and recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Architecture Overview&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;True Dual-Destination Pattern&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📱 Reading App
    ↓
🔄 Enhanced Lambda Function
    ↓                    ↓
🔥 Redshift Firehose   🔥 S3 Firehose
    ↓                    ↓
🏭 Redshift (Real-time) 📦 S3 (Backup)
    ↑                    ↓
    ←── ETL Reconciliation ──
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Core Principle&lt;/strong&gt;: &lt;strong&gt;Write Twice, Query Once&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every data record flows to both destinations simultaneously&lt;/li&gt;
&lt;li&gt;Real-time analytics from Redshift&lt;/li&gt;
&lt;li&gt;Backup and recovery from S3&lt;/li&gt;
&lt;li&gt;Periodic reconciliation ensures data consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enhanced Lambda Function Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dual-Destination Lambda Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="n"&gt;firehose_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firehose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Dual-destination configuration
&lt;/span&gt;&lt;span class="n"&gt;REDSHIFT_FIREHOSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analytics-realtime-delivery-dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;S3_FIREHOSE_BOOK_ACTIVITY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analytics-backup-s3-dev-book-activity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;S3_FIREHOSE_DYNAMIC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analytics-backup-s3-dev-dynamic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No request body provided&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid JSON format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})}&lt;/span&gt;

    &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid data format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})}&lt;/span&gt;

    &lt;span class="c1"&gt;# Prepare dual-destination records
&lt;/span&gt;    &lt;span class="n"&gt;s3_records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;S3 bucket&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dynamic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;
    &lt;span class="n"&gt;redshift_records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Validation
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ledgerCategory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing required fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})}&lt;/span&gt;

        &lt;span class="c1"&gt;# Add server timestamp
&lt;/span&gt;        &lt;span class="n"&gt;utc_now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serverTimestamp&lt;/span&gt;&lt;span class="sh"&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;utc_now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%dT%H:%M:%SZ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pipelineTimestamp&lt;/span&gt;&lt;span class="sh"&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;utc_now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%dT%H:%M:%SZ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# S3 backup records (original format)
&lt;/span&gt;        &lt;span class="n"&gt;s3_target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;S3 Bucket&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;S3 Bucket&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dynamic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;s3_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="n"&gt;s3_records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s3_target&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s3_record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Redshift real-time records (optimized format)
&lt;/span&gt;        &lt;span class="n"&gt;redshift_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recordId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ledgerCategory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ledgerCategory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eventData&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processedAt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%dT%H:%M:%SZ&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;redshift_records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redshift_record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Dual-destination delivery
&lt;/span&gt;        &lt;span class="n"&gt;delivery_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

        &lt;span class="c1"&gt;# 1. Send to S3 Firehose (backup)
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;firehose_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;S3 Bucket&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;S3_FIREHOSE_BOOK_ACTIVITY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dynamic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;S3_FIREHOSE_DYNAMIC&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="n"&gt;s3_records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;s3_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;firehose_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_record_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;DeliveryStreamName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;firehose_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Records&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s3_records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;delivery_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delivered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s3_records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s3_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FailedPutCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;s3_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FailedPutCount&lt;/span&gt;&lt;span class="sh"&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;# 2. Send to Redshift Firehose (real-time)
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;redshift_records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;redshift_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;firehose_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_record_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;DeliveryStreamName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REDSHIFT_FIREHOSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Records&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;redshift_records&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;delivery_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redshift_realtime&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delivered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redshift_records&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;redshift_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FailedPutCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;redshift_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FailedPutCount&lt;/span&gt;&lt;span class="sh"&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;# Check for failures
&lt;/span&gt;        &lt;span class="n"&gt;total_failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;delivery_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_failed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Partial delivery failure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;details&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;delivery_results&lt;/span&gt;
            &lt;span class="p"&gt;})}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delivery_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;delivery_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dual_destination_enabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;})}&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dual-destination delivery failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;200&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;h2&gt;
  
  
  &lt;strong&gt;Redshift Direct Delivery Configuration&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Real-Time Firehose to Redshift Setup&lt;/strong&gt;
&lt;/h3&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;"DeliveryStreamName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"analytics-realtime-delivery-dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"RedshiftDestinationConfiguration"&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;"RoleARN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/AnalyticsFirehoseRedshiftRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ClusterJDBCURL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jdbc:redshift://analytics-cluster.us-east-2.redshift.amazonaws.com:5439/analytics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"CopyCommand"&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;"DataTableName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"events.realtime_staging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"DataTableColumns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"record_id,ledger_category,user_id,event_data,processed_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"CopyOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FORMAT AS JSON 'auto' TIMEFORMAT 'auto' TRUNCATECOLUMNS ACCEPTINVCHARS"&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;"Username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"firehose_user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SecurePassword123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"S3Configuration"&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;"RoleARN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/AnalyticsFirehoseS3Role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"BucketARN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::analytics-backup-dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"redshift-backup/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"BufferingHints"&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;"SizeInMBs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"IntervalInSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;"CompressionFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GZIP"&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;"ProcessingConfiguration"&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;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Processors"&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;"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;"Lambda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Parameters"&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;"ParameterName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LambdaArn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"ParameterValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:lambda:us-east-2:123456789012:function:analytics-data-transformer"&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="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;"RetryOptions"&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;"DurationInSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Real-Time Staging Table Design&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Real-time staging table for immediate analytics&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtime_staging&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;record_id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ledger_category&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_data&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;65535&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;processed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;load_timestamp&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DISTKEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SORTKEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processed_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ledger_category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Production analytics views&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtime_activity&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;JSON_EXTRACT_PATH_TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'bookID'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;book_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;JSON_EXTRACT_PATH_TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'eventType'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;JSON_EXTRACT_PATH_TEXT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pageNumber'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;page_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;processed_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;load_timestamp&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtime_staging&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ledger_category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'book_activity'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;processed_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring and Alerting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CloudWatch Dashboard Configuration**
&lt;/h3&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;"widgets"&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;"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;"metric"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"metrics"&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="s2"&gt;"AWS/Kinesis/Firehose"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DeliveryToRedshift.Records"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DeliveryStreamName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"analytics-realtime-delivery-dev"&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="s2"&gt;"AWS/Kinesis/Firehose"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DeliveryToS3.Records"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DeliveryStreamName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"analytics-backup-s3-dev-book-activity"&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="s2"&gt;"AWS/Lambda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invocations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FunctionName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"analytics-dual-destination-processor"&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="s2"&gt;"AWS/Lambda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Errors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FunctionName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"analytics-reconciliation-processor"&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;"period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"stat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dual-Destination Pipeline Health"&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;"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;"log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SOURCE '/aws/lambda/analytics-dual-destination-processor'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;| fields @timestamp, @message&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;| filter @message like /ERROR/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;| sort @timestamp desc&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;| limit 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;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Recent Pipeline Errors"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Automated Alerting&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# CloudWatch alarm for dual-destination failures
&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt; &lt;span class="n"&gt;cloudwatch&lt;/span&gt; &lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;alarm&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;alarm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dual-Destination-Pipeline-Failures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;alarm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alert on dual-destination delivery failures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Errors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS/Lambda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;statistic&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;comparison&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GreaterThanThreshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;dimensions&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dual&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;alarm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:sns:us-east-2:123456789012:analytics-alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Data discrepancy alarm
&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt; &lt;span class="n"&gt;cloudwatch&lt;/span&gt; &lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;alarm&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;alarm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data-Reconciliation-Discrepancy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;alarm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alert on high data discrepancy between S3 and Redshift&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DiscrepancyPercentage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analytics/Reconciliation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;statistic&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Average&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; \
    &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;comparison&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GreaterThanThreshold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Deployment Architecture&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Infrastructure as Code (CloudFormation)&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2010-09-09'&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Dual-Destination&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Analytics&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Pipeline'&lt;/span&gt;

&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Environment&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;String&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
    &lt;span class="na"&gt;AllowedValues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;qa&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Redshift Serverless Namespace&lt;/span&gt;
  &lt;span class="na"&gt;RedshiftNamespace&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::RedshiftServerless::Namespace&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;NamespaceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;analytics-ledger-${Environment}'&lt;/span&gt;
      &lt;span class="na"&gt;AdminUsername&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
      &lt;span class="na"&gt;AdminUserPassword&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;RedshiftPassword&lt;/span&gt;
      &lt;span class="na"&gt;DbName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analytics&lt;/span&gt;

  &lt;span class="c1"&gt;# Redshift Serverless Workgroup&lt;/span&gt;
  &lt;span class="na"&gt;RedshiftWorkgroup&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::RedshiftServerless::Workgroup&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;WorkgroupName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;analytics-datalake-${Environment}'&lt;/span&gt;
      &lt;span class="na"&gt;NamespaceName&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;RedshiftNamespace&lt;/span&gt;
      &lt;span class="na"&gt;PubliclyAccessible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;BaseCapacity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;

  &lt;span class="c1"&gt;# Dual-Destination Lambda&lt;/span&gt;
  &lt;span class="na"&gt;DualDestinationLambda&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::Lambda::Function&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;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;analytics-dual-destination-${Environment}'&lt;/span&gt;
      &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.9&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dual_destination.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;LambdaExecutionRole.Arn&lt;/span&gt;
      &lt;span class="na"&gt;Code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ZipFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Lambda function code here&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;REDSHIFT_FIREHOSE&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;RedshiftFirehose&lt;/span&gt;
          &lt;span class="na"&gt;S3_FIREHOSE_BOOK&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;S3FirehoseBook&lt;/span&gt;
          &lt;span class="na"&gt;S3_FIREHOSE_DYNAMIC&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;S3FirehoseDynamic&lt;/span&gt;

  &lt;span class="c1"&gt;# Redshift Firehose Stream&lt;/span&gt;
  &lt;span class="na"&gt;RedshiftFirehose&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::KinesisFirehose::DeliveryStream&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;DeliveryStreamName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;analytics-realtime-delivery-${Environment}'&lt;/span&gt;
      &lt;span class="na"&gt;DeliveryStreamType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DirectPut&lt;/span&gt;
      &lt;span class="na"&gt;RedshiftDestinationConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;RoleARN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;FirehoseRole.Arn&lt;/span&gt;
        &lt;span class="na"&gt;ClusterJDBCURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jdbc:redshift://${RedshiftEndpoint}:5439/analytics'&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;RedshiftEndpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;RedshiftWorkgroup.Workgroup.Endpoint.Address&lt;/span&gt;
        &lt;span class="na"&gt;CopyCommand&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DataTableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;events.realtime_staging&lt;/span&gt;
          &lt;span class="na"&gt;CopyOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FORMAT&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;JSON&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'auto'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;TIMEFORMAT&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'auto'"&lt;/span&gt;
        &lt;span class="na"&gt;Username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
        &lt;span class="na"&gt;Password&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;RedshiftPassword&lt;/span&gt;

  &lt;span class="c1"&gt;# EventBridge Rule for Reconciliation&lt;/span&gt;
  &lt;span class="na"&gt;ReconciliationSchedule&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::Events::Rule&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;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;analytics-reconciliation-${Environment}'&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Daily&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reconciliation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;between&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;S3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Redshift'&lt;/span&gt;
      &lt;span class="na"&gt;ScheduleExpression&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cron(0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;6&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*)'&lt;/span&gt;  &lt;span class="c1"&gt;# Daily at 6 AM&lt;/span&gt;
      &lt;span class="na"&gt;State&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLED&lt;/span&gt;
      &lt;span class="na"&gt;Targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;ReconciliationLambda.Arn&lt;/span&gt;
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ReconciliationTarget&lt;/span&gt;

&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;RedshiftEndpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Redshift&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Serverless&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Endpoint'&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;RedshiftWorkgroup.Workgroup.Endpoint.Address&lt;/span&gt;
    &lt;span class="na"&gt;Export&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${AWS::StackName}-RedshiftEndpoint'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Optimization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Redshift Table Design Best Practices
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Optimized real-time staging table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtime_staging&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;record_id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ledger_category&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_data&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;65535&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;processed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;load_timestamp&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;partition_date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processed_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DISTKEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SORTKEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processed_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ledger_category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Automatic table maintenance&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EVENT&lt;/span&gt; &lt;span class="n"&gt;analytics_table_maintenance&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;SCHEDULE&lt;/span&gt; &lt;span class="s1"&gt;'cron(0 2 * * ? *)'&lt;/span&gt;  &lt;span class="c1"&gt;-- Daily at 2 AM&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="c1"&gt;-- Vacuum and analyze tables&lt;/span&gt;
  &lt;span class="k"&gt;VACUUM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtime_staging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtime_staging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;-- Drop old partitions (older than 90 days)&lt;/span&gt;
  &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtime_staging&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;partition_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DATEADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GETDATE&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Firehose Optimization Settings**
&lt;/h3&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;"BufferingHints"&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;"SizeInMBs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"IntervalInSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;"CompressionFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GZIP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"DataTransformation"&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;"ProcessorType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lambda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Parameters"&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;"LambdaArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:lambda:us-east-2:123456789012:function:analytics-data-optimizer"&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;"DynamicPartitioning"&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;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"RetryOptions"&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;"DurationInSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cost Analysis and Optimization**
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cost Breakdown
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Component&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Monthly Cost (Dev)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Monthly Cost (Prod)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redshift Serverless&lt;/td&gt;
&lt;td&gt;$45-75&lt;/td&gt;
&lt;td&gt;$200-400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kinesis Firehose&lt;/td&gt;
&lt;td&gt;$15-25&lt;/td&gt;
&lt;td&gt;$50-100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda Executions&lt;/td&gt;
&lt;td&gt;$5-10&lt;/td&gt;
&lt;td&gt;$20-40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 Storage&lt;/td&gt;
&lt;td&gt;$10-20&lt;/td&gt;
&lt;td&gt;$50-100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Transfer&lt;/td&gt;
&lt;td&gt;$5-15&lt;/td&gt;
&lt;td&gt;$25-50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$80-145&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$345-690&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cost Optimization Strategies&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Redshift Serverless Scaling&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="c1"&gt;-- Set appropriate base capacity&lt;/span&gt;
   &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;WORKGROUP&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;datalake&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;
   &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;base_capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- Start small, auto-scale up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;S3 Lifecycle Policies&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"Rules"&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;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"Transitions"&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;"Days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="nl"&gt;"StorageClass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STANDARD_IA"&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;"Days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                       &lt;/span&gt;&lt;span class="nl"&gt;"StorageClass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GLACIER"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Firehose Buffer Optimization&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Increase buffer size to reduce frequency&lt;/li&gt;
&lt;li&gt;Use compression to reduce storage costs&lt;/li&gt;
&lt;li&gt;Optimize batch sizes for Redshift COPY operations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Advantages of Dual-Destination Architecture&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;✅ Real-Time Analytics with Safety Net&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sub-5-minute latency&lt;/strong&gt; for business-critical dashboards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete data backup&lt;/strong&gt; in S3 for recovery scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero data loss&lt;/strong&gt; guarantee through dual writes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic reconciliation&lt;/strong&gt; ensures data consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;✅ Operational Resilience&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fault tolerance&lt;/strong&gt;: Failure in one stream doesn't affect the other&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery capabilities&lt;/strong&gt;: Can rebuild Redshift from S3 if needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disaster recovery&lt;/strong&gt;: Geographic backup through S3 replication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version control&lt;/strong&gt;: S3 maintains historical data versions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;✅ Performance Benefits&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Optimized queries&lt;/strong&gt;: Purpose-built Redshift tables for analytics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent workloads&lt;/strong&gt;: S3 and Redshift serve different use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced load&lt;/strong&gt;: Redshift not used for data lake operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable architecture&lt;/strong&gt;: Each component scales independently&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;✅ Business Value&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Immediate insights&lt;/strong&gt;: Real-time user behavior analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical analysis&lt;/strong&gt;: Deep-dive analytics using S3 data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance ready&lt;/strong&gt;: Audit trails and data lineage tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future-proof&lt;/strong&gt;: Ready for ML and advanced analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Considerations and Trade-offs&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;❌ Increased Complexity&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More components&lt;/strong&gt;: Additional Firehose streams and monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual maintenance&lt;/strong&gt;: Both S3 and Redshift schemas need updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling&lt;/strong&gt;: More failure scenarios to handle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost overhead&lt;/strong&gt;: ~30-50% increase over single-destination&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;❌ Operational Overhead&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring complexity&lt;/strong&gt;: Track multiple data flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconciliation requirements&lt;/strong&gt;: Daily consistency checks needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema coordination&lt;/strong&gt;: Changes must be synchronized&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging challenges&lt;/strong&gt;: Multiple data paths to troubleshoot&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;❌ Resource Utilization&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compute costs&lt;/strong&gt;: Additional Lambda executions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage duplication&lt;/strong&gt;: Data exists in both S3 and Redshift&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network bandwidth&lt;/strong&gt;: Higher data transfer volumes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always-on costs&lt;/strong&gt;: Redshift Serverless base capacity charges&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When to Choose Dual-Destination&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Ideal Use Cases&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mission-critical analytics&lt;/strong&gt; requiring real-time insights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-value data&lt;/strong&gt; where loss is unacceptable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance requirements&lt;/strong&gt; for data retention and auditability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed workloads&lt;/strong&gt; requiring both real-time and batch processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Growing organizations&lt;/strong&gt; planning to scale analytics capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Not Recommended When&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple analytics needs&lt;/strong&gt; satisfied by batch processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-sensitive environments&lt;/strong&gt; where latency is acceptable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small data volumes&lt;/strong&gt; that don't justify complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited operational resources&lt;/strong&gt; for monitoring multiple systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Future Enhancements and Roadmap&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 2: Advanced Analytics&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Machine Learning Integration&lt;/strong&gt;: Real-time ML inference on streaming data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex Event Processing&lt;/strong&gt;: Multi-stream correlation and pattern detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictive Analytics&lt;/strong&gt;: User behavior prediction models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anomaly Detection&lt;/strong&gt;: Real-time fraud and abuse detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 3: Multi-Region Architecture&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global data replication&lt;/strong&gt; for disaster recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional analytics clusters&lt;/strong&gt; for performance optimization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-region reconciliation&lt;/strong&gt; for data consistency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance-specific regions&lt;/strong&gt; for data sovereignty&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Phase 4: Advanced Data Governance&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data lineage tracking&lt;/strong&gt; across all pipeline components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated data quality&lt;/strong&gt; monitoring and alerting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII detection and masking&lt;/strong&gt; for privacy compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated schema evolution&lt;/strong&gt; and compatibility checking&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The dual-destination analytics pipeline represents a sophisticated approach to modern data architecture, balancing the immediate needs for real-time analytics with the long-term requirements for data durability, compliance, and recovery. This architecture pattern is particularly valuable for organizations that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cannot afford data loss&lt;/strong&gt; in their analytics pipeline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need both real-time and historical analytics&lt;/strong&gt; capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan to scale&lt;/strong&gt; their data and analytics operations significantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value operational resilience&lt;/strong&gt; over architectural simplicity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Success Factors&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive monitoring&lt;/strong&gt; across all pipeline components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated reconciliation&lt;/strong&gt; to ensure data consistency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper cost management&lt;/strong&gt; to justify the additional complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear operational procedures&lt;/strong&gt; for failure scenarios and recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected Outcomes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;99.9% data availability&lt;/strong&gt; with sub-5-minute analytics latency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero data loss&lt;/strong&gt; through redundant storage and processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable foundation&lt;/strong&gt; supporting future analytics and ML workloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational confidence&lt;/strong&gt; through comprehensive backup and recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture serves as a robust foundation for organizations transitioning from batch-oriented data lakes to real-time analytics platforms while maintaining the safety and compliance benefits of traditional data lake approaches.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>analytics</category>
      <category>architecture</category>
      <category>aws</category>
    </item>
    <item>
      <title>Automating Publisher Reporting on AWS: A Serverless Architecture with Slack Alerts</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Wed, 03 Sep 2025 03:27:51 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/automating-publisher-reporting-on-aws-a-serverless-architecture-with-slack-alerts-3004</link>
      <guid>https://dev.to/gurudev_prasadteketi/automating-publisher-reporting-on-aws-a-serverless-architecture-with-slack-alerts-3004</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In this article, I walk you through building an automated reporting pipeline using AWS services. The goal was to generate daily summary reports on publisher readership, detect missing metadata (like publishers), store the results as CSVs in S3, and deliver structured Slack notifications to internal stakeholders — all without manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Diagram
&lt;/h2&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%2Frjt361b6z0v9flvw60ui.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%2Frjt361b6z0v9flvw60ui.png" alt=" " width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow:&lt;/strong&gt;&lt;br&gt;
1.Amazon EventBridge (Scheduler) triggers the workflow daily.&lt;/p&gt;

&lt;p&gt;2.Lambda Function #1: Report Generator&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs a named query on Amazon Athena that calculates reading activity data.&lt;/li&gt;
&lt;li&gt;Stores results in an S3 bucket using a structured naming convention.&lt;/li&gt;
&lt;li&gt;Sends a Slack message once the report is ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3.Lambda Function #2: Publisher Summary Aggregator&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetches the latest CSV report from S3.&lt;/li&gt;
&lt;li&gt;Aggregates per-publisher read counts by quarter.&lt;/li&gt;
&lt;li&gt;Posts a clean, readable table to Slack showing publisher performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4.Lambda Function #3: Missing Publisher Detector (optional)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs a separate Athena query to find books with missing publisher info.&lt;/li&gt;
&lt;li&gt;Sends a notification to Slack with a direct link to the generated file in S3.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publisher-reporting/
│
├── deploy.sh                         # Infra provisioning and Lambda packaging
├── config.yaml                       # Runtime config (bucket names, cron schedules)
│
├── lambda/
│   ├── report_generator/
│   │   └── handler.py                # Runs Athena query and stores report in S3
│   ├── summary_report_notifier/
│   │   └── handler.py                # Aggregates data and posts to Slack
│   └── missing_publisher_report/
│       └── handler.py                # Detects and reports missing metadata
│
└── terraform/                        # Infra as Code (Lambda, EventBridge, IAM, etc.)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Code Snippets
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lambda: Report Generator&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exec_response = athena.start_query_execution(
    QueryString=query_string,
    QueryExecutionContext={"Database": ATHENA_DATABASE},
    ResultConfiguration={
        "OutputLocation": f"s3://{ATHENA_OUTPUT_BUCKET}/temporary-athena-query-results/"
    }
)
...
s3.copy_object(
    Bucket=TARGET_REPORT_BUCKET,
    CopySource={"Bucket": source_bucket, "Key": source_key},
    Key=final_key
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lambda: Slack Summary Formatter&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for row in reader:
    if row["book_read_counts"].strip().upper() == "TRUE":
        yq = row["year_quarter"].strip()
        pub = row["publisher"].strip() or "Unknown"
        counts[yq][pub] += 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scheduling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------------------------+------------------------------------------+-------------------------+
| Lambda Function              | Purpose                                  | Schedule (Cron Format)  |
+------------------------------+------------------------------------------+-------------------------+
| report_generator             | Run Athena query and save CSV to S3      | cron(0 0 * * ? *)       |
| summary_report_notifier      | Read latest CSV and post Slack summary   | cron(10 0 * * ? *)      |
| missing_publisher_report     | Detect books without publisher info      | cron(15 0 * * ? *)      |
+------------------------------+------------------------------------------+-------------------------+

# Notes:
# - Times are in UTC
# - 10–15 min stagger prevents overlap and race conditions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Challenges &amp;amp; Fixes
&lt;/h2&gt;

&lt;p&gt;1.Slack showed same data daily&lt;/p&gt;

&lt;p&gt;Issue:&lt;br&gt;
Slack message was posting the same publisher data every day.&lt;/p&gt;

&lt;p&gt;Fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EventBridge schedule was set to run only on the 1st of each month.
Updated to run daily using: cron(0 0 * * ? *)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.No new publisher summary files after Aug 1&lt;/p&gt;

&lt;p&gt;Issue:&lt;br&gt;
S3 bucket had no updated files after August 1st.&lt;/p&gt;

&lt;p&gt;Fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Found that the publisher report generator Lambda was not running.
Corrected the EventBridge schedule to trigger daily.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Lambda race condition&lt;/p&gt;

&lt;p&gt;Issue:&lt;br&gt;
The Slack posting Lambda was sometimes reading an older CSV file instead of the one just generated.&lt;/p&gt;

&lt;p&gt;Fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Introduced a 10-minute delay between the generator Lambda and the Slack reporter Lambda using separate schedules.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4.Slack output was hard to read&lt;/p&gt;

&lt;p&gt;Issue:&lt;br&gt;
The publisher read counts in Slack were misaligned and difficult to follow.&lt;/p&gt;

&lt;p&gt;Fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Formatted the message using Slack-compatible triple backticks (```

...

```) to show preformatted blocks.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5.S3 bucket getting cluttered&lt;/p&gt;

&lt;p&gt;Issue:&lt;br&gt;
Temporary Athena result files were crowding the output bucket.&lt;/p&gt;

&lt;p&gt;Fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Moved results to a 'temporary-athena-query-results/' folder prefix
Added a lifecycle policy to auto-delete them after 3 days.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Impact
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Eliminated all manual report generation&lt;/li&gt;
&lt;li&gt;Improved team visibility into reader engagement&lt;/li&gt;
&lt;li&gt;Ensured scalable, serverless infrastructure using AWS best practices&lt;/li&gt;
&lt;li&gt;Automated alerts improved issue tracking and data consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project was a great example of combining Athena, Lambda, S3, and EventBridge into a cost-efficient, automated reporting pipeline. If you’re working with serverless data workflows, this pattern is easily adaptable to product analytics, user activity tracking, sales dashboards, and more.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Kickstarting Generative AI with Amazon Bedrock: Building a No-Code Product Description Generator</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Sat, 31 May 2025 05:03:22 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/kickstarting-generative-ai-with-amazon-bedrock-building-a-no-code-product-description-generator-1blh</link>
      <guid>https://dev.to/gurudev_prasadteketi/kickstarting-generative-ai-with-amazon-bedrock-building-a-no-code-product-description-generator-1blh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: What and Why
&lt;/h2&gt;

&lt;p&gt;In today’s fast-moving digital world, the ability to generate personalized, high-quality content at scale is a game-changer for businesses across industries. Whether it’s marketing copy, product descriptions, chatbot responses, or internal documentation, Generative AI has the potential to significantly reduce content creation time while improving consistency and creativity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That’s where Amazon Bedrock comes in.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Amazon Bedrock&lt;/strong&gt; is a fully managed service that lets developers build and scale generative AI applications using foundation models (FMs) from leading AI providers like Anthropic (Claude), AI21, and Amazon Titan—without needing to manage any infrastructure.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This project explores how non-developers and beginners can quickly prototype a generative AI solution directly from the AWS Console. Our use case: generating fun, compelling product descriptions with no code using the Claude model via Bedrock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use Case:&lt;/strong&gt;&lt;br&gt;
Generate AI-powered product descriptions for e-commerce products (e.g., a waterproof smartwatch for kids).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Bedrock (for text generation)&lt;/li&gt;
&lt;li&gt;Claude model (Anthropic's foundation model)&lt;/li&gt;
&lt;li&gt;AWS Console (no programming required)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step Instructions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Login to AWS Console&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Go to Amazon Bedrock → Model Access → Enable Claude &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use Claude 3 Sonnet&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It offers a great balance between speed, cost, and reasoning power.&lt;/li&gt;
&lt;li&gt;Perfect for text generation tasks like product descriptions.&lt;/li&gt;
&lt;/ul&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%2Fu39nyfjgjzyo0hj1k9ym.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%2Fu39nyfjgjzyo0hj1k9ym.png" alt="Image description" width="800" height="360"&gt;&lt;/a&gt;&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%2F5lt41gh6n8fmy9nwl380.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%2F5lt41gh6n8fmy9nwl380.png" alt="Image description" width="800" height="358"&gt;&lt;/a&gt;&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%2Fg11k674ngdjrbkhkjwsw.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%2Fg11k674ngdjrbkhkjwsw.png" alt="Image description" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Playground&lt;/li&gt;
&lt;li&gt;Use the “Text Generation” option&lt;/li&gt;
&lt;li&gt;Prompt:
"Generate a fun, engaging product description for a waterproof smartwatch designed for children ages 6 to 12."&lt;/li&gt;
&lt;/ol&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%2F4eoxp7bibjcyz11zxe30.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%2F4eoxp7bibjcyz11zxe30.png" alt="Image description" width="800" height="355"&gt;&lt;/a&gt;&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%2Fvv2nz93wx71k9ubyqdn4.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%2Fvv2nz93wx71k9ubyqdn4.png" alt="Image description" width="800" height="355"&gt;&lt;/a&gt;&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%2Fo0mzqrkfqm0ihjm5zz37.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%2Fo0mzqrkfqm0ihjm5zz37.png" alt="Image description" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explore variations with different Prompts and their results&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Generate a fun, engaging product description for a waterproof smartwatch designed for children ages 6 to 12.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;`Here's a fun, engaging product description for a waterproof smartwatch designed for children ages 6 to 12:&lt;/p&gt;

&lt;p&gt;Dive into Adventure with the Aqua Kiddo Smartwatch!&lt;/p&gt;

&lt;p&gt;Calling all little adventurers! Get ready to make a splash with the Aqua Kiddo Smartwatch – the ultimate waterproof companion for your wild and wacky explorations. Whether you're splashing in the pool, braving the sprinklers, or caught in a surprise rain shower, this smartwatch is built to keep up with your aquatic antics.&lt;/p&gt;

&lt;p&gt;With its vibrant, customizable watchfaces and fun games, the Aqua Kiddo is more than just a timepiece – it's a portable playground strapped to your wrist! Challenge your friends to epic underwater dance-offs, or race against the clock in exciting tap-tastic games. And when you're not making waves, stay connected with your crew through fun emoji messages and reminders.&lt;/p&gt;

&lt;p&gt;But the fun doesn't stop there! This smartwatch is also a trusty sidekick for your land adventures. Track your steps, set fitness goals, and even unlock cool virtual rewards for your active accomplishments. And with its durable, water-resistant design, you don't have to worry about a little mud, sand, or spills slowing you down.&lt;/p&gt;

&lt;p&gt;So what are you waiting for? Dive headfirst into endless fun and excitement with the Aqua Kiddo Smartwatch – the ultimate aquatic accessory for kids who love to make a splash!&lt;br&gt;
`&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Write a playful and humorous product description for a kids’ waterproof smartwatch.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;`Model: Here's a playful and humorous product description for a kids' waterproof smartwatch:&lt;/p&gt;

&lt;p&gt;Unleash the Aqua Adventure with the Splish-Splash SmartKid Watch!&lt;/p&gt;

&lt;p&gt;Are your little ones always making a splash, whether in the pool, at the beach, or even in the bathtub? Fear not, because the Splish-Splash SmartKid Watch is here to ensure their aquatic escapades are both fun and functional! This waterproof wonder is designed to withstand even the most epic water battles, complete with cannonball dives and whirlpool whirls.&lt;/p&gt;

&lt;p&gt;But wait, there's more! This smartwatch isn't just a sidekick for your child's underwater antics; it's a multi-talented marvel that'll keep them entertained and engaged, even when they're back on dry land. With its vibrant display and kid-friendly interface, they can track their steps, set reminders for playdates and chores (good luck with that one!), and even challenge their friends to virtual pet-raising competitions.&lt;/p&gt;

&lt;p&gt;And let's not forget the coolest feature of all – the built-in water detector! That's right, this watch knows when your little fish has taken the plunge, and it'll automatically switch to "aqua mode," complete with underwater games and challenges to keep them occupied while they're making waves.&lt;/p&gt;

&lt;p&gt;So, whether your kiddo is a budding marine biologist or just a professional puddle-jumper, the Splish-Splash SmartKid Watch is the perfect companion for their wet and wild adventures. Just be prepared for them to never want to leave the water (or your bathtub) again!`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other Options to Try&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temperature&lt;/strong&gt; - Controls creativity. Lower = focused, Higher = diverse/random - Try 0.7 (default), 0.3 (precise), 0.9 (creative)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Top P&lt;/strong&gt; - Controls randomness, like temperature but in a probabilistic way - Keep it at 1.0 unless experimenting &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Max Tokens&lt;/strong&gt; - Controls length of output - Start with 300–500&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%2F04dwxhg9vs3my7p3tdob.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%2F04dwxhg9vs3my7p3tdob.png" alt="Image description" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;No-code start: You don’t need to write any code to start experimenting with generative AI on AWS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prompt engineering matters: The way you phrase your input prompt greatly affects the quality and tone of the output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Great for rapid prototyping: Bedrock is ideal for trying different use cases like product content, customer support scripts, or email generation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mini-project demonstrates how easy and powerful it is to prototype generative AI use cases using Amazon Bedrock and Claude 3 Sonnet—without writing a single line of code. It's a perfect starting point for anyone exploring content automation, prompt engineering, or AI-driven creativity on AWS.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automate AWS Cost Monitoring &amp; Explanations using EventBridge + Bedrock</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Fri, 25 Apr 2025 13:54:43 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/automate-aws-cost-monitoring-explanations-using-eventbridge-bedrock-57l1</link>
      <guid>https://dev.to/gurudev_prasadteketi/automate-aws-cost-monitoring-explanations-using-eventbridge-bedrock-57l1</guid>
      <description>&lt;h2&gt;
  
  
  Key concepts that we are going to explore
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS Cost Tools - Cost Explorer, Budgets, Trusted Advisor, Compute Optimizer&lt;/li&gt;
&lt;li&gt;EventBridge - Rule-based automation, Event pattern filtering&lt;/li&gt;
&lt;li&gt;Bedrock + LangChain (light) - Trigger LLM to explain cost anomalies&lt;/li&gt;
&lt;li&gt;Lambda - Glue logic between AWS services&lt;/li&gt;
&lt;li&gt;SNS - Cost alerts via email/SMS are going to explore.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🛠️ Architecture:&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%2F796ni38a1zi33dlwthdn.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%2F796ni38a1zi33dlwthdn.png" alt="Image description" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1:
&lt;/h2&gt;

&lt;p&gt;Enable AWS Cost Explorer&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%2F64havsnbfdqwjjjxktoi.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%2F64havsnbfdqwjjjxktoi.png" alt="Image description" width="800" height="350"&gt;&lt;/a&gt;&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%2Fhmdidddefd0tll92no0i.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%2Fhmdidddefd0tll92no0i.png" alt="Image description" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2:
&lt;/h2&gt;

&lt;p&gt;Configure AWS Budgets for service cost threshold (e.g., EC2 &amp;gt; $20/day)&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%2Fc5xmztq2ico9p0jxdywo.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%2Fc5xmztq2ico9p0jxdywo.png" alt="Image description" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3:
&lt;/h2&gt;

&lt;p&gt;Create an SNS Topic (e.g., CostAlertsTopic) and subscribe via email&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%2Fabvqxs2c8bbqc51z5t45.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%2Fabvqxs2c8bbqc51z5t45.png" alt="Image description" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4:
&lt;/h2&gt;

&lt;p&gt;Enable Amazon Bedrock which is available in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;us-east-1 (N. Virginia)&lt;/li&gt;
&lt;li&gt;us-west-2 (Oregon)&lt;/li&gt;
&lt;li&gt;eu-central-1 (Frankfurt)&lt;/li&gt;
&lt;li&gt;ap-southeast-1 (Singapore)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommended Model: Anthropic Claude v2 (via Amazon Bedrock)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why Claude v2?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent at summarization and natural explanations&lt;/li&gt;
&lt;li&gt;Fast and reliable via Bedrock API&lt;/li&gt;
&lt;li&gt;Supports secure inference with no fine-tuning needed&lt;/li&gt;
&lt;li&gt;Default model in many Bedrock-based examples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Model Details for Bedrock Invocation&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model ID: "anthropic.claude-v2"&lt;/li&gt;
&lt;li&gt;Provider: Anthropic&lt;/li&gt;
&lt;li&gt;API Method: InvokeModel&lt;/li&gt;
&lt;li&gt;Region: us-east-1 or other supported region&lt;/li&gt;
&lt;li&gt;Input format: Anthropic-specific prompt (not plain OpenAI style)&lt;/li&gt;
&lt;/ul&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%2Fvflj97hrht8yeed2yvxq.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%2Fvflj97hrht8yeed2yvxq.png" alt="Image description" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5:
&lt;/h2&gt;

&lt;p&gt;Setup Event bridge rule - Target Lambda Function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "source": ["aws.budgets"],
  "detail-type": ["Budget Threshold Breached"]
}

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

&lt;/div&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%2Fa69oxlov4fi7jnkyjvo6.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%2Fa69oxlov4fi7jnkyjvo6.png" alt="Image description" width="800" height="362"&gt;&lt;/a&gt;&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%2F8l067a4zvaivfaqkn64b.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%2F8l067a4zvaivfaqkn64b.png" alt="Image description" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6:
&lt;/h2&gt;

&lt;p&gt;Lambda Function: Analyze &amp;amp; Explain Cost Spike&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
import json
import os

def lambda_handler(event, context):
    bedrock_runtime = boto3.client("bedrock-runtime", region_name="us-east-1")

    anomaly_detail = json.dumps(event['detail'], indent=2)
    prompt = f"Explain the following AWS cost anomaly in plain English:\n\n{anomaly_detail}"

    response = bedrock_runtime.invoke_model(
        modelId="anthropic.claude-v2",  # or other Bedrock-supported LLM
        body=json.dumps({"prompt": prompt, "max_tokens_to_sample": 500}),
        contentType="application/json"
    )

    output = json.loads(response['body'].read())
    explanation = output.get("completion", "No explanation generated.")

    sns = boto3.client('sns')
    sns.publish(
        TopicArn=os.environ["SNS_TOPIC_ARN"],
        Subject="AWS Cost Alert 📈",
        Message=explanation
    )

    return {"statusCode": 200, "body": explanation}

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Note - Set SNS_TOPIC_ARN as an environment variable in Lambda
&lt;/h1&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%2Fe4pxy45yvo2f4mcuryqr.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%2Fe4pxy45yvo2f4mcuryqr.png" alt="Image description" width="800" height="369"&gt;&lt;/a&gt;&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%2Fpws8lfmzsgr20ksa655g.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%2Fpws8lfmzsgr20ksa655g.png" alt="Image description" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this hands-on project, we combined the power of AWS automation and Generative AI to create an intelligent, event-driven cost monitoring system. &lt;br&gt;
By leveraging AWS Budgets, EventBridge, Lambda, and Amazon Bedrock, we not only detected cost anomalies in real-time but also generated human-readable explanations using LLMs — making cloud spend analysis more accessible and actionable.&lt;/p&gt;

&lt;p&gt;This is just the beginning of what’s possible when AI meets Cloud FinOps.&lt;br&gt;
Imagine extending this to proactive optimization suggestions, ticket creation, or even Slack alerts — all powered by serverless workflows and GenAI.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Design and Manage Pub/Sub Messaging with Amazon Simple Notification Service (SNS)</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Fri, 21 Mar 2025 03:14:14 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/design-and-manage-pubsub-messaging-with-amazon-simple-notification-service-sns-42en</link>
      <guid>https://dev.to/gurudev_prasadteketi/design-and-manage-pubsub-messaging-with-amazon-simple-notification-service-sns-42en</guid>
      <description>&lt;h2&gt;
  
  
  Amazon Simple Notification Service (Amazon SNS)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Simple Notification Service (Amazon SNS) is a fully managed messaging service that enables the coordination of message delivery from publishers to subscribers. &lt;/li&gt;
&lt;li&gt;It provides a highly available, secure, and scalable messaging infrastructure, allowing applications to send notifications to various endpoints, such as email addresses, mobile devices, or AWS services. &lt;/li&gt;
&lt;li&gt;With Amazon SNS, you can decouple message producers from consumers, enabling asynchronous communication and event-driven architectures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Objectives
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Configure message filtering policies in Amazon SNS to control message delivery to subscribers.&lt;/li&gt;
&lt;li&gt;Set up CloudWatch metrics to monitor SNS message activity.&lt;/li&gt;
&lt;li&gt;Create SNS topic subscriptions with multi-attribute filtering rules.&lt;/li&gt;
&lt;li&gt;Use CloudWatch metrics to monitor message delivery and filtering effectiveness.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Task 1: Subscribe to an SNS topic
&lt;/h2&gt;

&lt;p&gt;Set up multiple subscriptions to the Amazon SNS topic to simulate different teams receiving notifications. You’ll start with a basic subscription and then create additional subscriptions for the customer support and inventory management teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 1.1: Configure Initial Subscription&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Simple Notification Service.&lt;/li&gt;
&lt;li&gt;select Topics.&lt;/li&gt;
&lt;li&gt;Select ecommerce-notifications.&lt;/li&gt;
&lt;li&gt;On the ecommerce-notifications topic page, choose Create subscription.&lt;/li&gt;
&lt;li&gt;On the Create subscription page, configure the following settings:&lt;/li&gt;
&lt;li&gt;Topic ARN: The ARN of the ecommerce-notifications topic is already filled in. Leave this unchanged.&lt;/li&gt;
&lt;li&gt;Protocol: Choose Email.&lt;/li&gt;
&lt;li&gt;Endpoint: Enter an email address where you can receive notifications during this lab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Note&lt;/strong&gt;: You can create multiple subscriptions for a single topic, and each subscription can use different protocols including email, SMS (text messages), or other AWS services. This allows you to send the same message to multiple destinations simultaneously.&lt;/li&gt;
&lt;li&gt;Choose Create subscription.&lt;/li&gt;
&lt;li&gt;Check your email for a message from AWS Notifications.&lt;/li&gt;
&lt;li&gt;In the email body, choose the Confirm subscription link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Note&lt;/strong&gt;: Now that your email address is subscribed to the topic, you will receive a notification whenever a new message is published to it.&lt;/li&gt;
&lt;/ol&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%2Fsjaewl45wcjs3f0vdd4x.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%2Fsjaewl45wcjs3f0vdd4x.png" alt="Image description" width="800" height="397"&gt;&lt;/a&gt;&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%2F2iet35hgdv6td2kk65th.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%2F2iet35hgdv6td2kk65th.png" alt="Image description" width="800" height="363"&gt;&lt;/a&gt;&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%2F6n42j9po8f21foq83xak.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%2F6n42j9po8f21foq83xak.png" alt="Image description" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 1.2: Create Team-Specific Subscriptions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the breadcrumbs at the top of the screen, choose the ecommerce-notifications link.&lt;/li&gt;
&lt;li&gt;On the ecommerce-notifications topic page, choose Create subscription.&lt;/li&gt;
&lt;li&gt;Configure the following settings:

&lt;ul&gt;
&lt;li&gt; Protocol: Choose Email&lt;/li&gt;
&lt;li&gt; Endpoint: Enter your email address with +support added before the @ symbol. Example: &lt;a href="mailto:student+support@example.com"&gt;student+support@example.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Choose Create subscription.&lt;/li&gt;

&lt;li&gt;Repeat the process for the inventory management team:&lt;/li&gt;

&lt;li&gt;Return to the ecommerce-notifications page.&lt;/li&gt;

&lt;li&gt;Choose Create subscription

&lt;ul&gt;
&lt;li&gt; Protocol to Email&lt;/li&gt;
&lt;li&gt; Endpoint, enter your email address with +inventory added before the @ symbol. Example: &lt;a href="mailto:student+inventory@example.com"&gt;student+inventory@example.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Choose Create subscription&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Task 1.3: Verify All Subscriptions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check your email inbox for two additional confirmation messages from AWS Notifications.&lt;/li&gt;
&lt;li&gt;Confirm both new subscriptions by clicking the Confirm subscription link in each email.&lt;/li&gt;
&lt;li&gt;Return to the SNS console and select Subscriptions from the left navigation menu.&lt;/li&gt;
&lt;li&gt;Verify that you see the following three confirmed subscriptions:

&lt;ul&gt;
&lt;li&gt;Your original email address&lt;/li&gt;
&lt;li&gt;Your email address with +support&lt;/li&gt;
&lt;li&gt;Your email address with +inventory&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&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%2Fr843jae6onlu004d1sq8.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%2Fr843jae6onlu004d1sq8.png" alt="Image description" width="800" height="433"&gt;&lt;/a&gt;&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%2Fh0l7idr6kma8s3yq2mbh.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%2Fh0l7idr6kma8s3yq2mbh.png" alt="Image description" width="800" height="441"&gt;&lt;/a&gt;&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%2Fzz8srd227zkl0dj8l0y8.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%2Fzz8srd227zkl0dj8l0y8.png" alt="Image description" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Task 2: Monitor SNS Performance with CloudWatch
&lt;/h2&gt;

&lt;p&gt;_- Amazon CloudWatch provides valuable insights into the performance and health of your SNS topics. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lets create a dashboard to monitor key metrics, including delivery and failures rates. &lt;/li&gt;
&lt;li&gt;This visibility helps you ensure your notification system is working efficiently and allows you to quickly identify any potential issues.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;By tracking metrics like the number of messages published, successful deliveries, and failed notifications, you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify that messages are being delivered to subscribers&lt;/li&gt;
&lt;li&gt;Identify potential delivery problems&lt;/li&gt;
&lt;li&gt;Monitor the overall health of your SNS topic&lt;/li&gt;
&lt;li&gt;Make data-driven decisions about your notification system_&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open CloudWatch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under Metrics, choose All metrics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the Metrics search bar, enter SNS and press Enter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the filtered lit of SNS Metrics, select SNS &amp;gt; Topic Metrics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A list of topics and available metrics appears.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Identify the metrics for the ecommerce-notifications topic and select the checkboxes next to the following metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NumberOfMessagesPublished&lt;/li&gt;
&lt;li&gt;NumberOfNotificationsDelivered&lt;/li&gt;
&lt;li&gt;NumberOfNotificationsFailed
&lt;strong&gt;- WARNING: Do not select the PublishSize metric.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Now that you’ve selected metrics to track, you create a new CloudWatch Dashboard and add them to it.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Open the Actions  menu at the top of the screen and select Add to dashboard.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;In the Add to dashboard popup window, choose Create new.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Configure the following for your dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create new dashboard: Enter SNSMetricsDashboard.&lt;/li&gt;
&lt;li&gt;Widget type: Choose Stacked area.&lt;/li&gt;
&lt;li&gt;Customize widget title: Enter SNS Performance and Health Metrics.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Choose the Create button.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Choose Add to dashboard button.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Save&lt;/p&gt;&lt;/li&gt;

&lt;/ul&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%2F5qage5ymq7vlz9qnhmq0.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%2F5qage5ymq7vlz9qnhmq0.png" alt="Image description" width="800" height="350"&gt;&lt;/a&gt;&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%2F7imqg0ek43jmp5nran9c.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%2F7imqg0ek43jmp5nran9c.png" alt="Image description" width="800" height="363"&gt;&lt;/a&gt;&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%2Fool8yzp51zwstu4hkt98.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%2Fool8yzp51zwstu4hkt98.png" alt="Image description" width="800" height="351"&gt;&lt;/a&gt;&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%2F2fbj35yj174bi8z8ycl5.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%2F2fbj35yj174bi8z8ycl5.png" alt="Image description" width="800" height="354"&gt;&lt;/a&gt;&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%2Fx93m8bljkrfwfu3m8v5k.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%2Fx93m8bljkrfwfu3m8v5k.png" alt="Image description" width="800" height="403"&gt;&lt;/a&gt;&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%2F3i9x978y77c67twairo2.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%2F3i9x978y77c67twairo2.png" alt="Image description" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Task 3: Implement Message Filtering
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Implementing message filtering to ensure that different teams receive only the notifications relevant to their responsibilities. You work with an existing Lambda function that publishes various types of messages to your SNS topic:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;High-value order notifications for the sales team&lt;/li&gt;
&lt;li&gt;Support tickets for the customer service team&lt;/li&gt;
&lt;li&gt;Inventory alerts for the warehouse team&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;By configuring message filtering policies, you’ll ensure that each team receives only the notifications they need to act upon, reducing noise and improving operational efficiency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Task 3.1: Configure Filtering Policies&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Simple Notification Service.&lt;/li&gt;
&lt;li&gt;Choose Subscriptions.&lt;/li&gt;
&lt;li&gt;You start by updating the main subscription (your original email address).&lt;/li&gt;
&lt;li&gt;Select the radio next to the subscription with your original email address and choose the Edit button at the top of the screen.&lt;/li&gt;
&lt;li&gt;On the Edit subscription screen, expand the  Subscription filter policy - optional section.&lt;/li&gt;
&lt;li&gt;Use the Subscription filter policy toggle button to enable filtering for this subscription.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Note&lt;/strong&gt;: The filter policy scope in Amazon SNS determines whether filter conditions are applied to message attributes or the message body. Choosing Message attributes applies filtering based on metadata, whereas Message body filters based on content, allowing precise control over message delivery to subscribers.&lt;/li&gt;
&lt;li&gt;Choose Message attributes.&lt;/li&gt;
&lt;li&gt;In the JSON editor text box, enter the following policy:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
     "category": ["high_value"],
     "message_type": ["order_notification"]
}

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;li&gt;Repeat the same for support and inventory subscriptions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "message_type": ["support_ticket"],
  "category": ["customer_service"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "message_type": ["inventory_alert"],
  "category": ["inventory"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fh9x3qoc9nq7p3farh6ym.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%2Fh9x3qoc9nq7p3farh6ym.png" alt="Image description" width="800" height="343"&gt;&lt;/a&gt;&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%2Fnfoavj1c7rfo8uqp6k04.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%2Fnfoavj1c7rfo8uqp6k04.png" alt="Image description" width="800" height="410"&gt;&lt;/a&gt;&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%2Fg5x1kfbhwtn9pmzgy8zk.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%2Fg5x1kfbhwtn9pmzgy8zk.png" alt="Image description" width="800" height="402"&gt;&lt;/a&gt;&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%2Fu3a89t3hz7qnoftaq3fj.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%2Fu3a89t3hz7qnoftaq3fj.png" alt="Image description" width="800" height="429"&gt;&lt;/a&gt;&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%2Fztpfxd5ae65cuoh8g162.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%2Fztpfxd5ae65cuoh8g162.png" alt="Image description" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 3.2: Test Message Filtering&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return to the Topics page and select your topic.&lt;/li&gt;
&lt;li&gt;Choose Publish message.&lt;/li&gt;
&lt;li&gt;Start by confirming that your main email address receives notifications when high value orders are placed.&lt;/li&gt;
&lt;li&gt;Choose the Publish message button at the top of the screen and create a test message for a high value order:

&lt;ul&gt;
&lt;li&gt;Subject: Enter Test - High value order alert&lt;/li&gt;
&lt;li&gt;Message body: Enter An order for $750 has been placed&lt;/li&gt;
&lt;li&gt;Add message attribute:&lt;/li&gt;
&lt;li&gt;Type: Choose String&lt;/li&gt;
&lt;li&gt;Name: Enter category&lt;/li&gt;
&lt;li&gt;Value: Enter high_value&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Choose Add another attribute

&lt;ul&gt;
&lt;li&gt;Add message attribute:&lt;/li&gt;
&lt;li&gt;Type: Choose String&lt;/li&gt;
&lt;li&gt;Name: Enter message_type&lt;/li&gt;
&lt;li&gt;Value: Enter order_notification&lt;/li&gt;
&lt;li&gt;Choose Publish message&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Check your email inbox:&lt;/li&gt;

&lt;li&gt;Look at the email header and confirm that it was sent to your original email address
The +support and +inventory aliases should not receive this message
&lt;strong&gt;Note&lt;/strong&gt;: When using multiple attributes in a filter policy, SNS applies an AND operation between them. This means a subscription will only receive a message if ALL attributes in its filter policy match the message’s attributes. For example, given the policy you just created:
&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
     "category": ["high_value"],
     "message_type": ["order_notification"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your email address will only receive notification that have both:&lt;br&gt;
&lt;em&gt;category = high_value AND&lt;br&gt;
message_type = order_notification&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This powerful filtering capability has many applications beyond e-commerce. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A weather alert system could filter by both severity (critical) AND location (west-coast)&lt;/li&gt;
&lt;li&gt;A security monitoring system could filter by both threat level (high) AND asset type (database)&lt;/li&gt;
&lt;li&gt;A manufacturing system could filter by both machine status (maintenance-required) AND facility location (plant-A)&lt;/li&gt;
&lt;li&gt;This allows you to create very specific routing rules for your messages, ensuring that subscribers only receive the exact subset of messages they need to act upon.&lt;/li&gt;
&lt;/ul&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%2Fasfhu62xaqxi8fzlyalp.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%2Fasfhu62xaqxi8fzlyalp.png" alt="Image description" width="800" height="349"&gt;&lt;/a&gt;&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%2Fi1wjg233bcw8y5p651ww.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%2Fi1wjg233bcw8y5p651ww.png" alt="Image description" width="800" height="341"&gt;&lt;/a&gt;&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%2Fpg7ctuvc6yu06nrz1p6d.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%2Fpg7ctuvc6yu06nrz1p6d.png" alt="Image description" width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Now test that your support team subscription receives customer service tickets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose the Publish message button at the top of the screen and create the following test message for support tickets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subject: Enter Test - Customer support ticket&lt;/li&gt;
&lt;li&gt;Message body: Enter New support ticket opened&lt;/li&gt;
&lt;li&gt;Add message attribute:&lt;/li&gt;
&lt;li&gt;Type: Choose String&lt;/li&gt;
&lt;li&gt;Name: Enter category&lt;/li&gt;
&lt;li&gt;Value: Enter customer_service&lt;/li&gt;
&lt;li&gt;Choose Add another attribute&lt;/li&gt;
&lt;li&gt;Add message attribute:&lt;/li&gt;
&lt;li&gt;Type: Choose String&lt;/li&gt;
&lt;li&gt;Name: Enter message_type&lt;/li&gt;
&lt;li&gt;Value: Enter support_ticket&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Choose Publish message&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Check your email inbox:&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Look at the email header and confirm that it was sent to your +support email address&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Your original email and +inventory alias should not receive this message&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Create a test message for inventory alerts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subject: Enter Test - Inventory alert&lt;/li&gt;
&lt;li&gt;Message body: Enter Low stock warning&lt;/li&gt;
&lt;li&gt;Add message attribute:&lt;/li&gt;
&lt;li&gt;Type: Choose String&lt;/li&gt;
&lt;li&gt;Name: Enter category&lt;/li&gt;
&lt;li&gt;Value: Enter inventory&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Choose Add another attribute&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add message attribute:&lt;/li&gt;
&lt;li&gt;Type: Choose String&lt;/li&gt;
&lt;li&gt;Name: Enter message_type&lt;/li&gt;
&lt;li&gt;Value: Enter inventory_alert&lt;/li&gt;
&lt;li&gt;Choose Publish message&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Check your email inbox:&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Look at the email header and confirm that it was sent to your +inventory email address&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Your original email and +support alias should not receive this message&lt;br&gt;&lt;br&gt;
The following table shows a summary of your filtering configuration:&lt;/p&gt;&lt;/li&gt;

&lt;/ul&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%2F9ciiuemkfh05trwlf8oe.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%2F9ciiuemkfh05trwlf8oe.png" alt="Image description" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Task 4: Analyze SNS Performance Using CloudWatch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Task 4.1: Review and update CloudWatch Dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose CloudWatch.&lt;/li&gt;
&lt;li&gt;choose Dashboards.&lt;/li&gt;
&lt;li&gt;Select the dashboard named SNSMetricsDashboard that you created earlier.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Note&lt;/strong&gt;: To adjust the dashboard size, look for the resize handle in the bottom-right corner. You can drag this handle to increase the viewing area of your dashboard.&lt;/li&gt;
&lt;li&gt;In the time series bar above the dashboard, select Custom  and then choose 30 minutes.&lt;/li&gt;
&lt;li&gt;In the dashboard, observe the following metrics:

&lt;ul&gt;
&lt;li&gt;NumberOfMessagesPublished: Shows how many messages the Lambda function is publishing&lt;/li&gt;
&lt;li&gt;NumberOfNotificationsDelivered: Shows successful message deliveries to subscribers&lt;/li&gt;
&lt;li&gt;NumberOfNotificationsFailed: Shows failed delivery attempts&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt; &lt;strong&gt;Note&lt;/strong&gt;: You should see:&lt;/li&gt;

&lt;li&gt;The number of MessagesPublished should be largely consistent, as this is controlled by a Lambda function triggering at regular intervals&lt;/li&gt;

&lt;li&gt;Few or no NotificationsFailed (indicating successful delivery)&lt;/li&gt;

&lt;li&gt;Hover over any data point in the graphs to see exact values at that time.&lt;/li&gt;

&lt;li&gt;Now, let’s investigate how filtering has affected your SNS topic.&lt;/li&gt;

&lt;li&gt;In the upper-right corner of your dashboard, select the icon with three vertical dots.&lt;/li&gt;

&lt;li&gt;From the menu that appears on screen, choose Edit.&lt;/li&gt;

&lt;li&gt;Choose the Browse tab.&lt;/li&gt;

&lt;li&gt;Enter NumberOfNotificationsFilteredOut-MessageAttributes in the search bar and press Enter.&lt;/li&gt;

&lt;li&gt;Choose SNS &amp;gt; Topic Metrics.&lt;/li&gt;

&lt;li&gt;Select the checkbox next to ecommerce-notifications and then choose Update widget.&lt;/li&gt;

&lt;li&gt;Choose Save.&lt;/li&gt;

&lt;li&gt;With the additional metric added to your dashboard, you can now see how filtering has affected your SNS topic.&lt;/li&gt;

&lt;/ul&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%2Flee6tjv70sjcjhxws6ee.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%2Flee6tjv70sjcjhxws6ee.png" alt="Image description" width="800" height="372"&gt;&lt;/a&gt;&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%2Ft5g9fufzl53uljp107ze.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%2Ft5g9fufzl53uljp107ze.png" alt="Image description" width="800" height="1041"&gt;&lt;/a&gt;&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%2Ff36prux5at74jvrd4bp4.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%2Ff36prux5at74jvrd4bp4.png" alt="Image description" width="800" height="851"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task 4.2: Create CloudWatch Alarms&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the CloudWatch console, expand the navigation menu by choosing the menu icon  in the upper-left corner, then open the Alarms menu and select All alarms.&lt;/li&gt;
&lt;li&gt;Choose Create alarm.&lt;/li&gt;
&lt;li&gt;Choose Select metric.&lt;/li&gt;
&lt;li&gt;Select the Browse tab.&lt;/li&gt;
&lt;li&gt;Enter NumberOfNotificationsFailed in the search bar and press Enter.&lt;/li&gt;
&lt;li&gt;Choose SNS &amp;gt; Topic Metrics.&lt;/li&gt;
&lt;li&gt;Select the checkbox next to ecommerce-notifications and then choose Select metric.&lt;/li&gt;
&lt;li&gt;Configure the following alarm conditions:&lt;/li&gt;
&lt;li&gt;Under Metric, leave the default settings&lt;/li&gt;
&lt;li&gt;Under Conditions:

&lt;ul&gt;
&lt;li&gt;For Threshold type, choose Static&lt;/li&gt;
&lt;li&gt;For Whenever NumberOfNotificationsFailed is…, choose Greater&lt;/li&gt;
&lt;li&gt;For than…, enter 0&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt; Note: This alarm will trigger whenever any message fails to deliver. Setting the threshold to 0 ensures you’re notified of even a single failure, which is important for maintaining reliable message delivery in a production environment.&lt;/li&gt;

&lt;li&gt;Choose Next.&lt;/li&gt;

&lt;li&gt;Configure the notification:&lt;/li&gt;

&lt;li&gt;Under Notification, choose Create new topic

&lt;ul&gt;
&lt;li&gt;For Create a new topice, enter sns-alarms&lt;/li&gt;
&lt;li&gt;For Email endpoints, enter your email address&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Choose Create topic&lt;/li&gt;

&lt;li&gt;Choose Next.&lt;/li&gt;

&lt;li&gt;For Alarm name, enter SNSFailedNotifications.&lt;/li&gt;

&lt;li&gt;Choose Next.&lt;/li&gt;

&lt;li&gt;Review your alarm configuration and choose Create alarm.&lt;/li&gt;

&lt;li&gt;Check your email and confirm the subscription to the new SNS topic.&lt;/li&gt;

&lt;/ul&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%2Fjqtf7iyyicexgzf6x0i2.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%2Fjqtf7iyyicexgzf6x0i2.png" alt="Image description" width="800" height="344"&gt;&lt;/a&gt;&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%2Fqa54xyrsr1ir6oe8d4ub.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%2Fqa54xyrsr1ir6oe8d4ub.png" alt="Image description" width="800" height="345"&gt;&lt;/a&gt;&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%2F73mbqgjt318nncfllgex.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%2F73mbqgjt318nncfllgex.png" alt="Image description" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Review
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Configured message filtering policies in Amazon SNS to control message delivery to subscribers.&lt;/li&gt;
&lt;li&gt;Set up CloudWatch metrics to monitor SNS message activity.&lt;/li&gt;
&lt;li&gt;Created SNS topic subscriptions with multi-attribute filtering rules.&lt;/li&gt;
&lt;li&gt;Used CloudWatch metrics to monitor message delivery and filtering effectiveness.&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Zero Trust Architecture for Service-to-Service Workload in AWS</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Thu, 20 Mar 2025 10:25:13 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/zero-trust-architecture-for-service-to-service-workload-in-aws-57j3</link>
      <guid>https://dev.to/gurudev_prasadteketi/zero-trust-architecture-for-service-to-service-workload-in-aws-57j3</guid>
      <description>&lt;h2&gt;
  
  
  Zero Trust Architecture in AWS
&lt;/h2&gt;

&lt;p&gt;is a security framework that implements Zero Trust principles across AWS services and workloads. It’s designed to verify every request for access to resources—regardless of its origin—based on identity, device state, and security posture.&lt;/p&gt;

&lt;p&gt;This hands-on lab focuses on applying Zero Trust principles to the &lt;strong&gt;service-to-service architectures&lt;/strong&gt; used in many micro-architecture or distributed environments today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-level architecture of the lab&lt;/strong&gt;&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%2F4o0ucgc12jh3bk7jaqjc.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%2F4o0ucgc12jh3bk7jaqjc.png" alt="Image description" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scenario is comprised of two services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;calling services&lt;/strong&gt; component has multiple compute nodes which make API calls to the target services. The API calls are made by a fleet of Amazon Elastic Compute Cloud (Amazon EC2) instances and AWS Lambda functions. These compute nodes resides in different subnets within multiple Amazon VPCs.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;target services&lt;/strong&gt; component has multiple REST APIs hosted by the API Gateway, multiple backend Lambda functions and other backend services like Amazon DynamoDB tables.&lt;/li&gt;
&lt;li&gt;Because the REST APIs are set as private, VPC Endpoints are configured within the calling services subnets to provide a network path for the API calls from the calling services using AWS PrivateLink.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Task 1: Review the lab scenario current state service-to-service architecture
&lt;/h2&gt;

&lt;p&gt;Recommendation service (ServiceA) : Generates recommendations based on previous orders retrieved from the Order History service (ServiceB). ServiceA valid compute nodes are represented by the EC2 instance denoted as Expected Caller.&lt;/p&gt;

&lt;p&gt;Order History service (ServiceB) : Provides access to customers past orders. This is represented by the ServiceB API. The API communicates at the backend with a Lambda function and a DynamoDB table to store the order table.&lt;/p&gt;

&lt;p&gt;In addition to the above, there are other unwanted callers and unknown APIs which perform other functions but should not be able to interact with the main two services above.&lt;/p&gt;

&lt;p&gt;There are many conditions that you can use to allow or block the API call using different security controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the caller need to make an API call to the target service?&lt;/li&gt;
&lt;li&gt;Does the API call has a valid path to the target service?&lt;/li&gt;
&lt;li&gt;Is the API call sourced from a valid CIDR IP range?&lt;/li&gt;
&lt;li&gt;Does the caller have the correct IAM permissions to make the API call via IAM role?&lt;/li&gt;
&lt;li&gt;Is the API call authenticated (signed) using AWS Signature V4 (SigV4) algorithm?&lt;/li&gt;
&lt;li&gt; Note: AWS Signature V4 is an algorithm used to sign the API calls. When enforced, it requires that the caller uses valid IAM credentials to sign the API calls.&lt;/li&gt;
&lt;/ul&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%2Fnrl285va4d51uoinwcer.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%2Fnrl285va4d51uoinwcer.png" alt="Image description" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Task 2: Review the existing security controls
&lt;/h2&gt;

&lt;p&gt;where you can apply security controls in the solution:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Gateway:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM authorization on the API level: This is where you can require that the caller has valid IAM credentials and the API call is signed using SigV4. If turned off, the API can be called by any entity without having valid IAM credentials.&lt;/li&gt;
&lt;li&gt;Resource policy: This is where you can apply an IAM resource policy with specific conditions to restrict making calls to the API.&lt;/li&gt;
&lt;li&gt;API Endpoint Type: You can control where the API can be called from depending on the type you choose. In the lab scenario, both the ServiceB API and Unknown API are set to Private. This means that calls must be initiated from a API Gateway VPC endpoint from within a VPC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;VPC Endpoint:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resource policy: This is where you can apply an IAM resource policy with specific conditions to restrict making calls to the API. This is similar to the API Gateway resource policy but configured closer to the source of the API call.&lt;/li&gt;
&lt;li&gt;Security Group: You can define inbound/outbound rules to control the traffic traversing through the VPC endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Open API Gateway&lt;br&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; In the list of APIs, under the name column, choose the ServiceBAPI link.&lt;br&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; In the Resources pane, under /orders, choose GET.&lt;br&gt;
&lt;strong&gt;Step 4:&lt;/strong&gt; In the /orders-GET-Method Execution section, choose the Method request tab.&lt;br&gt;
&lt;strong&gt;Step 5:&lt;/strong&gt; In the Settings page, verify that Authorization is set to NONE.&lt;br&gt;
&lt;strong&gt;Step 6:&lt;/strong&gt; In the navigation pane at the left of the page, in the API:ServiceBAPI section, choose Resource policy.&lt;br&gt;
&lt;strong&gt;Step 7:&lt;/strong&gt; Check the existing IAM resource policy configured on the ServiceBAPI. The policy allows any call sourced from an IP address within the 10.199.0.0/24 range. This range is similar to the range of Private subnet 1 in the ServiceA VPC and also the Private subnet 2 in the Other VPC. You can verify the ranges using the Detailed architecture diagram and/or the Callers table presented earlier.&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%2F6sqdildkfn3gqy1y0wup.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%2F6sqdildkfn3gqy1y0wup.png" alt="Image description" width="800" height="352"&gt;&lt;/a&gt;&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%2F50ebwp4b7o0g4czp1ar6.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%2F50ebwp4b7o0g4czp1ar6.png" alt="Image description" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8:&lt;/strong&gt; Open VPC&lt;br&gt;
&lt;strong&gt;Step 9:&lt;/strong&gt; In the navigation pane at the left of the page, in the Virtual private cloud section, choose Endpoints&lt;br&gt;
&lt;strong&gt;Step 10:&lt;/strong&gt; In the Endpoints page, select the endpoint with a VPC endpoint ID that matches the APIGWVPCEndpointID value that is listed to the left of these instructions.&lt;br&gt;
&lt;strong&gt;Step 11:&lt;/strong&gt; In the VPC endpoint Details section under the list of Endpoints, choose the Tags tab, then choose Manage tags.&lt;br&gt;
&lt;strong&gt;Step 12:&lt;/strong&gt; Choose Add new tag.&lt;br&gt;
&lt;strong&gt;Step 13:&lt;/strong&gt; On the Manage tags page, in the Tags section:&lt;br&gt;
For Key, enter Name.&lt;br&gt;
For Value - optional, enter ServiceA-APIGW-EP.&lt;br&gt;
&lt;strong&gt;Step 14:&lt;/strong&gt; Save and While the ServiceA-APIGW-EP endpoint name is selected, choose the Policy tab.&lt;br&gt;
Note that the existing policy allows all traffic to traverse through the endpoint to make a call to any Private API in the same region with no restrictions.&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%2Fnqwv8kxnqzldurpn25fg.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%2Fnqwv8kxnqzldurpn25fg.png" alt="Image description" width="800" height="352"&gt;&lt;/a&gt;&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%2F3ahc1zcd22w72aixywpw.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%2F3ahc1zcd22w72aixywpw.png" alt="Image description" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Task 3: Run an assessment to evaluate your current security posture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Connect to Service A EC2 instance.&lt;br&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; run the command "runscanner" to run the scan.&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%2F2w0gnz8xjyxoqprrqqk8.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%2F2w0gnz8xjyxoqprrqqk8.png" alt="Image description" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on the assessment results above, only two out of six API calls patterns comply with desired security outcome. This really raises an alarm and does not fit well with the Zero Trust principles that you need comply with.&lt;/p&gt;
&lt;h2&gt;
  
  
  Task 4: Improve your security posture using IAM authorization on the API Gateway
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt;  choose API Gateway.&lt;br&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt;  In the list of APIs, under the name column, choose the ServiceBAPI link.&lt;br&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; In the Resources panel, under the /orders, choose GET.&lt;br&gt;
&lt;strong&gt;Step 4:&lt;/strong&gt; In the /orders-GET-Method Execution section, choose the Method request tab.&lt;br&gt;
&lt;strong&gt;Step 5:&lt;/strong&gt; In the Method request settings section, choose Edit, and then from the Authorization drop-down menu, select AWS IAM.&lt;br&gt;
&lt;strong&gt;Step 6:&lt;/strong&gt; To save your selection, choose Save.&lt;br&gt;
&lt;strong&gt;Step 7:&lt;/strong&gt; After saving your selection, you need to deploy the API for your changes to take effect.&lt;br&gt;
&lt;strong&gt;Step 8:&lt;/strong&gt; In the Resources panel, choose Deploy API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Stage, select api.&lt;/li&gt;
&lt;li&gt;Choose Deploy.&lt;/li&gt;
&lt;/ul&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%2Frf4l623z4uspcvp9jxnr.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%2Frf4l623z4uspcvp9jxnr.png" alt="Image description" width="800" height="354"&gt;&lt;/a&gt;&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%2Frgqtydxbfwgn5y2cgpyu.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%2Frgqtydxbfwgn5y2cgpyu.png" alt="Image description" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Task 5: Improve your security posture using API Gateway resource policy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Return to the API Gateway Console browser tab.&lt;br&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; In the ServiceBAPI left panel, choose Resource policy.&lt;br&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; Choose Edit, and then delete the entire policy.&lt;br&gt;
&lt;strong&gt;Step 4:&lt;/strong&gt; In the Policy details pane, add the following policy after making these two changes:&lt;br&gt;
Replace the INSERT_AWS_ACCOUNT_ID placeholder value with the AWSAccountID value that is listed to the left of these instructions (keep the quotes).&lt;br&gt;
Replace the INSERT_APIGW_VPC_ENDPOINT_ID placeholder value with the APIGWVPCEndpointID value that is listed to the left of these instructions (keep the quotes).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:*:*:*"
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:*:*:*",
            "Condition": {
                "StringNotEquals": {
                    "aws:PrincipalAccount": "INSERT_AWS_ACCOUNT_ID"
                }
            }
        },
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:*:*:*",
            "Condition": {
                "StringNotEquals": {
                    "aws:SourceVpce": "INSERT_APIGW_VPC_ENDPOINT_ID"
                }
            }
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Save&lt;br&gt;
After saving the policy, you need to deploy the API for your changes to take effect.&lt;br&gt;
&lt;strong&gt;Step 6:&lt;/strong&gt; In the navigation pane at the left of the page, in the API:ServiceBAPI section, choose Resources.&lt;br&gt;
&lt;strong&gt;Step 7:&lt;/strong&gt; In the Resources panel, choose Deploy API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Stage, select api.&lt;/li&gt;
&lt;li&gt;Choose Deploy.&lt;/li&gt;
&lt;/ul&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%2F3azfmcen0kb0alhi5h06.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%2F3azfmcen0kb0alhi5h06.png" alt="Image description" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8:&lt;/strong&gt; Open the Session Manager terminal session to the ServiceA-Instance. &lt;br&gt;
&lt;strong&gt;Step 9:&lt;/strong&gt; Run command "runscanner"&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%2Feichfm4km9nn7t3j7nlo.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%2Feichfm4km9nn7t3j7nlo.png" alt="Image description" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Task 6: Improve the security posture using VPC endpoint policy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Return to the API Gateway Console browser tab.&lt;br&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; At the top of the AWS Management Console, in the search bar, search for and choose VPC.&lt;br&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; In the navigation pane at the left of the page, in the Virtual private cloud section, choose Endpoints.&lt;br&gt;
&lt;strong&gt;Step 4:&lt;/strong&gt; In the list of Endpoints, under the Name column, locate the ServiceA-APIGW-EP and select it.&lt;br&gt;
&lt;strong&gt;Step 5:&lt;/strong&gt; In the VPC endpoint Details section under the list of Endpoints, choose the Policy tab.&lt;br&gt;
&lt;strong&gt;Step 6:&lt;/strong&gt; In the Policy pane, choose Edit Policy.&lt;br&gt;
&lt;strong&gt;Step 7:&lt;/strong&gt; In the Edit policy page, ensure that  Custom is selected.&lt;br&gt;
&lt;strong&gt;Step 8:&lt;/strong&gt; Select the existing policy and delete it.&lt;br&gt;
&lt;strong&gt;Step 9:&lt;/strong&gt; In the Policy pane, add the following policy after making these two changes:&lt;/p&gt;

&lt;p&gt;Replace the INSERT_SERVICEA_INSTANCE_ROLE_ARN placeholder value with the ServiceAInstanceRoleARN value that is listed to the left of these instructions (keep the quotes).&lt;br&gt;
Replace the INSERT_API_METHOD_ARN placeholder value with the APIMethodARN value that is listed to the left of these instructions (keep the quotes).&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%2Ftur9tnlvp2ijgkwtvv45.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%2Ftur9tnlvp2ijgkwtvv45.png" alt="Image description" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 10:&lt;/strong&gt; Save&lt;br&gt;
&lt;strong&gt;Step 11:&lt;/strong&gt; Open TerminalA and run command "runscanner"  &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%2F6je8fwlurc5syr2l9k2y.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%2F6je8fwlurc5syr2l9k2y.png" alt="Image description" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Task 7: Improve the security posture by tuning the VPC endpoint security group
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; In the list of Endpoints, select ServiceA-APIGW-EP endpoint if not already selected.&lt;br&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; Choose the Security Groups tab. There is one security group applied to the VPC endpoint.&lt;br&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; Under the Group ID, choose the security group link.&lt;br&gt;
&lt;strong&gt;Step 4:&lt;/strong&gt; On the Security Groups page, choose the security group link.&lt;br&gt;
&lt;strong&gt;Step 5:&lt;/strong&gt; In the Security Group Details section, choose the Inbound rules tab and examine the existing inbound rule.&lt;br&gt;
&lt;strong&gt;Step 6:&lt;/strong&gt; The current rule allows all HTTPS (TCP:443) traffic inbound to the VPC endpoint from the whole ServiceA VPC CIDR range of 10.199.0.0/16. This represents any subnet in the ServiceA VPC even though the expected caller resides only in Private subnet 1. This is a broad range of IP addresses which you need to narrow down.&lt;br&gt;
&lt;strong&gt;Step 7:&lt;/strong&gt; First, you delete the current rule and then add a new rule.&lt;br&gt;
&lt;strong&gt;Step 8:&lt;/strong&gt; Choose Edit inbound rules.&lt;br&gt;
&lt;strong&gt;Step 9:&lt;/strong&gt; To delete the existing rule, choose Delete.&lt;br&gt;
&lt;strong&gt;Step 10:&lt;/strong&gt; To add a new rule, choose Add rule.&lt;br&gt;
&lt;strong&gt;Step 11:&lt;/strong&gt; For the new rule, configure the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Type, choose HTTPS&lt;/li&gt;
&lt;li&gt;For Source, choose Custom, then in the search field, select the security group that has ServiceASecurityGroup in its name.&lt;/li&gt;
&lt;li&gt;For Description - optional, enter From ServiceA Instances Security Group
&lt;strong&gt;Step 12:&lt;/strong&gt; Choose Save rules.
&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%2F8hrj2z46epvamz3vetqb.png" alt="Image description" width="800" height="262"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 13:&lt;/strong&gt; Open TerminalA and run command "runscanner" &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%2Fwm5szyv0mn0lgx3tnv7b.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%2Fwm5szyv0mn0lgx3tnv7b.png" alt="Image description" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the assessment results, you can see that all security requirements are still met. However, Unwanted Callers #1, #2, and #3 are now blocked by the VPC endpoint security group level which is closer to the source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/security/zero-trust-architectures-an-aws-perspective/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/security/zero-trust-architectures-an-aws-perspective/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Hands-on Lab - Event Driven Architecture with Amazon API Gateway, Amazon EventBridge and AWS Lambda</title>
      <dc:creator>Gurudev Prasad Teketi</dc:creator>
      <pubDate>Tue, 11 Mar 2025 04:54:44 +0000</pubDate>
      <link>https://dev.to/gurudev_prasadteketi/hands-on-lab-event-driven-architecture-with-amazon-api-gateway-amazon-eventbridge-and-aws-lambda-kn2</link>
      <guid>https://dev.to/gurudev_prasadteketi/hands-on-lab-event-driven-architecture-with-amazon-api-gateway-amazon-eventbridge-and-aws-lambda-kn2</guid>
      <description>&lt;h2&gt;
  
  
  Event-driven architecture
&lt;/h2&gt;

&lt;p&gt;is a design pattern where events are utilized to trigger and facilitate communication between decoupled services, making it a common choice for modern applications built with microservices. Serverless architecture, on the other hand, provides a model for building and running applications and services without the need to provision or manage underlying infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objective of the Lab
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Configure an HTTP API on API Gateway to redirect requests to EventBridge.
&lt;/li&gt;
&lt;li&gt;Create event bus rules that match incoming requests and route events to Lambda functions.
&lt;/li&gt;
&lt;li&gt;Process events with Lambda functions and send the processed events back to the event bus as new events.
&lt;/li&gt;
&lt;li&gt;Publish events to the event bus, triggering separate Lambda functions.
&lt;/li&gt;
&lt;li&gt;Lambda functions receive events and post them back to the client application via a WebSocket connection hosted on API Gateway.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  High Level Architecture Diagram
&lt;/h2&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%2Famdo6cqrllhj4zq4og5l.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%2Famdo6cqrllhj4zq4og5l.png" alt="Image description" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implement five Lambda functions using below configurations and code and deploy each lambda&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Function name, enter: make_pizza&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Runtime, select: Python 3.12&lt;/li&gt;
&lt;li&gt;For Change default execution role, choose - lab_lambda_make_pizza&lt;/li&gt;
&lt;li&gt;Environment variable: Key: EVENT_BUS, Value: lab_event_bus&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Function name, enter: cook_pizza&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Runtime, select: Python 3.12&lt;/li&gt;
&lt;li&gt;For Change default execution role, choose - lab_lambda_cook_pizza&lt;/li&gt;
&lt;li&gt;Environment variable: Key: EVENT_BUS, Value: lab_event_bus&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Function name, enter: deliver_pizza&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Runtime, select: Python 3.12&lt;/li&gt;
&lt;li&gt;For Change default execution role,choose - lab_lambda_deliver_pizza&lt;/li&gt;
&lt;li&gt;Environment variable: Key: EVENT_BUS, Value: lab_event_bus&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Function name, enter: websocket_connect&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Runtime, select: Python 3.12&lt;/li&gt;
&lt;li&gt;For Change default execution role,choose - lab_lambda_websocket_connection&lt;/li&gt;
&lt;li&gt;Environment variable: Key: TABLENAME, Value: websocket_connections&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Function name, enter: receive_events&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Runtime, select: Python 3.12&lt;/li&gt;
&lt;li&gt;For Change default execution role,choose - lab_lambda_receive_events&lt;/li&gt;
&lt;li&gt;Environment variable: Key: TABLENAME, Value: receive_events&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&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%2Fwice3684erosks0u7jl0.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%2Fwice3684erosks0u7jl0.png" alt="Image description" width="800" height="400"&gt;&lt;/a&gt;&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%2Fxjftufe85d0g9yrg715t.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%2Fxjftufe85d0g9yrg715t.png" alt="Image description" width="800" height="432"&gt;&lt;/a&gt;&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%2F7el2dwrh9lh7u1tmahy7.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%2F7el2dwrh9lh7u1tmahy7.png" alt="Image description" width="800" height="314"&gt;&lt;/a&gt;&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%2Fifqwf902bfxgm5yq7kla.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%2Fifqwf902bfxgm5yq7kla.png" alt="Image description" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sample Lambda function code for Make Pizza:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import boto3
import os
from botocore.exceptions import ClientError

client = boto3.client('events')

def lambda_handler(event, context):
  try:
    detail = event["detail"]
    detail["item"]["eventtype"]="cook_pizza"
    response = client.put_events(
      Entries=[
        {
          'DetailType': 'eventtype',
          'Detail': json.dumps(detail),
          'EventBusName': os.environ.get('EVENT_BUS'),
          'Source':"make_pizza"
        },
      ]
    )
    print(response)
  except ClientError as err:
    print(err)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sample Lambda function code for Cook Pizza:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import boto3
import os
from botocore.exceptions import ClientError

client = boto3.client('events')

def lambda_handler(event, context):
  try:
    detail = event["detail"]
    detail["item"]["eventtype"]="deliver_pizza"
    response = client.put_events(
      Entries=[
        {
          'DetailType': 'eventtype',
          'Detail': json.dumps(detail),
          'EventBusName': os.environ.get('EVENT_BUS'),
          'Source':"cook_pizza"
        },
      ]
    )
    print(response)
  except ClientError as err:
    print(err)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sample Lambda function code for Deliver Pizza:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import boto3
import os
from botocore.exceptions import ClientError

client = boto3.client('events')

def lambda_handler(event, context):
  try:
    detail = event["detail"]
    detail["item"]["eventtype"]="delivered"
    response = client.put_events(
      Entries=[
        {
          'DetailType': 'eventtype',
          'Detail': json.dumps(detail),
          'EventBusName': os.environ.get('EVENT_BUS'),
          'Source':"deliver_pizza"
        },
      ]
    )
  except ClientError as err:
    print(err)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sample Lambda function code for Websocket Connect:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
import json
import os
from botocore.exceptions import ClientError

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ.get('TABLENAME'))

def lambda_handler(event, context):
  try:
    order_id=event['queryStringParameters']['order_id']
    response = table.put_item(
      Item={
      'order_id': order_id,
      'connection_id': event["requestContext"]["connectionId"]
      })

    return {
      "statusCode": 200,
      "headers": {
      "Content-Type": "application/json"
    }
    }
  except ClientError as err:
    print(err)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sample Lambda function code for Receive Events:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
import json
import os
from botocore.exceptions import ClientError

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ.get('TABLENAME'))
management = boto3.client('apigatewaymanagementapi', endpoint_url=os.getenv('APIGW_ENDPOINT'))

def lambda_handler(event, context):
  try:
    response = table.get_item(
      Key={
      'order_id': event["detail"]['item']['order_id']
      }
    )
    management.post_to_connection(
      Data=json.dumps(event["detail"]),
      ConnectionId=response["Item"]["connection_id"]
    )
  except ClientError as err:
    print(err)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Configure EventBridge&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure the rules with the following settings and select Next .&lt;/li&gt;
&lt;li&gt;Build event pattern screen, under the Creation method section, select  Custom pattern (JSON editor) for the Method. &lt;/li&gt;
&lt;li&gt;Copy and paste the  json object into the Event pattern editor, &lt;/li&gt;
&lt;li&gt;On the Select target(s) page, from the Target 1 section under Target types, choose  AWS service.&lt;/li&gt;
&lt;li&gt;From the Select a target drop-down list, choose Lambda function.&lt;/li&gt;
&lt;li&gt;From the Function drop-down list, choose make_pizza.&lt;/li&gt;
&lt;li&gt;Choose the correspondoing role&lt;/li&gt;
&lt;li&gt;Select Next and select Next again on the Configure tags - optional screen.&lt;/li&gt;
&lt;li&gt;On the Review and create screen, select Create rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Configurations for each rule&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Name: lab_make_pizza_rule&lt;br&gt;
Event bus: lab_event_bus&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "detail": {
    "item": {
      "eventtype": ["make_pizza"]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name: lab_cook_pizza_rule&lt;br&gt;
Event bus: lab_event_bus&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "detail": {
    "item": {
      "eventtype": ["cook_pizza"]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name: lab_deliver_pizza_rule&lt;br&gt;
Event bus: lab_event_bus&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "detail": {
    "item": {
      "eventtype": ["deliver_pizza"]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name: lab_receive_events_rule&lt;br&gt;
Event bus: lab_event_bus&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "detail": {
    "item": {
      "eventtype": ["make_pizza","cook_pizza","deliver_pizza"]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F0rtca8rr76o50tmutmoq.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%2F0rtca8rr76o50tmutmoq.png" alt="Image description" width="800" height="289"&gt;&lt;/a&gt;&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%2Fbc7hqaw8xppya9kl00i1.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%2Fbc7hqaw8xppya9kl00i1.png" alt="Image description" width="800" height="320"&gt;&lt;/a&gt;&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%2Fpcldwwals7osciwgoymj.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%2Fpcldwwals7osciwgoymj.png" alt="Image description" width="800" height="350"&gt;&lt;/a&gt;&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%2Fjlu2e83vxblug73ih3ad.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%2Fjlu2e83vxblug73ih3ad.png" alt="Image description" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Configure API Gateway&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;In the console Choose API Gateway.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;In the Choose an API type select Build from the &lt;strong&gt;HTTP API section&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;API Name&lt;/strong&gt; field, enter &lt;strong&gt;lab_http_api&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select Next until you get to the Review and Create screen, then choose Create.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under the Develop section in the left-hand navigation pane, select Routes, then choose Create .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;POST&lt;/strong&gt; from the drop-down list, and select Create.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Under the Develop select Integrations, choose the POST route link and then select Create and attach an integration&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For Integration target, configure the following options.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration type: Amazon EventBridge&lt;/strong&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration action: PutEvents&lt;/strong&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;EventBridge - PutEvents, configure the following options.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Detail: $request.body&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Detail type: eventtype&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Source: lab_http_api&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Invocation role, copy the APIExecutionRoleARN &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Expand Advanced settings, and configure the remaining fields. Select Create when you are finished.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For Event bus name - optional, enter the event bus ARN you saved in Task 2.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Region - optional.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From the left-hand navigation pane, select CORS and choose Configure in the top right corner.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access-Control-Allow-Origin enter *.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access-Control-Allow-Methods select, POST.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access-Control-Allow-Headers, enter *.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From the navigation menu on the left-hand side of the screen, navigate to the endpoint details screen by selecting API: lab_http_api. Copy and save the Invoke URL from the Stages for lab_http_api section of the API: lab_api screen. The URL is needed in Task 4.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now create the web socket API endpoint, which is used to send events back to the web application. Web socket connections are two-way persistent connections allowing bi-directional communication and are often used by chat applications.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the top of the left-hand navigation pane, select APIs, then select Create API .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the Choose an API type select **Build from the WebSocket API **section.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure the following fields and select Next .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Name field: lab_websocket_api&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Route selection expression: request.body.action&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the Add routes page choose Add &lt;strong&gt;$connect&lt;/strong&gt; route and select Next .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The websocket_connect function invokes when the application establishes the connection. This function saves the connection ID to a DynamoDB table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration type: Lambda.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lambda function websocket_connect.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select Next .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the Add Stages page select Next, followed by Create and deploy .&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select Stages from the left-hand navigation page and choose the Production stage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy and save the WebSocket URL, as it is needed shortly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the Connection URL up to, but do not include the @connections text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate back to the Lambda console and open the receive_events Lambda function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the configuration tab select Environment variables, choose Edit and select Add environment variable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure the variable with the following settings, and select Save when you are finished.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key&lt;/strong&gt;: &lt;code&gt;APIGW_ENDPOINT&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Value&lt;/strong&gt;: the copied &lt;strong&gt;Connection URL&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Testing the ED Architecture&lt;/strong&gt;&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%2Fh4gv5u2dibtsiu2ts2wy.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%2Fh4gv5u2dibtsiu2ts2wy.png" alt="Image description" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
