<?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: Claudio Taverna</title>
    <description>The latest articles on DEV Community by Claudio Taverna (@ctaverna).</description>
    <link>https://dev.to/ctaverna</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%2F1077550%2F05ec5e62-80c4-45c5-a7d4-90dfa2f1b424.jpeg</url>
      <title>DEV Community: Claudio Taverna</title>
      <link>https://dev.to/ctaverna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ctaverna"/>
    <language>en</language>
    <item>
      <title>Stop Drawing, Start Coding: Modern AWS Architecture with LikeC4</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Wed, 17 Dec 2025 11:44:19 +0000</pubDate>
      <link>https://dev.to/aws-builders/stop-drawing-start-coding-modern-aws-architecture-with-likec4-31fc</link>
      <guid>https://dev.to/aws-builders/stop-drawing-start-coding-modern-aws-architecture-with-likec4-31fc</guid>
      <description>&lt;h1&gt;
  
  
  What is the C4 Model?
&lt;/h1&gt;

&lt;p&gt;The &lt;a href="https://c4model.com" rel="noopener noreferrer"&gt;C4 model&lt;/a&gt; is an approach for structuring and visualizing software architecture design. It was created by &lt;a href="https://simonbrown.je" rel="noopener noreferrer"&gt;Simon Brown&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
It aims to provide different levels of detail, much like maps: from a high-level view down to the code.&lt;br&gt;
It stands for Context, Containers, Components, and Code.&lt;br&gt;
These are the four hierarchical levels of abstraction:&lt;/p&gt;
&lt;h3&gt;
  
  
  Context (Level 1)
&lt;/h3&gt;

&lt;p&gt;The Big Picture. Shows the software system and its relationships to users and other systems.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus&lt;/strong&gt;: Why the system exists and who it interacts with.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Audience&lt;/strong&gt;: Technical and non-technical stakeholders (everyone).&lt;/p&gt;
&lt;h3&gt;
  
  
  Containers (Level 2)
&lt;/h3&gt;

&lt;p&gt;The Deployable Units. Breaks down the system into its major building blocks (e.g., web app, mobile app, database, microservice, serverless function).&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus&lt;/strong&gt;: How responsibilities are distributed across separate running processes.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Audience&lt;/strong&gt;: Technical people (Architects, Developers, Ops).  &lt;/p&gt;
&lt;h3&gt;
  
  
  Components (Level 3)
&lt;/h3&gt;

&lt;p&gt;The Modules inside a Container. Zooms into a single Container to show its internal structure.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus&lt;/strong&gt;: How the code is organized into logical groups (e.g., services, repositories, controllers).&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Audience&lt;/strong&gt;: Developers and Architects.  &lt;/p&gt;
&lt;h3&gt;
  
  
  Code (Level 4)
&lt;/h3&gt;

&lt;p&gt;The Deepest Detail. Focuses on the implementation details, often using UML, ERDs, or generated code-level diagrams.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Focus&lt;/strong&gt;: Specific classes, interfaces, and functions.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Audience&lt;/strong&gt;: Developers.  &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%2F0knes01f8rj2h6os87b4.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%2F0knes01f8rj2h6os87b4.png" alt="Overview of C4 layers" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key advantages of this approach:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardization&lt;/strong&gt;: A common language for discussing architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive Detail&lt;/strong&gt;: Stakeholders can choose the level of detail they need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarity&lt;/strong&gt;: It focuses on boxes and lines (simple notation) rather than complex UML shapes, making it easier to read.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Advantages of "Diagram as Code"
&lt;/h1&gt;

&lt;p&gt;When documenting complex modern systems, treating your architecture diagrams as code (Diagram as Code or "D.a.C.") offers significant benefits over traditional drawing tools.&lt;/p&gt;
&lt;h2&gt;
  
  
  Speed and Efficiency
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster Creation&lt;/strong&gt;: You define the architecture using a text-based language instead of dragging and dropping shapes. This is typically much quicker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rapid Updates&lt;/strong&gt;: Changes to the architecture can be implemented and propagated across multiple diagrams instantly by editing a single source file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Friction&lt;/strong&gt;: No need to open external desktop applications. Diagrams can be edited directly in your IDE (e.g., VS Code).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Consistency and Accuracy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single Source of Truth&lt;/strong&gt;: The architecture definition is in one place. Every diagram level (Context, Container, Component) is guaranteed to be consistent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Stale Diagrams&lt;/strong&gt;: By integrating the diagram source files into your version control system, the diagrams are versioned alongside the code they represent. They are always up-to-date with the codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Review&lt;/strong&gt;: Diagrams can be reviewed in a Pull Request just like source code. This enforces architectural rigor and collaboration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ADR friendly&lt;/strong&gt;: Keeping diagrams updated is a great way to help you identify when a new &lt;a href="https://dev.to/ctaverna/a-practical-overview-on-architecture-decision-records-adr-d7d"&gt;Architectural Decision Record&lt;/a&gt; (ADR) should be created. Once your diagrams are aligned, every PR in the code of your diagrams should &lt;em&gt;probably&lt;/em&gt; be the consequence of a new ADR. And viceversa, of course.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Culture and Developer Ownership
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Closer to Code&lt;/strong&gt;: Defining architecture in a text file (rather than a GUI tool) makes documentation feel like code. This lowers the barrier to entry for developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increased Ownership&lt;/strong&gt;: Developers are comfortable with text files, IDEs, and Git. This integration helps the development team take greater ownership of the architecture diagrams, ensuring they are maintained actively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source of Truth Shift&lt;/strong&gt;: The architectural definition lives within the repository, making it a first-class citizen alongside the source code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Maintainability and Automation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version Control&lt;/strong&gt;: You get a full history of architectural changes, allowing you to track who changed what and when. You can easily revert to previous versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text-Based Diff&lt;/strong&gt;: Comparing architectural versions becomes simple; Git shows the specific lines of text that changed, not just a binary file difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt;: The text files can be processed and rendered as part of your CI/CD pipeline. This means the documentation is automatically published upon every merge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Searchable&lt;/strong&gt;: The architectural definition is plain text, making it easily searchable by developers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Why LikeC4 is a Good Option
&lt;/h1&gt;

&lt;p&gt;As a software architect working extensively with AWS and complex distributed systems, I have found that certain tools dramatically improve documentation efficiency.&lt;br&gt;&lt;br&gt;
This is my opinionated list of the core advantages that make &lt;a href="https://likec4.dev" rel="noopener noreferrer"&gt;LikeC4&lt;/a&gt; the preferred choice for C4 modeling.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Relationship Focus&lt;/strong&gt;: Core definition focuses on explicit relationships, accurately mapping communication paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Web Viewer&lt;/strong&gt;: Generates a fast, modern web application for viewing. Stakeholders can easily navigate and drill down through C4 levels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy Deployment&lt;/strong&gt;: The output is a static web viewer, perfect for deployment to common cloud services like AWS S3/CloudFront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddable&lt;/strong&gt;: Simple to integrate into existing documentation (Confluence, GitBook, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Layout&lt;/strong&gt;: Architects focus only on the elements and relationships; LikeC4 handles the complex visual arrangement, saving time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IntelliSense/Validation&lt;/strong&gt;: Integrates with IDEs (e.g., VS Code) for auto-completion and syntax checking, minimizing errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tags and Styling&lt;/strong&gt;: Easy tagging (e.g., tag:aws-lambda) allows for custom styling, enhancing readability and technology recognition.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Let's Build an Example of an Architecture Documentation with LikeC4
&lt;/h1&gt;

&lt;p&gt;To truly appreciate the power of Diagrams as Code and LikeC4, let's move from theory to practice.&lt;br&gt;&lt;br&gt;
We will document a common, modern, cloud-native architecture using LikeC4's DSL (Domain Specific Language).&lt;/p&gt;

&lt;p&gt;Imagine we are documenting a small, serverless e-commerce application running on AWS. The system needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow customers to browse products and place orders via a frontend.&lt;/li&gt;
&lt;li&gt;Provide a backend API for processing transactions.&lt;/li&gt;
&lt;li&gt;Store data in a highly scalable and resilient manner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This scenario is perfect for illustrating the C4 Model's multiple views and LikeC4's ability to reuse component definitions.&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%2Fkh1k7iqhmk7iklf9r7lc.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%2Fkh1k7iqhmk7iklf9r7lc.png" alt="Brainstorming handmade diagram" width="800" height="572"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Setting up the Project Structure
&lt;/h2&gt;

&lt;p&gt;To build a documentation that scales, we shouldn't put everything in one file. We will split our architecture into logical files: global definitions, our specific system, and the views.  &lt;/p&gt;

&lt;p&gt;My suggestion is to use VSCode and the great &lt;a href="https://marketplace.visualstudio.com/items?itemName=likec4.likec4-vscode" rel="noopener noreferrer"&gt;LikeC4 Extension&lt;/a&gt; that will enable you to see the diagram preview in real time inside VSCode itself.  &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%2Fmitdavcrvygoctu94d4c.jpg" 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%2Fmitdavcrvygoctu94d4c.jpg" alt="The VSCode LikeC4 extension" width="640" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we create a basic project structure. We will place all our LikeC4 definition files (using the .c4 extension) inside a dedicated&lt;br&gt;
&lt;/p&gt;

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



``` bash
my-git-repository/
├── docs/
|   ├── specs.c4             # Define the vocabulary
│   ├── workspace.c4         # Define how the company projects are organized
│   ├── ecommerce/
|       ├── model.c4         # Define the system structure and relations
│       ├── views.c4         # Define the views that we need
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Defining the Language
&lt;/h2&gt;

&lt;p&gt;First, we define our "vocabulary." This ensures consistent styling across all diagrams.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// docs/specs.c4

specification &lt;span class="o"&gt;{&lt;/span&gt;
  element actor &lt;span class="o"&gt;{&lt;/span&gt;
    style &lt;span class="o"&gt;{&lt;/span&gt; shape person &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
  element system
  element container
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: The corporate landscape
&lt;/h2&gt;

&lt;p&gt;Here we define the "Global Context."&lt;br&gt;&lt;br&gt;
This includes, for example, an External Warehouse System that provides stock data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// docs/workspace.c4

// External systems
model &lt;span class="o"&gt;{&lt;/span&gt;

  // External System defined at the corporate level
  warehouse_api &lt;span class="o"&gt;=&lt;/span&gt; system &lt;span class="s2"&gt;"Warehouse Management System"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    description &lt;span class="s2"&gt;"External legacy system providing stock availability."&lt;/span&gt;

    style &lt;span class="o"&gt;{&lt;/span&gt;
      color muted
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: The E-commerce Architecture
&lt;/h2&gt;

&lt;p&gt;Now, we define our internal system.&lt;br&gt;&lt;br&gt;
Notice, as a suggested best practice, how relationships are defined inside the source element (the active actor).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// docs/ecommerce/model.c4

model &lt;span class="o"&gt;{&lt;/span&gt;

  customer &lt;span class="o"&gt;=&lt;/span&gt; actor &lt;span class="s2"&gt;"Customer"&lt;/span&gt; &lt;span class="s2"&gt;"A retail customer"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      description &lt;span class="s2"&gt;"A user who browses and purchases products via the e-commerce platform."&lt;/span&gt;

      // High level relationship to the ecommerce system
      -&amp;gt; ecommerce &lt;span class="s2"&gt;"Uses"&lt;/span&gt;
      // Define how the user enters the system
      -&amp;gt; ecommerce.static_assets &lt;span class="s2"&gt;"Browses via Browser"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;


  ecommerce &lt;span class="o"&gt;=&lt;/span&gt; system &lt;span class="s2"&gt;"E-commerce Platform"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    description &lt;span class="s2"&gt;"Allows customers to browse and purchase products online."&lt;/span&gt;

    // High level relationship to external warehouse system
    -&amp;gt; warehouse_api &lt;span class="s2"&gt;"Checks stock availability from"&lt;/span&gt;

    // 1. Frontend Layer
    spa &lt;span class="o"&gt;=&lt;/span&gt; container &lt;span class="s2"&gt;"Web Portal"&lt;/span&gt; &lt;span class="s2"&gt;"React Application"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      -&amp;gt; api_gateway &lt;span class="s2"&gt;"Sends requests to"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    static_assets &lt;span class="o"&gt;=&lt;/span&gt; container &lt;span class="s2"&gt;"S3 Bucket"&lt;/span&gt; &lt;span class="s2"&gt;"Static Web Hosting"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      -&amp;gt; spa &lt;span class="s2"&gt;"Serves JS/Assets to"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    // 2. API Layer
    api_gateway &lt;span class="o"&gt;=&lt;/span&gt; container &lt;span class="s2"&gt;"API Gateway"&lt;/span&gt; &lt;span class="s2"&gt;"REST Interface"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      -&amp;gt; order_service &lt;span class="s2"&gt;"Routes requests to"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    // 3. Logic Layer
    order_service &lt;span class="o"&gt;=&lt;/span&gt; container &lt;span class="s2"&gt;"Order Lambda"&lt;/span&gt; &lt;span class="s2"&gt;"Node.js / TypeScript"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      description &lt;span class="s2"&gt;"Handles order placement and validation."&lt;/span&gt;

      -&amp;gt; db &lt;span class="s2"&gt;"Reads/Writes data"&lt;/span&gt;
      -&amp;gt; inventory_proxy &lt;span class="s2"&gt;"Requests stock check"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    inventory_proxy &lt;span class="o"&gt;=&lt;/span&gt; container &lt;span class="s2"&gt;"Inventory Lambda"&lt;/span&gt; &lt;span class="s2"&gt;"Python"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      description &lt;span class="s2"&gt;"Integrates with external warehouse."&lt;/span&gt;

      // Relationship to external system defined here &lt;span class="o"&gt;(&lt;/span&gt;the &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      -&amp;gt; warehouse_api &lt;span class="s2"&gt;"Fetches stock levels via REST API"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    // 4. Data Layer
    db &lt;span class="o"&gt;=&lt;/span&gt; container &lt;span class="s2"&gt;"Products &amp;amp; Orders"&lt;/span&gt; &lt;span class="s2"&gt;"DynamoDB"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      description &lt;span class="s2"&gt;"Stores product catalog and order information."&lt;/span&gt;
      style &lt;span class="o"&gt;{&lt;/span&gt;
        shape cylinder
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Visualizing the Architecture
&lt;/h2&gt;

&lt;p&gt;Finally, we create some sample views.&lt;br&gt;&lt;br&gt;
LikeC4 allows for very clean view definitions with powerful filtering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// docs/ecommerce/views.c4

views &lt;span class="o"&gt;{&lt;/span&gt;

  // 1. System Context View
  view index &lt;span class="o"&gt;{&lt;/span&gt;
    title &lt;span class="s2"&gt;"System Context - E-commerce Platform"&lt;/span&gt;
    include &lt;span class="k"&gt;*&lt;/span&gt;
    exclude ecommerce.&lt;span class="k"&gt;*&lt;/span&gt; // Only show the high-level boundaries
  &lt;span class="o"&gt;}&lt;/span&gt;

  // 2. Backend Container View &lt;span class="o"&gt;(&lt;/span&gt;AWS focus&lt;span class="o"&gt;)&lt;/span&gt;
  view containerView of ecommerce &lt;span class="o"&gt;{&lt;/span&gt;
    title &lt;span class="s2"&gt;"AWS Backend Architecture"&lt;/span&gt;

    // Include backend containers within ecommerce plus the external warehouse

    include ecommerce
    include ecommerce.api_gateway
    include ecommerce.order_service
    include ecommerce.inventory_proxy
    include ecommerce.db
    include warehouse_api

    autoLayout LeftRight
  &lt;span class="o"&gt;}&lt;/span&gt;

  // 3. Specific focus on Inventory Integration
  view inventoryFlow of ecommerce &lt;span class="o"&gt;{&lt;/span&gt;
    title &lt;span class="s2"&gt;"Integration Detail: Inventory Check"&lt;/span&gt;
    include 
      ecommerce.order_service,
      ecommerce.inventory_proxy,
      warehouse_api
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  LikeC4 CLI and local testing
&lt;/h1&gt;

&lt;p&gt;If you have used VSCode and LikeC4 extension you have already seen how it works in the live preview inside VSCode, but now it's time to see the real power of this solution.&lt;/p&gt;

&lt;p&gt;With just these four files in our local directory, we have defined a full, navigable C4 model.&lt;br&gt;
Now, let's install LikeC4 CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @likec4/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check LikeC4 binary installation and version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;likec4 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally start a local dev server to preview LikeC4 views:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;likec4 start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the local server and open your browser at the address &lt;code&gt;http://localhost:5173&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
The first web version of our DaC is now available and ready to be explored:&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%2Fckkk0a1f62pey7wmox25.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%2Fckkk0a1f62pey7wmox25.png" alt="The main view of your diagrams" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to play with your local deployment, enjoying the features of LikeC4.&lt;br&gt;&lt;br&gt;
This is just a first release of our diagrams, in future articles we will improve the styling and will deep dive into best practices, but I think that for now it's enough to understand the power.&lt;/p&gt;

&lt;p&gt;If everything is like expected, we are finally ready to deploy our brand new DaC on AWS.&lt;/p&gt;
&lt;h1&gt;
  
  
  Shipping your Architecture Docs to AWS (The Manual Way)
&lt;/h1&gt;

&lt;p&gt;Now that we have our .c4 files ready, it’s time to move from "Code" to "Cloud".&lt;br&gt;&lt;br&gt;
In this section, we will manually build our documentation and host it on AWS.&lt;br&gt;&lt;br&gt;
We’ll use Amazon S3 for storage and Amazon CloudFront as our Content Delivery Network (CDN) to serve our diagrams securely via HTTPS.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Build the Static Site Locally
&lt;/h2&gt;

&lt;p&gt;First, we need to transform our DSL into a set of web-ready files. LikeC4 CLI tool can generate an optimized Single Page Application.&lt;/p&gt;

&lt;p&gt;Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate the static site in the 'dist' folder&lt;/span&gt;
likec4 build &lt;span class="nt"&gt;-o&lt;/span&gt; ./dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the ./dist folder, you will now see an index.html file and an /assets folder.&lt;br&gt;
This is your entire architecture portal, ready to be hosted.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Create a Private S3 Bucket
&lt;/h2&gt;

&lt;p&gt;Even though we are hosting a website, we won't use the "S3 Static Website Hosting" feature.&lt;br&gt;&lt;br&gt;
Instead, we’ll keep the bucket private and serve the content exclusively through CloudFront for better security and performance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log into the AWS Management Console, navigate to S3 and create a new bucket.&lt;/li&gt;
&lt;li&gt;Choose a bucket name in your preferred region&lt;/li&gt;
&lt;li&gt;Object Ownership: Leave as "&lt;strong&gt;ACLs disabled.&lt;/strong&gt;"&lt;/li&gt;
&lt;li&gt;Block Public Access settings: Keep "&lt;strong&gt;Block all public access&lt;/strong&gt;" checked.&lt;/li&gt;
&lt;li&gt;Bucket versioning: not needed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 3: Upload your Files
&lt;/h2&gt;

&lt;p&gt;Now open your newly created bucket.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Upload&lt;/li&gt;
&lt;li&gt;Drag and drop all the contents of your local dist folder (not the folder itself, just the files inside) into the S3 console.&lt;/li&gt;
&lt;li&gt;Confirm and upload the files (~5MB)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 4: Configure CloudFront for Secure Delivery
&lt;/h2&gt;

&lt;p&gt;Now, we’ll set up CloudFront to act as the gateway to our private S3 bucket.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the CloudFront console and click Create distribution (Free plan is ok).&lt;/li&gt;
&lt;li&gt;Type a name and continue&lt;/li&gt;
&lt;li&gt;Origin type: S3&lt;/li&gt;
&lt;li&gt;S3 Origin: Select your S3 bucket from the dropdown&lt;/li&gt;
&lt;li&gt;Default root object: "index.html"&lt;/li&gt;
&lt;li&gt;Private S3 bucket access: Leave the default settings&lt;/li&gt;
&lt;li&gt;Security options: Leave the default settings&lt;/li&gt;
&lt;li&gt;Cache settings: Leave the default settings&lt;/li&gt;
&lt;li&gt;Click Create distribution and wait the deployment to be completed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 5: View Your Architecture
&lt;/h2&gt;

&lt;p&gt;It will take a few minutes for the CloudFront distribution to deploy (Status: "Enabled").&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find and copy your distribution domain name (e.g., abcdd123456789.cloudfront.net).&lt;/li&gt;
&lt;li&gt;Paste it into your browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Congratulations! You now have a professional, secure, and globally available architecture documentation site.&lt;br&gt;&lt;br&gt;
Your stakeholders can now navigate through the layers of your AWS system, from the high-level Context down to specific Lambda components.&lt;/p&gt;

&lt;p&gt;Here is my &lt;a href="https://d39cysq6ourkgr.cloudfront.net" rel="noopener noreferrer"&gt;LikeC4 demo deployment&lt;/a&gt; if you want to take a look.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6: Embedding the Diagram (The "Live" Documentation)
&lt;/h2&gt;

&lt;p&gt;Now that our architecture is live on CloudFront, we can do something powerful: embed it directly into our technical documentation, wiki, or even this blog article.&lt;/p&gt;

&lt;p&gt;Unlike a static PNG export, embedding the interactive viewer allows your readers to zoom, pan, and click to navigate between the views without leaving your page.&lt;br&gt;&lt;br&gt;
Plus, since it points to your CloudFront URL, whenever you update the code and redeploy, your blog post will be automatically updated with the latest architecture!&lt;/p&gt;

&lt;p&gt;Here is the simplest way to do it using a standard HTML iframe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://abcd123456789.cloudfront.net/index.html?view=myView"&lt;/span&gt; 
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; 
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"500px"&lt;/span&gt; 
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"border: 1px solid #e5e7eb; border-radius: 8px;"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Result
&lt;/h3&gt;

&lt;p&gt;The DEV platform does not allow iframes or any javascript, but you can find a working example of this live integration in the &lt;a href="https://ctaverna.github.io/likec4-deploy-aws/#the-result" rel="noopener noreferrer"&gt;original post&lt;/a&gt; from my blog.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;As already said, there are several advantages with this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactivity&lt;/strong&gt;: Stakeholders can explore the model themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single Source of Truth&lt;/strong&gt;: You don't need to copy-paste screenshots every time you rename, add or update something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual Deep Links&lt;/strong&gt;: You can link directly to specific views (e.g., ?view=myView) to highlight specific discussions in your online documentation, wiki or article.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;Manual deployment is great for learning, but in real-world "Architecture as Code" or "Diagram as Code" workflows, we want this to happen automatically whenever we commit changes.&lt;br&gt;&lt;br&gt;
This blog post was just the first taste of LikeC4. In the next articles, we will deep dive into all LikeC4 features, improve the styling, the files structure, and automate the entire Diagram as Code process using tools like Terraform and GitHub Actions.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>diagrams</category>
      <category>architecture</category>
      <category>c4model</category>
    </item>
    <item>
      <title>OpenSearch metrics challenge: can you spot the performance flaw?</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Tue, 07 Jan 2025 13:14:52 +0000</pubDate>
      <link>https://dev.to/aws-builders/opensearch-metrics-challenge-can-you-spot-the-performance-flaw-3imd</link>
      <guid>https://dev.to/aws-builders/opensearch-metrics-challenge-can-you-spot-the-performance-flaw-3imd</guid>
      <description>&lt;p&gt;&lt;strong&gt;How a deep analysis of metrics can reveal a hidden issue&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you run an OpenSearch cluster, as with all other cloud services, performance and cost optimization are a must.&lt;br&gt;
By carefully analyzing performance metrics, we can uncover hidden issues and significantly boost the efficiency of our systems.&lt;/p&gt;

&lt;p&gt;In this article, we will explore a specific real world case involving an error in the code, and how a deep analysis of the metrics led to a surprising revelation and a subsequent performance improvement.&lt;/p&gt;

&lt;p&gt;To make the reading experience a little bit more interactive, instead of simply starting from the ending revelation, let's try to live the same experience that our team faced while managing the system.&lt;br&gt;
  If you don't want to play this little game, go ahead and you'll find the interesting details in the paragraph "The hidden anomaly" below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project context
&lt;/h2&gt;

&lt;p&gt;The OpenSearch cluster was used in our system for different application features, but mostly as a regular document store, with documents added and updated at a high frequency and queries executed mostly by Id.&lt;br&gt;
The indexes were configured with 5 shards and 1 replica, and all documents had a consistent schema.&lt;br&gt;
As often happens, the adoption of OpenSearch started with a single small feature and gradually more features were added.&lt;/p&gt;

&lt;p&gt;In the previous months, the growing performance requirements and the high cpu usage brought the original 3-node cluster to grow up to 12 nodes (3 master nodes + 9 data nodes) and worryingly it seemed that the performance couldn't improve anymore, even adding more nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three high-level metrics
&lt;/h2&gt;

&lt;p&gt;This is the recurring daily shape that we see in many metrics, due to the load pattern that follows the business hours, with the lunch break in the middle of the hill.&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%2F50rgxdtczhdo5ov1wxfs.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%2F50rgxdtczhdo5ov1wxfs.png" alt="Recurring shape" width="400" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS offers a lot of different metrics, but in this article, we will focus on just 3 of them.&lt;br&gt;
All the following charts represent the average value for each metric in a time range of 3 days.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Indexing Data Rate&lt;/strong&gt; (IndexingRate): &lt;em&gt;The number of indexing operations per minute.&lt;/em&gt;&lt;br&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%2F9uo9h3f4n29vpoh2iawj.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%2F9uo9h3f4n29vpoh2iawj.png" alt="Indexing rate" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP requests&lt;/strong&gt; by response code: &lt;em&gt;The number of requests to a domain.&lt;/em&gt;&lt;br&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%2Fgtt893j90rxnb4rx0v3z.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%2Fgtt893j90rxnb4rx0v3z.png" alt="HTTP requests" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Search rate&lt;/strong&gt; (SearchRate): &lt;em&gt;The number of search operations per minute for all shards in the cluster.&lt;/em&gt;&lt;br&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%2Fu2aqff0kxv4pa2uur7rl.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%2Fu2aqff0kxv4pa2uur7rl.png" alt="Search rate" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Challenge:&lt;/strong&gt; Looking at these metrics and knowing the context given before, can you see anything strange in these metrics?&lt;/p&gt;

&lt;h2&gt;
  
  
  Need a little help?
&lt;/h2&gt;

&lt;p&gt;If you have already found the anomaly, congratulations, you're a true master of OpenSearch monitoring!&lt;br&gt;
Otherwise, try to read the following clues, which will gradually lead you to the solution.&lt;br&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%2F4a9krn9d1hjx96s3v2qh.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%2F4a9krn9d1hjx96s3v2qh.png" alt="Question" width="180" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Clue n.1
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The anomaly is not related to the shape of the metric chart.
Okay, this clue is important but probably it doesn't help so much.
Let's go on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Clue n.2
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Focus on the metric SearchRate.
Anything yet?&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Clue n.3
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Compare the metric SearchRate and HTTPRequests.
Uhm... yes, the unbalance between the morning hill and the afternoon hill is strange, but here it's not meaningful.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Clue n.4
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Think about this detail given in the context: "queries are mostly executed by Id".
Still nothing?&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Clue n.5
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Consider the fact that &lt;em&gt;searches per Id&lt;/em&gt; should actually contribute to the &lt;em&gt;SearchRate&lt;/em&gt; metric, &lt;strong&gt;but&lt;/strong&gt; should be performed &lt;strong&gt;only on the shard&lt;/strong&gt; that is relevant, not all shards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The hidden anomaly
&lt;/h2&gt;

&lt;p&gt;The interesting point here, that has been in front of our eyes but remained unnoticed for months, is &lt;strong&gt;the scale&lt;/strong&gt; of the SearchRate metric.&lt;/p&gt;

&lt;p&gt;The shape of the metric was pretty much regular and well overlapped with the others, but we missed an important detail.&lt;br&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%2Fryam4p37ugpqaj3t3hls.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%2Fryam4p37ugpqaj3t3hls.png" alt="Idea" width="180" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at the top values during peak hours for these metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8,000 HTTP requests per minute&lt;/li&gt;
&lt;li&gt;80,000 searches per minute&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%2F1bagblp6zeb4n4horbfu.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%2F1bagblp6zeb4n4horbfu.png" alt="Metrics comparison" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why is the search rate ~10 times higher than the request rate?&lt;/p&gt;

&lt;p&gt;As said the indexes are configured to have 5 shards, so for each HTTP request there should be &lt;strong&gt;at most 5 search operations&lt;/strong&gt;, one for each shard of the index, but here we can see an unexplainable 10x ratio.&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%2F860zynqx7g00ctnmlr50.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%2F860zynqx7g00ctnmlr50.png" alt="Think" width="180" height="171"&gt;&lt;/a&gt;&lt;br&gt;
Also, if we think that these searches are not full-text searches but are simple &lt;em&gt;per-Id&lt;/em&gt; queries, for each request we should see &lt;strong&gt;just one search&lt;/strong&gt;, because after hashing the document id and applying the internal routing algorithm, we should expect the OpenSearch engine to go straight and ask for the document only to the relevant shard.&lt;/p&gt;

&lt;p&gt;So, what are we missing here?&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifying the Root Cause
&lt;/h2&gt;

&lt;p&gt;Our investigation revealed that the root cause of the anomaly was due to how our queries were executed.&lt;br&gt;
The queries were executed by our components leveraging a common library shared by many of our Java micro-services. Because of a bug introduced years before, probably when the engine was still ElasticSearch, &lt;strong&gt;the queries were always run without specifying the index pattern&lt;/strong&gt;.&lt;br&gt;
The caller software was regularly executing each query specifying &lt;strong&gt;both&lt;/strong&gt; the document Id &lt;strong&gt;and&lt;/strong&gt; the index pattern, but the index pattern parameter was routinely lost inside the shared library and the resulting query that was sent to OpenSearch was lacking of it.&lt;/p&gt;

&lt;p&gt;The default behavior of OpenSearch, not so obvious for people thinking with a traditional relational data approach, is that if you don't specify the index name or the index pattern, the search operation is performed on the whole cluster.&lt;br&gt;
This way, every single query, instead of being addressed to the right index and to the right shard, was addressed to each index with a compatible schema, with a significant and unintended consumption of resources. &lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the Issue
&lt;/h2&gt;

&lt;p&gt;Once we identified the root cause, we corrected our code to ensure that the index pattern was always specified in the queries.&lt;br&gt;
This small yet crucial change led to a significant improvement in performance and resource utilization. &lt;/p&gt;

&lt;p&gt;Let's see what happened when we deployed the new software version of the shared library that fixed the error.&lt;/p&gt;

&lt;h4&gt;
  
  
  HTTP Requests
&lt;/h4&gt;

&lt;p&gt;This is the &lt;strong&gt;HTTP requests&lt;/strong&gt; metric, where we can see the usual system load and the interruption created by the maintenance window while the deployment was made:&lt;br&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%2F64slxkblongajafafngi.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%2F64slxkblongajafafngi.png" alt="HTTP requests" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  SearchRate
&lt;/h4&gt;

&lt;p&gt;Immediately after the deploy, we could see an &lt;strong&gt;amazing drop&lt;/strong&gt; in the number of searches operations:&lt;br&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%2Fvk0f3qn0tx4e6fx7gpg5.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%2Fvk0f3qn0tx4e6fx7gpg5.png" alt="Search rate" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Average Request Time
&lt;/h4&gt;

&lt;p&gt;The highest level representation of the real impact of the perfromance improvement, as perceived by the caller clients, is the request time measured by DataDog APM.&lt;br&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%2F7b91puwmp9h4zpuaa3sp.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%2F7b91puwmp9h4zpuaa3sp.png" alt="Average Request Time" width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  ThreadPoolSearchQueue
&lt;/h4&gt;

&lt;p&gt;This is another metric that highlighted very well the load reduction on the cluster. It represents &lt;em&gt;the number of queued tasks in the search thread pool&lt;/em&gt;.&lt;br&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%2Fdy6wwyvy6uagyjvjkk7d.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%2Fdy6wwyvy6uagyjvjkk7d.png" alt="ThreadPool Search Queue" width="800" height="205"&gt;&lt;/a&gt;&lt;br&gt;
The documentation also states that &lt;em&gt;if the queue size is consistently high, consider scaling your cluster&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling-in the cluster
&lt;/h2&gt;

&lt;p&gt;Of course, given the situation, we could evaluate an unexpected horizontal scale-in of our cluster, in order to reduce our cloud cost. The next consequent step was a progressive reduction of the number of nodes in the cluster.&lt;br&gt;
This is what happened during a 2 weeks time window in which we gradually reduced the number of data nodes from 9 to 6, and eventually to 3.&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%2F2qhobe2my8her0960sug.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%2F2qhobe2my8her0960sug.png" alt="OpenSearch nodes reduction" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is the last chart, where we can see that the impact on the cluster &lt;strong&gt;CPU utilization&lt;/strong&gt; was almost unnoticeable, even if we were executing the queries on a cluster whose data nodes pool had been reduced by 66%. &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%2Fy8mfrs0thvyqe6eb5fee.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%2Fy8mfrs0thvyqe6eb5fee.png" alt="OpenSearch CPU utilization" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;This experience taught us several valuable lessons.&lt;/p&gt;

&lt;p&gt;Firstly, specifying the index name or the index pattern in search queries per Id is crucial to prevent waste of resources.&lt;br&gt;
Quite an obvious concept, but not so easily noticeable if the lack of index reference has been introduced by mistake.&lt;br&gt;
OpenSearch's good performances can hide very well the additional load created by unoptimized queries.&lt;/p&gt;

&lt;p&gt;Secondly, monitoring performance metrics can reveal hidden issues that might otherwise go unnoticed and can have very bad consequences. But this kind of monitoring requires great attention to detail, a deep understanding of metrics and logs, and deepening down into hundreds of metrics is very time-consuming.&lt;br&gt;
It is really easy to be deceived by superficial analysis.&lt;/p&gt;

&lt;p&gt;Lastly, as a developer, never forget that the impact of a simple bug due to distraction, like forgetting to pass an optional parameter, can cost so much more than you can imagine.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>devops</category>
      <category>aws</category>
      <category>performance</category>
    </item>
    <item>
      <title>Hands-on introduction to Apache Iceberg</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Mon, 28 Oct 2024 22:12:39 +0000</pubDate>
      <link>https://dev.to/aws-builders/hands-on-introduction-to-apache-iceberg-2hd2</link>
      <guid>https://dev.to/aws-builders/hands-on-introduction-to-apache-iceberg-2hd2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Lessons learned through a PoC for a challenging use-case&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Apache Iceberg format is a great solution that enables a variety of interesting options for cost-effective data collection.&lt;br&gt;
The learning curve can be quite steep, but with the right knowledge and some starting tips can become a very interesting tool.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Iceberg
&lt;/h2&gt;

&lt;p&gt;Apache &lt;a href="https://iceberg.apache.org" rel="noopener noreferrer"&gt;Iceberg&lt;/a&gt; is an open source table format, designed to store large data tables. It is based on simple files that can be stored anywhere, and works well with data processing and analytics engines like Apache Spark, Hive, Trino and similar tools.&lt;br&gt;&lt;br&gt;
Iceberg tables are designed to be scalable, durable, and high-performing.&lt;br&gt;&lt;br&gt;
The format supports a variety of data formats, including JSON, CSV, and Parquet.&lt;br&gt;&lt;br&gt;
Tables can be partitioned, which allows for more efficient storage and retrieval of data using also parallel processing, if needed.&lt;/p&gt;
&lt;h3&gt;
  
  
  How it works and how data is stored
&lt;/h3&gt;

&lt;p&gt;The basic concept on which Iceberg is based is dividing the dataset into smaller chunks, each of which is represented as a separate &lt;strong&gt;data file&lt;/strong&gt; (the lower layer in the diagram below).&lt;br&gt;&lt;br&gt;
Each data file is assigned a unique ID, and each file can be linked by one or more &lt;strong&gt;manifest files&lt;/strong&gt;, each one containing one row for each underlying data file, together with its metrics.&lt;br&gt;&lt;br&gt;
Groups of manifest files are then stored into &lt;strong&gt;manifest list files&lt;/strong&gt;, each one containing references to multiple manifest files, together with stats and data file counts.&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%2Fkwxl6dere2pv8bmx7bez.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%2Fkwxl6dere2pv8bmx7bez.png" alt="Iceberg diagram" width="445" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These layers of metadata are used like a sort of index to avoid reading manifests and data files that are not required for an operation.&lt;br&gt;
Both storage usage and retrieval times are reduced.&lt;/p&gt;

&lt;p&gt;Let's look at how it works with a couple of examples:&lt;/p&gt;

&lt;p&gt;If a user wants to read a specific record in a dataset, they can specify the record's ID and Iceberg will only read some metadata files and then retrieve only the data file that contains the record.&lt;/p&gt;

&lt;p&gt;If a query needs to perform a count operation, or any other aggregation value like min, max, average and so on, it is likely that only metadata files will be read, avoiding an expensive full table-scan. This is because, if properly configured, the metadata layer itself stores statistics for each data column in each data file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Other advantages of Iceberg
&lt;/h2&gt;

&lt;p&gt;Apache Iceberg offers many advantages that makes it a great solution for data warehouses and data lakes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Schema evolution
&lt;/h3&gt;

&lt;p&gt;When the schema of the data is updated, these changes are just &lt;strong&gt;&lt;em&gt;metadata changes&lt;/em&gt;&lt;/strong&gt;, and existing data files don't need to be rewritten to consolidate the update.&lt;/p&gt;
&lt;h3&gt;
  
  
  Hidden partitioning
&lt;/h3&gt;

&lt;p&gt;Iceberg supports a variety of partitioning strategies, and tables can be partitioned on multiple columns. Partitioning offers the benefit of more efficient querying and updating of the data, because it can quickly locate the subset of data files that is relevant.&lt;br&gt;&lt;br&gt;
In addition to this, Iceberg handles &lt;em&gt;hidden&lt;/em&gt; partitioning, meaning that the client doesn't need to be aware of it, and can normally query the data, without explicitly reference partition-dedicated columns.&lt;br&gt;&lt;br&gt;
Also, if you need for any reason to change the partitioning, you can fix your table without having to plan an expensive migration, because the partition scheme can change over time, and Iceberg can handle this without ever touching the existing data.&lt;/p&gt;
&lt;h3&gt;
  
  
  Time travel
&lt;/h3&gt;

&lt;p&gt;Iceberg stores data as a series of &lt;em&gt;snapshots.&lt;/em&gt;. &lt;br&gt;
Each one is a point-in-time snapshot of the data, which can be used to restore the state of the data in that specific instant. This pattern makes possible time travel, which means that you can query the data at any point in time, not just the current content at query-time.&lt;br&gt;&lt;br&gt;
It's up to you to decide how many snapshot you want to keep, configuring Iceberg according to your needs.&lt;/p&gt;
&lt;h2&gt;
  
  
  A challenging use case
&lt;/h2&gt;

&lt;p&gt;In the context of a web application for the monitoring of fleet of vehicles, we decided to try Apache Iceberg for a challenging use-case.&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%2F2vi1m6wjm3ru08wk5v8c.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%2F2vi1m6wjm3ru08wk5v8c.png" alt="tightrope walker" width="487" height="221"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Size of data
&lt;/h3&gt;

&lt;p&gt;Our need was to store about 1 million records every day, and to make them available for at least 10 years. Each record is a medium-length data structure that can vary from few to some kilobytes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Near-real-time data feeding
&lt;/h3&gt;

&lt;p&gt;The goal was also to make clients able to get their data on a per-day basis, but also to get data near-real-time, meaning new data being available just &lt;em&gt;a few minutes&lt;/em&gt; after it is produced in the main platform.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reading pattern
&lt;/h3&gt;

&lt;p&gt;The data must be selectively accessed by the owner of the data, ideally through an API.&lt;br&gt;&lt;br&gt;
A remarkable point is that some clients are owners of &lt;em&gt;tens&lt;/em&gt; of record per day, while other clients can have &lt;em&gt;tens of thousands&lt;/em&gt; of records per day. And of course the response time must be always acceptable.&lt;/p&gt;
&lt;h2&gt;
  
  
  The plan
&lt;/h2&gt;

&lt;p&gt;As often happens in the IT world, ideally cost and complexity should be kept as lower as possible, while at the same time, performance and scalability should unexplainably tend to infinite.&lt;br&gt;&lt;br&gt;
Our plan was to find a compromise for those requirements using Iceberg on S3 as a data store, and then exposing the data through an API with mandatory parameters to force the clients to trigger data queries that leverage the partitioning columns at every request.&lt;br&gt;&lt;br&gt;
To validate our plan, we started a POC that could then easily be converted in a production feature.&lt;br&gt;&lt;br&gt;
It was our first experience with Iceberg, so we needed to learn by doing.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a new Iceberg table
&lt;/h3&gt;

&lt;p&gt;We created a new AWS Glue table on an S3 bucket, configured to use &lt;em&gt;Apache Iceberg&lt;/em&gt; as &lt;em&gt;Table Format&lt;/em&gt;, and &lt;em&gt;Parquet&lt;/em&gt; for &lt;em&gt;File Format&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
We configured the partitioning on 2 data columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the company that owns the entity&lt;/li&gt;
&lt;li&gt;the calendar day from the record timestamp&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Feeding the table with data
&lt;/h3&gt;

&lt;p&gt;We implemented a writing process this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data is produced in the platform, through many decoupled different pipelines&lt;/li&gt;
&lt;li&gt;A Flink job produces the aggregate that represents our final denormalized record&lt;/li&gt;
&lt;li&gt;Records are saved into the S3 Iceberg table defined in AWS Glue&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The PoC
&lt;/h2&gt;

&lt;p&gt;We built and deployed the artifacts, and at every data flush performed by Flink we started seeing new metadata files like these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metadata/&amp;lt;version&amp;gt;-&amp;lt;UUID&amp;gt;.metadata.json
metadata/snap-&amp;lt;number&amp;gt;-&amp;lt;UUID&amp;gt;.metadata.json
metadata/&amp;lt;UUID&amp;gt;.metadata.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a lot of new new data files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data/&amp;lt;randomstring&amp;gt;/companyId=123/timestamp_day=YYYY-MM-DD/&amp;lt;randomfilename&amp;gt;.parquet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything seemed to be working as expected.&lt;br&gt;
The query pattern used by our clients, based on company and date, could leverage the partitioning and gave extraordinary good performance while being very cost-effective.&lt;br&gt;&lt;br&gt;
For example, launching a query with Athena we could get very good reading perfomance, and Athena scanned only an incredibly small amount of data, in proportion to the whole table size.&lt;/p&gt;
&lt;h3&gt;
  
  
  An expected problem
&lt;/h3&gt;

&lt;p&gt;We expected that this first release would have had a problem due to our feeding strategy. In fact, after a few days, while the table was growing, we started seeing a little degradation in performance.&lt;br&gt;&lt;br&gt;
Even if the size of the scanned data was still very low, the performance was getting worse.  &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%2Fwkb5nolvtncq7daqsa00.jpg" 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%2Fwkb5nolvtncq7daqsa00.jpg" alt="fragmentation" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our writing process was configured to write the data on average &lt;em&gt;every 5 minutes&lt;/em&gt;, and the partitioning was &lt;em&gt;per day&lt;/em&gt;.&lt;br&gt;
As a consequence we had a lot of small files, and a lot of metadata references to them.&lt;br&gt;&lt;br&gt;
After a few days feeding the table, the size of metadata had grown so much that it was about 90% of the data folder itself.&lt;br&gt;
The analysis of the metadata became far more complex and costly, even if the number of real data files that were eventually read was very small.&lt;/p&gt;

&lt;p&gt;The problem was that we were continuously adding data, and with this basic configuration, Iceberg was creating a new snapshot for each data insertion, and was retaining all of them.&lt;br&gt;&lt;br&gt;
In addition to that, the data files were very small and fragmented, because a new file was added at every insert transaction.&lt;/p&gt;
&lt;h3&gt;
  
  
  The easy (but just partial) solution
&lt;/h3&gt;

&lt;p&gt;We replaced the table with a new one, then we enabled the &lt;em&gt;compaction&lt;/em&gt; managed function on the Glue table, confident that this would have had resolved the problem.  &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%2Ffqtec89jhhowvqzt3kn4.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%2Ffqtec89jhhowvqzt3kn4.png" alt="Compression" width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few days we queried Athena about the size of the metadata, calling this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&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="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"dbname"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"tablename$manifests"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number of manifests was actually smaller than before, and the performance was good and not giving any sign of degradation.&lt;br&gt;&lt;br&gt;
So far so good.&lt;br&gt;&lt;br&gt;
But then we made a double check launching from the AWS console a "Calculate total size" operation on the metadata and data folders.&lt;br&gt;&lt;br&gt;
Interestingly, the S3 folders were not smaller than before but were far bigger. The increase on metadata folder was a scary 500% in number of files, and 150% in size.&lt;br&gt;&lt;br&gt;
Of course we were missing something, and the cost of the S3 storage would have been unsustainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we missed
&lt;/h3&gt;

&lt;p&gt;The problem was an oversight reading the documentation, because the managed compaction, by design, has just the responsibility to rewrite data files into a more optimized layout, based on their size and number.  &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%2Fnokdrwkczoodg4q5v8q8.jpg" 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%2Fnokdrwkczoodg4q5v8q8.jpg" alt="Vacuum" width="300" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In other words, if inside a partition there is a high number of small files (the exact number and size depends on config parameters) the compaction job rewrites them as a single data file, simplifying also the metadata references to that chunk of data. This is the only intended meaning of &lt;em&gt;"compacted"&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
What we missed was that &lt;em&gt;compacting&lt;/em&gt; the data doesn't imply that the orphan files are also deleted. This behavior is exactly as happens with some RDBMS, where deleting records doesn't mean that you are actually freeing disk space.&lt;br&gt;&lt;br&gt;
So, even if the metadata complexity was lower, the performance was good, and the compaction process was effectively optimizing the data reducing its fragmentation, the system was leaving behind on S3 a huge quantity of junk files.&lt;/p&gt;

&lt;h3&gt;
  
  
  The final solution
&lt;/h3&gt;

&lt;p&gt;We ended up creating a Lambda function that continuously executes a &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-data-optimization.html#querying-iceberg-vacuum" rel="noopener noreferrer"&gt;VACUUM&lt;/a&gt; command leveraging Athena, so that all expired snapshots and all orphan files (both data and metadata) are physically removed from S3.&lt;/p&gt;

&lt;p&gt;Here is a comparison of some key metrics collected while using the three different configurations described above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Just write&lt;/th&gt;
&lt;th&gt;+Compaction&lt;/th&gt;
&lt;th&gt;+Compaction +Vacuum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Number of snapshots&lt;/td&gt;
&lt;td&gt;~320/day&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of &lt;strong&gt;metadata&lt;/strong&gt; files per day&lt;/td&gt;
&lt;td&gt;2,200&lt;/td&gt;
&lt;td&gt;10,500&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size of &lt;strong&gt;metadata&lt;/strong&gt; files per day&lt;/td&gt;
&lt;td&gt;5,200 MB&lt;/td&gt;
&lt;td&gt;7,900 MB&lt;/td&gt;
&lt;td&gt;100 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of &lt;strong&gt;data&lt;/strong&gt; files per day&lt;/td&gt;
&lt;td&gt;~300K&lt;/td&gt;
&lt;td&gt;~800K&lt;/td&gt;
&lt;td&gt;50K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size of &lt;strong&gt;data&lt;/strong&gt; files per day&lt;/td&gt;
&lt;td&gt;5,600 MB&lt;/td&gt;
&lt;td&gt;8,500 MB&lt;/td&gt;
&lt;td&gt;900 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of files referenced per day (excluding orphan files)&lt;/td&gt;
&lt;td&gt;300K&lt;/td&gt;
&lt;td&gt;50K&lt;/td&gt;
&lt;td&gt;50K&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The improvement and the savings in terms of disk usage is clear.&lt;/p&gt;

&lt;p&gt;The downside is that &lt;strong&gt;compaction&lt;/strong&gt; and &lt;strong&gt;vacuum&lt;/strong&gt; operations performs a lot of API calls on S3, so it is necessary to tune the parameters in order to find a good balance that works for each use case, balancing cost of storage, cost of API calls, optimal file size for Iceberg to work good with your access pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR - Key takeaways
&lt;/h2&gt;

&lt;p&gt;The road to mastering Apache Iceberg is long and hard for newbies, but it's definitely a game-changer technology.&lt;br&gt;&lt;br&gt;
Writing big batches of data is far easier than adding it little by little. If you have to do so, consider that you'll have to master the compaction and the vacuum processes.&lt;/p&gt;

</description>
      <category>awsdatalake</category>
      <category>iceberg</category>
      <category>aws</category>
      <category>bigdata</category>
    </item>
    <item>
      <title>I challenged Amazon Q with 3 cloud architecture questions</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Wed, 31 Jan 2024 01:50:37 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-challenged-amazon-q-with-3-cloud-architecture-questions-2jmd</link>
      <guid>https://dev.to/aws-builders/i-challenged-amazon-q-with-3-cloud-architecture-questions-2jmd</guid>
      <description>&lt;p&gt;&lt;strong&gt;And compared the result with Copilot and ChatGPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After the release announcement of &lt;em&gt;Amazon Q&lt;/em&gt; at re:Invent 2023, I immediately started to wonder if it could &lt;strong&gt;really&lt;/strong&gt; be a useful tool to help me in my daily job as a cloud architect using AWS services.&lt;br&gt;&lt;br&gt;
Is it just a cool toy or a game-changer?  &lt;/p&gt;
&lt;h3&gt;
  
  
  The expectation
&lt;/h3&gt;

&lt;p&gt;My basic expectation is that Amazon Q can be a good assistant in the daily boring and time-consuming task of searching for information in the AWS documentation. After all, we are in 2024, we are talking about AWS, we are used to seeing and using generative AI tools, and the wow effect has (almost) gone. Less than this would be not enough.&lt;br&gt;&lt;br&gt;
I appreciate a lot the AWS idea of creating a &lt;em&gt;“generative AI–powered assistant designed for work”&lt;/em&gt;, designed for real-world users and not just for demonstrations. &lt;br&gt;
My objective is to understand if Q is something more than other general-purpose generative AI tools.  &lt;/p&gt;
&lt;h3&gt;
  
  
  How I made the test
&lt;/h3&gt;

&lt;p&gt;So, focusing on its generic ability to be an expert in AWS I made my tests using the Amazon Q assistant in the sidebar of the AWS management console. This is just one of the capabilities of Amazon Q, but is for sure the most immediate to interact with.&lt;br&gt;&lt;br&gt;
I started chatting with Q about a simple architectural design question, then I asked for help with a technical issue, and finally a cost evaluation.&lt;br&gt;&lt;br&gt;
As an additional quality check, I submitted each question also to Copilot and ChatGPT 3.5, and then I expressed my evaluation of the quality of each response.&lt;/p&gt;

&lt;p&gt;This is the result of my far-from-analytical analysis.&lt;/p&gt;
&lt;h2&gt;
  
  
  Question 1 - A general architectural question
&lt;/h2&gt;

&lt;p&gt;I asked a high-level architectural question, not too difficult but not too simple, with many possible right options.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;I have a system with very unpredictable load.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;



&lt;p&gt;&lt;code&gt;I need to implement a feature to show simple polls to the users and collect their feedbacks.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;



&lt;p&gt;&lt;code&gt;Users should be able to express one vote in a 2 minutes time window.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;



&lt;p&gt;&lt;code&gt;The results should be hidden for the first minute and then they should see the results being updated in real time.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;



&lt;p&gt;&lt;code&gt;Can you suggest me a good architecture?&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon Q Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I apologize, but your request seems to be outside my domain of expertise. However, I'm happy to try discussing related topics that I may have more information on. How can I help further our conversation productively?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Okay, this approach for some unknown reason is not working.&lt;br&gt;&lt;br&gt;
May a more precise question sound better to Q?&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;I have to collect votes from the users of my platforms.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;



&lt;p&gt;&lt;code&gt;What services could be a good solution to ingest this data and at the same time be able to read at a high frequency?&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon Q Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Here are a few options on AWS that would be suitable for ingesting vote data at high frequency while being able to read the data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Amazon Kinesis Data Streams - This service allows you to continuously capture and store terabytes of data per hour from hundreds of thousands of sources. It supports stream processing frameworks like Kinesis Client Library, Apache Storm, and Apache Spark Streaming. The ingested data can then be processed in real-time. &lt;/li&gt;
&lt;li&gt;Amazon DynamoDB - Being a fully managed NoSQL database service, DynamoDB provides fast and predictable performance with seamless scalability. It can handle millions of requests per second and can scale to handle any workload. The vote data can be ingested into DynamoDB tables and queried for aggregation and analysis at high frequency.&lt;/li&gt;
&lt;li&gt;Amazon ElastiCache for Redis - Using ElastiCache which provides in-memory data store solutions like Redis, the vote data can be written to Redis very quickly due to its in-memory speed. The data can then be aggregated and read in real-time for high frequency needs. ElastiCache also auto scales the cache capacity as needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;This answer is pretty good.&lt;br&gt;&lt;br&gt;
Of course, it's just a starting point and it's necessary to deepen into many details.&lt;br&gt;&lt;br&gt;
But in my opinion, this is absolutely a good answer: it explores the problem of ingesting the votes, where to store the data, and how to read the data at high frequency. Amazon Q does not mention any computing service, but probably this is not a crucial point at this level of design.&lt;/p&gt;

&lt;p&gt;Let's check what other competitors can do.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NB: Being outside AWS, in these cases I used identical wording but I added a short premise in the beginning "I have a project in AWS."&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Copilot answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Searching for: &lt;code&gt;AWS services for collecting votes from users&lt;/code&gt;&lt;br&gt;&lt;br&gt;
If you’re looking for an AWS service to collect votes from users, you might want to consider using &lt;strong&gt;Amazon Simple Queue Service (SQS)&lt;/strong&gt;. SQS is a fully managed message queuing service that enables you to decouple and scale microservices, distributed systems, and serverless applications. It can be used to transmit any volume of data, at any level of throughput, without losing messages or requiring other services to be available. You can use SQS to collect votes from your users and then process them at a high frequency. SQS is also highly available and fault-tolerant, so you can rely on it to handle your data with minimal downtime  &lt;/p&gt;

&lt;p&gt;Another AWS service that could be useful for your use case is &lt;strong&gt;Amazon Kinesis Data Streams&lt;/strong&gt;. Kinesis Data Streams is a fully managed service that enables you to build custom applications that process or analyze streaming data for specialized needs. It can be used to collect and process large streams of data records in real time. You can use Kinesis Data Streams to ingest votes from your users and then process them in real time. Kinesis Data Streams is also highly scalable and can handle millions of data records per second.&lt;br&gt;&lt;br&gt;
I hope this helps! Let me know if you have any other questions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This answer is not completely wrong, but for sure it is not complete.&lt;br&gt;&lt;br&gt;
Copilot is shortening and simplifying the question, moving the focus only on the ingestion services, SQS or Kinesis.&lt;/p&gt;
&lt;h3&gt;
  
  
  ChatGPT answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;For collecting votes from users on your platform and handling high-frequency reads, several AWS services can be employed to create a robust and scalable solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Amazon API Gateway&lt;/strong&gt;: Use API Gateway to create RESTful APIs that can receive and process incoming votes from your users. API Gateway can handle high-frequency requests and integrate seamlessly with other AWS services.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Kinesis&lt;/strong&gt;: If you're dealing with a high volume of incoming data (votes), consider using Amazon Kinesis. Kinesis Data Streams can ingest and store data in real-time and scale elastically to accommodate varying loads.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon DynamoDB&lt;/strong&gt;: DynamoDB is a fully managed NoSQL database service that offers low-latency performance at any scale. You can use DynamoDB to store and retrieve the votes submitted by your users. Its flexible schema and automatic scaling capabilities make it ideal for handling high-frequency reads and writes.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon S3&lt;/strong&gt;: For storing large volumes of data or for backup purposes, you can utilize Amazon S3. S3 is highly scalable, durable, and secure, making it suitable for storing raw or processed data related to the votes.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda&lt;/strong&gt;: AWS Lambda can be used to execute code in response to events such as incoming votes. You can trigger Lambda functions to process and analyze the votes, perform validation checks, and store the data in DynamoDB or S3.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon CloudWatch&lt;/strong&gt;: Monitor the performance of your AWS resources and set up alarms using Amazon CloudWatch. This service can help you track metrics such as API Gateway request rates, DynamoDB throughput, and Lambda function invocations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By combining these AWS services, you can design a highly available, scalable, and resilient architecture for collecting and processing votes from users on your platform. Make sure to consider factors like data consistency, security, and cost optimization while designing your solution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first glance, this answer seems even better than the one given by Amazon Q. But if you pay attention to the details, you can see that ChatGPT is just aggregating generic service descriptions.&lt;br&gt;&lt;br&gt;
This is ok, and there is for sure a considerable value in giving a good selection of services that could be used in this architecture.&lt;br&gt;
However, in the response given by Amazon Q, each service description is customized based on the specific use case of vote ingestion. I don't know if this is just a cosmetic difference, but it seems to me an indicator of a deeper "understanding" of the problem.&lt;/p&gt;

&lt;p&gt;In my opinion, Amazon Q is the winner of this test.&lt;br&gt;&lt;br&gt;
These are the points I decided to assign:&lt;/p&gt;

&lt;p&gt;AmazonQ: &lt;strong&gt;3 points&lt;/strong&gt; / Copilot: &lt;strong&gt;0 points&lt;/strong&gt; / ChatGPT: &lt;strong&gt;2 points&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Question 2 - A technical issue
&lt;/h2&gt;

&lt;p&gt;For my experiment, I want Q now to support me in understanding a particular behavior of a service.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;In the last few days I can see an increase in the metric "Read throughput exceeded" of a Kinesis Data stream. What's the problem?&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon Q Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;It looks like you need help with network connectivity issues. Amazon Q works with VPC Reachability Analyzer to provide an interactive generative AI experience for troubleshooting network connectivity issues. You can try [...]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No good.&lt;br&gt;&lt;br&gt;
I tried to rephrase the question in many ways but without success, always this dumb answer.&lt;br&gt;&lt;br&gt;
In the end, after a lot of tries, I got a meaningful answer using this format:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Explain me the Read throughput exceeded metric exposed by Kinesis Data Stream&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon Q Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The "Read throughput exceeded" metric indicates that the read capacity limits of the Kinesis data stream shard have been exceeded.&lt;/p&gt;

&lt;p&gt;Some potential reasons for this to occur are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The consumer application is not able to read data from the stream as fast as records are being produced. Each Kinesis shard can support a maximum of 2MB/s of read throughput on average.&lt;/li&gt;
&lt;li&gt;Multiple consumer applications are reading from the same shard and their combined read throughput exceeds 2MB/s.&lt;/li&gt;
&lt;li&gt;The GetRecords API call that the consumer uses to read data is retrieving too many records at once (over 10,000 records or 10MB of data) from the shard. This can throttle subsequent reads for 5 seconds.&lt;/li&gt;
&lt;li&gt;The processing of each record within the consumer application is taking too long, causing a backlog to build up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To resolve this, the consumer application should check if it can optimize record processing to keep pace with the incoming data rate. Other options are adding more shards to scale up throughput, or distributing reads across applications/shards in a way that balances utilization. Referring to the AWS documentation on Kinesis data stream quotas and limits would also help determine the appropriate configuration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think that Amazon Q is giving a barely sufficient answer.&lt;br&gt;&lt;br&gt;
The reason 1 and 4 are both wrong, we have read throttling, not a high iterator age: the consumer is too fast, not too slow.&lt;/p&gt;
&lt;h3&gt;
  
  
  Copilot Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The “Read throughput exceeded” metric of a Kinesis Data stream indicates that the number of read operations on the stream has exceeded the provisioned read capacity. This can happen when the number of consumers reading from the stream increases, or when the size of the data being read increases. To identify the root cause of the issue, you can monitor the Amazon Kinesis Data Streams Service with Amazon CloudWatch.&lt;br&gt;&lt;br&gt;
Reviewing the following metrics in CloudWatch can help you identify the root cause of the error:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ReadProvisionedThroughputExceeded&lt;/strong&gt;: This metric indicates that the number of read operations on the stream has exceeded the provisioned read capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GetRecords.Bytes&lt;/strong&gt;: This metric indicates the number of bytes read from the stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GetRecords.Latency&lt;/strong&gt;: This metric indicates the time taken to read data from the stream.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have identified the root cause of the issue, you can take appropriate actions to resolve it. For example, you can increase the provisioned read capacity of the stream, or reduce the number of consumers reading from the stream. I hope this helps!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I only copied here the text from the Copilot answer, but the output was full of good links to various sources, official AWS documentation pages and other sites.&lt;br&gt;&lt;br&gt;
Good job from Copilot, concise but relevant.&lt;/p&gt;
&lt;h3&gt;
  
  
  ChatGPT Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;An increase in the "Read throughput exceeded" metric for a Kinesis Data stream typically indicates that your application is attempting to read more data from the stream than what it can handle. This could be due to various reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Increased Traffic&lt;/strong&gt;: A sudden surge in incoming data to the Kinesis Data stream might be causing the read throughput to exceed its provisioned capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inefficient Consumer Code&lt;/strong&gt;: If your consumer application is inefficient or experiencing bottlenecks, it may not be able to keep up with the rate at which data is being written to the stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling Issues&lt;/strong&gt;: If your application is not properly scaled to handle the increased load, it may struggle to process the incoming data from the stream efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misconfigured Shard Count&lt;/strong&gt;: If the shard count for your Kinesis Data stream is not properly configured to handle the incoming data rate, you may encounter throughput issues.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To address the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monitor Metrics&lt;/strong&gt;: Continuously monitor the metrics for your Kinesis Data stream to understand the patterns and identify any anomalies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increase Shard Count&lt;/strong&gt;: If the increased throughput is sustained and not just a temporary spike, consider increasing the number of shards in your Kinesis Data stream to handle the higher load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize Consumer Code&lt;/strong&gt;: Review and optimize your consumer application code to ensure it can efficiently process the data from the stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement Backpressure Mechanisms&lt;/strong&gt;: Implement backpressure mechanisms in your consumer application to regulate the rate at which data is read from the stream, allowing it to keep up with the incoming data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider AWS Lambda&lt;/strong&gt;: If applicable, consider using AWS Lambda functions to process data from the Kinesis Data stream. AWS Lambda can automatically scale to handle varying workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By addressing these factors, you can effectively manage the increased read throughput on your Kinesis Data stream and ensure the smooth operation of your AWS project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This answer seems again very good at first glance, but if you go deeper you can find the same errors that had Amazon Q. The reasons 2, 3 and 4 are wrong, and so are most of the suggested solutions.  &lt;/p&gt;

&lt;p&gt;The second round goes to Copilot.&lt;/p&gt;

&lt;p&gt;AmazonQ: &lt;strong&gt;1 point&lt;/strong&gt; / Copilot: &lt;strong&gt;3 points&lt;/strong&gt; / ChatGPT: &lt;strong&gt;1 point&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Question 3 - A cost evaluation
&lt;/h2&gt;



&lt;p&gt;&lt;code&gt;I have an S3 bucket where kinesis firehose is writing 1,000 files every day, each one weighting on average 10 MB.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;



&lt;p&gt;&lt;code&gt;The probability to have the need of accessing the data is very low, and the data should be retained for 2 years.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;



&lt;p&gt;&lt;code&gt;How much could I save each month switching from Stardard tier to Glacier Deep Archive tier?&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon Q Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Based on the information provided, here is how you could estimate the potential savings:&lt;br&gt;
Kinesis Data Firehose is writing 1000 files to your S3 bucket every day. Each file is approximately 10MB in size.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;So daily incoming data is 1000 files x 10MB per file = 10GB per day&lt;/li&gt;
&lt;li&gt;Monthly incoming data is approximately 10GB x 30 days = 300GB per month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the data needs to be retained for 2 years, you could consider archiving it to Glacier Deep Archive tier.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standard tier pricing is $0.023 per GB per month&lt;/li&gt;
&lt;li&gt;Glacier Deep Archive tier pricing is $0.00099 per GB per month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To calculate estimated monthly savings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monthly data stored in Standard tier would cost 300GB x $0.023 = $6.90&lt;/li&gt;
&lt;li&gt;Monthly data stored in Glacier Deep Archive tier would cost 300GB x $0.00099 = $0.30&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Estimated monthly savings by switching to Glacier Deep Archive tier = $6.90 - $0.30 = $6.60&lt;/p&gt;

&lt;p&gt;For the most accurate pricing information, you should check the AWS documentation or pricing pages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The calculation is not completely wrong, but Q is not considering the data accumulation.&lt;br&gt;&lt;br&gt;
The first month the storage needed is 300 GB, but the second month it will be 600 GB, the third 900 GB and so on, up to the 24th month, when it will stop growing at about 7.2 TB. At this point, the total storage will be constant, and each month the oldest data will be replaced by the new.  &lt;/p&gt;

&lt;p&gt;When fully operational, the AWS cost calculator, configured with 7.2 TB and 30.000 monthly PUT requests / Lifecycle Transitions, gives this estimation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monthly cost of ~169$ for S3 Standard
&lt;/li&gt;
&lt;li&gt;Monthly cost of ~11$ for S3 Glacier Deep Archive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The monthly saving after 2 years will be something around 150$.&lt;br&gt;&lt;br&gt;
In this scenario, it would probably have been more useful to ask something simpler like: "Give me a comparison between the cost of S3 standard tier and glacier deep archive".&lt;br&gt;
For the purpose of this test it's interesting to point out that the answer from Amazon Q is good but not great.&lt;/p&gt;

&lt;h3&gt;
  
  
  Copilot Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Searching for: &lt;code&gt;AWS S3 bucket kinesis firehose 1000 files 10 MB 2 years cost comparison&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Based on the information you provided, you could save a significant amount of money by switching from the Standard tier to the Glacier Deep Archive tier. The Standard tier is designed for frequent access to data, while the Glacier Deep Archive tier is intended for long-term data retention with infrequent access.&lt;/p&gt;

&lt;p&gt;The cost of storing 1,000 files per day, each weighing 10 MB, for 2 years in the Standard tier would be approximately &lt;strong&gt;$6.912&lt;/strong&gt;.  &lt;br&gt;
However, if you switch to the Glacier Deep Archive tier, the cost would be reduced to approximately &lt;strong&gt;$1.152&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
This means that you could save around &lt;strong&gt;$5.760&lt;/strong&gt; per month by switching to the Glacier Deep Archive tier.&lt;/p&gt;

&lt;p&gt;Please note that the cost estimates are based on the current AWS pricing as of January 2024 and are subject to change.  Also, keep in mind that the retrieval time for data stored in the Glacier Deep Archive tier is longer than that of the Standard tier. If you need to access your data frequently, the Standard tier might be a better option for you.&lt;/p&gt;

&lt;p&gt;I hope this helps! Let me know if you have any other questions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same problem shown by Q, but without any useful detail about the calculations that have been made to get the evaluation.&lt;br&gt;&lt;br&gt;
Not sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatGPT Answer
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;To calculate the potential savings from switching from the Standard tier to the Glacier Deep Archive tier for your S3 storage, we need to consider the difference in pricing between the two storage classes.&lt;/p&gt;

&lt;p&gt;Here are the main differences in pricing between Standard S3 storage and Glacier Deep Archive:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Standard S3 Storage Pricing&lt;/strong&gt;: Standard S3 storage has a higher storage cost compared to Glacier Deep Archive. As of my last update, the standard S3 storage cost was around $0.023 per GB-month for the first 50 TB tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glacier Deep Archive Pricing&lt;/strong&gt;: Glacier Deep Archive is significantly cheaper but incurs additional costs for data retrieval. As of my last update, the storage cost for Glacier Deep Archive was around $0.00099 per GB-month.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Given that you have 1,000 files per day, each averaging 10 MB, let's calculate the monthly storage usage:&lt;br&gt;
Each file: 10 MB = 0.01 GB&lt;br&gt;&lt;br&gt;
Total daily storage: 1,000 files * 0.01 GB = 10 GB&lt;br&gt;
Monthly storage: 10 GB * 30 days = 300 GB&lt;/p&gt;

&lt;p&gt;Now, let's calculate the monthly storage cost for both tiers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Standard S3 Storage Cost&lt;/strong&gt;: Monthly cost = 300 GB * $0.023 per GB = $6.90&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glacier Deep Archive Storage Cost&lt;/strong&gt;: Monthly cost = 300 GB * $0.00099 per GB = $0.297&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let's calculate the potential monthly savings:&lt;/p&gt;

&lt;p&gt;Savings = Standard S3 Storage Cost - Glacier Deep Archive&lt;br&gt;
Storage Cost Savings = $6.90 - $0.297 = $6.603&lt;/p&gt;

&lt;p&gt;So, by switching from the Standard tier to the Glacier Deep Archive tier, you could potentially save around $6.603 per month on storage costs.&lt;/p&gt;

&lt;p&gt;Please note that this calculation does not include additional costs for data retrieval from Glacier Deep Archive, which would be incurred if you need to access the data. Make sure to consider those costs if you anticipate needing to retrieve data from Glacier Deep Archive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result is very similar to the one from Amazon Q.&lt;br&gt;
So these are my votes for the third test:&lt;br&gt;
AmazonQ: &lt;strong&gt;2 points&lt;/strong&gt; / Copilot: &lt;strong&gt;1 point&lt;/strong&gt; / ChatGPT: &lt;strong&gt;2 points&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;This is my final &lt;strong&gt;personal&lt;/strong&gt; ranking:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Amazon Q&lt;/th&gt;
&lt;th&gt;Copilot&lt;/th&gt;
&lt;th&gt;ChatGPT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Architecture question&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical issue&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost evaluation&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In my opinion, &lt;strong&gt;the winner is Amazon Q&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
It has been fun to make this experiment, and the result is not far from what I was expecting from the test.  &lt;/p&gt;

&lt;p&gt;My experience with Amazon Q as a companion while architecting on AWS is absolutely positive. Like all other generative AI tools it’s far from being perfect but it can be an invaluable ally in my everyday work as a software architect.&lt;/p&gt;

&lt;p&gt;I am sure that in the future it will be improved and will become smarter, but &lt;strong&gt;today&lt;/strong&gt; it is already a useful tool to retrieve information, validate options and get good suggestions.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>cloud</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Introducing Mr-Know-It-All-O-Matic</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Fri, 17 Nov 2023 00:17:34 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-mr-know-it-all-o-matic-3bpp</link>
      <guid>https://dev.to/aws-builders/introducing-mr-know-it-all-o-matic-3bpp</guid>
      <description>&lt;p&gt;&lt;strong&gt;An AI-driven dissenter built with AWS PartyRock&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's PartyRock?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://partyrock.aws/"&gt;&lt;strong&gt;PartyRock&lt;/strong&gt;&lt;/a&gt; is a space where you can build &lt;em&gt;AI-generated apps&lt;/em&gt; in a playground powered by &lt;em&gt;Amazon Bedrock&lt;/em&gt;. You can use it to experiment and create applications at the speed of light.  &lt;/p&gt;

&lt;p&gt;Any attempt to explain how this application works would probably take longer than simply opening it and start playing. So I won’t spend my and your time explaining something that basically doesn’t need an explanation. And, of course, there is the &lt;a href="https://partyrock.aws/guide/getStarted"&gt;official guide&lt;/a&gt; for that.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PartyRock&lt;/em&gt; is not a tool for technicians or generative AI experts. Even those who have never written a single line of code can create and deploy a web application with AI capabilities in minutes. In this case, the expression &lt;em&gt;“in minutes”&lt;/em&gt;, it’s not just a slogan, but it's real: I created my first &lt;strong&gt;online and working&lt;/strong&gt; generative AI application literally in &lt;strong&gt;10 minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mr-Know-It-All-O-Matic
&lt;/h2&gt;

&lt;p&gt;While driving the car and thinking about an idea for a test application, I started reasoning on the hundreds of implementations already seen in the last months. What do have in common almost every generative AI application? They are &lt;strong&gt;repetitive&lt;/strong&gt;, a little bit &lt;strong&gt;opinionated&lt;/strong&gt;, &lt;strong&gt;pedant&lt;/strong&gt;, and with a slightly &lt;strong&gt;tedious&lt;/strong&gt; know-it-all approach.&lt;br&gt;&lt;br&gt;
So, what about taking these defects and transforming them into a strength?&lt;/p&gt;

&lt;p&gt;Here we are.&lt;br&gt;&lt;br&gt;
I am proud to present to the world, the great and revolutionary &lt;a href="https://partyrock.aws/u/ctaverna/GUOuGJTz3/Mr-Know-It-All-O-Matic"&gt;&lt;strong&gt;Mr-Know-It-All-O-Matic&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Whatever is your job, whoever you talk to, your ideas will be questioned.&lt;br&gt;
If you think that an idea that you will present in a slide is a little bit weak, probably you are right.&lt;br&gt;
If you think that a friend or anyone else will be hard to convince about something, be sure that the discussion will be long and passionate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option one&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
You are the good. You are wise and every day you strive to be a better person. This tool will support you in thinking about the possible weaknesses of your argument and improve it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option two&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
You are a bad person, and your only goal in the life is to annoy people with irritating objections.&lt;br&gt;
This tool will be an invaluable ally in your infinite disputes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://partyrock.aws/u/ctaverna/GUOuGJTz3/Mr-Know-It-All-O-Matic"&gt;The choice is yours.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://partyrock.aws/u/ctaverna/GUOuGJTz3/Mr-Know-It-All-O-Matic"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TNZR61Eb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0aqdxb8h5z2pev26e4x.jpg" alt="" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Some examples
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;"Swimming is a healthy activity"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a condensed list of potential objections to the claim that swimming is a healthy activity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can be dangerous if proper precautions are not taken (e.g. risk of drowning, especially for non-swimmers)&lt;/li&gt;
&lt;li&gt;Risk of injury if not done with proper form/technique&lt;/li&gt;
&lt;li&gt;Risk of illness from swimming in contaminated water&lt;/li&gt;
&lt;li&gt;Overexertion or strain on joints/muscles if swum excessively&lt;/li&gt;
&lt;li&gt;Requires access to a pool or body of water suitable for swimming&lt;/li&gt;
&lt;li&gt;Time commitment to travel to a pool and change clothes can be a barrier&lt;/li&gt;
&lt;li&gt;Chlorine and other pool chemicals can irritate eyes, skin and lungs&lt;/li&gt;
&lt;li&gt;Can be costly if pool membership or swimming lessons are required&lt;/li&gt;
&lt;li&gt;Weather constraints for outdoor swimming&lt;/li&gt;
&lt;li&gt;Social/body image concerns about wearing a swimsuit&lt;/li&gt;
&lt;li&gt;Not a practical form of exercise for some people with disabilities or medical conditions&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;"The fire is hot"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a condensed list of potential objections to the rationale that "the fire is hot":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fires can have varying temperatures, so not all fires are necessarily hot. A small, dying fire may not be hot.&lt;/li&gt;
&lt;li&gt;"Hot" is subjective and depends on one's heat tolerance. What's hot to one person may not be considered hot by someone else.&lt;/li&gt;
&lt;li&gt;The sensation of heat depends on how close one is to the fire. A fire may not feel hot from a distance.&lt;/li&gt;
&lt;li&gt;Different parts of a fire may be hotter than others. The center may be hotter than the edges, for example.&lt;/li&gt;
&lt;li&gt;"Hot" is a relative term. Compared to an oven, a campfire may not be considered hot.&lt;/li&gt;
&lt;li&gt;Certain types of fires, like alcohol fires, burn cooler than wood fires. So those types of fires may not be classified as hot.&lt;/li&gt;
&lt;li&gt;The rationale assumes all fires produce high temperatures, though in reality fire temperature varies greatly.&lt;/li&gt;
&lt;li&gt;Simply saying "the fire is hot" is too vague. More specifics are needed to properly evaluate the claim.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;"The addition of two whole numbers results in the total amount or sum of those values combined."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a condensed list of potential objections to the rationale that the addition of two whole numbers results in the total amount or sum of those values combined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It assumes all numbers being added are whole numbers, but does not account for fractions or decimals.&lt;/li&gt;
&lt;li&gt;It does not specify that the numbers need to be positive - adding a positive and negative number does not result in their total amount.&lt;/li&gt;
&lt;li&gt;It assumes a simple addition of two numbers, but does not address more complex operations like adding three or more numbers.&lt;/li&gt;
&lt;li&gt;It does not define what a "whole number" is. Integers? Non-negative integers?&lt;/li&gt;
&lt;li&gt;It describes the basic principle of addition but does not provide any deeper mathematical foundation or proof.&lt;/li&gt;
&lt;li&gt;It does not specify the mathematical system - addition in abstract algebra has more nuance than simple whole number addition.&lt;/li&gt;
&lt;li&gt;The words "total amount" and "sum" are vague and informal mathematical terms. More precise language is needed in a formal proof.&lt;/li&gt;
&lt;li&gt;It does not address potential limits like overflow when adding very large numbers.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>webdev</category>
      <category>partyrockplayground</category>
    </item>
    <item>
      <title>How to run Jekyll on Kubernetes</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Mon, 02 Oct 2023 15:40:22 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-to-run-jekyll-on-kubernetes-aen</link>
      <guid>https://dev.to/aws-builders/how-to-run-jekyll-on-kubernetes-aen</guid>
      <description>&lt;p&gt;&lt;strong&gt;A step-by-step guide executing Jekyll inside a local k8s cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I created my blog using &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;, a great open-source tool that can transform your markdown content into a simple, old-fashioned-but-trendy, static site.&lt;br&gt;&lt;br&gt;
What are the advantages of this approach?&lt;br&gt;&lt;br&gt;
The site is super-light, super-fast, super-secure and SEO-friendly. Of course, it’s not always the best solution, but for some use cases, like a simple personal blog, it’s really a good option.&lt;/p&gt;
&lt;h2&gt;
  
  
  Aim of this guide
&lt;/h2&gt;

&lt;p&gt;Running Jekyll locally can be a little bit tricky, at least for me, as I’m not very comfortable with Ruby. &lt;br&gt;
So I decided to go for a containerized solution using Rancher Desktop.&lt;/p&gt;

&lt;p&gt;This guide can be a good starting point to familiarize both with Jekyll and with Kubernetes. If you already know something about Kubernetes but you have never used it before, it could be an effective hands-on experience.&lt;/p&gt;

&lt;p&gt;The idea is to make it easy to manage your blog using this pretty simple flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write your blog posts locally as a bunch of simple markdown text files&lt;/li&gt;
&lt;li&gt;Jekyll generates in real time the static site and serves it locally&lt;/li&gt;
&lt;li&gt;When you are satisfied with the result, just commit and push the changes&lt;/li&gt;
&lt;li&gt;An automated workflow will update the online "production" static website&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow is now up and running, and I am very happy with it, but I had some troubles making it work.&lt;br&gt;
I’m on a Mac with macOS Sonoma 14.0.&lt;br&gt;
So, here is the step-by-step guide to get it working.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1 - Install Rancher Desktop
&lt;/h2&gt;

&lt;p&gt;Nothing special to say here. I used brew, but you can of course install it as you prefer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;rancher 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I configured Rancher Desktop to user &lt;strong&gt;&lt;em&gt;dockerd&lt;/em&gt;&lt;/strong&gt; as a container engine because I am more familiar with Docker.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OP-XdWtd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmojkqtjhun2mvwms0zt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OP-XdWtd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmojkqtjhun2mvwms0zt.png" alt="Containerd selected as current container engine" width="540" height="302"&gt;&lt;/a&gt;&lt;br&gt;
 &lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2 - Check that everything is ok
&lt;/h2&gt;

&lt;p&gt;Check with &lt;strong&gt;&lt;em&gt;kubectl&lt;/em&gt;&lt;/strong&gt; that your local k8s cluster has been configured properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;johndoe@macbook ~ % kubectl config get-contexts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;CURRENT   NAME                CLUSTER            AUTHINFO          NAMESPACE
*         rancher-desktop     rancher-desktop    rancher-desktop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that you container engine can download images and execute them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;johndoe@macbook ~ % docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
70f5ac315c5a: Pull complete
Digest: sha256:4f53e2564790c8e7856ec08e384732aa38dc43c52f02952483e3f003afbf23db
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
&lt;/span&gt;&lt;span class="gp"&gt; $&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; ubuntu bash
&lt;span class="go"&gt;
Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During this step I had some network troubles with docker daemon pulling images.&lt;br&gt;&lt;br&gt;
It was needed to add some rules to my firewall to make Docker able to download them properly.  &lt;/p&gt;

&lt;p&gt;If you have an output like the one above, you can go on.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3 - Create the environment for your Jekyll site
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Main folder
&lt;/h3&gt;

&lt;p&gt;Create a folder on your local machine. It will be the root folder of your git repo.&lt;br&gt;&lt;br&gt;
For example: &lt;code&gt;/Users/johndoe/myblog&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Kubernetes manifests' folder
&lt;/h3&gt;

&lt;p&gt;Create another folder for your YAML files. &lt;br&gt;
For example: &lt;code&gt;/Users/johndoe/myblog/k8s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the folder where we will place our &lt;code&gt;.yaml&lt;/code&gt; files and execute &lt;code&gt;kubectl&lt;/code&gt; commands&lt;br&gt;
I created it inside the main folder, but it would be better to use a separate folder, because it's not a good idea to push these file inside your future blog public repository. Remember to move it outside before your first commit.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4 - Kubernetes entities
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Create the namespace
&lt;/h3&gt;

&lt;p&gt;Create a new file named &lt;code&gt;namespace.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="s"&gt;jekyll&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;johndoe@macbook k8s % kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; namespace.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;namespace/jekyll created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create PV and PVC
&lt;/h3&gt;

&lt;p&gt;Now we can create a &lt;strong&gt;&lt;em&gt;persistent volume&lt;/em&gt;&lt;/strong&gt; and a &lt;strong&gt;&lt;em&gt;persistent volume claim&lt;/em&gt;&lt;/strong&gt;, to map a local folder into the container that will execute Jekyll.&lt;br&gt;&lt;br&gt;
Create a new file named &lt;code&gt;volume.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolume&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="s"&gt;jekyll-pv&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll&lt;/span&gt;
  &lt;span class="na"&gt;labels&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;local&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hostpath&lt;/span&gt;
  &lt;span class="na"&gt;capacity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256Mi&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteMany&lt;/span&gt;
  &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/Users/johndoe/myblog&lt;/span&gt;
  &lt;span class="na"&gt;persistentVolumeReclaimPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retain&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="s"&gt;jekyll-pvc&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hostpath&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteMany&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256Mi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;johndoe@macbook k8s % kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; volume.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;persistentvolume/jekyll-pv created
persistentvolumeclaim/jekyll-pvc created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scaffold a new Jekyll site
&lt;/h3&gt;

&lt;p&gt;In this step we will create a &lt;strong&gt;&lt;em&gt;job&lt;/em&gt;&lt;/strong&gt; to execute &lt;code&gt;jekyll&lt;/code&gt; command to create a brand new site.&lt;/p&gt;

&lt;p&gt;The docker image used is the official one: &lt;a href="https://hub.docker.com/r/jekyll/jekyll/"&gt;https://hub.docker.com/r/jekyll/jekyll/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new file named &lt;code&gt;job-create-jekyll.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;batch/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Job&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="s"&gt;jekyll-create&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;jekyll-create-job&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll/jekyll:latest&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;jekyll-volume&lt;/span&gt;
            &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/jekyll&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/sh"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jekyll&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;docs"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Never&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;jekyll-volume&lt;/span&gt;
          &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll-pvc&lt;/span&gt;
  &lt;span class="na"&gt;backoffLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;johndoe@macbook k8s % kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; job-create-jekyll.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;job.batch/jekyll-create created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything works fine, the job should log something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Running bundle install in /srv/jekyll/docs...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NB: to get k8s logs you can use kubectl, the kubernetes dashboard, k9s, Lens, Rancher... if you don't know how to do it, take your time to get confidence with that.&lt;/p&gt;

&lt;p&gt;Check your local folder, now you should find a new &lt;code&gt;docs&lt;/code&gt; subfolder with a brand new Jekyll site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o-ZrFD0h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0hq6un66snsd2eyz84da.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o-ZrFD0h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0hq6un66snsd2eyz84da.png" alt="The new docs folder created by Jekyll" width="608" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you like having a clean k8s cluster, it's time to delete the job before going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 - Jekyll deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Update the PV path
&lt;/h3&gt;

&lt;p&gt;Now that the site is ready to be served, we need to change the path of the persistent volume, because the folder that should be mounted in &lt;code&gt;/srv/jekyll&lt;/code&gt; is the &lt;code&gt;docs&lt;/code&gt; folder that has just been created.&lt;/p&gt;

&lt;p&gt;Edit the file &lt;code&gt;volume.yaml&lt;/code&gt; and add &lt;code&gt;/docs&lt;/code&gt; at the end of the path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/Users/johndoe/myblog/docs&lt;/span&gt;
&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using your preferred Kubernetes management delete the volume and the claim.&lt;br&gt;&lt;br&gt;
For example with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pvc jekyll-pvc &lt;span class="nt"&gt;-n&lt;/span&gt; jekyll
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pv jekyll-pv &lt;span class="nt"&gt;-n&lt;/span&gt; jekyll
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And apply it again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;johndoe@macbook k8s % kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; volume.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;persistentvolume/jekyll-pv created
persistentvolumeclaim/jekyll-pvc created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create the deployment
&lt;/h3&gt;

&lt;p&gt;Create a new file named &lt;code&gt;deployment.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="s"&gt;jekyll-preview&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll-preview&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll-preview&lt;/span&gt;  
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;website&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll/jekyll:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8081&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;jekyll-volume&lt;/span&gt;
            &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/jekyll&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;JEKYLL_ENV&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production"&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/sh"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jekyll&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;serve&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--trace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--watch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--force_polling&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--port&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8080&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--livereload&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--livereload-port&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8081"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;jekyll-volume&lt;/span&gt;
          &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll-pvc-2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;johndoe@macbook k8s % kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;deployment.apps/jekyll-preview created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6 - Fix problems, if any
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Possible problem #1
&lt;/h3&gt;

&lt;p&gt;When the pod started the first time I got this error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt; /usr/gem/gems/jekyll-4.2.2/lib/jekyll/commands/serve/servlet.rb:3:in `require': cannot load such file -- webrick (LoadError)
&lt;/span&gt;&lt;span class="gp"&gt; from /usr/gem/gems/jekyll-4.2.2/lib/jekyll/commands/serve/servlet.rb:3:in `&amp;lt;top (required)&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'
&lt;/span&gt;&lt;span class="go"&gt; from /usr/gem/gems/jekyll-4.2.2/lib/jekyll/commands/serve.rb:179:in `require_relative'
 from /usr/gem/gems/jekyll-4.2.2/lib/jekyll/commands/serve.rb:179:in `setup'
 from /usr/gem/gems/jekyll-4.2.2/lib/jekyll/commands/serve.rb:100:in `process'
 from /usr/gem/gems/jekyll-4.2.2/lib/jekyll/command.rb:91:in `block in process_with_graceful_fail'
 from /usr/gem/gems/jekyll-4.2.2/lib/jekyll/command.rb:91:in `each'
 from /usr/gem/gems/jekyll-4.2.2/lib/jekyll/command.rb:91:in `process_with_graceful_fail'
 from /usr/gem/gems/jekyll-4.2.2/lib/jekyll/commands/serve.rb:86:in `block (2 levels) in init_with_program'
 from /usr/gem/gems/mercenary-0.4.0/lib/mercenary/command.rb:221:in `block in execute'
 from /usr/gem/gems/mercenary-0.4.0/lib/mercenary/command.rb:221:in `each'
 from /usr/gem/gems/mercenary-0.4.0/lib/mercenary/command.rb:221:in `execute' 
 from /usr/gem/gems/mercenary-0.4.0/lib/mercenary/program.rb:44:in `go' 
 from /usr/gem/gems/mercenary-0.4.0/lib/mercenary.rb:21:in `program' 
&lt;/span&gt;&lt;span class="gp"&gt; from /usr/gem/gems/jekyll-4.2.2/exe/jekyll:15:in `&amp;lt;top (required)&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;' 
&lt;/span&gt;&lt;span class="go"&gt; from /usr/gem/bin/jekyll:25:in `load' 
&lt;/span&gt;&lt;span class="gp"&gt; from /usr/gem/bin/jekyll:25:in `&amp;lt;main&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's an error related with this issue: &lt;a href="https://github.com/jekyll/jekyll/issues/8523"&gt;https://github.com/jekyll/jekyll/issues/8523&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It can be resolved by simply adding &lt;code&gt;gem "webrick"&lt;/code&gt; at the bottom of the &lt;code&gt;Gemfile&lt;/code&gt; located in the root folder of the site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem&lt;/span&gt;
&lt;span class="c1"&gt;# do not have a Java counterpart.&lt;/span&gt;
&lt;span class="s"&gt;gem "http_parser.rb", "~&amp;gt; 0.6.0", :platforms =&amp;gt; [:jruby]&lt;/span&gt;

&lt;span class="s"&gt;gem "webrick"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And of course, you then have to delete the pod to trigger k8s to restart a new one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Possible problem #2
&lt;/h3&gt;

&lt;p&gt;After overcoming the previous problem, the pod started logging this error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[...]
chown: .jekyll-cache/Jekyll/Cache: Permission denied
chown: .jekyll-cache/Jekyll: Permission deniedchown: .jekyll-cache/Jekyll: Permission denied
chown: .jekyll-cache: Permission denied
chown: .jekyll-cache: Permission denied
[...]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm pretty sure that there could be a cleaner solution, but I resolved the issue with a rough but effective manual deletion of two folders before executing jekyll:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Cleaning&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;temporary&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;files...;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-r&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;_site;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-r&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;.jekyll-cache;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;---Done---&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;jekyll&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;serve&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--trace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--watch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--force_polling&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--port&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8080&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--livereload&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--livereload-port&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8081"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7 - Create the service or the port forward
&lt;/h2&gt;

&lt;p&gt;Now you should finally have this output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hLRRqbXD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xc072ixhe3j7e9x22n05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hLRRqbXD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xc072ixhe3j7e9x22n05.png" alt="Jekyll logs after a successful start" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make a quick check if the site is finally working, it is now possible to create a port forward from the pod to local host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward jekyll-preview-xyz123xyz-xyz12 8080:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better solution is, of course, creating a service, which will be a more stable solution to reach your deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the service
&lt;/h3&gt;

&lt;p&gt;Create a new file named &lt;code&gt;service.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="s"&gt;jekyll-svc&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll-preview&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;http&lt;/span&gt; 
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&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="s"&gt;http-livereload&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8081&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;LoadBalancer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;johndoe@macbook k8s % kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;service/jekyll-svc created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now point your browser to &lt;code&gt;http://localhost:8080&lt;/code&gt; and here we are!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BpMcNQtj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ciuf8ubb1l5q7sxkyg3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BpMcNQtj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ciuf8ubb1l5q7sxkyg3s.png" alt="Jekyll output in your local browser" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enjoy livereload
&lt;/h3&gt;

&lt;p&gt;It's time to try the complete solution, with the cool live-preview feature, from the original markdown file to the local online static site.&lt;br&gt;&lt;br&gt;
Edit a file, for example the sample post that Jekyll has created for you, and try to change something.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KO8JcG9G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87ntr34x2mw3wq355bu4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KO8JcG9G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/87ntr34x2mw3wq355bu4.png" alt="Markdown file with a little text change" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the file and, after a few seconds, you should see your browser automatically refresh the page and show your change.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jXh647Q_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/33l1uab2at31j30jgpqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jXh647Q_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/33l1uab2at31j30jgpqx.png" alt="Jekyll output with the change just made" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I find that this is perfect if you work with two screens, editing the markdown in the first screen while checking the final result on the second one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build your site
&lt;/h3&gt;

&lt;p&gt;Now that you have a local working deployment of Jekyll, you can play with it and build your site. You can add themes, change styles and add contents.&lt;br&gt;&lt;br&gt;
When you are satisfied with the result, you are ready to put it online, accessible to everybody.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8 - Go online
&lt;/h2&gt;

&lt;p&gt;Being a simple static site you have now many options to expose the site on the public web, and the interesting point is that you don't need costly computing resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A - Deploy on Amazon S3
&lt;/h3&gt;

&lt;p&gt;If you are familiar with AWS, this is definitely one of the simplest options to put your site online.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new S3 bucket, disabling the option &lt;em&gt;Block all public access&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NolYmpsN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1f9vs6u13bdorfk7l9nj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NolYmpsN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1f9vs6u13bdorfk7l9nj.png" alt="'Block all public access' deselected" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Upload all the content of the &lt;code&gt;_site&lt;/code&gt; folder into the bucket.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the &lt;em&gt;Properties&lt;/em&gt; tab and enable the &lt;em&gt;Static website content&lt;/em&gt; feature.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NnOWIklG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/abb46z1g7utrj4vys911.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NnOWIklG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/abb46z1g7utrj4vys911.png" alt="'Static website hosting' enabled" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Confirm the index document filename as &lt;code&gt;index.html&lt;/code&gt; and save.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the &lt;em&gt;Permissions&lt;/em&gt; tab and edit the bucket policy as follows (use your bucket name in the &lt;em&gt;Resource&lt;/em&gt; field):&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PublicReadGetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&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:::johndoe-blog/*"&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;ul&gt;
&lt;li&gt;&lt;p&gt;Now you can point your browser to the bucket web address:&lt;br&gt;&lt;br&gt;
&lt;code&gt;http://johndoe-blog.s3-website.eu-north-1.amazonaws.com&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
(You can find it at the bottom of the &lt;em&gt;Properties&lt;/em&gt; tab)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The last action is to create or update a CNAME record in your DNS provider configuration console, in order to map your custom domain name to the S3 website url:&lt;br&gt;&lt;br&gt;
&lt;code&gt;johndoe.com CNAME my-awesome-site.com.s3-website-us-east-1.amazonaws.com&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To automate the deploy there are many options.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The simpler one is probably to use the AWS CLI with the command &lt;code&gt;aws s3 sync&lt;/code&gt;. In this case you will simply sync your output folder &lt;code&gt;_site&lt;/code&gt; with the bucket root.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A cleaner solution could be using CodeCommit and CodePipeline to automate the S3 bucket update when you push your changes to your git repository. You can follow &lt;a href="https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-s3deploy.html"&gt;this tutorial&lt;/a&gt; for more information about this procedure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option B - GitHub pages
&lt;/h3&gt;

&lt;p&gt;Another interesting option is using GitHub + &lt;a href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt; + GitHub Actions.&lt;br&gt;&lt;br&gt;
In this case you can leverage this interesting workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put all your source code in a repository named &lt;code&gt;&amp;lt;your-github-username&amp;gt;.github.io&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Commit and push your changes&lt;/li&gt;
&lt;li&gt;GitHub Action will do all the magic&lt;/li&gt;
&lt;li&gt;Point your browser to &lt;code&gt;https://&amp;lt;your-github-username&amp;gt;.github.io&lt;/code&gt; and enjoy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to try GitHub Actions this could be a good way to start familiarizing with it.&lt;br&gt;&lt;br&gt;
An important point to highlight, that can be considered good or bad depending on the point of view, is that in this case the static site will be generated by another instance of Jekyll managed by GitHub, so you will have to check that everything is fine and corresponds to the output of you local Jekyll instance.&lt;/p&gt;

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

&lt;p&gt;I hope that this guide can be useful to someone, for me it has been an interesting learn-by-doing experience.&lt;br&gt;&lt;br&gt;
If you see errors or you have any suggestions to handle the process in a better or cleaner way, I will be happy to integrate it and improve the solution.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>devops</category>
      <category>aws</category>
      <category>github</category>
    </item>
    <item>
      <title>Study Guide for AWS Associate Level Certifications - Part 1</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Sun, 09 Jul 2023 13:45:12 +0000</pubDate>
      <link>https://dev.to/ctaverna/study-guide-for-aws-associate-level-certifications-part-1-b5c</link>
      <guid>https://dev.to/ctaverna/study-guide-for-aws-associate-level-certifications-part-1-b5c</guid>
      <description>&lt;h2&gt;
  
  
  My recap notes for the 3 certification exams
&lt;/h2&gt;

&lt;p&gt;While studying for the 3 AWS Associate level certifications I started taking notes in markdown format.&lt;/p&gt;

&lt;p&gt;This is because to remember things I have to write them down, producing short summaries of the information I am reading. And this is the only method that works for me.&lt;br&gt;&lt;br&gt;
I later realized that this &lt;strong&gt;recap content&lt;/strong&gt;, in addition to being a support to me as a refresher before the exams, might also be useful to others, so I decided to publish it on my blog.&lt;br&gt;
I think and hope that it could be used as an additional study source, as a refresher, or even as a super-fast introduction to some AWS topics that you are approaching.&lt;/p&gt;

&lt;p&gt;I will release the content gradually &lt;strong&gt;over the coming weeks&lt;/strong&gt;, after a small review.&lt;br&gt;&lt;br&gt;
The content of the three exams is different, but there is a lot of overlap, and a general overview can be useful in any case, if you are studying for &lt;a href="https://aws.amazon.com/certification/certified-developer-associate/"&gt;AWS Certified Developer - Associate&lt;/a&gt;, &lt;a href="https://aws.amazon.com/certification/certified-developer-associate/"&gt;AWS Certified Developer - Associate&lt;/a&gt; or &lt;a href="https://aws.amazon.com/certification/certified-sysops-admin-associate/"&gt;AWS Certified SysOps Administrator - Associate&lt;/a&gt;.&lt;br&gt;
In the end, I will also share a table with a matrix to better understand what are the services on which each exam is focused.&lt;/p&gt;

&lt;p&gt;This is the content that comes with &lt;strong&gt;Part 1&lt;/strong&gt;, released today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concepts&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/concepts/definitions/"&gt;Definitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/concepts/aws-infrastructure/"&gt;The AWS infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/concepts/services-overview/"&gt;Services overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/concepts/dr-and-support/"&gt;DR &amp;amp; Support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Networking&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/networking/vpc/"&gt;VPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/networking/route53/"&gt;Route 53&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/networking/elb/"&gt;ELB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/networking/cloudfront-and-caching/"&gt;Cloudfront&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Management&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/management/cloudformation-and-sam/"&gt;CloudFormation and SAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/management/systems-manager/"&gt;Systems Manager (SSM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/management/opsworks-beanstalk-lightsail/"&gt;OpsWorks, Beanstalk, Lightsail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/management/cloudwatch/"&gt;CloudWatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/management/cloud-trail-vpc-flow-logs/"&gt;CloudTrail, VPC Flow Logs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security, Identity and compliance&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/security-identity-compliance/authentication-and-authorization/"&gt;Authentication and authorization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/security-identity-compliance/iam/"&gt;IAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ctaverna.github.io/aws-study-guide/security-identity-compliance/secrets/"&gt;Secrets&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next part I will share my notes about computing, databases, storage, developer tools, and many more.&lt;br&gt;&lt;br&gt;
I will also release a collection of the most interesting &lt;strong&gt;sample exam questions&lt;/strong&gt; that I have come across while studying and practicing.&lt;/p&gt;

&lt;p&gt;Please let me know if you find this useful in any way or if you find errors or inaccuracies in the content.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>certification</category>
      <category>cloud</category>
      <category>learning</category>
    </item>
    <item>
      <title>How to deal with 40 million documents per day with AWS OpenSearch</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Mon, 03 Jul 2023 14:28:02 +0000</pubDate>
      <link>https://dev.to/ctaverna/how-to-deal-with-40-million-documents-per-day-with-aws-opensearch-40pp</link>
      <guid>https://dev.to/ctaverna/how-to-deal-with-40-million-documents-per-day-with-aws-opensearch-40pp</guid>
      <description>&lt;p&gt;&lt;strong&gt;An approach to compress data without losing (almost) any data&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;This story is about an IoT project, hosted on AWS, ingesting data produced by some devices. &lt;br&gt;
The system is a distributed architecture, where after the ingestion stage, the raw data pass through an &lt;em&gt;Amazon Kinesis Data Stream&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
This stream is the source for a bunch of services, each one responsible for different processing that we need to perform on the data.&lt;br&gt;&lt;br&gt;
Kinesis is a key point for the system, because it allows each consumer to read events and process them &lt;strong&gt;at its speed&lt;/strong&gt;, making each processing pipeline independent from the others. Kinesis can also absorb all traffic peaks and allows the ingestion to continue when one or more processing pipelines are temporarily disabled, for any reason.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7d_bOdNp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lpvysabailz57glm181p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7d_bOdNp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lpvysabailz57glm181p.jpg" alt="Kinesis Data Stream is the source of all processing pipelines" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The cross-checking need
&lt;/h2&gt;

&lt;p&gt;During the project's evolution, it became important to validate the processings performed on the data stream. The idea was to find a way to save &lt;em&gt;somewhere&lt;/em&gt; the raw data, so that it could become possible to cross-check the result, make quick troubleshooting, and validate the processing.&lt;br&gt;&lt;br&gt;
When the quantity of data is huge you cannot simply save it on a relational database and query it in all possible ways, present and future. But OpenSearch (and &lt;em&gt;ElasticSearch&lt;/em&gt; too, of course) can do it pretty well, and is also a great tool to query and filter data, make advanced aggregations, and visualize the results, literally &lt;strong&gt;in seconds&lt;/strong&gt;, without the need for preliminary &lt;em&gt;data preparation&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
So we added a sort of a &lt;strong&gt;sniffer component&lt;/strong&gt;, that started to consume events from Kinesis and to save them into OpenSearch, after a quick enrichment/denormalization process.&lt;br&gt;&lt;br&gt;
The result was an index with all the data from all devices, and OpenSearch has proven to be a great tool for our troubleshooting needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BkgsintQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/motugd7wa8badg5wjh8m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BkgsintQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/motugd7wa8badg5wjh8m.jpg" alt='Data "sniffing" to enable cross-checking on processed data' width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and cost considerations
&lt;/h2&gt;

&lt;p&gt;OpenSearch is very good with big quantities of data, but in our scenario each document is about 2KB and we have to store 40 million documents every day.&lt;br&gt;&lt;br&gt;
We started using daily indexes. From a performance perspective, OpenSearch worked very well, but each index was about 22 Gigabytes, which means something like 8 Terabytes per year of data. A bit too much for a service that after all is not strictly &lt;em&gt;part of the system&lt;/em&gt;, but is something like a "data observability tool".&lt;br&gt;&lt;br&gt;
The cost started soon to be too high, and the first option that we saw was to delete old data and keep just the head, for example, the last two or three months.&lt;br&gt;&lt;br&gt;
It was for sure an option, but sometimes it could have been interesting to execute queries on long-term data, for example just the data from a single device, but over a long period of time, for example one year, or even more.&lt;br&gt;&lt;br&gt;
A better alternative was finding a way to aggregate the data, reduce the data size and so keep more data by paying a similar cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollup jobs
&lt;/h2&gt;

&lt;p&gt;The cleaner way seemed to be using &lt;a href="https://opensearch.org/docs/latest/im-plugin/index-rollups/index/"&gt;Index rollups&lt;/a&gt;, a feature that automatically reduces data granularity by making metric aggregations, in a completely transparent way.&lt;br&gt;&lt;br&gt;
After some tests, we noticed that with our data we had some errors. Probably it was related to some problem in our data, or may be there was a bug because OpenSearch was relatively young. We didn't discover the reason behind those failures, we had no useful logs and no time to spend on that investigation.&lt;br&gt;&lt;br&gt;
In addition to that, this solution is limited in the types of aggregations, because &lt;em&gt;rollups jobs&lt;/em&gt; can perform only &lt;em&gt;All&lt;/em&gt;, &lt;em&gt;Min&lt;/em&gt;, &lt;em&gt;Max&lt;/em&gt;, &lt;em&gt;Sum&lt;/em&gt;, &lt;em&gt;Avg&lt;/em&gt;, and &lt;em&gt;Value Count&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
In our scenario it could be useful to us more freedom in the aggregation logic, for example, collecting the &lt;em&gt;distribution&lt;/em&gt; of a metric.&lt;br&gt;&lt;br&gt;
So we explored the option of using &lt;a href="https://opensearch.org/docs/latest/im-plugin/index-transforms/index/"&gt;index transform&lt;/a&gt;, and through this feature we finally reached our goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transform job
&lt;/h2&gt;

&lt;p&gt;The solution is based on the idea of having 2 different sets of data, for different needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;sliding window&lt;/strong&gt; of uncompressed &lt;strong&gt;raw data&lt;/strong&gt;
(in our case configured to be the last 2 months)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;historical set&lt;/strong&gt; of &lt;strong&gt;compressed data&lt;/strong&gt;, going into the past as long as possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jdPZkoXV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y37ov5l7tefxcu5gngqm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jdPZkoXV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y37ov5l7tefxcu5gngqm.jpg" alt="The timeline with the two datasetsn" width="800" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shortly, this is how the solution works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;em&gt;EventBridge&lt;/em&gt; rule is scheduled to be executed every day and start a Lambda function&lt;/li&gt;
&lt;li&gt;The lambda creates a monthly index and its &lt;a href="https://opensearch.org/docs/2.4/opensearch/mappings/"&gt;mapping&lt;/a&gt; for the compressed data (in OpenSearch these operations are idempotent)&lt;/li&gt;
&lt;li&gt;The lambda deletes the daily indexes of uncompressed data older than the configured sliding window (in our case this means that is deleted the 61st daily index before execution time)&lt;/li&gt;
&lt;li&gt;The lambda creates the daily transform job, for immediate execution&lt;/li&gt;
&lt;li&gt;The transform job is executed, and works on the data of the previous day&lt;/li&gt;
&lt;li&gt;The data is aggregated in blocks of 1 hour, per single device&lt;/li&gt;
&lt;li&gt;Some metrics are aggregated using the available standard aggregations (&lt;em&gt;min&lt;/em&gt;, &lt;em&gt;max&lt;/em&gt;, &lt;em&gt;average&lt;/em&gt; and so on)&lt;/li&gt;
&lt;li&gt;Other metrics are processed using custom &lt;a href="https://opensearch.org/docs/latest/aggregations/metric-agg/#scripted_metric"&gt;scripted_metric aggregations&lt;/a&gt;, and leveraging the flexibility of map/combine/reduce scripting, in our case the data is reduced into custom distribution reports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advantages and disadvantages
&lt;/h2&gt;

&lt;p&gt;According to the investigation we need to take, we can decide whether it is better to use the most recent raw data as-is, or the historical compressed data.&lt;br&gt;&lt;br&gt;
If, for example, the need is to check what exactly a specific device has sent the last week we can use the daily indexes of the uncompressed raw data.&lt;br&gt;&lt;br&gt;
Instead, if the need is for example to study a trend during the last year, the compressed monthly indexes are the right data source.  &lt;/p&gt;

&lt;p&gt;A negative aspect of this solution is that when we start working on a visualization we have to decide in advance on which of the two data sources we want to work. The schema is different so a visualization created on the daily index will not work on the monthly indexes and vice-versa.&lt;br&gt;&lt;br&gt;
It's not really a major problem, because usually you know well what source fits well on the specific need. Anyway, the index rollup feature is for sure better from this point of view, because the data source is the same and you don't have to handle this double data source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some quick calculations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Daily raw-data uncompressed indexes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each JSON document is about 1.6 KB&lt;/li&gt;
&lt;li&gt;Each index contains on average 40 million documents&lt;/li&gt;
&lt;li&gt;The daily index size is on average &lt;strong&gt;23 GB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;One month of data is about &lt;strong&gt;700 GB&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Monthly aggregated indexes&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each JSON document is about 2.2 KB&lt;/li&gt;
&lt;li&gt;Each index contains on average 15 million documents&lt;/li&gt;
&lt;li&gt;The index monthly size is on average &lt;strong&gt;6 GB&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final considerations
&lt;/h2&gt;

&lt;p&gt;It's easy to see that the resulting &lt;strong&gt;compression ratio&lt;/strong&gt; is roughly &lt;strong&gt;100:1&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
This means that, even configuring historical indexes for 1 replica and so doubling their size, the disk space needed to handle &lt;strong&gt;one month&lt;/strong&gt; of raw data allows us to store more than &lt;strong&gt;4 years&lt;/strong&gt; of aggregated data.&lt;br&gt;&lt;br&gt;
In addition to that OpenSearch is much faster querying the aggregated indexes.&lt;/p&gt;

&lt;p&gt;Before implementing this solution we had stored 1 year of raw data, but due to performance troubles, we had to scale out the OpenSearch cluster up to 6 nodes. This generated a considerable cost, both for computing and storage resources.&lt;br&gt;&lt;br&gt;
With this &lt;strong&gt;serverless solution&lt;/strong&gt; that automates the data aggregation, we were able to &lt;strong&gt;reduce the cluster size&lt;/strong&gt; to just 3 nodes, each one with &lt;strong&gt;smaller storage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In other words, with a small and acceptable loss of data, today we &lt;strong&gt;spend less money&lt;/strong&gt; while we can afford to &lt;strong&gt;retain all historical data&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>architecture</category>
      <category>nosql</category>
      <category>aws</category>
    </item>
    <item>
      <title>Design for failure by using Dead Letter Queues (DLQ)</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Sat, 17 Jun 2023 08:19:07 +0000</pubDate>
      <link>https://dev.to/ctaverna/design-for-failure-by-using-dead-letter-queues-dlq-2g65</link>
      <guid>https://dev.to/ctaverna/design-for-failure-by-using-dead-letter-queues-dlq-2g65</guid>
      <description>&lt;p&gt;&lt;strong&gt;What are dead letters and why they are essential in any distributed system&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a dead letter
&lt;/h2&gt;

&lt;p&gt;Let's start with a short definition, effective in the context of distributed systems and messaging platforms:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A &lt;em&gt;dead letter&lt;/em&gt; is simply a container where &lt;em&gt;messages&lt;/em&gt; that cannot be delivered are collected and stored.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This container is usually a queue, but it's not strictly mandatory, the concept is broader and not necessarily based on it.&lt;br&gt;&lt;br&gt;
This article is not focused on a specific broker or technology, but addresses the subject from a general perspective. Each system has its specificities, and the official documentation is definitely the way to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short off-topic about the name
&lt;/h2&gt;

&lt;p&gt;The term "dead letter" is due to an expression borrowed by the postal world.&lt;br&gt;&lt;br&gt;
There are strong analogies between real-world mail deliveries and how, in distributed systems, messages are delivered between software components.&lt;br&gt;&lt;br&gt;
In the real world, there are many reasons for a letter or a package to be marked as "undeliverable": both recipient and sender addresses are incorrect, the envelope is damaged and addresses are no more readable, both recipient and sender are no more available, the content is not compliant with postal regulations, and many others.&lt;br&gt;&lt;br&gt;
All postal companies in different countries have faced the problem of handling all undeliverable mail, usually with a dedicated office having the critical responsibility of opening private letters and packages, searching for clues about the recipient or the sender, to finally becoming able to deliver it or move to the extreme solution of destroying or auctioning the content.&lt;br&gt;&lt;br&gt;
If you like to waste some time reading fun facts, consider googling about the story and the statistics of &lt;em&gt;Dead Letter Offices&lt;/em&gt; all around the world, you'll find many astonishing numbers and funny stories about strange contents, from alive rattlesnakes to smelly dead fishes, human skulls, bags full of money, drugs, and weapons.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Design for failure
&lt;/h2&gt;

&lt;p&gt;Jumping back to the field of distributed systems, when a messaging system has the responsibility of the asynchronous communication between components, it's essential to provide a mechanism to handle the failures.&lt;br&gt;&lt;br&gt;
This is, in general, a piece of good advice. Like the Amazon CTO Werner Vogels has wisely said: &lt;strong&gt;"Everything fails, all the time"&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
In other words, the point is not &lt;em&gt;IF&lt;/em&gt; something will fail, but only &lt;em&gt;WHEN&lt;/em&gt; this will happen.&lt;br&gt;&lt;br&gt;
Hence we need to design systems so that they will continue to work, as much as possible, also &lt;em&gt;during&lt;/em&gt; and &lt;em&gt;after&lt;/em&gt; these foreseeable failures.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.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%2Fwofcbqzxwmp6sr0nh0tz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fwofcbqzxwmp6sr0nh0tz.jpg" alt="A fire estinguisher"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to handle errors
&lt;/h2&gt;

&lt;p&gt;Any asynchronous communication can potentially encounter different kinds of problems related to delivery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The recipient is not available&lt;/li&gt;
&lt;li&gt;The recipient explicitly refuses the message&lt;/li&gt;
&lt;li&gt;The recipient is not giving an &lt;em&gt;acknowledge&lt;/em&gt; to the message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem can be transient or persistent, and the consequent action should change accordingly.&lt;br&gt;&lt;br&gt;
But when a delivery failure happens, the broker between two components has only a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Try again&lt;/li&gt;
&lt;li&gt;Lock the queue&lt;/li&gt;
&lt;li&gt;Discard the message and continue&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Retrying is a good option for transient errors, but it is useless, when not counterproductive, if the error is persistent.&lt;br&gt;&lt;br&gt;
Retrying should be done waiting some time between each attempt, through an &lt;a href="https://en.wikipedia.org/wiki/Exponential_backoff" rel="noopener noreferrer"&gt;exponential backoff algorithm&lt;/a&gt;. In this case, the retry process can take a long time and this results actually in a temporary lock, which can be unacceptable for high-load systems, as discussed in the next bullet point.&lt;br&gt;&lt;br&gt;
Also, even a transient error, if repeated many times, should be considered in practice a persistent error. An infinite loop is never a good idea, and at a certain point, in any case, it is advisable to stop trying if it is not working.&lt;br&gt;&lt;br&gt;
We can say that generally speaking retrying is good, but it's usually not enough.&lt;/p&gt;

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

&lt;p&gt;The option of locking the queue is mandatory when there is a strict constraint about message ordering, but fortunately, this is not the most common scenario. In most cases the distributed systems are designed to handle unordered messages, and &lt;em&gt;poison messages&lt;/em&gt; can simply be put aside for retry or future investigation.&lt;br&gt;&lt;br&gt;
Locking can sometimes be a reasonable option, but in many cases, especially when there is a critical process with a huge quantity of data to be processed, it is simply not acceptable.&lt;br&gt;&lt;br&gt;
A locked queue means indeed that the queue is growing indefinitely, and this is not sustainable for a long time, and can bring other even worse infrastructure problems. In addition to that, the consumers are not allowed to proceed on a locked queue, and this means that someone (user or software) is not receiving the expected data, experiencing a malfunction or an unexpected delay.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Discard and continue
&lt;/h3&gt;

&lt;p&gt;And here we are to the third option: discard the message and continue to process the others. Here, "discard" doesn't mean to delete the message but means "remove from the queue and put it somewhere".&lt;br&gt;&lt;br&gt;
This is usually the best option because the consumer can forget the problematic message and continue to work, but the message is not lost and can be analyzed and/or recovered later on.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Dead letter queue vs other options
&lt;/h2&gt;

&lt;p&gt;The general concept of &lt;em&gt;dead lettering&lt;/em&gt; a message can be carried on in multiple ways.&lt;br&gt;&lt;br&gt;
Just to list a few, the &lt;em&gt;poison messages&lt;/em&gt; could be simply saved into log files, it could be inserted in a database, it could be written to disk in a text file, or it could be saved into an object storage service like AWS S3 or Azure Blob Storage.&lt;br&gt;&lt;br&gt;
In some specific cases, one of these strategies might even be the best option, but using a dead letter queue has many advantages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster retry and purging
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;dead letter queue&lt;/em&gt; is, after all, a normal queue, with all the advantages offered by this kind of entity. For example, it's usually very easy to "&lt;em&gt;forward&lt;/em&gt;" the messages from the DLQ to the original queue. If the consumer had a bug that meanwhile has been corrected, for example, we can simply re-enqueue the dead-lettered messages, and the new version of the consumer will be now able to process the "old" dead-lettered messages together with the ordinary flow of real-time messages.&lt;br&gt;&lt;br&gt;
On the contrary, if after an investigation we know that the messages are wrong and can be deleted, with one click we can purge the entire queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability for free
&lt;/h3&gt;

&lt;p&gt;If you are using a message broker you likely already have a monitoring platform.&lt;br&gt;&lt;br&gt;
In this case, it will be almost immediate to activate observability principles also on the DLQ, with very little additional effort. No need to generate custom metrics in your code or analyze log files to understand and count errors, because the broker, if configured properly, can do it for you.&lt;br&gt;&lt;br&gt;
The rate of messages entering the DLQ, compared with the rate of the main queue, is a good metric for the health status of the process run by the consumers. The absolute quantity of messages in the dead letter is telling us how many failures we had since the last purge, and if the rate has a predictable trend or DLQ is usually empty, these metrics are also good choices for automatic alerts.  &lt;/p&gt;

&lt;h3&gt;
  
  
  No custom code
&lt;/h3&gt;

&lt;p&gt;Most brokers, when sending a message to the dead letter, add in the header of the message a field with the reason why this has happened. It may be a broker-generated error like "too many retries" or "message expired", or maybe a consumer-generated and broker-forwarded error like "explicitly nacked with this error message: unknown id".&lt;br&gt;&lt;br&gt;
The reason for dead-lettering is put inside the message itself, and this comes for free, without writing code, because it is implemented &lt;em&gt;in the broker&lt;/em&gt;, not in the consumer software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not so good for analysis
&lt;/h3&gt;

&lt;p&gt;The only little disadvantage that I experienced using DLQs, compared to other solutions like a database, is the fact that messages in a queue cannot be easily queried and counted, but can only be peeked one by one. In case of high volumes and considerable quantities of messages ending up in a dead letter, it can be a good idea to attach a consumer and move them into a data store where it is possible to analyze them statistically.  &lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;Dead letter queues are definitely the best place to save error messages and poison messages. Monitoring is richer and easier, re-publishing and purging is super fast and the broker itself owns the responsibility of this process.  &lt;/p&gt;

</description>
    </item>
    <item>
      <title>A practical overview on Architecture Decision Records (ADR)</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Fri, 26 May 2023 01:33:44 +0000</pubDate>
      <link>https://dev.to/ctaverna/a-practical-overview-on-architecture-decision-records-adr-d7d</link>
      <guid>https://dev.to/ctaverna/a-practical-overview-on-architecture-decision-records-adr-d7d</guid>
      <description>&lt;h2&gt;
  
  
  How to start and why this could be your most valuable action as a software architect
&lt;/h2&gt;

&lt;p&gt;I have been using ADRs for some years.&lt;br&gt;&lt;br&gt;
In this article I'll try to give an introduction, share my feedback, and encourage you to start writing them if you already don't.&lt;/p&gt;
&lt;h2&gt;
  
  
  A little about the architecture decisional process
&lt;/h2&gt;

&lt;p&gt;Taking architecture decisions is tough.&lt;br&gt;&lt;br&gt;
It can be the result of a bunch of technical and non-technical evaluations, made of tricky balances between technology, cost, time, and stakeholders' requests.&lt;br&gt;&lt;br&gt;
There is rarely one single, optimal solution. Instead, there are many reasonable solutions, no true/false, only better/worse options.&lt;br&gt;&lt;br&gt;
The final decision may seem like a small thing, but a lot of work may have been required to come to that decision.&lt;br&gt;&lt;br&gt;
For this reason, architectural decisions are usually considered &lt;a href="https://en.wikipedia.org/wiki/Wicked_problem"&gt;"wicked problems"&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;Nevertheless, most teams come to a decision verbally during meetings, and those decisions are sooner or later lost or forgotten. And all that remains is just the state of things.&lt;/p&gt;

&lt;p&gt;This is a shame, because in a nutshell, the real value of a decision is what is stated by the "&lt;em&gt;Second Law of Software Architecture&lt;/em&gt;":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why is more important than how&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are curious about the first law, I’d wholeheartedly recommend that you get and read the great book &lt;a href="http://fundamentalsofsoftwarearchitecture.com"&gt;Fundamentals of Software Architecture&lt;/a&gt; written by Mark Richards &amp;amp; Neal Ford.&lt;/p&gt;

&lt;p&gt;Before going deep into considerations and tips, it's useful to clarify the discussion by giving a quick definition:  &lt;/p&gt;

&lt;p&gt;{: .box-note}&lt;br&gt;
&lt;em&gt;An ADR is simply a concise document that explains&lt;br&gt;&lt;br&gt;
**why&lt;/em&gt;* something is going to be made &lt;strong&gt;this way&lt;/strong&gt;*.&lt;/p&gt;
&lt;h2&gt;
  
  
  The structure of an ADR
&lt;/h2&gt;

&lt;p&gt;This is a basic example of an ADR template, in markdown format, as suggested by &lt;a href="http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions"&gt;Michael Nygard&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Title&lt;/span&gt;

&lt;span class="gu"&gt;## Date&lt;/span&gt;

&lt;span class="gu"&gt;## Status&lt;/span&gt;
What is the status, such as proposed, accepted, rejected, deprecated, superseded, etc.?

&lt;span class="gu"&gt;## Context&lt;/span&gt;
What is the issue that we're seeing that is motivating this decision or change?

&lt;span class="gu"&gt;## Decision&lt;/span&gt;
What is the change that we're proposing and/or doing?

&lt;span class="gu"&gt;## Consequences&lt;/span&gt;
What becomes easier or more difficult to do because of this change?

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

&lt;/div&gt;



&lt;p&gt;There are a lot of other templates, with different levels of complexity, but in my opinion it is better to keep it short and easy. And Nygard's template is perfect, according to me.&lt;/p&gt;

&lt;p&gt;If you want to see some real examples, you will find at the end of the article some links to pages with many detailed resources and examples.&lt;/p&gt;

&lt;p&gt;Every decision should have a unique code, usually in the filename and in the title, resulting in something like:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;ADR0001 - Use an SQS queue to manage outgoing emails&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Every ADR should explain &lt;strong&gt;one single decision&lt;/strong&gt;, not many of them.&lt;br&gt;&lt;br&gt;
It's frequent to write multiple ADRs after a long analysis, but each one should have its own code and its own lifecycle, because in the future some of them will change status, while others will stay in &lt;em&gt;accepted&lt;/em&gt; state.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHY are ADRs important?
&lt;/h2&gt;

&lt;p&gt;Now that we know what's an ADR, it's important to understand in-depth the advantages of the adoption of this framework.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking the knowledge
&lt;/h3&gt;

&lt;p&gt;Architecture decisions are pieces of &lt;em&gt;knowledge&lt;/em&gt;, among the most important, in my opinion.&lt;br&gt;&lt;br&gt;
People move between teams and companies, and it's important not to lose that knowledge when someone leaves the team.&lt;br&gt;&lt;br&gt;
On the other side, when a new member joins the team, they will face exactly the same lack of knowledge, and for sure they will ask themselves: &lt;em&gt;"Why the hell was the system designed in this way?"&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
We developers are opinionated guys, and an &lt;em&gt;architecture decisions log&lt;/em&gt; can be a great way to share knowledge and prevent frustrations.&lt;br&gt;&lt;br&gt;
When given to new team members it can be a really powerful document, whose reading can save days, during the onboarding process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Empowering the team culture
&lt;/h3&gt;

&lt;p&gt;Writing down architectural decisions is the first fundamental step to making them first-class entities in the teamwork final result.&lt;br&gt;&lt;br&gt;
It's the most structured way to align &lt;em&gt;officially&lt;/em&gt; the whole team about the work behind important decisions.&lt;br&gt;&lt;br&gt;
If the team is big, it can reduce the distance between &lt;em&gt;design&lt;/em&gt; and &lt;em&gt;development&lt;/em&gt;. In this way, everybody can understand decisions that otherwise, from the bottom, without an overall view, could seem incomprehensible.&lt;br&gt;
Tracking decisions is also something that contributes positively to the team culture, because between the lines it says &lt;em&gt;"for this team, the design counts"&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A matter of responsibility
&lt;/h3&gt;

&lt;p&gt;As a software architect, or as a technical leader, or even as a team with shared leadership, one of your most important responsibilities is taking these decisions. Not writing them down could dangerously suggest that things are happening without a reason, driven by pure instinct, or worse randomly.&lt;/p&gt;

&lt;p&gt;Your decisions could have an impact for many years, even if you will leave the project. It's a matter of professionalism, seriousness, and respect for those who end up with the system in the future.&lt;br&gt;&lt;br&gt;
Also, in the future, the ability to go back to old decisions and understand why they were taken, has many advantages, which include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will have the possibility to remember details that you will undoubtedly have forgotten.&lt;/li&gt;
&lt;li&gt;If the decision, seen from the new context, will still appear as a good decision, good news for your self-esteem. You have been forward-looking or at least you're a lucky person.&lt;/li&gt;
&lt;li&gt;If it will seem a bad decision, well, you will have the possibility to go deep into it, and you will be able to make a valuable &lt;strong&gt;retrospective&lt;/strong&gt; and understand what happened:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Option 1:&lt;/strong&gt; The context was completely different, and now you can say that &lt;em&gt;in that context&lt;/em&gt; it was the best option, or at least a good one. You did your job well. You are a software engineer, not a fortune-teller.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Option 2:&lt;/strong&gt; The context is &lt;em&gt;pretty much&lt;/em&gt; the same, but you are now able to consider other variables, that you missed in the past, and that change your point of view. You are growing, you have improved your judgment, and your next decision will be more accurate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Option 3:&lt;/strong&gt; The context is &lt;em&gt;exactly&lt;/em&gt; the same, and at this point, it's official what was already clear to everybody: you made a mistake. Anyway, it was a difficult decision, and at least you have a document that shows that you did your best, you’re not just a fool. Learning from your mistake, you're already a better professional now.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other indirect advantages
&lt;/h3&gt;

&lt;p&gt;Usually inside an ADR are also documented, in a more or less detailed fashion, the findings from investigations explored but not implemented. Describing the option that you are identifying as the best one, it's almost inevitable to explain also why the other options are not so good.&lt;br&gt;&lt;br&gt;
This information can have a huge value in the future, but is frequently lost, because there are no evident and direct reasons to track a solution that seems inappropriate.  &lt;/p&gt;

&lt;p&gt;Another last-but-not-least advantage is that writing down an important decision is a way to force ourselves to be objective and wise.&lt;br&gt;&lt;br&gt;
No one would ever write something like "We do this because we want to play with that sexy technology", but this is something that happens often. If you are a developer you know that it's true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is just-plain-text mandatory?
&lt;/h2&gt;

&lt;p&gt;In this field there are no absolute laws.&lt;br&gt;&lt;br&gt;
If a decision involves a complex relationship that could easily be described with a diagram, I think that it's okay to add it.&lt;br&gt;&lt;br&gt;
Anyway, this should be only considered as an option if strictly necessary to avoid confusion, because in most cases it's not needed.&lt;br&gt;&lt;br&gt;
It's likely, on the contrary, that if you feel the need for a diagram there is something wrong. It's a wake-up call that you are mixing more decisions into one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lifecycle of an ADR
&lt;/h2&gt;

&lt;p&gt;Every decision should start from a draft version, usually in &lt;em&gt;proposed&lt;/em&gt; status, then move to &lt;em&gt;accepted&lt;/em&gt; or &lt;em&gt;rejected&lt;/em&gt;, and so on.&lt;/p&gt;

&lt;p&gt;The document should be managed with an append-only approach. Of course, you can edit any part of the decision to fix a &lt;em&gt;typo&lt;/em&gt;, but the only part of the document that should evolve is the STATUS paragraph.&lt;br&gt;&lt;br&gt;
If the decision is no more relevant it should simply change its status to &lt;em&gt;deprecated&lt;/em&gt;. If the context changes, a new ADR should be created, and the old one should move to status &lt;em&gt;superseded&lt;/em&gt;, and so on.&lt;/p&gt;

&lt;p&gt;In my experience, it's useful to add cross-links between ADRs. I'm used to creating links both when the relation is direct (for example "Superseded by ADR0027") and when there is simply some kind of relation between two decisions (for example the second one is a consequence of the first one).&lt;br&gt;&lt;br&gt;
If you've ever used Jira or a similar issue-tracking tool you probably get my point.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHERE should ADRs be stored?
&lt;/h2&gt;

&lt;p&gt;I think that there is no right answer, it depends on the team culture.&lt;br&gt;&lt;br&gt;
The most common solutions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;em&gt;wiki&lt;/em&gt; platform&lt;/li&gt;
&lt;li&gt;a &lt;em&gt;git&lt;/em&gt; repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think that there are pros and cons for each solution. By the way, this decision could be a good candidate as a subject for the first ADR... ;-)  &lt;/p&gt;

&lt;h3&gt;
  
  
  Store into a wiki
&lt;/h3&gt;

&lt;p&gt;If you think that ADRs should be easily accessible to everyone in your team, including non-technical people, this is for sure the best option.&lt;br&gt;
In addition to that, having them in the wiki is good because it's easier to link the ADR from other project documentation pages, without leaving the platform.&lt;br&gt;&lt;br&gt;
In my opinion, this advantage is also a drawback, because it puts the architectural decision in the field of "documentation", which is not exactly the passion of each developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Store in a &lt;em&gt;git&lt;/em&gt; repository
&lt;/h3&gt;

&lt;p&gt;If you want the developers to perceive ADRs as something important, using &lt;em&gt;git&lt;/em&gt; is for sure a good starting point. Developers love code and love git, and in their mind, the subliminal message is "if it goes into a repo, it's important".&lt;br&gt;
Git is a great tool also to enable collaborative management of the release process of a new ADR, leveraging &lt;em&gt;pull requests&lt;/em&gt; for decisions in &lt;em&gt;proposed&lt;/em&gt; state.&lt;br&gt;
There are also some good command line tools that could work well to automate the process. I still haven't tried them, because for the time being I have not felt the need, but it could be an interesting option in the future.&lt;/p&gt;

&lt;p&gt;There is also an interesting win-win tool called &lt;a href="https://github.com/thomvaill/log4brains"&gt;log4brains&lt;/a&gt;, that can turn your markdown into a static website. It's also integrated into &lt;a href="https://adr.github.io/madr/"&gt;MADR template&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
I haven’t had time to try them yet, but it could be something to look into in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHO should be in charge of tracking ADRs?
&lt;/h2&gt;

&lt;p&gt;It doesn't matter.&lt;br&gt;&lt;br&gt;
This simply depends on the team and its culture. It could be a software architect, an entire team, a single developer, an ivory-tower architect, a technical leader, a senior developer or anyone else.&lt;br&gt;&lt;br&gt;
Just to be clear, this is a very important issue, but from this perspective is irrelevant.&lt;br&gt;
What really matters is rather WHEN.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHEN should an ADR be created?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When the decision will affect the work of the developers&lt;/li&gt;
&lt;li&gt;When the decision will be hardly reversible&lt;/li&gt;
&lt;li&gt;When the decision is not obvious to the entire team
(but pay attention, the team will change, sooner or later)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Some final tips
&lt;/h2&gt;

&lt;p&gt;While compiling the ADR, be very &lt;strong&gt;detailed&lt;/strong&gt; about the &lt;strong&gt;context&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
This is the part that in the future might differ more, so it's important not to be vague and not to assume anything.&lt;br&gt;
Describe the context with just &lt;strong&gt;facts&lt;/strong&gt;, not opinions.&lt;/p&gt;

&lt;p&gt;Take your time &lt;strong&gt;thinking&lt;/strong&gt; about the &lt;strong&gt;consequences&lt;/strong&gt;, because it's at that moment when the deepest reflections will come to light.&lt;br&gt;
And maybe you could even completely reconsider a decision that just a few minutes before seemed so good.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This is the fundamental &lt;a href="http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions"&gt;article from Michael Nygard&lt;/a&gt;, published in 2011, that first introduced the idea of ADRs.&lt;/li&gt;
&lt;li&gt;The GitHub repo &lt;a href="https://github.com/joelparkerhenderson/architecture-decision-record"&gt;joelparkerhenderson/architecture-decision-record&lt;/a&gt; is a good source to get an overview of tools, articles and examples.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://adr.github.io/"&gt;ADR GitHub organization&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://adr.github.io/madr/"&gt;MADR template&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;If you are responsible for architecture decisions and already don't, start documenting them. Now.&lt;br&gt;&lt;br&gt;
It's fast, easy, not time-consuming, and there are tons of good reasons.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>documentation</category>
    </item>
    <item>
      <title>How to pick the right managed service</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Mon, 22 May 2023 18:19:09 +0000</pubDate>
      <link>https://dev.to/ctaverna/how-to-pick-the-right-managed-service-2928</link>
      <guid>https://dev.to/ctaverna/how-to-pick-the-right-managed-service-2928</guid>
      <description>&lt;h2&gt;
  
  
  A wide range of options is great but an informed choice can be quite time-consuming
&lt;/h2&gt;

&lt;p&gt;During a feature development, it may happen that it's advisable to introduce a new managed service, for example a new database, a new storage type, or a computing or integration service.&lt;/p&gt;

&lt;p&gt;In some cases the choice is quite easy, because in the context of the project, there are no equally valid alternatives or because that service is universally recognized as optimal, or at least the preferable one.&lt;br&gt;
To be clear, there are &lt;em&gt;always&lt;/em&gt; alternatives to consider, but in some cases it doesn't make sense to spend a lot of time evaluating all the options because, one of them is very clearly recommended.&lt;/p&gt;

&lt;h2&gt;
  
  
  A &lt;em&gt;fairly&lt;/em&gt; easy example
&lt;/h2&gt;

&lt;p&gt;Let's take the example of a use case where a software component needs a cache to reduce the number of requests made to the database and increase the throughput. It is an application hosted on a cloud provider, and there are no tight budget constraints. Data volumes are not extremely heavy and we need a simple in-memory key-value store. The best strategy could be almost obvious: use &lt;em&gt;Redis&lt;/em&gt; as a managed service. Minimal infrastructure impact for the DevOps team, adoption of an established and standard tool, known and loved by developers, etc etc.&lt;br&gt;
We could talk for hours about all the possible alternatives, nuances of configuration and different possible options, but all in all, I think that 90% of people would agree and would suggest the same strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  A &lt;em&gt;not-so-easy&lt;/em&gt; example
&lt;/h2&gt;

&lt;p&gt;The choice is not always so easy, sometimes there are &lt;em&gt;many&lt;/em&gt; managed services that would perfectly fit on the use case, or that at least, in slightly different ways, could let us achieve the desired result.&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s5Ql-FOo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9bnhobvuuhu1j8rjted5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s5Ql-FOo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9bnhobvuuhu1j8rjted5.jpg" alt="A woman with a headache" width="300" height="222"&gt;&lt;/a&gt;&lt;br&gt;
In these cases the variables to consider are many, and it's not easy to put them all on the table and make the right decision.&lt;/p&gt;

&lt;p&gt;An example that recently happened to me is: which storage should I use for metrics coming from IoT devices? This is a project on AWS, and the possible options here are many:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Timestream? With what lifecycle policy?
&lt;/li&gt;
&lt;li&gt;Amazon DynamoDB? With what primarykey configuration?
&lt;/li&gt;
&lt;li&gt;OpenSearch or ElasticSearch could work well too?
&lt;/li&gt;
&lt;li&gt;Or Amazon Managed Service for Prometheus?
&lt;/li&gt;
&lt;li&gt;And what about a good old partitioned table on Amazon RDS for PostgreSQL?
&lt;/li&gt;
&lt;li&gt;In addition to the write load, do we have to manage intensive reading?
&lt;/li&gt;
&lt;li&gt;What reading patterns should we use?
&lt;/li&gt;
&lt;li&gt;What will be the data life cycle?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue is not simple, and the effect of this choice can have a huge impact on costs, on the time needed to achieve the result, and on its final value.&lt;/p&gt;

&lt;h2&gt;
  
  
  A rational analytic approach
&lt;/h2&gt;

&lt;p&gt;The most natural approach, since after all we are software engineers, is to ask ourselves some questions, pull out something measurable, and weigh the pros and cons.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dDrwYZEE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4x5d1camunxuswrd4wyc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dDrwYZEE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4x5d1camunxuswrd4wyc.jpg" alt="An abacus" width="285" height="300"&gt;&lt;/a&gt; &lt;br&gt;
If it's a new service, for example, it would be right to ask ourselves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is the cost model? Is it sustainable?&lt;/li&gt;
&lt;li&gt;Having no costs' historical data, are we sure that there are no side costs?&lt;/li&gt;
&lt;li&gt;How long can we invest in a POC to validate the theory and test it?&lt;/li&gt;
&lt;li&gt;What is the delta value that the service would provide in the short and long term?&lt;/li&gt;
&lt;li&gt;Have we considered all the extra costs of an extra service? For example those related to additional effort for DevOps processes, monitoring, and maintenance?&lt;/li&gt;
&lt;li&gt;Are there any indirect benefits to its adoption? Would it bring value to the project also for other future features?&lt;/li&gt;
&lt;li&gt;Could this new technology become a problem in the future because of the lack of skills in the team or in the company?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we are evaluating an already used service, the questions will be slightly different, but still quite complicated, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the cost model that we already have a 100% valid time series, in this new scenario?&lt;/li&gt;
&lt;li&gt;Are we making compromises on alternatives?&lt;/li&gt;
&lt;li&gt;Are we sure that we’ve considered all of the alternatives?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's easy to opt for a &lt;strong&gt;good&lt;/strong&gt; risk-free solution instead of a &lt;strong&gt;great&lt;/strong&gt; one with some risk margin, but it may not always be a winning strategy, especially in the long run.&lt;/p&gt;

&lt;p&gt;There are also many other general questions, not so exciting for a typical developer, that are often forgotten:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the service mature and production-ready?&lt;/li&gt;
&lt;li&gt;Is it already listed as &lt;em&gt;generally available&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;Is the support service suitable for our production environment?&lt;/li&gt;
&lt;li&gt;Is the service geographically available in the region where the project will be deployed?&lt;/li&gt;
&lt;li&gt;Is it or will it be available also in the regions where an expansion is planned?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not meant to be an exhaustive list, I’m definitely forgetting several points, and of course the specificity of the single project would produce more specific and detailed questions.&lt;/p&gt;

&lt;p&gt;A further bad news is that to follow such an analytical approach we should answer all these questions &lt;em&gt;for each&lt;/em&gt; of the possible options.&lt;br&gt;
Also, when the choice is not only on a single component but involves more than one, the amount of possible variables grows exponentially, and consequently the number of assessments in charge to the decision maker.&lt;br&gt;
In these cases, this type of approach, which is always preferable in principle, could become very challenging, or even unsustainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  An intuitive approach - the expert's opinion
&lt;/h2&gt;

&lt;p&gt;In order not to get bogged down into a never-ending analysis, at this point the only way out is to use the experience, built mostly on past mistakes and successes, to make a choice dictated &lt;em&gt;also&lt;/em&gt; by a small instinctive component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z185b729--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zkn56c6ycr5jqdr0zndo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z185b729--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zkn56c6ycr5jqdr0zndo.jpg" alt="Plato" width="300" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Opinion is the medium between knowledge and ignorance.&lt;br&gt;&lt;br&gt;
          &lt;strong&gt;--Plato&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The aim is certainly not to make irrational and not-data-driven choices. But from my point of view, it is sometimes crucial to try to simplify the problem by removing some options from the table without major time investments, for example looking for just a single reason to discard them. Sometimes these options are possible, reasonable, and maybe even could bring some remarkable strengths, but we can still assume with a certain degree of security that they will not be those with the best cost/benefit balance.&lt;br&gt;
A few examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The service costs &lt;em&gt;much more&lt;/em&gt; than other options, and for the project the cost is a critical point (has ever existed one where it wasn't?)&lt;/li&gt;
&lt;li&gt;The service demands very specific knowledge that is not available in the team, and there is no time to acquire that competence&lt;/li&gt;
&lt;li&gt;High performance is required, and other solutions are definitely better&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reducing the number of options by relying on experience is the best way to make sure that the remaining options are an acceptable number and so that an analytical approach is sustainable.&lt;br&gt;
When it comes to software architecture is always good to be flexible and find a balance, sometimes accepting some compromise.&lt;/p&gt;

&lt;p&gt;Last but not least, once the decision is made, it is &lt;em&gt;fundamental&lt;/em&gt; to document it as an ADR (architectural decision record), for a long series of good reasons, that maybe in the future I will talk about in a dedicated article.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;Making a conscious choice from the rich offer of cloud providers is tough, and requires a multi-disciplinary approach. But there are some questions that we can ask ourselves to simplify the process.&lt;br&gt;
It's good to be analytical but to be faster enough for the business it's equally important to be pragmatic and leverage your experience in order to reduce the amount of evaluations.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>architecture</category>
      <category>aws</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Software architecture diagram arrows</title>
      <dc:creator>Claudio Taverna</dc:creator>
      <pubDate>Sat, 13 May 2023 21:40:53 +0000</pubDate>
      <link>https://dev.to/ctaverna/software-architecture-diagram-arrows-30lb</link>
      <guid>https://dev.to/ctaverna/software-architecture-diagram-arrows-30lb</guid>
      <description>&lt;p&gt;&lt;strong&gt;How to manage a relationship that sometimes "it's complicated"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It's just an arrow!
&lt;/h2&gt;

&lt;p&gt;Arrows are used in almost every types of diagrams, and software architectural diagrams are no exception.&lt;/p&gt;

&lt;p&gt;Although it may seem something obvious, almost elementary, the meaning of arrows is by no means a universal concept.&lt;/p&gt;

&lt;p&gt;Let’s take a minimal, clear and simple scheme:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LYsGEsd8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ctaverna.github.io/assets/img/diagram-arrows/ab.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LYsGEsd8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ctaverna.github.io/assets/img/diagram-arrows/ab.jpg" alt="Component A has *some relation* with component B" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Component A has &lt;em&gt;some relation&lt;/em&gt; with component B&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ok, that's not just an arrow
&lt;/h2&gt;

&lt;p&gt;The clarity is only apparent, because observing these two components connected by an arrow we can imagine many different interpretations, each of which does make sense but with very different meanings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;calls&lt;/strong&gt; B&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;depends&lt;/strong&gt; on B&lt;/li&gt;
&lt;li&gt;A is a &lt;strong&gt;client&lt;/strong&gt; of B&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;calls an API&lt;/strong&gt; exposed by B&lt;/li&gt;
&lt;li&gt;There is &lt;strong&gt;data stream&lt;/strong&gt; from A to B&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;sends a message&lt;/strong&gt; to B&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can stop here because the sense is definitely clear. Some of these interpretations are partly overlapping, but excluding the nuances of meaning we can reduce them to two fundamental relationships, however diametrically opposed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;strong&gt;sends data to&lt;/strong&gt; B&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;makes a request to&lt;/strong&gt; B&lt;br&gt;
&lt;em&gt;(therefore assuming that B will send some data back to A)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the &lt;em&gt;signifier&lt;/em&gt; assumes an opposite &lt;em&gt;signified&lt;/em&gt; for different recipients, it's clear that we have a problem. The primary purpose of drawing a diagram is usually to clarify the relationships between different components, so it is not acceptable to communicate in such an ambiguous way.&lt;/p&gt;

&lt;p&gt;There are also other aspects, definitely no less important, that an arrow itself is not able to express: is the communication synchronous or asynchronous? Is it a single call or there are dozens of them? If the recipient of the arrow is a database, is it a query or an update? If it’s a message, is it an event, a publish/subscribe stream, or a double channel for request/response? Is it an HTTP or grpc call? Or maybe it’s a TCP socket connection?&lt;/p&gt;

&lt;p&gt;A single diagram cannot provide all the answers, and this is the reason why I think that when you draw a good diagram you should try to provide only &lt;strong&gt;some&lt;/strong&gt; information, as clearly as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what?
&lt;/h2&gt;

&lt;p&gt;To add details there are only a couple of possibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add a &lt;em&gt;label&lt;/em&gt; to each arrow&lt;/strong&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VFXIHPzZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7nfds84u5n8fwy0nwxsj.jpg" alt="Labels on each arrow" width="300" height="139"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This has the advantage of making the meaning quite clear, but it's not always the best option because the diagram will inevitably lose readability.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Introduce a &lt;em&gt;legend&lt;/em&gt; to the diagram&lt;/strong&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nUyYv2Sn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ph0j2wh7x31xfjl9b4m.jpg" alt="Introduce a legend" width="300" height="191"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this way, each graphical variation of the arrow will have a different meaning. A great option to keep the diagram clean, but for the viewer can be tiring to learn the linguistic convention.&lt;/p&gt;

&lt;p&gt;In both cases, there is a considerable trade-off between readability and explicitness.&lt;/p&gt;

&lt;p&gt;In addition to that we can also leverage a good &lt;strong&gt;title&lt;/strong&gt; to define clearly the purpose of the diagram, and, as a consequence, the most likely meaning of the arrows.&lt;br&gt;
For example, in a diagram titled "&lt;em&gt;Data ingestion flow&lt;/em&gt;", it's probably pretty much easy to imagine that the arrows are here to explain the direction of the data through the system, and not if the single call is a &lt;em&gt;pull&lt;/em&gt; or a &lt;em&gt;push&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In my experience it is also useful to attach a short &lt;strong&gt;description&lt;/strong&gt; to help the reader interpret the diagram, providing a high-level overview that describes its purpose.&lt;/p&gt;

&lt;p&gt;Obviously writing a description or a legend are time-consuming activities, and therefore you have to evaluate the cost/benefit ratio.&lt;/p&gt;

&lt;p&gt;Fortunately, in some cases the &lt;strong&gt;context&lt;/strong&gt; can be very useful to clarify the meaning of an arrow, or any other symbol within a diagram, but it's risky to assume that the context is clear to everyone and that any recipient will always be able to understand what we have in mind.&lt;/p&gt;

&lt;p&gt;Of course if the recipients of the diagram are people with whom you usually work, there is likely a kind of &lt;strong&gt;&lt;em&gt;shared common language&lt;/em&gt;&lt;/strong&gt;, so in most cases it's not necessary to be super specific. Also, in case of doubt, it's easy to ask for clarification.&lt;br&gt;&lt;br&gt;
But when the diagram is going to be shared with another team, it is definitely a good idea to spend some extra time to make it as much clear as possible.&lt;br&gt;&lt;br&gt;
This can minimize the likelihood of future misunderstandings, which could cost much, much more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--osqZydhD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7pcyts1cof335slcne5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--osqZydhD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7pcyts1cof335slcne5.jpg" alt="A road sign" width="185" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;There's no secret recipe.&lt;br&gt;&lt;br&gt;
An arrow itself just tells that &lt;strong&gt;probably&lt;/strong&gt; at least one of the connected parts is aware that the other one exists.&lt;br&gt;&lt;br&gt;
To add more information you have to add details, and the tough part is to decide how much it makes sense to be detailed.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>diagrams</category>
      <category>softwareengineering</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
