<?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</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.to</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/"/>
    <language>en</language>
    <item>
      <title>Context in Context: Why AI Tools Degrade Over Longer Work Sessions</title>
      <dc:creator>Keith MacKay</dc:creator>
      <pubDate>Tue, 12 May 2026 20:48:07 +0000</pubDate>
      <link>https://dev.to/keithjmackay/context-in-context-why-ai-tools-degrade-over-longer-work-sessions-4m0m</link>
      <guid>https://dev.to/keithjmackay/context-in-context-why-ai-tools-degrade-over-longer-work-sessions-4m0m</guid>
      <description>&lt;h1&gt;
  
  
  Context in Context: Why AI Tools Degrade Over Longer Work Sessions
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;What every executive needs to understand about AI context windows (and how to get the best performance from your AI assistants)&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;You invested in AI assistants. You were excited. Your initial tests were impressive. But three months in, as sessions are getting longer, you are hearing (or find yourself thinking): "I didn't notice before, but it seems to be forgetting stuff I told it just a little while ago."&lt;/p&gt;

&lt;p&gt;You're not imagining it.&lt;/p&gt;

&lt;p&gt;A hidden constraint sits at the heart of every AI assistant. It determines whether these tools accelerate your work, or frustrate you by losing important information that has been provided. Many users don't know this constraint exists.&lt;/p&gt;

&lt;p&gt;It's called the &lt;strong&gt;context window&lt;/strong&gt;. And if you're using AI for any significant tasks, you need to understand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-Second Explanation
&lt;/h2&gt;

&lt;p&gt;Did you ever take a test where you were allowed to create only a single page of reference notes? That page was your context window for that test (especially if you were someone who slept through classes and saw homework as an optional exercise). It contained your entire understanding of the subject at hand.&lt;/p&gt;

&lt;p&gt;Another way to think about it: picture your AI assistant with a notepad. It uses this pad as a working memory where it tracks: your conversation, the files it reads, the domain knowledge it adds for this task, and the instructions it follows. That notepad has a fixed size. Everything the AI needs must fit on that pad--your prompts, the LLM responses, supporting documents, tools--all of it. When the notepad fills up, the AI doesn't "remember" earlier information like a human might (if they actually attended the lectures!). It loses access. Or worse, it gets confused trying to juggle too much at once.&lt;/p&gt;

&lt;p&gt;That notepad is the "context window." And it fills up faster than a conference room whiteboard on day three of a strategy offsite.&lt;/p&gt;

&lt;p&gt;Even worse, your LLM front-end provides additional opportunities to load EVEN MORE things into your context window, taking up precious space: MCP server details, skills, local documents, and so forth. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Hits Your Bottom Line
&lt;/h2&gt;

&lt;p&gt;Currently, typical AI assistants have context windows of roughly &lt;strong&gt;200,000 tokens&lt;/strong&gt;: about 150,000 words, or a 500-page book. Sounds enormous.&lt;/p&gt;

&lt;p&gt;It's not.&lt;/p&gt;

&lt;p&gt;Here's what actually fills that space:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every message&lt;/strong&gt; exchanged (yours and the AI's responses)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every file&lt;/strong&gt; the AI reads to understand your code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System instructions&lt;/strong&gt; governing the AI's behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool definitions&lt;/strong&gt; for every capability you've enabled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search results&lt;/strong&gt;, error messages, test outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One complex coding session can exhaust that entire budget in minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the budget runs low, three things break:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality tanks.&lt;/strong&gt; Responses become generic, like getting advice from someone who skimmed your email instead of reading it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory fails.&lt;/strong&gt; Decisions from early in the conversation get pushed out by newer information. The AI develops corporate amnesia (ever see the movie "Memento"?).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistency disappears.&lt;/strong&gt; The AI contradicts itself. It ignores patterns it established an hour ago. It's like working with someone who wasn't at the last three meetings but won't admit it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your impression wasn't wrong. The tool really does degrade the longer it's used.&lt;/p&gt;

&lt;h2&gt;
  
  
  ...And You Can't Even Use ALL of It
&lt;/h2&gt;

&lt;p&gt;Many experiments have shown that using ALL of the context window available to you is, to use the technical term, "a very bad idea". The models have been trained on human data, and the analogs to human cognition even extend to things like giving greater weight to information at the beginning and end of the context window, yielding a mushy middle that is de-emphasized (I think of this as analogous to primacy and recency bias in humans... I haven't researched whether this was designed or entirely an emergent property, feel free to educate me in the comments!).&lt;/p&gt;

&lt;p&gt;In many tools, when the window is full, it will automatically compact the information to what it deems important, in order to clear workspace and continue its task. This is "lossy", actually throwing away some of the information that has been provided for context and keeping only a summary. That is a serious limitation for complex tasks.&lt;/p&gt;

&lt;p&gt;Many coding shops have standards around how to better preserve project context as the context window fills up -- I have often seen the figure "80%" as the target for maximum amount of context window used for a task. Given my own personal experience, I like to take action when my context window is about 60% full, meaning even LESS available room for context. I've seen some well-respected AI coders in the field recommend using no more than 30%-40% of your context window...so YMMV.&lt;/p&gt;

&lt;p&gt;What do you do when you hit your context window target limit? Historically, my colleagues and I would generally used a strategy of pushing session context into an external file, starting a brand-new session, reloading the context from the file, and continuing where we left off. That proves to be an annoying interruption, but it's a small price to pay for continued high-quality work. That strategy has evolved. Now, where possible, we break tasks into small independent components that can be distributed across a swarm of multiple agents, each of which has its own context window. With upfront thinking and strategizing, we can use the tools in such a way that we (almost) never hit the context window limits...but there are still real limits when working with massive codebases, very intertwined systems, high levels of detail, and so forth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Business Impact
&lt;/h2&gt;

&lt;p&gt;This isn't a minor annoyance. It directly affects four things you care about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer productivity.&lt;/strong&gt; Developers who don't understand context waste hours fighting degraded AI. Those who &lt;strong&gt;do&lt;/strong&gt; understand achieve 2-4x the output from the same tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI costs.&lt;/strong&gt; Many commercial AI tools charge effectively by token. Poor context management means paying for waste: the AI reading unnecessary files, repeating explanations, loading unused tools, spinning in circles with polluted context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code quality.&lt;/strong&gt; An AI drowning in context produces inconsistent code, misses requirements, and makes errors that must be caught and fixed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adoption success.&lt;/strong&gt; Nothing kills AI enthusiasm faster than unpredictable performance. Teams that hit degradation repeatedly often abandon tools that could have been transformative. You paid for a sports car; they're convinced it's a lemon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight That Separates Winners from Strugglers
&lt;/h2&gt;

&lt;p&gt;One mental shift changes everything:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context is a budget, not a bucket.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Buckets fill passively until they overflow. Budgets demand active allocation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Teams that treat context as a budget&lt;/strong&gt; ask: What does the AI need to see &lt;em&gt;right now&lt;/em&gt;? What can we exclude? When do we start fresh? How do we move tasks AWAY FROM the AI and into deterministic code where possible? (For instance: a colleague of mine had a terrific retro agent that would review completed sessions and improve processes for subsequent runs. I added an instruction to look for opportunities to write scripts for things that could be done deterministically in future runs, and it estimated a 60-80% future savings in tokens. This was almost certainly overstated, but even a 20% savings in any budget has real value!)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Teams that treat context as a bucket&lt;/strong&gt; keep piling in information until performance craters. Then they blame the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Good Looks Like
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Developers who get it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run workflows that break all projects into discrete, achievable-within-a-single-context-window tasks&lt;/li&gt;
&lt;li&gt;Start fresh sessions for distinct tasks instead of running marathon conversations&lt;/li&gt;
&lt;li&gt;Delegate focused tasks to specialized "subagents" that return summaries, not raw data dumps (the tools themselves are beginning to do this: for instance, Claude Code tasks can now launch a subagent per task that runs in its own context window)&lt;/li&gt;
&lt;li&gt;Write scripts for repetitive operations instead of walking the AI through each step&lt;/li&gt;
&lt;li&gt;Maintain project docs that give the AI architectural context without reading every file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Teams that get it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establish clear conventions for when to start new sessions&lt;/li&gt;
&lt;li&gt;Create shared documentation that bootstraps AI context efficiently, and constantly edit the document with new learning&lt;/li&gt;
&lt;li&gt;Run regular retrospectives to find tasks that should be scripts, not conversations&lt;/li&gt;
&lt;li&gt;Track context-related productivity metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Organizations that get it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recognize that AI tool performance depends on usage patterns, not just tool selection&lt;/li&gt;
&lt;li&gt;Invest in training for context-aware workflows&lt;/li&gt;
&lt;li&gt;Choose AI tools that show context consumption (you can't manage what you can't see), or use tool add-ins or augmentation to allow context monitoring&lt;/li&gt;
&lt;li&gt;Set realistic expectations: AI assistants are powerful, but they have constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hidden Tax on Every Session
&lt;/h2&gt;

&lt;p&gt;Modern AI tools are extensible. They connect to databases, file systems, APIs, and specialized capabilities through plugins (generally via MCP, the Model Context Protocol...a widely-adopted standard for allowing existing software to outline its capabilities and available information for AI agents). Many thousands of MCP integration implementations already exist to allow AI agents to connect to existing software tools and data sources.&lt;/p&gt;

&lt;p&gt;Each integration provides power. Each also consumes context: silently, automatically, whether used or not. This is a great simplifier for connectivity/integration, but the MCP standard has no inherent security provisions and comes with a high up-front token cost.&lt;/p&gt;

&lt;p&gt;Think of it like subscribing to every streaming service, every magazine, and every meal kit delivery "just in case." Except instead of cluttering your mailbox, you're cluttering your AI's brain.&lt;/p&gt;

&lt;p&gt;When you connect an MCP extension, its entire capability description loads into the context window. One extension with 20 features might consume 10,000-15,000 tokens just by being referenced in your project. Organizations that enable every available extension "just in case" can burn &lt;strong&gt;30-40% of their context budget before anyone types a prompt&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The lesson is counterintuitive but critical: &lt;strong&gt;More capability often means less intelligence.&lt;/strong&gt; Enable what you'll use. Disable what you won't.&lt;/p&gt;

&lt;p&gt;Alternative strategies include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connect with necessary tools via their API through scripts created by the LLM&lt;/li&gt;
&lt;li&gt;use alternate "progressive disclosure" strategies like "skills" in Claude Code (where the skill components are only loaded into the context window on an "as-and-when needed" basis)&lt;/li&gt;
&lt;li&gt;create separate processes or agents with their own context windows for specific tasks or specific MCP instances.
## The Counterintuitive Truth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what surprises most people:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goal isn't to maximize the amount of information that goes into the AI. It's to optimize what it pays attention to.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;More context often produces &lt;em&gt;worse&lt;/em&gt; results. An AI with focused, relevant context significantly outperforms one drowning in information. Just like a human does.&lt;/p&gt;

&lt;p&gt;Experienced AI users spend effort curating what goes into context. Amateurs just collect stuff and pile more in. Curate for quality, don't collect for completeness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warning Signs Your Team Has Context Problems
&lt;/h2&gt;

&lt;p&gt;Listen for these phrases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"It keeps forgetting what I told it"&lt;/strong&gt;: Classic context overflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Sometimes it's brilliant, sometimes it's useless"&lt;/strong&gt;: Inconsistent context load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The longer I work, the worse it gets"&lt;/strong&gt;: Session running too long&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"I have to keep re-explaining the same thing"&lt;/strong&gt;: Important context pushed out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"I just restart when it stops working"&lt;/strong&gt;: Context pollution forcing fresh starts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Five Questions for Your Next Leadership Meeting
&lt;/h2&gt;

&lt;p&gt;If your organization uses AI development tools, ask these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do we train developers on context management?&lt;/strong&gt; Most teams don't. They pay for it in lost productivity every day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can we measure context consumption?&lt;/strong&gt; If you can't see it, you can't manage it. Does your tooling provide visibility?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do we have conventions for session management?&lt;/strong&gt; Ad-hoc approaches leave productivity on the table. What triggers a fresh start?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Are we using extensions judiciously?&lt;/strong&gt; Or does everyone enable everything "just in case"?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Have we identified tasks that should be scripted?&lt;/strong&gt; Repetitive AI-assisted tasks often work better as simple automation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Relief Is Coming Soon (But Not Here Just Yet)
&lt;/h2&gt;

&lt;p&gt;The good news: context constraints won't stay this tight forever.&lt;/p&gt;

&lt;p&gt;Context windows are growing. Gemini has a 1-million-token context window. Claude Code allows 1-million-token context windows via API usage, but not (yet) to subscribers. Context windows are expected to continue to grow in size, and management of them will only continue to improve.&lt;/p&gt;

&lt;p&gt;Usage strategies are also improving. A recently-described technique from MIT researchers called Recursive Language Models (RLMs) shows particular promise. RLM isn't a specific product. It's a strategy that can be applied to any LLM: decompose large problems into smaller pieces, analyze each in its own focused context, then synthesize the results. For those familiar with MapReduce (the strategy Google invented to distribute search problems across many different servers and then synthesize results), you might think about this strategy as vaguely analogous. Divide-and-conquer for AI.&lt;/p&gt;

&lt;p&gt;I fully expect that, within 12 months, RLM-based tools will navigate multi-million-line codebases with the coherence that today's tools bring to small projects. "Codebase too large" will stop being a hard limit.&lt;/p&gt;

&lt;p&gt;But here's the catch: RLMs solve scale, not quality. Understanding &lt;em&gt;what&lt;/em&gt; code does differs from understanding &lt;em&gt;why&lt;/em&gt; it exists. And the fundamentals of context hygiene (attention, relevance, signal-to-noise) will still matter. Larger windows just mean more opportunities to drown in noise if you're not deliberate.&lt;/p&gt;

&lt;p&gt;Master context management now, and you'll be ready when the constraints loosen. Ignore it, and you'll just have more rope to hang yourself with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;AI coding assistants are genuinely transformative. But they're not magic. They operate within real constraints (constraints that, when ignored, undermine the entire investment).&lt;/p&gt;

&lt;p&gt;Context management is one of the highest-leverage skills for AI-assisted development. Teams that understand it extract dramatically more value from identical tools. Teams that don't blame the technology for problems rooted in usage patterns.&lt;/p&gt;

&lt;p&gt;The AI isn't getting worse. Its notepad just filled up.&lt;/p&gt;

&lt;p&gt;Once you understand this, everything changes: how you deploy AI tools, how you train teams to use them, how you measure success.&lt;/p&gt;

&lt;p&gt;The notepad is finite. How you manage it determines whether AI becomes your organization's secret weapon, or its most expensive frustration.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The technical details matter less than the organizational practices built around them. Context management isn't an engineering problem. It's a capability your teams either have or don't.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>convex</category>
      <category>productivity</category>
      <category>coding</category>
      <category>management</category>
    </item>
    <item>
      <title>Use Data Stores In Application Development | 🏗️ Build A Product Catalog API</title>
      <dc:creator>Ntombizakhona Mabaso</dc:creator>
      <pubDate>Tue, 12 May 2026 20:45:57 +0000</pubDate>
      <link>https://dev.to/aws-builders/use-data-stores-in-application-development-build-a-product-catalog-api-404f</link>
      <guid>https://dev.to/aws-builders/use-data-stores-in-application-development-build-a-product-catalog-api-404f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Exam Guide:&lt;/strong&gt; Developer - Associate&lt;br&gt;
&lt;strong&gt;🏗️ Domain 1:&lt;/strong&gt;  &lt;strong&gt;&lt;em&gt;Development with AWS Services&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
📘 &lt;strong&gt;Task 3:&lt;/strong&gt; &lt;em&gt;Use Data Stores In Application Development&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DynamoDB dominates this task. The need to understand table design, key selection, indexing, consistency models, and how to write efficient queries is essential. As well as caching with ElastiCache and DAX. Plus specialized stores like OpenSearch.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  📘 Concepts
&lt;/h2&gt;
&lt;h3&gt;
  
  
  DynamoDB Key Concepts
&lt;/h3&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Primary Keys&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Every table needs one. Two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple primary key:&lt;/strong&gt; Partition key only (PK). Each item has a unique PK.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composite primary key:&lt;/strong&gt; Partition key (PK) + Sort key (SK). Multiple items can share a PK if they have different SKs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Partition Key Selection&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;The partition key determines which physical partition stores your data. A good partition key has &lt;strong&gt;high cardinality&lt;/strong&gt; which refers to many distinct values so that the data spreads evenly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Good Partition Keys&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Bad Partition Keys&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;userId, orderId, sessionId&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;status&lt;/em&gt; ("active"/"inactive")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;deviceId, transactionId&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;country&lt;/em&gt; (few values, uneven distribution)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;email, accountId&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;date&lt;/em&gt; (hot partition for today)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Consistency Models
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Behaviour&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Available On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Eventually Consistent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;May return stale data&lt;/em&gt; (usually consistent within 1 second)&lt;/td&gt;
&lt;td&gt;1x read capacity&lt;/td&gt;
&lt;td&gt;Base table + GSIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strongly Consistent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Always returns the most up-to-date data&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;2x read capacity&lt;/td&gt;
&lt;td&gt;Base table only (NOT GSIs)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Query vs Scan
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Operation&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;How It Works&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;When to Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Finds items by partition key + optional sort key condition&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Reads only matching items&lt;/td&gt;
&lt;td&gt;Always prefer this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Reads every item in the table&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Reads entire table&lt;/td&gt;
&lt;td&gt;Analytics, one-time migrations only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GetItem&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Fetches one item by its full primary key&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Reads exactly one item&lt;/td&gt;
&lt;td&gt;When you know the exact key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;FilterExpression&lt;/code&gt; does NOT reduce the amount of data read. It only filters what's returned to you. You still pay for the full scan/query. To reduce reads, use better key design or GSIs&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Global Secondary Index (GSI) vs Local Secondary Index (LSI)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;GSI&lt;/th&gt;
&lt;th&gt;LSI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partition Key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Different from base table&lt;/td&gt;
&lt;td&gt;Same as base table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sort Key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Different from base table&lt;/td&gt;
&lt;td&gt;Different from base table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;When To Create&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anytime&lt;/td&gt;
&lt;td&gt;At table creation only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Has its own (separate from base table)&lt;/td&gt;
&lt;td&gt;Shares with base table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Eventually consistent only&lt;/td&gt;
&lt;td&gt;Supports strongly consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20 per table&lt;/td&gt;
&lt;td&gt;5 per table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;GSI Projection Types:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ALL:&lt;/strong&gt; &lt;em&gt;all attributes&lt;/em&gt; (most flexible, most storage cost)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KEYS_ONLY:&lt;/strong&gt; &lt;em&gt;only key attributes&lt;/em&gt; (cheapest)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;INCLUDE:&lt;/strong&gt; &lt;em&gt;keys + specified attributes&lt;/em&gt; (balanced)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Caching Options
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Use Case&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Works With&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DAX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;DynamoDB read cache&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Microseconds&lt;/td&gt;
&lt;td&gt;DynamoDB only, eventually consistent only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ElastiCache Redis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;General-purpose cache&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Sub-millisecond&lt;/td&gt;
&lt;td&gt;Any data source, complex data types, persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ElastiCache Memcached&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Simple caching&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Sub-millisecond&lt;/td&gt;
&lt;td&gt;Any data source, multi-threaded, no persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Specialized Data Stores
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Store&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Use Case&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DynamoDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Key-value lookups, known access patterns, serverless&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RDS/Aurora&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Relational data, complex joins, ACID transactions&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenSearch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Full-text search, log analytics, complex queries&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Object storage, data lake, large files&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ElastiCache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Session storage, leaderboards, real-time analytics&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Data Lifecycle
&lt;/h3&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;DynamoDB TTL&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Automatically deletes expired items at no cost. Eventually consistent (up to 48 hours delay). &lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;S3 Lifecycle Policies&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Transition objects between storage classes (Standard → IA → Glacier) or expire them after a set time.&lt;/p&gt;


&lt;h2&gt;
  
  
  🏗️ Build A Product Catalog API
&lt;/h2&gt;

&lt;p&gt;Now let's put these concepts into practice by builidng a &lt;strong&gt;Product Catalog API&lt;/strong&gt; backed by DynamoDB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A DynamoDB table with a composite primary key and a Global Secondary Index (GSI)&lt;/li&gt;
&lt;li&gt;A Lambda function that performs queries, scans, and writes&lt;/li&gt;
&lt;li&gt;TTL configured for automatic data expiration&lt;/li&gt;
&lt;li&gt;DAX caching in front of DynamoDB&lt;/li&gt;
&lt;li&gt;A clear understanding of when to use query vs scan, GSI vs LSI, and strong vs eventual consistency&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ntombizakhona.medium.com/amazon-web-services-a8e57a9c6084" rel="noopener noreferrer"&gt;An AWS account&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(free tier covers DynamoDB and Lambda)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Part I
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Design the DynamoDB Table
&lt;/h3&gt;

&lt;p&gt;Before creating anything, let's think about access patterns. This is the most important step in DynamoDB design.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Our Access Patterns&lt;/strong&gt;
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Access Pattern&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;How We'll Query&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Get a product by ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Query PK = &lt;code&gt;PRODUCT#123&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;List all products in a category&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Query PK = &lt;code&gt;CATEGORY#electronics&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Get a product's reviews&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Query PK = &lt;code&gt;PRODUCT#123&lt;/code&gt;, SK begins_with &lt;code&gt;REVIEW#&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Find products by price range in a category&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Query GSI with category + price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;List recently added products&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Query GSI with status + createdAt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Create the Table&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 01:&lt;/strong&gt; Open the &lt;strong&gt;DynamoDB&lt;/strong&gt; console&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 02:&lt;/strong&gt; Click &lt;strong&gt;Create table&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Table name:&lt;/strong&gt; &lt;code&gt;ProductCatalog&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partition key:&lt;/strong&gt; &lt;code&gt;PK&lt;/code&gt; (String)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort key — &lt;em&gt;optional&lt;/em&gt;:&lt;/strong&gt; &lt;code&gt;SK&lt;/code&gt; (String)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 03:&lt;/strong&gt; Under &lt;strong&gt;Table settings&lt;/strong&gt;, choose &lt;strong&gt;Customize settings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 04:&lt;/strong&gt; &lt;strong&gt;Read/write capacity settings:&lt;/strong&gt; &lt;code&gt;On-demand&lt;/code&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Don't create the just table yet, let's add a GSI first&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Add a Global Secondary Index&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 05:&lt;/strong&gt; Scroll down to &lt;strong&gt;Secondary indexes&lt;/strong&gt; → click &lt;strong&gt;Create global index&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Index name:&lt;/strong&gt; &lt;code&gt;GSI1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partition key:&lt;/strong&gt; &lt;code&gt;GSI1PK&lt;/code&gt; (String)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort key:&lt;/strong&gt; &lt;code&gt;GSI1SK&lt;/code&gt; (String)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attribute projections:&lt;/strong&gt; &lt;code&gt;All&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Create index&lt;/strong&gt;, then click &lt;strong&gt;Create table&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; The ProductCatalog table was created successfully.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Why This Design?&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;We're using the &lt;strong&gt;single-table design&lt;/strong&gt; pattern with &lt;strong&gt;overloaded keys&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base table:
  PK = "PRODUCT#laptop-001"    SK = "METADATA"           → product details
  PK = "PRODUCT#laptop-001"    SK = "REVIEW#2026-04-24"  → a review
  PK = "CATEGORY#electronics"  SK = "PRODUCT#laptop-001" → category listing

GSI1:
  GSI1PK = "CATEGORY#electronics"  GSI1SK = "PRICE#00079.99" → find by price
  GSI1PK = "STATUS#active"         GSI1SK = "2026-04-24"     → find by date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Partition Key Selection: A good partition key has &lt;em&gt;high cardinality&lt;/em&gt; (many distinct values). Bad examples: &lt;code&gt;status&lt;/code&gt; (only a few values → hot partition), &lt;code&gt;country&lt;/code&gt; (uneven distribution). Good examples: &lt;code&gt;userId&lt;/code&gt;, &lt;code&gt;productId&lt;/code&gt;, &lt;code&gt;orderId&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Part II
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add Sample Data
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Using the Console&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 01:&lt;/strong&gt;  In the &lt;strong&gt;DynamoDB console&lt;/strong&gt;, click on &lt;code&gt;ProductCatalog&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 02:&lt;/strong&gt; Click &lt;strong&gt;Explore table items&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 03:&lt;/strong&gt; Click &lt;strong&gt;Create item&lt;/strong&gt;&lt;br&gt;
Switch to &lt;strong&gt;JSON view&lt;/strong&gt; (toggle at the top)&lt;br&gt;
Paste this item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"PK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PRODUCT#laptop-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"METADATA"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"GSI1PK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CATEGORY#electronics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"GSI1SK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PRICE#00999.99"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pro Laptop 15"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15-inch laptop with 16GB RAM"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&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="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"999.99"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-20T10:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stock"&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="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"50"&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;p&gt;&lt;strong&gt;Step 04:&lt;/strong&gt; Click &lt;strong&gt;Create item&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a few more products:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"PK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PRODUCT#mouse-002"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"METADATA"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"GSI1PK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CATEGORY#electronics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"GSI1SK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PRICE#00029.99"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wireless Mouse"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ergonomic wireless mouse"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&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="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"29.99"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-22T10:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stock"&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="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"200"&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;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;"PK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PRODUCT#laptop-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REVIEW#2026-04-24#user-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rating"&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="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"comment"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Great laptop, fast and reliable"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userId"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-24T14:30:00Z"&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;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;"PK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CATEGORY#electronics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PRODUCT#laptop-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pro Laptop 15"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&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="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"999.99"&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;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;"PK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CATEGORY#electronics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PRODUCT#mouse-002"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wireless Mouse"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&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="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"29.99"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part III
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Query vs Scan
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Query (Efficient)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 01:&lt;/strong&gt; In the &lt;strong&gt;▼ Scan or query items&lt;/strong&gt; tab, switch from &lt;strong&gt;Scan&lt;/strong&gt; to &lt;strong&gt;Query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 02:&lt;/strong&gt; Set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Partition key Value:&lt;/strong&gt; &lt;code&gt;PRODUCT#laptop-001&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort key Value:&lt;/strong&gt; Begins with ▼ &lt;code&gt;REVIEW&lt;/code&gt;
Click &lt;strong&gt;Run&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; Completed · Items returned: 1 · Items scanned: 1 · Efficiency: 100% · RCUs consumed: 0.5&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You'll see only the review items for that product. DynamoDB read exactly the items you asked for.&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;Efficient.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Scan (Expensive)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 03:&lt;/strong&gt; Switch back to &lt;strong&gt;Scan&lt;/strong&gt;&lt;br&gt;
Click &lt;strong&gt;Run&lt;/strong&gt; with no filters&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; Completed · Items returned: 5 · Items scanned: 5 · Efficiency: 100% · RCUs consumed: 2&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You'll see ALL items in the table. DynamoDB read every single item. On a table with millions of items.&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;This is slow and expensive.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 04:&lt;/strong&gt; Click &lt;strong&gt;Add filter&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attribute name: &lt;code&gt;category&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type: String&lt;/li&gt;
&lt;li&gt;Condition: Equal to&lt;/li&gt;
&lt;li&gt;Value: &lt;code&gt;electronics&lt;/code&gt;
Click &lt;strong&gt;Run&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; Completed · Items returned: 2 · Items scanned: 5 · Efficiency: 40% · RCUs consumed: 2&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You'll see only electronics items, but &lt;em&gt;DynamoDB still read the entire table&lt;/em&gt; and filtered afterward. The filter doesn't reduce the read cost.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;💡 &lt;code&gt;FilterExpression&lt;/code&gt; does NOT reduce the amount of data read. It only filters what's returned to you. You still pay for the full scan. To reduce reads, use better key design or GSIs.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Query the GSI&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 05:&lt;/strong&gt; Switch to &lt;strong&gt;Query&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 06:&lt;/strong&gt; Change the &lt;strong&gt;Index&lt;/strong&gt; dropdown to &lt;strong&gt;GSI1&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Partition key (GSI1PK) Value:&lt;/strong&gt; &lt;code&gt;CATEGORY#electronics&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort key (GSI1SK) Value:&lt;/strong&gt; Begins with ▼ &lt;code&gt;PRICE#&lt;/code&gt;
Click &lt;strong&gt;Run&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt;: Completed · Items returned: 2 · Items scanned: 2 · Efficiency: 100% · RCUs consumed: 0.5&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This efficiently finds all electronics products, sorted by price. That's the power of a well-designed GSI.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Part IV
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Build the API with Lambda
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Create the Lambda Function
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 01:&lt;/strong&gt; Open the &lt;strong&gt;Lambda&lt;/strong&gt; console → &lt;strong&gt;Create function&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Function name:&lt;/strong&gt; &lt;code&gt;ProductCatalogAPI&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; &lt;code&gt;Python 3.12&lt;/code&gt;
Click &lt;strong&gt;Create function&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt;: Successfully created the function "ProductCatalogAPI".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 02:&lt;/strong&gt; &lt;strong&gt;Configuration&lt;/strong&gt; → *&lt;em&gt;General configuration&lt;/em&gt; → click Edit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory:&lt;/strong&gt; &lt;code&gt;256 MB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeout:&lt;/strong&gt; &lt;code&gt;10 seconds&lt;/code&gt;
Click &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 03:&lt;/strong&gt;Add DynamoDB Permissions&lt;br&gt;
&lt;strong&gt;Configuration&lt;/strong&gt; → &lt;strong&gt;Permissions&lt;/strong&gt; → click the role name&lt;br&gt;
&lt;strong&gt;Add permissions&lt;/strong&gt; → &lt;strong&gt;Attach policies&lt;/strong&gt;&lt;br&gt;
Search for and attach &lt;code&gt;AmazonDynamoDBFullAccess&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;In production, scope this down to only the &lt;code&gt;ProductCatalog&lt;/code&gt; table. For this tutorial, full access keeps things simple. &lt;em&gt;Brevity.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 04:&lt;/strong&gt; &lt;strong&gt;Write the Function Code&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;boto3.dynamodb.conditions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Attr&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize OUTSIDE the handler — reused across warm invocations
&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dynamodb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ProductCatalog&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DecimalEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONEncoder&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;DynamoDB returns Decimal types — this converts them to float for JSON.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Routes requests based on the HTTP method and path.
    Demonstrates query, scan, get_item, and put_item operations.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;httpMethod&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;path_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pathParameters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;queryStringParameters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;list_products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/products/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;productId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;create_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/products/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/reviews&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;productId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get_reviews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Not found&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Internal server error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    List products by category using QUERY (efficient).
    Falls back to SCAN if no category is specified (expensive — avoid in production).
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# QUERY — efficient, reads only matching items
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Querying products in category: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CATEGORY#&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# SCAN — reads entire table, expensive!
&lt;/span&gt;        &lt;span class="c1"&gt;# In production, require a category or use pagination
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WARNING: Scanning entire table — this is expensive!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;FilterExpression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;METADATA&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Items&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scannedCount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ScannedCount&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Shows the difference between query and scan
&lt;/span&gt;    &lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Get a single product by ID using GET_ITEM.
    This is the most efficient read — directly accesses one item by its full key.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PRODUCT#&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;METADATA&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c1"&gt;# ConsistentRead=True  # Uncomment for strongly consistent read (2x cost)
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Item&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_reviews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Get all reviews for a product using QUERY with sort key condition.
    begins_with on the sort key efficiently finds all reviews.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PRODUCT#&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
            &lt;span class="nc"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;begins_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;REVIEW#&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;ScanIndexForward&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Sort descending (newest first)
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;productId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reviews&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Items&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Create a new product using PUT_ITEM.
    Also creates the category listing item for efficient category queries.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;productId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;  &lt;span class="c1"&gt;# DynamoDB requires Decimal, not float!
&lt;/span&gt;
    &lt;span class="c1"&gt;# Write the product metadata
&lt;/span&gt;    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PRODUCT#&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;METADATA&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GSI1PK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CATEGORY#&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GSI1SK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PRICE#&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;010.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Zero-padded for correct sort order
&lt;/span&gt;        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;# Write the category listing (denormalization for efficient queries)
&lt;/span&gt;    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CATEGORY#&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PRODUCT#&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Product created&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;productId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DecimalEncoder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 05:&lt;/strong&gt; Click &lt;strong&gt;Deploy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; Successfully updated the function "ProductCatalogAPI".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice &lt;code&gt;Decimal(str(body['price']))&lt;/code&gt;. DynamoDB does NOT support Python &lt;code&gt;float&lt;/code&gt;. You must use &lt;code&gt;Decimal&lt;/code&gt;. This is a common gotcha.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 06:&lt;/strong&gt; &lt;strong&gt;Test The Function&lt;/strong&gt;&lt;br&gt;
Click the &lt;strong&gt;Test&lt;/strong&gt; tab&lt;br&gt;
Create test events for each operation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List products by category:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"httpMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"queryStringParameters"&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="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"electronics"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pathParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Get a single product:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"httpMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/products/laptop-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pathParameters"&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="nl"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"laptop-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"queryStringParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Get reviews:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"httpMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/products/laptop-001/reviews"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pathParameters"&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="nl"&gt;"productId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"laptop-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"queryStringParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create a product:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"httpMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&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="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;productId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;keyboard-003&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Mechanical Keyboard&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;electronics&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:89.99,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;stock&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:100}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pathParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"queryStringParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Run each test and check the results. Look at the &lt;code&gt;count&lt;/code&gt; vs &lt;code&gt;scannedCount&lt;/code&gt; in the list response. When using query, they'll be equal. When scanning, &lt;code&gt;scannedCount&lt;/code&gt; will be higher.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Part V
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Consistency Models
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;See the Difference&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 01:&lt;/strong&gt; In the Lambda function, find the &lt;code&gt;get_product&lt;/code&gt; function&lt;br&gt;
Uncomment the &lt;code&gt;ConsistentRead=True&lt;/code&gt; line&lt;br&gt;
&lt;strong&gt;Deploy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; Successfully updated the function "ProductCatalogAPI".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now &lt;code&gt;get_product&lt;/code&gt; uses &lt;strong&gt;strongly consistent reads&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always returns the latest data&lt;/li&gt;
&lt;li&gt;Costs &lt;strong&gt;2x&lt;/strong&gt; the read capacity&lt;/li&gt;
&lt;li&gt;Only works on the &lt;strong&gt;base table&lt;/strong&gt; (not GSIs)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;💡 &lt;strong&gt;GSIs only support eventually consistent reads.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;When to Use Each&lt;/strong&gt;
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Use Case&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Consistency&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Why&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Product catalog browsing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Eventually consistent&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Stale data for a second is fine&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shopping cart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strongly consistent&lt;/td&gt;
&lt;td&gt;&lt;em&gt;User expects to see what they just added&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inventory check before purchase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strongly consistent&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Must be accurate to avoid overselling&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Analytics dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Eventually consistent&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Slight delay is acceptable&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Part VI
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;TTL:&lt;/strong&gt; Automatic Data Expiration
&lt;/h3&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Enable TTL&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Step 01:&lt;/strong&gt; In the &lt;strong&gt;DynamoDB&lt;/strong&gt; console, click on &lt;code&gt;ProductCatalog&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 02:&lt;/strong&gt; Click &lt;strong&gt;▼ Actions&lt;/strong&gt; &lt;br&gt;
Click &lt;strong&gt;Turn on TTL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 03:&lt;/strong&gt; &lt;strong&gt;Turn on Time to Live (TTL)&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;TTL attribute name:&lt;/strong&gt; &lt;code&gt;ttl&lt;/code&gt;&lt;br&gt;
Click &lt;strong&gt;Turn on TTL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; Successfully activated Time to Live for the ProductCatalog table with the ttl attribute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 04:&lt;/strong&gt; &lt;strong&gt;Add Items with TTL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create an item with a TTL value (Unix epoch timestamp):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"PK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SESSION#user-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SK"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DATA"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userId"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-001"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cartItems"&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="nl"&gt;"L"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"laptop-001"&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="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mouse-002"&lt;/span&gt;&lt;span class="p"&gt;}]},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ttl"&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="nl"&gt;"N"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1745625600"&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;blockquote&gt;
&lt;p&gt;The &lt;code&gt;ttl&lt;/code&gt; value is a Unix timestamp. Items are deleted after this time. Use an &lt;a href="https://www.epochconverter.com/" rel="noopener noreferrer"&gt;epoch converter&lt;/a&gt; to set a time a few minutes in the future for testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;💡 TTL deletion is &lt;strong&gt;eventually consistent:&lt;/strong&gt; items may persist for up to 48 hours after expiration. Don't rely on TTL for exact timing. Always filter out expired items in your queries as a safety measure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part VII
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Caching with DAX
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DAX (DynamoDB Accelerator) is an in-memory cache that sits in front of DynamoDB. It's a drop-in replacement. Same API, just change the client endpoint&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;When to Use DAX&lt;/strong&gt;
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Scenario&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Use DAX?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read-heavy workload, same items queried repeatedly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Microsecond response times needed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write-heavy workload&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (DAX is a read cache)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Need strongly consistent reads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (DAX returns eventually consistent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Diverse access patterns, rarely same item twice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (low cache hit rate)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;How DAX Works&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without DAX:
  App → DynamoDB (single-digit millisecond reads)

With DAX:
  App → DAX (microsecond reads if cached) → DynamoDB (on cache miss)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Console Walkthrough&lt;/strong&gt; &lt;em&gt;(Don't Create.Just Understand)&lt;/em&gt;
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;💸 &lt;strong&gt;DAX requires a VPC and costs money even when idle. We'll walk through the setup without creating it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 01:&lt;/strong&gt; Click &lt;strong&gt;▼ DAX&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 02:&lt;/strong&gt; Click &lt;strong&gt;Create cluster&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cluster name:&lt;/strong&gt; &lt;code&gt;product-cache&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node type family:&lt;/strong&gt; &lt;code&gt;t-type family&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node type:&lt;/strong&gt; &lt;code&gt;dax.t3.small&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster size:&lt;/strong&gt; &lt;code&gt;3&lt;/code&gt; nodes (for high availability)
Click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 03:&lt;/strong&gt; &lt;strong&gt;Configure networks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network Type:&lt;/strong&gt; &lt;code&gt;IPv4&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnet group:&lt;/strong&gt; &lt;code&gt;Create new&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnet group name:&lt;/strong&gt; &lt;code&gt;MySubnetGroup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC ID:&lt;/strong&gt; &lt;code&gt;defaultVPC&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnets:&lt;/strong&gt; &lt;code&gt;Select all&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security group:&lt;/strong&gt; &lt;code&gt;default ▼&lt;/code&gt;
Click &lt;strong&gt;View in EC2 console&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 04:&lt;/strong&gt; Allow port 8111 from your Lambda functions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; Inbound security group rules successfully modified on security group&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 05:&lt;/strong&gt; &lt;strong&gt;Configure Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM Service role for DynamoDB access:&lt;/strong&gt; &lt;code&gt;Create new&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM role name:&lt;/strong&gt; &lt;code&gt;DaxToDynamoDB&lt;/code&gt;
Click &lt;strong&gt;Next&lt;/strong&gt; → Click &lt;strong&gt;Next&lt;/strong&gt; → Click &lt;strong&gt;Create cluster&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅Green banner:&lt;/strong&gt; Successfully created the cluster product-cache.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 06:&lt;/strong&gt; In your Lambda code, you'd change one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Without DAX
&lt;/span&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dynamodb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# With DAX — same API, just different endpoint
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;amazondax&lt;/span&gt;
&lt;span class="n"&gt;dax_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amazondax&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AmazonDaxClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product-cache.abc123.dax-clusters.us-east-1.amazonaws.com:8111&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dax_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ProductCatalog&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# All your existing code works unchanged!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DAX is a drop-in replacement for DynamoDB reads. Same API, same code. Just change the client. 💡 But remember: DAX only supports eventually consistent reads and is for read-heavy workloads.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🏗️ What You Built | 📘Exam Concepts Recap
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;What You Did&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Exam Concept&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Designed a table around access patterns first&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Access-pattern-driven DynamoDB design&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Created a composite primary key (PK + SK)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Single-table design, sort key relationships&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Added a Global Secondary Index with different keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;GSI for alternate access patterns&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Used overloaded keys (&lt;code&gt;PRODUCT#&lt;/code&gt;, &lt;code&gt;CATEGORY#&lt;/code&gt;, &lt;code&gt;REVIEW#&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Single-table design pattern&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Queried by partition key with &lt;code&gt;begins_with&lt;/code&gt; on sort key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Efficient query operations&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ran a scan and compared &lt;code&gt;Count&lt;/code&gt; vs &lt;code&gt;ScannedCount&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Scan is expensive: it reads the entire table&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Added a &lt;code&gt;FilterExpression&lt;/code&gt; to a scan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Filters run AFTER reading: don't save capacity&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Used &lt;code&gt;Decimal(str(price))&lt;/code&gt; instead of &lt;code&gt;float&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;DynamoDB type system: no float support&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Toggled &lt;code&gt;ConsistentRead=True&lt;/code&gt; on &lt;code&gt;get_item&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Strongly vs eventually consistent reads&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Noted GSIs only support eventual consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;GSI limitations&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enabled TTL on a &lt;code&gt;ttl&lt;/code&gt; attribute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Automatic data lifecycle management&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Walked through DAX setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Read caching for DynamoDB, microsecond latency&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  ⚠️ Clean Up Protocol
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; &lt;strong&gt;DynamoDB&lt;/strong&gt; → Delete the &lt;code&gt;ProductCatalog&lt;/code&gt; table&lt;br&gt;
&lt;strong&gt;2.&lt;/strong&gt; &lt;strong&gt;Lambda&lt;/strong&gt; → Delete &lt;code&gt;ProductCatalogAPI&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;3.&lt;/strong&gt; &lt;strong&gt;IAM&lt;/strong&gt; → Delete the Lambda execution role&lt;br&gt;
&lt;strong&gt;4.&lt;/strong&gt; &lt;strong&gt;CloudWatch&lt;/strong&gt; → Delete the log groups&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Partition key cardinality:&lt;/strong&gt; high cardinality = even distribution = good performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query &amp;gt; Scan:&lt;/strong&gt; always prefer query. Scan reads the entire table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FilterExpression doesn't save reads:&lt;/strong&gt; it filters after reading. Use key design or GSIs instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GSIs&lt;/strong&gt; can be added anytime. &lt;strong&gt;LSIs&lt;/strong&gt; must be created with the table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GSIs are eventually consistent only:&lt;/strong&gt; no strongly consistent reads on GSIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Decimal, not float&lt;/strong&gt; for DynamoDB numbers in Python&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL&lt;/strong&gt; is free but eventually consistent (up to 48 hours delay)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DAX&lt;/strong&gt; = DynamoDB read cache (microsecond reads). Same API as DynamoDB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DAX doesn't support strongly consistent reads&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-table design&lt;/strong&gt; with overloaded keys is the recommended DynamoDB pattern&lt;/li&gt;
&lt;/ol&gt;




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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html" rel="noopener noreferrer"&gt;Best practices for designing and architecting with DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html" rel="noopener noreferrer"&gt;Querying tables in DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html" rel="noopener noreferrer"&gt;Using Global Secondary Indexes in DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.html" rel="noopener noreferrer"&gt;In-memory acceleration with DynamoDB Accelerator (DAX)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html" rel="noopener noreferrer"&gt;Using Time to Live (TTL) in DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;🏗️&lt;/p&gt;

</description>
      <category>aws</category>
      <category>certification</category>
      <category>cloud</category>
      <category>developer</category>
    </item>
    <item>
      <title>Stop Sharing Prompts — Start Shipping Claude Plugins</title>
      <dc:creator>Michel Sánchez Montells</dc:creator>
      <pubDate>Tue, 12 May 2026 20:43:23 +0000</pubDate>
      <link>https://dev.to/montells/stop-sharing-prompts-start-shipping-claude-plugins-od2</link>
      <guid>https://dev.to/montells/stop-sharing-prompts-start-shipping-claude-plugins-od2</guid>
      <description>&lt;p&gt;The license arrives. The whole team starts using Claude. At first, everything is bright: &lt;em&gt;"This thing is amazing!"&lt;/em&gt; Everyone improves, everyone shares, everyone discovers new things.&lt;/p&gt;

&lt;p&gt;And then, almost without noticing, as a natural evolution, a new problem appears.&lt;/p&gt;

&lt;p&gt;Everyone has configured it on their own — on their machine, in their own interface. The one who found the perfect prompt for reviewing PRs keeps it to themselves. The one who built a skill to generate Jira tickets has it on their machine and nowhere else.&lt;/p&gt;

&lt;p&gt;You have as many versions of Claude as you have team members.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first solution, the obvious one, doesn't work for everyone
&lt;/h2&gt;

&lt;p&gt;The first thing every team tries is exporting and importing a file through Cowork. It works. But only between Cowork users.&lt;/p&gt;

&lt;p&gt;The developers on Ubuntu running Claude Code don't have that option at first glance. And even if they did, every time someone updates a skill, you're back to sending the file through the team Slack channel. The previous one is now outdated. And nobody knows which version they have installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real solution: a repository as a marketplace
&lt;/h2&gt;

&lt;p&gt;Claude Code and Cowork both support importing skills, commands, and agents packaged as plugins from an external source that follows the plugin marketplace structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That means you can build your own:&lt;/strong&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Git repository, accessible to your whole team, that acts as the single source of truth for your Claude tooling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What one person updates, everyone receives. And each team member can import it regardless of whether they work on Windows with Cowork, on Mac, or on Ubuntu with Claude Code.&lt;/p&gt;

&lt;p&gt;Here's how to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create the repository
&lt;/h3&gt;

&lt;p&gt;It can be on GitHub, GitLab, Bitbucket — whatever you use. It just needs to be accessible from your team's machines.&lt;/p&gt;

&lt;p&gt;The repo follows this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude-plugin-marketplace/         ← git repository
├── .claude-plugin/
│   └── marketplace.json          ← marketplace index
├── README.md
└── greetings/                    ← one directory per plugin
    ├── .claude-plugin/           
    │   └── plugin.json           ← plugin metadata
    ├── commands/
    │   └── welcome.md            ← command
    └── skills/
        └── greet-in-language/    ← skill
            └── SKILL.md

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The &lt;code&gt;marketplace.json&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;This lives inside the &lt;code&gt;.claude-plugin&lt;/code&gt; directory at the repo root. It lists the available plugins, each pointing to its directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-montells-plugins"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Michel Sánchez Montells"&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;"plugins"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"greetings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./greetings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A collection of greeting utilities: a /welcome command that introduces the session with date and directory context, and a skill that greets the user in any language they request."&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;name&lt;/strong&gt;: is how the marketplace will be listed.&lt;br&gt;
&lt;strong&gt;plugins&lt;/strong&gt;: list of plugins with &lt;em&gt;name&lt;/em&gt; and &lt;em&gt;source&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. The &lt;code&gt;plugin.json&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;Each plugin defines its metadata in &lt;code&gt;.claude-plugin/plugin.json&lt;/code&gt; inside its own directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"greetings"&lt;/span&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;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A collection of greeting utilities: a /welcome command that introduces the session with date and directory context, and a skill that greets the user in any language they request."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Michel Sánchez Montells"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"montells@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://github.com/montells"&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;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/montells/claude-plugin-marketplace"&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;name&lt;/strong&gt;: the name of the plugin. Act as a namespace for the commands and skills.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. A skill example
&lt;/h3&gt;

&lt;p&gt;Each skill is a folder named after the skill, containing a &lt;code&gt;SKILL.md&lt;/code&gt; file. The content is the prompt Claude will invoke autonomously when the context calls for it:&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="c"&gt;&amp;lt;!-- pluginName/skills/skillName/SKILL.md --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- greetings/skills/greet-in-language/SKILL.md --&amp;gt;&lt;/span&gt;

Greet the user in the language they request.
If no language is specified, greet them in English.
Keep the greeting warm and brief.

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. A command example
&lt;/h3&gt;

&lt;p&gt;Commands live in &lt;code&gt;commands/&amp;lt;name&amp;gt;.md&lt;/code&gt;. Unlike skills, they are invoked explicitly with &lt;code&gt;/name&lt;/code&gt; and can receive arguments via &lt;code&gt;$ARGUMENTS&lt;/code&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="c"&gt;&amp;lt;!-- greetings/commands/commandName.md --&amp;gt;&lt;/span&gt;
&lt;span class="gh"&gt;&amp;lt;!-- greetings/commands/welcome.md --&amp;gt;
---
&lt;/span&gt;description: "Introduce the session with date and directory context"
&lt;span class="gh"&gt;allowed-tools: Bash(date), Bash(pwd)
---
&lt;/span&gt;
Greet the user and provide session context.

&lt;span class="gu"&gt;## Context&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Current date and time: !&lt;span class="sb"&gt;`date`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Working directory: !&lt;span class="sb"&gt;`pwd`&lt;/span&gt;

&lt;span class="gu"&gt;## Task&lt;/span&gt;
Say hello and let the user know where they are and what time it is.

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Import from Cowork
&lt;/h3&gt;

&lt;p&gt;The flow in Cowork depends on your plan:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team or Enterprise plan&lt;/strong&gt; — the ideal path for teams. An admin connects the GitHub repository from the organization settings. From that point, the plugins appear in every team member's catalog and can be installed (or auto-installed) without anyone having to do anything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Individual plan (Pro/Max)&lt;/strong&gt; — the repository can't be connected directly, but you can export the plugin as a &lt;code&gt;.zip&lt;/code&gt; and upload it manually: &lt;code&gt;Customize → Browse plugins → Upload plugin file&lt;/code&gt;. It works, though it loses the benefit of automatic sync.&lt;/p&gt;

&lt;p&gt;In both cases, once the plugin is installed, its skills appear when you type &lt;code&gt;/&lt;/code&gt; in Cowork.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Import from Claude Code
&lt;/h3&gt;

&lt;p&gt;From Claude Code, type &lt;code&gt;/plugins&lt;/code&gt; to open the plugin manager. Go to the &lt;strong&gt;Marketplaces&lt;/strong&gt; tab and select &lt;strong&gt;+ Add Marketplace&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll be prompted for the marketplace source. It supports several formats:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;owner/repo                              ← GitHub shorthand
git@github.com:owner/repo.git          ← SSH
https://example.com/marketplace.json   ← HTTPS
./path/to/marketplace                  ← local path

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

&lt;/div&gt;



&lt;p&gt;In our case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;montells/claude-plugin-marketplace

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

&lt;/div&gt;



&lt;p&gt;Or with SSH if the repo is private:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git@github.com:montells/claude-plugin-marketplace.git

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwebsgx1v5xfzk1g9bw4x.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%2Fwebsgx1v5xfzk1g9bw4x.png" alt=" " width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once added, go to the &lt;strong&gt;Discover&lt;/strong&gt; tab. You'll see the plugins available in the marketplace. Select one to view its details and choose the installation scope:&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%2F9mqanpq52jcodzm6mtzf.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%2F9mqanpq52jcodzm6mtzf.png" alt=" " width="800" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Install for you (user scope)&lt;/strong&gt; — just for you, across all your projects&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Install for all collaborators on this repository (project scope)&lt;/strong&gt; — for the whole team on this repo&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Install for you, in this repo only (local scope)&lt;/strong&gt; — just you, just here&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After installing, Claude Code will prompt you to run &lt;code&gt;/reload-plugins&lt;/code&gt; to activate the changes.&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%2Fjk7i1eki17w0xgt7oysw.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%2Fjk7i1eki17w0xgt7oysw.png" alt=" " width="800" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From that point on, the plugin's commands are invoked with the plugin prefix: &lt;code&gt;/greetings:welcome&lt;/code&gt;. Skills activate autonomously when Claude determines the context calls for them.&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%2Fnrvvp7wpdr8tfyp4hihl.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%2Fnrvvp7wpdr8tfyp4hihl.png" alt=" " width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Update flow
&lt;/h3&gt;

&lt;p&gt;From here, updating a skill is exactly like updating code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Edit the skill file in the repo&lt;/li&gt;
&lt;li&gt; Commit and push&lt;/li&gt;
&lt;li&gt; The team receives the update next time they sync&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No files through Slack. No "which version do you have?". No diverging configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;One repository. One single source of truth. And every team member — regardless of their platform — with exactly the same tools, at their latest version.&lt;/p&gt;

&lt;p&gt;Onboarding a new team member to Claude goes from "set it up yourself" to "add this marketplace and you're ready."&lt;/p&gt;




&lt;p&gt;The example used in this post can be found &lt;a href="https://github.com/montells/claude-plugin-marketplace" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
    </item>
    <item>
      <title>Introduction to Dinghy: a Swiss-army knife for everyday engineering</title>
      <dc:creator>mrduguo</dc:creator>
      <pubDate>Tue, 12 May 2026 20:39:11 +0000</pubDate>
      <link>https://dev.to/mrduguo/introduction-to-dinghy-a-swiss-army-knife-for-everyday-engineering-20l2</link>
      <guid>https://dev.to/mrduguo/introduction-to-dinghy-a-swiss-army-knife-for-everyday-engineering-20l2</guid>
      <description>&lt;p&gt;If you ship anything for a living — diagrams, infrastructure, docs, slide decks — you have probably noticed that each of those things lives in its own world, with its own toolchain, its own opinionated editor, and its own subtle ways to drift out of sync.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dinghy.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Dinghy&lt;/strong&gt;&lt;/a&gt; is an open-source toolchain that puts all of them under one roof. One CLI. One install. One consistent way to &lt;em&gt;describe&lt;/em&gt; what you want &lt;strong&gt;as code&lt;/strong&gt; and let the tools deliver the final artwork.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;A Swiss-army knife for engineers who like writing code more than clicking through dialogs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Diagram as Code&lt;/strong&gt; — render architecture diagrams to draw.io.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code&lt;/strong&gt; — render OpenTofu / Terraform from React TSX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Site Builder&lt;/strong&gt; — author docs sites with Docusaurus, live-preview them, deploy to S3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide Builder&lt;/strong&gt; — author RevealJS presentations in YAML / Markdown / HTML, with Prezi-style zoom-and-pan as a Dinghy-exclusive bonus.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is powered by &lt;strong&gt;Deno&lt;/strong&gt; and &lt;strong&gt;Docker&lt;/strong&gt;, so you do not need to babysit Node versions, Python virtualenvs, Terraform providers, or any of the rest of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a DevOps engineer's bench
&lt;/h2&gt;

&lt;p&gt;Dinghy was built by a DevOps engineer, so it is shaped to walk with your code across the whole software development lifecycle — from local development through to CI/CD — instead of stopping at the edge of one stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it is shaped
&lt;/h2&gt;

&lt;p&gt;Dinghy splits cleanly into a thin &lt;strong&gt;CLI&lt;/strong&gt; and a fat &lt;strong&gt;engine&lt;/strong&gt; that runs in Docker. The CLI is what you install on your machine; the engine is a versioned image that ships every dependency Dinghy needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.dinghy.dev/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single command gets you the CLI. From then on, everything Dinghy does happens inside the engine image — which means &lt;strong&gt;every machine on your team gets the exact same versions&lt;/strong&gt; of Deno, Node, OpenTofu / Terraform, and every TF provider, pinned through a single &lt;code&gt;.dinghyrc&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;DINGHY_ENGINE_VERSION&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One lock. Predictable versions. No more "it works on my laptop" — and no more two-year-old projects you can't rebuild because the dependencies have rotted away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it's for
&lt;/h2&gt;

&lt;p&gt;Anyone who needs to write code for any of this work — a diagram, a piece of infrastructure, a docs site or web app, a slide deck. You do not need to use them all, and you do not need to use them all the time. Reach for one when that is all you need; pull in a few when the work spans more than one.&lt;/p&gt;

&lt;p&gt;If any of that sounds useful for what you are building, the rest of this series digs into each capability one post at a time &lt;em&gt;(coming soon — links will go live as each post is published)&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why React for IaC?&lt;/strong&gt; — the origin of the Dinghy project and core concept.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diagram as Code&lt;/strong&gt; — author architecture diagrams as TSX components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code&lt;/strong&gt; — 248 lines of Terraform from 8 lines of source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Site Builder&lt;/strong&gt; — from one Markdown file to a deployed site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slide Builder&lt;/strong&gt; — RevealJS in YAML, with Prezi-style zoom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dinghy in the AI age&lt;/strong&gt; — still relevant? How it works alongside AI.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Ready to set sail?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.dinghy.dev/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guides and examples are at &lt;a href="https://dinghy.dev" rel="noopener noreferrer"&gt;https://dinghy.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>revealjs</category>
      <category>terraform</category>
      <category>opentofu</category>
    </item>
    <item>
      <title>What Nigeria's Stock Market Taught Me About System Reliability</title>
      <dc:creator>Okeke Chukwudubem</dc:creator>
      <pubDate>Tue, 12 May 2026 20:32:49 +0000</pubDate>
      <link>https://dev.to/okeke_chukwudubem_5f3bf49/what-nigerias-stock-market-taught-me-about-system-reliability-19n4</link>
      <guid>https://dev.to/okeke_chukwudubem_5f3bf49/what-nigerias-stock-market-taught-me-about-system-reliability-19n4</guid>
      <description>&lt;p&gt;As a software engineering student, I spend a lot of time thinking about systems how they're built, how they fail, and how they recover.&lt;/p&gt;

&lt;p&gt;This week, Nigeria's stock market gave me a masterclass.&lt;/p&gt;

&lt;p&gt;The Crash&lt;/p&gt;

&lt;p&gt;The Central Bank introduced a new rule limiting bank investments in foreign subsidiaries. Within hours, the market lost ₦1.92 trillion. Banking stocks collapsed. Panic spread.&lt;/p&gt;

&lt;p&gt;One regulatory change. One massive system failure.&lt;/p&gt;

&lt;p&gt;In software terms, this is a single point of failure—one component that, when it breaks, brings down the entire system.&lt;/p&gt;

&lt;p&gt;The Recovery&lt;/p&gt;

&lt;p&gt;The next day, the market regained ₦1.71 trillion. The same banks. The same fundamentals. The only thing that changed was sentiment.&lt;/p&gt;

&lt;p&gt;In system design, we call this eventual consistency—a distributed system's ability to return to a correct state after a disruption, as long as the underlying data remains intact.&lt;/p&gt;

&lt;p&gt;The Real Lesson&lt;/p&gt;

&lt;p&gt;But here's what most people missed.&lt;/p&gt;

&lt;p&gt;Beneath the daily swings, something deeper was happening. Nigeria's market has undergone a structural transformation. Total transactions hit ₦4.15 trillion in Q1 2026—almost double the previous year. And 86.9% of that money is domestic, anchored by pension funds with ₦29.43 trillion in assets.&lt;/p&gt;

&lt;p&gt;This isn't speculation. This is architecture.&lt;/p&gt;

&lt;p&gt;What This Means for Developers&lt;/p&gt;

&lt;p&gt;When a single CBN rule crashes banking stocks, that's a single point of failure. When panic selling spreads across sectors, that's a cascading failure. When the market recovers in 24 hours, that's eventual consistency. When pension funds anchor the market with patient capital, that's redundancy and fault tolerance. And when 86.9% of capital comes from domestic sources, that's a decentralized architecture.&lt;/p&gt;

&lt;p&gt;The stock market, it turns out, is just another distributed system. And distributed systems behave in predictable ways—whether they're made of microservices or market participants.&lt;/p&gt;

&lt;p&gt;What I'm Learning&lt;/p&gt;

&lt;p&gt;The best engineers don't just write code. They understand systems. They know that failures are inevitable, but collapse is optional. They design for resilience, not perfection.&lt;/p&gt;

&lt;p&gt;That's the kind of engineer I'm trying to become.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>softwareengineering</category>
      <category>learning</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Mini Shai-Hulud: un gusano de cadena de suministro que explotó TanStack y el ecosistema npm.</title>
      <dc:creator>Cristian Carrillo</dc:creator>
      <pubDate>Tue, 12 May 2026 20:32:29 +0000</pubDate>
      <link>https://dev.to/bush1dom4koto/mini-shai-hulud-un-gusano-de-cadena-de-suministro-que-exploto-tanstack-y-el-ecosistema-npm-4df7</link>
      <guid>https://dev.to/bush1dom4koto/mini-shai-hulud-un-gusano-de-cadena-de-suministro-que-exploto-tanstack-y-el-ecosistema-npm-4df7</guid>
      <description>&lt;p&gt;El día de ayer (10/05/2026) la comunidad de npm y PyPI vivió un nuevo capítulo de Mini Shai-Hulud, un campaña de ataque de cadena de suministro dirigida por el grupo TeamPCP que comprometió paquetes de TanStack, Mistral AI, UiPath, OpenSearch, guardrails‑ai y más.&lt;/p&gt;

&lt;p&gt;El impacto se estimó en más de 170 paquetes y 500M+ de descargas acumuladas, con un nivel de severidad crítico (CVSS ≈ 9.6) y un CVE asociado: CVE‑2026‑45321.&lt;/p&gt;

&lt;p&gt;1 ¿Qué pasó?&lt;/p&gt;

&lt;p&gt;El ataque inyectó código malicioso en paquetes de npm muy populares, empezando por el ecosistema de TanStack (por ejemplo, @tanstack/react‑router, @tanstack/vue‑router, etc.), y luego se extendió a Mistral AI, UiPath, OpenSearch, guardrails‑ai y otros.&lt;/p&gt;

&lt;p&gt;El malware, un gusano de credenciales (credential‑stealing worm), hacía lo siguiente al ejecutarse durante npm install o cuando se usaba el paquete en CI/CD.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;• Robaba tokens de GitHub, npm, AWS, GCP, Azure, Kubernetes, Vault, IDEs y hasta carteras de cripto.
• Se auto‑replegaba hacia otros paquetes que el mantenedor pudiera publicar, propagándose en el ecosistema.
• Establecía persistencia en máquinas de desarrolladores (Claude Code, VS Code, servicios de sistema) y podía borrar datos si se revocaba cierto token.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;También se ha visto una variante en PyPI (guardrails‑&lt;a href="mailto:ai@0.10.1"&gt;ai@0.10.1&lt;/a&gt;, &lt;a href="mailto:mistralai@2.4.6"&gt;mistralai@2.4.6&lt;/a&gt;) que descarga y ejecuta un payload remoto sin verificación de integridad, ampliando el ataque a Python y otros ecosistemas.&lt;/p&gt;

&lt;p&gt;2 ¿Cómo sucedió?&lt;/p&gt;

&lt;p&gt;El ataque combinó ingeniería de engaños en GitHub con un mal uso de GitHub Actions y OIDC, sin necesidad de robar directamente el token de npm.&lt;/p&gt;

&lt;p&gt;El flujo básico fue:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Inyección en el fork:

    ◦ Atacante hace fork de TanStack/router y sube un commit con un paquete @tanstack/setup que contiene un script prepare malicioso que ejecuta bun run tanstack_runner.js.
    ◦ El commit es “huérfano” y se hace pasar por algo legítimo gracias a la URL de GitHub.

2. Envenenamiento de caché en GitHub Actions:

    ◦ El workflow de CI/CD de TanStack ejecuta código del fork, lo que contamina el caché de pnpm y causa que se instale código controlado por el atacante.

3. Extracción de token OIDC y publicación de paquetes:

    ◦ El payload accede a la memoria del proceso del runner (/proc/*/mem) y extrae el OIDC token legítimo de GitHub, que el sistema de SLSA considera confiable.
    ◦ El atacante usa ese token para publicar versiones falsas de los paquetes de TanStack directamente al registro de npm, con probidad de SLSA válida, lo que hace que el paquete “se vea limpio” incluso con firmas.

4. Auto‑propagación del worm:

    ◦ El malware busca tokens de npm con bypass_2fa, enumera todos los paquetes del mantenedor y vuelve a publicar versiones comprometidas, generando el efecto de “gusano” en el ecosistema.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;3 ¿Qué debes tener en cuenta?&lt;/p&gt;

&lt;p&gt;Este incidente recalca varios peligros que ya no son "teóricos":&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;• GitHub Actions + OIDC = nuevo vector de ataque
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Un token OIDC, aunque sea de corta vida, puede usarse para publicar paquetes y firmarlos con SLSA, dando pinta de legítimo a un paquete malicioso.&lt;br&gt;
    • Un solo npm install puede ser suficiente&lt;br&gt;
        ◦ El malicioso router_init.js se ejecutaba en el pre‑instalación y se auto‑desprendía en segundo plano, sin dejar rastro claro en logs.&lt;br&gt;
        ◦ Una vez instalado, el gusano podía robar cualquier secreto a disposición en esa máquina o flujo de CI.&lt;br&gt;
    • Las salvaguardias “tradicionales” no bastan&lt;br&gt;
        ◦ Firmas de paquetes, SLSA, y hasta algunos scanners estáticos no detectaron el malware porque el código ejecutable era obfuscado y ejecutado en runtime.&lt;br&gt;
    • El impacto va más allá de npm&lt;br&gt;
El ataque ya se ha extendido a PyPI y otros ecosistemas (SAP, Bitwarden, Intercom, Lightning, etc.), lo que muestra que estamos ante una campaña operativa continua de TeamPCP, no un ataque aislado.&lt;/p&gt;

&lt;p&gt;4 ¿Qué hacer ahora?&lt;/p&gt;

&lt;p&gt;Si usas npm/PyPI en producción, estos pasos no son opcionales:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;• Identificar exposición:
    ◦ Revisa package‑lock.json / yarn.lock / pnpm‑lock.yaml (y requirements.txt o pipenv.lock en Python) buscando versiones afectadas de @tanstack/*, @uipath/*, mistralai, guardrails‑ai, @opensearch‑project/opensearch, etc.
    ◦ Busca router_init.js o setup.mjs en el árbol de node_modules y en tus tarballs.

• Rotar todos los secretos potencialmente expuestos:
    ◦ Tokens de npm, GitHub, AWS, Vault, Kubernetes, CI/CD, etc.
    ◦ Wiz y StepSecurity recomiendan rotar cualquier token si el entorno instaló alguna versión afectada, por el alcance de lo que el malware puede extraer.

• Eliminar persistencia en máquinas de desarrolladores:
    ◦ Borrar archivos como .claude/router_runtime.js, .claude/setup.mjs, .vscode/setup.mjs y revisar settings.json y tasks.json.
    ◦ En macOS/Linux, eliminar el servicio gh‑token‑monitor antes de tocar tokens de GitHub para evitar que el wiper se dispare.

• Bloquear infraestructura de C2:
    ◦ Bloquear a nivel DNS/proxy dominios como git‑tanstack.com, *.getsession.org y otras IPs/C2 documentadas por StepSecurity, Wiz, Socket, etc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;5 ¿Cómo protegerse a futuro?&lt;/p&gt;

&lt;p&gt;Este ataque es una hoja de ruta de hardening para cualquier equipo que consuma paquetes de terceros:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;• Reducir el alcance de OIDC en GitHub Actions:
    ◦ Usar solo id‑token: write en el job que realmente publica, y evitar OIDC en workflows de test/CI generales.

• Hardening de GitHub Actions:
    ◦ Asegurar que pull_request_target solo se ejecute en ramas protegidas y que no se ejecute código de forkeos sin validación.
    ◦ Limpiar caché de CI y usar restricciones de permisos entre fork y base.

• Adoptar buffers de tiempo para nuevos paquetes:
    ◦ Estrategias como “cooldown” de versiones nuevas (nuevas versiones de npm bloqueadas por 24–72 horas) dan margen para detectar y bloquear estos ataques antes de que lleguen a producción.

• Monitoreo de tiempo de ejecución en CI:
    ◦ Herramientas que monitorean tráfico de red, lectura de memoria de runner (/proc/*/mem) o uso de sudo / python3 de forma inusual ayudan a detectar estas inyecciones que evitan el escaneo estático.

• No confiar ciegamente en “provenance” o SLSA:
    ◦ Que un paquete tenga badges de SLSA no garantiza que el pipeline no haya sido comprometido; debes ver el provenance como una parte de tu cadena de confianza, no como la única.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;6 Reflexión para líderes técnicos y de seguridad:&lt;/p&gt;

&lt;p&gt;Este episodio muestra que:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;• La cadena de suministro ya es un vector de ataque operativo y automatizado.
• Un gusano puede escalar de un solo paquete a docenas de repositorios y nubes en cuestión de horas.
• Debes combinar:
    ◦ Control de cambios de dependencias.
    ◦ Hardening de CI/CD.
    ◦ Monitoreo de tiempo de ejecución.
    ◦ Respuesta rápida de rotación de credenciales.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Enlace de referencia rápida:&lt;br&gt;
    • Vulnerabilidad CVE‑2026‑45321 (TanStack): detalle de impacto y severidad.&lt;br&gt;
    • Mini‑Shai‑Hulud en npm y PyPI: artículos de The Hacker News, Socket, StepSecurity y Wiz con IOCs y pasos de remediation.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>supplychain</category>
      <category>npm</category>
      <category>ciberseguridad</category>
    </item>
    <item>
      <title>The compliance deadline banks aren't watching for</title>
      <dc:creator>Yaman Al Bochi</dc:creator>
      <pubDate>Tue, 12 May 2026 20:31:59 +0000</pubDate>
      <link>https://dev.to/albochi/the-compliance-deadline-banks-arent-watching-for-2l3a</link>
      <guid>https://dev.to/albochi/the-compliance-deadline-banks-arent-watching-for-2l3a</guid>
      <description>&lt;p&gt;Most financial institutions have good AI. Very few have good AI governance.&lt;/p&gt;

&lt;p&gt;There's a practical gap right now between what regulators are signaling (OSFI E-23, SR 11-7) and what institutions are actually tracking. That gap has a name: Shadow AI — the models and tools deployed inside an organization without any formal oversight.&lt;/p&gt;

&lt;p&gt;This isn't about being afraid of regulators. It's about realizing that governance isn't a barrier to speed; it's how you prove to the market that your AI is safe to scale.&lt;/p&gt;

&lt;p&gt;At Saillent, we help institutions close that gap through a structured, five-tier governance framework that aligns directly with what OSFI and the Fed are asking for. Not theory — applied, audit-ready work.&lt;/p&gt;

&lt;p&gt;If you're leading risk or compliance at a financial institution, now is the time to get visibility into what's actually running inside your organization.&lt;/p&gt;

&lt;p&gt;My inbox is open: &lt;a href="mailto:info@saillent.com"&gt;info@saillent.com&lt;/a&gt;&lt;br&gt;
Learn more: saillent.com&lt;/p&gt;

</description>
      <category>ai</category>
      <category>governance</category>
      <category>fintech</category>
      <category>compliance</category>
    </item>
    <item>
      <title>The mindset all junior developers should have</title>
      <dc:creator>Tran Manh Hung</dc:creator>
      <pubDate>Tue, 12 May 2026 20:29:43 +0000</pubDate>
      <link>https://dev.to/manhhungtran/the-mindset-all-junior-developers-should-have-3n5</link>
      <guid>https://dev.to/manhhungtran/the-mindset-all-junior-developers-should-have-3n5</guid>
      <description>&lt;p&gt;Are you beginning your journey as a developer? Well, I've got something for you. In this article, I want to share a few subjective points on the advice I give my junior devs.&lt;/p&gt;

&lt;p&gt;Over the years I've noticed recurring patterns, mistakes, and a few trends I really don't like. So I figured it was time to write them down.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; Before you start reading my takes, I'm assuming one important thing — that your tech leads aren't assholes and genuinely want to help you flourish. If you're not so blessed... boy, am I sorry for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TL;DR for the impatient:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bring your guess when you ask a question&lt;/li&gt;
&lt;li&gt;Seniors are wrong sometimes — push back&lt;/li&gt;
&lt;li&gt;It's &lt;em&gt;our&lt;/em&gt; code, never &lt;em&gt;his&lt;/em&gt; code&lt;/li&gt;
&lt;li&gt;Feeling like you know nothing? That's the job&lt;/li&gt;
&lt;li&gt;Use AI, but read what it gives you&lt;/li&gt;
&lt;li&gt;Don't compare yourself to others&lt;/li&gt;
&lt;li&gt;It's just a job. Sleep.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alright lets start...
&lt;/h2&gt;

&lt;h2&gt;
  
  
  When asking a question, also share what you think the answer is
&lt;/h2&gt;

&lt;p&gt;Let's say you're past the "how do I even start" phase and have a concrete question.&lt;/p&gt;

&lt;p&gt;If your question is genuinely complex, it's probably hard for the senior dev to answer too (unless they're Wozniak). It takes their time, and you might also miss context that's important for the solution.&lt;/p&gt;

&lt;p&gt;That's why I recommend pairing most questions with your own proposed answer. It can be a totally stupid guess — that's fine. What you'll notice is that a surprising number of times, you end up answering your own question while formulating it. And when you don't, you've given the senior a much better starting point for the conversation, which makes it far easier to get the answer you actually need.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: after writing this, I realized it's basically rubber duck debugging. :D&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your mentor/senior dev can also make mistakes
&lt;/h2&gt;

&lt;p&gt;Seniors aren't oracles. We've just been wrong more times than you have, which is mostly what experience is.&lt;/p&gt;

&lt;p&gt;If something a senior tells you doesn't add up — push back, ask why, dig in. A good senior will respect that far more than silent agreement. And occasionally, you'll be the one who's right. That's a great feeling, and it's how trust gets built both ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's &lt;em&gt;our&lt;/em&gt; code, not &lt;em&gt;yours&lt;/em&gt; or &lt;em&gt;mine&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;This is the most common issue I've encountered across almost every team. Someone pushes code that has a bug. I ask what happened. The first response is: &lt;em&gt;"It was XX, he should fix it."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I hate it.&lt;/p&gt;

&lt;p&gt;You're creating an environment where people are afraid to take risks. No risks means slower progress. Slower progress kills motivation. You see where this goes.&lt;/p&gt;

&lt;p&gt;Take ownership of the project as a whole team. Build is broken? Instead of pointing out who did it, suggest how you think it could be fixed. There will be times your colleague makes a mistake. There will also be times &lt;em&gt;you&lt;/em&gt; make the mistake. Don't forget — you all share the same goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you feel like the more you learn, the less you know? Good.
&lt;/h2&gt;

&lt;p&gt;Development is genuinely complex. If you think everything is easy, you're either a genius or you're only scratching the surface. The discomfort of "I don't know enough yet" is actually a sign you're starting to see the real shape of the problem space. Get comfortable with it — it doesn't really go away, and that's a feature, not a bug.&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%2F9dvakoh6hpotsw3opmaz.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%2F9dvakoh6hpotsw3opmaz.png" alt="Meme about not knowing things as a developer" width="700" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AI is great, but please always try to understand the code
&lt;/h2&gt;

&lt;p&gt;AI tools are amazing accelerators, and I'm not going to tell you to avoid them. But there's a difference between using AI to &lt;em&gt;help you build&lt;/em&gt; something and using AI to &lt;em&gt;build something for you&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you ship code you don't understand, you can't debug it, you can't extend it, and you can't defend it in code review. Worse, &lt;strong&gt;you don't grow&lt;/strong&gt;. Treat AI output the same way you'd treat a Stack Overflow answer from 2014 — useful, often correct, but always something you should read, question, and actually understand before it becomes part of your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop comparing yourself to others — everyone moves at their own pace
&lt;/h2&gt;

&lt;p&gt;Someone on your team picked up Kubernetes in a weekend. Someone else seems to know every keyboard shortcut ever invented. Someone got promoted before you.&lt;/p&gt;

&lt;p&gt;It doesn't matter.&lt;/p&gt;

&lt;p&gt;You don't see their full picture — the years they spent on something before you met them, the things they secretly struggle with, the parts of the job they're worse at than you. The only useful comparison is with the version of you from six months ago. If that person would be impressed by where you are now, you're doing fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the end, it's just a job — your health matters too
&lt;/h2&gt;

&lt;p&gt;I'll be direct: no project is worth burning out for. Not the deadline, not the production incident at 11 PM, not the feature your PM "really needs by Friday."&lt;/p&gt;

&lt;p&gt;Sleep, exercise, friends, hobbies that have nothing to do with code — these aren't distractions from your career, they're what make a long career possible. The best devs I know in their 40s and 50s all figured this out early. The ones who didn't... mostly aren't writing code anymore — they're running a goose farm somewhere.&lt;/p&gt;




&lt;h1&gt;
  
  
  A note for the seniors reading this
&lt;/h1&gt;

&lt;p&gt;This article is for juniors, but if you're a senior or tech lead who made it this far — a quick word for you too.&lt;/p&gt;

&lt;p&gt;Remember that you also started somewhere. Someone (hopefully) was patient with you when your PR was a mess, when you asked the "obvious" question for the third time, when you broke the build on a Friday afternoon. You didn't become a senior by accident — someone invested time in you, even when they probably didn't have it to spare.&lt;/p&gt;

&lt;p&gt;Yes, the deadlines are tight. They're always tight. There will never be a quarter where you have plenty of free time and your juniors happen to need exactly that much mentoring. That's not how it works.&lt;/p&gt;

&lt;p&gt;But please — don't skip the mentoring part. The 30 minutes you spend explaining why, not just what, is the highest-leverage time you'll spend all week. A junior you actually invested in becomes a mid-level dev who unblocks themselves, then a senior who unblocks others, then — eventually — the person carrying the team while you're on vacation.&lt;/p&gt;

&lt;p&gt;Skip that investment and you'll be the bottleneck forever. Every question routes through you. Every tricky bug lands on your desk. Every architectural decision waits for your review. That's not seniority, that's just being permanently on-call for your own team.&lt;/p&gt;

&lt;p&gt;Helping juniors isn't charity. It's the most selfish long-term move you can make. Future-you will thank present-you for it.&lt;/p&gt;




&lt;p&gt;To anyone on my current or previous teams reading this: the stories are definitely not about you. The timing is purely coincidental. :)&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this resonated — or if you violently disagree — drop a comment or even send me mail. I'm especially curious to hear from other tech leads about what mindsets you wish your juniors had earlier.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>mentorship</category>
    </item>
    <item>
      <title>Can AI Make Legal Work Less Painful? Join Rhett's Legal-Tech Hackathon</title>
      <dc:creator>Team Rhett</dc:creator>
      <pubDate>Tue, 12 May 2026 20:29:01 +0000</pubDate>
      <link>https://dev.to/team_rhett/can-ai-make-legal-work-less-painful-join-rhetts-legal-tech-hackathon-2da3</link>
      <guid>https://dev.to/team_rhett/can-ai-make-legal-work-less-painful-join-rhetts-legal-tech-hackathon-2da3</guid>
      <description>&lt;p&gt;Legal work is still full of manual review, scattered workflows, slow compliance checks, and unclear documentation.&lt;/p&gt;

&lt;p&gt;At Rhett, we're exploring a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can developers use AI to make legal execution faster, clearer, and more accessible?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the idea behind &lt;strong&gt;The Code of Law Challenge&lt;/strong&gt; — a legal-tech hackathon by Rhett.&lt;/p&gt;

&lt;h2&gt;
  
  
  📅 Hackathon Dates
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;30–31 May 2026&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Register here:&lt;/strong&gt; &lt;a href="https://rhett.legal/hackathon" rel="noopener noreferrer"&gt;rhett.legal/hackathon&lt;/a&gt;&lt;br&gt;
🔗 &lt;strong&gt;Detailed Info:&lt;/strong&gt; &lt;a href="https://drive.google.com/file/d/1z4iNH7e-mkxpRQm54PFw_lysOhX6IIy4/view" rel="noopener noreferrer"&gt;Brochure&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;em&gt;Early Bird Offer: ₹799&lt;/em&gt;
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Who Should Join?
&lt;/h2&gt;

&lt;p&gt;This hackathon is for builders (students, early-career, professionals) interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI and LLMs&lt;/li&gt;
&lt;li&gt;NLP&lt;/li&gt;
&lt;li&gt;Open-source tools&lt;/li&gt;
&lt;li&gt;Legal-tech&lt;/li&gt;
&lt;li&gt;Compliance automation&lt;/li&gt;
&lt;li&gt;Document intelligence&lt;/li&gt;
&lt;li&gt;Workflow automation&lt;/li&gt;
&lt;li&gt;Developer-first problem solving&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Team Limit: 1-4 Max&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You do not need to be a lawyer.&lt;/strong&gt; You need curiosity, execution, and the willingness to build something useful.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem Statements
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Contract Review &amp;amp; Redlining Tool
&lt;/h3&gt;

&lt;p&gt;Build a tool that can review contracts such as NDAs, employment agreements, or service agreements. The tool should help users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify risky, ambiguous, or non-standard clauses&lt;/li&gt;
&lt;li&gt;Suggest improved or alternative clauses&lt;/li&gt;
&lt;li&gt;Highlight changes in a clear redlined format&lt;/li&gt;
&lt;li&gt;Improve contract review speed and clarity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open stack — LLMs, RAG pipelines, rule engines, fine-tuned models, or a hybrid approach. Your call.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. AI for Legal Governance — Open Innovation Track
&lt;/h3&gt;

&lt;p&gt;Build a solution that improves legal governance using open-source technologies. Possible directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compliance checklist automation&lt;/li&gt;
&lt;li&gt;Policy review assistants&lt;/li&gt;
&lt;li&gt;Regulatory change trackers&lt;/li&gt;
&lt;li&gt;Legal Q&amp;amp;A systems&lt;/li&gt;
&lt;li&gt;Document classification and tagging tools&lt;/li&gt;
&lt;li&gt;Risk monitoring workflows&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  What We're Looking For
&lt;/h2&gt;

&lt;p&gt;We care about &lt;strong&gt;working prototypes, clear problem definition, practical utility, and thoughtful use of open-source technologies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best projects will not just look impressive. They will solve a real legal or compliance problem in a way users can actually understand.&lt;/p&gt;


&lt;h2&gt;
  
  
  🏆 Prizes &amp;amp; Opportunities
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;₹22,000 prize pool&lt;/strong&gt; for the top 2 winning teams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid internship opportunities at Rhett&lt;/strong&gt; for standout participants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Premium workshop / certification access&lt;/strong&gt; via LegalWiki&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1-year membership&lt;/strong&gt; to the Indian Society of AI and Law (ISAIL)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incubation and mentorship support&lt;/strong&gt; through HPNLU's Legal Innovation &amp;amp; Incubation Centre&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Legal systems are often slow, expensive, and difficult to navigate — especially for growing businesses and MSMEs that drive most of India's economic activity.&lt;/p&gt;

&lt;p&gt;Developers have a real opportunity to change that. The next generation of legal tooling won't come from law firms hiring engineers. It will come from engineers who decide the friction isn't acceptable anymore.&lt;/p&gt;

&lt;p&gt;If you're building at the intersection of AI, law, and open-source — or you've been waiting for an excuse to start — we'd love to see what you create.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rhett.legal/hackathon" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Register for The Code of Law Challenge&lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Which problem would you tackle — contract redlining or open-innovation governance? Drop a comment with your stack of choice or to know more about it&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hackathon</category>
      <category>opensource</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Living on My Own Server: An Indie Hacker's Work-Life Balance</title>
      <dc:creator>Mustafa ERBAY</dc:creator>
      <pubDate>Tue, 12 May 2026 20:28:27 +0000</pubDate>
      <link>https://dev.to/merbayerp/living-on-my-own-server-an-indie-hackers-work-life-balance-4nn0</link>
      <guid>https://dev.to/merbayerp/living-on-my-own-server-an-indie-hackers-work-life-balance-4nn0</guid>
      <description>&lt;h2&gt;
  
  
  Living on My Own Server: An Indie Hacker's Work-Life Balance
&lt;/h2&gt;

&lt;p&gt;Running my own servers has been the foundation of both my work and personal projects for years. This has provided me with incredible control and flexibility, but it has also inevitably required me to write my own rules regarding work-life balance. On this path as an "indie hacker," managing my own infrastructure has become not just a technical choice, but an integral part of my lifestyle. In this post, I will talk about the challenges and opportunities of living on my own servers and how this balance is established.&lt;/p&gt;

&lt;p&gt;This journey didn't always start in a planned way. Initially, I moved my server room into my own home just to get better performance and more control. However, over time, I realized that this wasn't just a matter of "infrastructure," but something that deeply affected my way of life. Managing my own data center meant being in a constant state of being "on," which could blur the line between professional and personal life.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Freedom and Responsibility of My Own Infrastructure
&lt;/h3&gt;

&lt;p&gt;The biggest plus of managing my own servers is definitely freedom. I can install any software I want, configure it as I wish, and develop my projects without getting stuck in the limitations of any third-party provider. This provides an invaluable advantage, especially when I want to prototype and test new ideas quickly. Debugging processes are also more direct; I have access to every layer from hardware to software.&lt;/p&gt;

&lt;p&gt;However, this freedom brings great responsibility. You have to constantly deal with the security, updates, backups, and potential failures of the servers. This can mean the necessity of immediate intervention in case of an alarm coming in the middle of the night or a service crash. This situation can make it difficult to fully relax even on vacations or weekends.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ℹ️ A Snippet from My Experience&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once, I received an alarm regarding PostgreSQL WAL rotation at 03:14 AM. Although it initially looked like a simple disk space issue, it was actually a bloat problem caused by WAL files being cleaned up slower than expected. Such situations require constant monitoring and rapid intervention.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Indie Hacker's Work-Life Balance
&lt;/h3&gt;

&lt;p&gt;The essence of being an "indie hacker" is to act independently and grow your own projects. While managing my own infrastructure, this independence is further reinforced. However, this is also an indicator of how difficult it has become to separate work and life. When I set up a data center in my own home, the noise and lights of the servers gave a constant sense of presence even when I wasn't working.&lt;/p&gt;

&lt;p&gt;I had to take conscious steps to deal with this. First, I physically separated my server room from the rest of my house. By installing sound insulation and a separate air conditioning system, I both reduced the noise and ensured the servers operated in optimum conditions. This allowed me to draw a clear line between my "workspace" and my "living space."&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Separation of Workspace&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Digital separation is just as important as physical separation. While managing my own servers, I separate the devices I use for work and my personal devices. This prevents work-oriented notifications or tasks from leaking into my personal time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Technical Challenges and Solutions
&lt;/h3&gt;

&lt;p&gt;The technical challenges I've encountered while managing my own servers are too many to count. One of them was the disk space issues I experienced, especially when dealing with container orchestration. The uncontrolled growth of Docker images and logs over time could quickly fill up server disks. To solve this problem, I implemented regular disk cleanup scripts and log rotation policies.&lt;/p&gt;

&lt;p&gt;Another important issue was network security and segmentation. Creating separate VLANs for different projects, managing firewall rules, and securing VPN connections requires constant effort. Integrating modern approaches like Zero Trust Network Access (ZTNA) into my own infrastructure increased both the learning process and the implementation cost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Example of a simple log cleanup script
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A scene where a person is working in a home office among multiple monitors and server hardware.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;LOG_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/log/my_app/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;MAX_LOG_AGE_DAYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_old_logs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&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="n"&gt;MAX_LOG_AGE_DAYS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;log_file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getmtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Removed old log file: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;log_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;clean_old_logs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script checks files with the &lt;code&gt;.log&lt;/code&gt; extension in the &lt;code&gt;/var/log/my_app/&lt;/code&gt; directory and deletes those older than 7 days. It can be run regularly using systemd timers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous Learning and Adaptation
&lt;/h3&gt;

&lt;p&gt;Managing one's own infrastructure requires a continuous learning process. New security vulnerabilities emerge, software is updated, and hardware ages. To keep up with this dynamic environment, I need to constantly research, learn new technologies, and adapt my existing systems. This is an inevitable part of the "indie hacker" lifestyle.&lt;/p&gt;

&lt;p&gt;For example, when I wanted to run AI models on my own servers, I had to deal deeply with topics like GPU resource management, memory optimization, and distributed training. Using different models and services like Gemini Flash, Groq, and Cerebras together provided me with flexibility but also increased complexity. Such projects both improve my technical skills and open new doors for me.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Learning Curve&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every new technology or tool comes with a learning curve. Learning by trial and error on my own infrastructure can sometimes lead to costly mistakes. Therefore, for important systems, always have a fallback plan.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion: Is a Balanced Life Possible?
&lt;/h3&gt;

&lt;p&gt;Living on my own servers has provided me with incredible freedom and control. As I move forward on my own path as an "indie hacker," this infrastructure has been my biggest supporter. However, this doesn't mean that the work-life balance is perfect. There is a constant need for alertness, continuous learning, and constant problem-solving.&lt;/p&gt;

&lt;p&gt;The important thing is to establish this balance consciously. Drawing physical and digital boundaries, using automation tools effectively, and setting aside time for yourself to rest are the cornerstones of this balance. Managing your own infrastructure is not just a technical project, but also a lifestyle choice, and accepting the responsibilities that come with this choice is essential for a sustainable "indie hacker" life in the long run. Living in my own data center has brought me both freedom and responsibility; and what I've learned on this journey has gained me invaluable experiences.&lt;/p&gt;

</description>
      <category>life</category>
      <category>indiehacker</category>
      <category>worklifebalance</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Open-source multi-agent pipeline: 61K Python, 12 agents, 5 quality gates...</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Tue, 12 May 2026 20:28:24 +0000</pubDate>
      <link>https://dev.to/alex_ea31e0501f5eb2156ecc/open-source-multi-agent-pipeline-61k-python-12-agents-5-quality-gates-4hl4</link>
      <guid>https://dev.to/alex_ea31e0501f5eb2156ecc/open-source-multi-agent-pipeline-61k-python-12-agents-5-quality-gates-4hl4</guid>
      <description>&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%2Fakpbzyu8hnxzgdq3s9u3.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%2Fakpbzyu8hnxzgdq3s9u3.png" alt=" " width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I spent the last month building an open-source (MIT) pipeline that takes a plain-language idea and runs it through 12 specialized agents — analyst, PM, architect, design critic, developer, QA, security, DevOps, marketing, and more — with 5 quality gates, a strict state machine with recovery, and an AI Director that autonomously manages the whole thing.&lt;br&gt;
Think Bolt.new or Lovable, but self-hosted, MIT licensed, with quality gates that actually prevent the model from shipping broken stubs.&lt;br&gt;
The interesting part isn't the LLM calls. Here's what broke in production.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;LLM failover creates consistency problems&lt;br&gt;
I have 6+ providers (DeepSeek, Anthropic, OpenAI, Ollama, Groq, etc.) with automatic health-check failover every 60s. The footgun: DeepSeek and Claude write different code. Same prompt, wildly different output structure. If the router switches providers mid-pipeline, the architect output (Claude) won't match what the developer agent (DeepSeek) expects.&lt;br&gt;
Solution: task-level pinning. Heavy tasks (architect, developer) stay locked to the primary provider. Light tasks (marketing copy, naming) can fall back freely. I also added a model capability matrix check before routing — otherwise you get an architect running on a 7B local model producing garbage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;State machines need to survive the model being wrong&lt;br&gt;
11 states, 34 valid transitions, JSON + SQLite dual persistence. Sounds solid until the model writes a corrupted artifact that crashes the state machine on the next task load.&lt;br&gt;
Had to add:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recovery fallback: if JSON parse fails, restore from SQLite snapshot&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stranded product recovery: products stuck in &lt;code&gt;pm_quality_fail&lt;/code&gt; because the model hallucinated a non-existent file path&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Async save with timeout guards so a slow disk write doesn't block the pipeline&lt;br&gt;
The lesson: your state machine needs to survive both a wrong model AND a corrupted disk. Not theoretical — happened in production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Director AI feedback loop problem&lt;br&gt;
The Director runs a 6-phase autonomous cycle: route chat → analyze metrics → generate decisions → apply actions → rank what to build next → log. &lt;br&gt;
The footgun: feedback loops. Director generates a decision → applies it → next cycle reads its own output → generates another decision based on that → infinite loop. Had to add noop detection that breaks the cycle when decisions become empty.&lt;br&gt;
The chat classification is also tricky. The Director classifies owner messages as &lt;code&gt;new_idea&lt;/code&gt;, &lt;code&gt;product_feedback&lt;/code&gt;, or &lt;code&gt;general_directive&lt;/code&gt; via LLM. If it misclassifies "fix the login page" as &lt;code&gt;new_idea&lt;/code&gt;, you get a duplicate product instead of a bug fix. I added an orphan feedback heuristic: if a message mentions a product name that doesn't exist yet, route to &lt;code&gt;new_idea&lt;/code&gt;; otherwise link to the existing product.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quality gates — what I wish I'd built first&lt;br&gt;
| Gate | What it checks |&lt;br&gt;
|------|---------------|&lt;br&gt;
| Demo quality | 12 checkpoints: contrast, CTA, broken links, spec coverage |&lt;br&gt;
| Browser E2E | Playwright crawl (desktop + mobile), JS errors, 404s |&lt;br&gt;
| Visual QA | 9 heuristics: contrast ratio, CSS vars, empty states, nav |&lt;br&gt;
| Security | AST scan: eval(), innerHTML, exposed tokens, hardcoded secrets |&lt;br&gt;
| Methodology | Domain packs: fintech, ecomm, healthcare, etc |&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Real example: visual QA flagged a white-on-white CTA button — the model generated &lt;code&gt;color: white&lt;/code&gt; on &lt;code&gt;background: white&lt;/code&gt; assuming a dark theme that wasn't applied. The gate caught it, sent it back to the developer with the exact CSS selector. Fixed next cycle.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preview fidelity is pure web engineering
When AI-generated code runs in a sandbox iframe, every web platform quirk amplifies: relative URLs break, &lt;code&gt;is missing, CSP blocks inline styles, `target="_top"` kills navigation. 
Had to write a dedicated URL rewriter that: injects&lt;/code&gt; pointing to the correct sandbox route, rewrites absolute &lt;code&gt;/&lt;/code&gt; links to relative, adds permissive CSP headers, strips &lt;code&gt;target="_top"&lt;/code&gt;. Not AI work. But without it, the preview is broken and users blame you, not the LLM.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;61,503 Python LOC, 22,997 TypeScript/TSX LOC&lt;/li&gt;
&lt;li&gt;12 specialized agents, 5 quality gates&lt;/li&gt;
&lt;li&gt;11 pipeline states, 34 valid transitions&lt;/li&gt;
&lt;li&gt;6+ LLM providers with auto-failover&lt;/li&gt;
&lt;li&gt;72 test files, MIT licensed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: github.com/alexar76/aicom — FastAPI + Next.js + Docker Compose, self-hosted, MIT, BYO API keys.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>My OpenClaw agent looked idle overnight and still burned through tokens</title>
      <dc:creator>Lars Winstand</dc:creator>
      <pubDate>Tue, 12 May 2026 20:26:43 +0000</pubDate>
      <link>https://dev.to/lars_winstand/my-openclaw-agent-looked-idle-overnight-and-still-burned-through-tokens-4ikj</link>
      <guid>https://dev.to/lars_winstand/my-openclaw-agent-looked-idle-overnight-and-still-burned-through-tokens-4ikj</guid>
      <description>&lt;p&gt;I found a small but very real r/openclaw thread recently: 14 upvotes, 29 comments, and a painfully familiar question.&lt;/p&gt;

&lt;p&gt;Why did an OpenClaw agent that looked basically idle overnight still torch the budget?&lt;/p&gt;

&lt;p&gt;The best answer from the thread was not exotic.&lt;/p&gt;

&lt;p&gt;It was heartbeats.&lt;/p&gt;

&lt;p&gt;More specifically: heartbeats that keep resending a fat conversation history back through the model.&lt;/p&gt;

&lt;p&gt;That means your agent can look asleep while still paying for context replay over and over.&lt;/p&gt;

&lt;p&gt;If you run OpenClaw with a long-lived thread, this is probably the first thing to inspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual failure mode
&lt;/h2&gt;

&lt;p&gt;A lot of people assume token burn comes from obvious work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generating lots of code&lt;/li&gt;
&lt;li&gt;browser automation&lt;/li&gt;
&lt;li&gt;long reasoning chains&lt;/li&gt;
&lt;li&gt;multi-step planning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes yes.&lt;/p&gt;

&lt;p&gt;But the thread kept converging on a more boring answer: long sessions plus frequent heartbeats.&lt;/p&gt;

&lt;p&gt;A simplified loop looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;heartbeat -&amp;gt; send current state + prior conversation -&amp;gt; model responds -&amp;gt; wait -&amp;gt; repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your session is already large, every heartbeat is expensive even when nothing interesting happened.&lt;/p&gt;

&lt;p&gt;That means cost tracks context size, not just visible activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "idle" is not idle
&lt;/h2&gt;

&lt;p&gt;OpenClaw can feel idle from the outside while still doing this internally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;full_conversation_history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_agent_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;maybe_run_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heartbeat_interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is obvious once you write it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;full_conversation_history&lt;/code&gt; keeps growing&lt;/li&gt;
&lt;li&gt;each heartbeat resends it&lt;/li&gt;
&lt;li&gt;cost compounds silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is how people wake up to a bill and think, "but the agent barely did anything."&lt;/p&gt;

&lt;h2&gt;
  
  
  The thread's best fix: stop keeping one immortal session alive
&lt;/h2&gt;

&lt;p&gt;The most useful suggestion in the comments was not "summarize harder."&lt;/p&gt;

&lt;p&gt;It was: use short sessions and write a handoff file.&lt;/p&gt;

&lt;p&gt;That pattern looks more like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;run a bounded task&lt;/li&gt;
&lt;li&gt;write the minimum state needed for the next task&lt;/li&gt;
&lt;li&gt;end the session&lt;/li&gt;
&lt;li&gt;start fresh later with only the handoff&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example handoff file:&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;# handoff.md&lt;/span&gt;

Current task: finish deployment validation
Status: staging deploy succeeded
Next step: run smoke test against /health and /login
Known issue: flaky timeout on user-profile endpoint
Files touched:
&lt;span class="p"&gt;-&lt;/span&gt; deploy.sh
&lt;span class="p"&gt;-&lt;/span&gt; smoke_test.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the next session can start with a much smaller prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read handoff.md and continue from there.
Do not reconstruct old discussion unless required.
Current goal: run smoke tests and report failures.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is less magical than persistent memory.&lt;/p&gt;

&lt;p&gt;It is also usually cheaper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why aggressive compaction is not a silver bullet
&lt;/h2&gt;

&lt;p&gt;The tempting answer is to summarize the thread every so often.&lt;/p&gt;

&lt;p&gt;That can help, but the Reddit discussion was right to be skeptical about doing it constantly mid-task.&lt;/p&gt;

&lt;p&gt;Because the real flow becomes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;read the giant thread&lt;/li&gt;
&lt;li&gt;generate a summary&lt;/li&gt;
&lt;li&gt;read the summary on the next call&lt;/li&gt;
&lt;li&gt;discover something important got dropped&lt;/li&gt;
&lt;li&gt;spend more tokens recovering context&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is often just a second conversation about the first conversation.&lt;/p&gt;

&lt;p&gt;For active coding or debugging work, losing one detail can be enough to trigger expensive repair loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better architecture beats better prompting
&lt;/h2&gt;

&lt;p&gt;The smartest part of the discussion was this: stop making the model do deterministic work.&lt;/p&gt;

&lt;p&gt;If a job can be handled by Python, bash, cron, or n8n, do that.&lt;/p&gt;

&lt;p&gt;Use the model for judgment, not polling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad pattern
&lt;/h3&gt;

&lt;p&gt;One always-on OpenClaw agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checks files&lt;/li&gt;
&lt;li&gt;polls APIs&lt;/li&gt;
&lt;li&gt;watches logs&lt;/li&gt;
&lt;li&gt;decides if anything changed&lt;/li&gt;
&lt;li&gt;keeps a giant context alive&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Better pattern
&lt;/h3&gt;

&lt;p&gt;Small background jobs do the boring work.&lt;/p&gt;

&lt;p&gt;Only wake the agent when there is something worth reasoning about.&lt;/p&gt;

&lt;p&gt;Example with cron:&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="k"&gt;*&lt;/span&gt;/15 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/bin/python3 /opt/check_build_status.py
0 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/bin/python3 /opt/check_error_budget.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example notifier script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# check_build_status.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;state_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/build_state.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;build_failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wake_openclaw.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Build state changed. Investigate latest CI failure.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;state_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a much better use of an LLM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scripts gather facts cheaply&lt;/li&gt;
&lt;li&gt;cron handles repetition cheaply&lt;/li&gt;
&lt;li&gt;OpenClaw gets called only when reasoning is needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Model tiering is the boring answer that actually works
&lt;/h2&gt;

&lt;p&gt;Another good point from the thread: stop paying premium model prices for maintenance tasks.&lt;/p&gt;

&lt;p&gt;If your heartbeat, orchestration, or lightweight routing is using Claude Opus or GPT-5 every time, you are probably overspending.&lt;/p&gt;

&lt;p&gt;A practical split looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Better default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Heartbeats / lightweight orchestration&lt;/td&gt;
&lt;td&gt;Qwen 9B, DeepSeek, or another cheap model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Normal coding / planning&lt;/td&gt;
&lt;td&gt;Claude Sonnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-stakes reasoning&lt;/td&gt;
&lt;td&gt;Claude Opus or GPT-5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local compression / summarization&lt;/td&gt;
&lt;td&gt;Granite 3B, Llama, or another local model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example config:&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;heartbeat_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;
&lt;span class="na"&gt;orchestrator_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qwen-9b&lt;/span&gt;
&lt;span class="na"&gt;heavy_reasoning_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet&lt;/span&gt;
&lt;span class="na"&gt;mission_critical_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-5&lt;/span&gt;
&lt;span class="na"&gt;local_compression_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;granite-3b&lt;/span&gt;
&lt;span class="na"&gt;session_rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;task&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;handoff.md&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fresh&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;session"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That setup is not glamorous.&lt;/p&gt;

&lt;p&gt;It is just sane.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical debugging checklist for OpenClaw token burn
&lt;/h2&gt;

&lt;p&gt;If your bill feels wrong, I would check these in order:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Measure heartbeat frequency
&lt;/h3&gt;

&lt;p&gt;If your agent is checking in every few minutes, stretch it out.&lt;/p&gt;

&lt;p&gt;Start with 1 hour for anything non-urgent.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Inspect payload size
&lt;/h3&gt;

&lt;p&gt;Log how much context is being sent per call.&lt;/p&gt;

&lt;p&gt;Even rough logging helps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;estimate_chars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context_chars=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;estimate_chars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_conversation_history&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Kill long-lived threads
&lt;/h3&gt;

&lt;p&gt;If a task is done, end the session.&lt;/p&gt;

&lt;p&gt;Do not keep dragging old context forward just because it feels convenient.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Replace repetitive reasoning with scripts
&lt;/h3&gt;

&lt;p&gt;If the model is repeatedly checking a condition, that is probably script territory.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Tier your models
&lt;/h3&gt;

&lt;p&gt;Cheap model for maintenance.&lt;br&gt;
Expensive model for hard calls.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Use local summarization if you need compression
&lt;/h3&gt;

&lt;p&gt;If you really must compact context, a local model can be a good tradeoff.&lt;/p&gt;
&lt;h2&gt;
  
  
  A concrete OpenClaw workflow that is harder to bankrupt
&lt;/h2&gt;

&lt;p&gt;Here is a pattern I would recommend for a lot of OpenClaw setups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cron/script layer
  -&amp;gt; checks deterministic conditions
  -&amp;gt; writes structured state files
  -&amp;gt; wakes OpenClaw only on change

OpenClaw session
  -&amp;gt; reads latest state file + handoff.md
  -&amp;gt; performs bounded reasoning task
  -&amp;gt; writes result + next-step handoff
  -&amp;gt; exits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;State file example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billing-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deploy_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failed_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"database migration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-12T02:10:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prompt example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read state.json and handoff.md.
Determine the most likely cause of the failed migration.
Suggest the smallest safe fix.
Do not re-check unrelated systems.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the model focused and keeps your context small.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger issue is not just cost, it is behavior
&lt;/h2&gt;

&lt;p&gt;Per-token billing changes how people build.&lt;/p&gt;

&lt;p&gt;Once you get burned a couple times, you start doing weird things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cutting prompts too aggressively&lt;/li&gt;
&lt;li&gt;avoiding retries&lt;/li&gt;
&lt;li&gt;reducing monitoring&lt;/li&gt;
&lt;li&gt;limiting experiments&lt;/li&gt;
&lt;li&gt;shutting down useful background loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not just a billing problem.&lt;/p&gt;

&lt;p&gt;It changes the architecture of the system because you are designing around fear.&lt;/p&gt;

&lt;p&gt;For OpenClaw users running always-on agents, that gets old fast.&lt;/p&gt;

&lt;p&gt;This is exactly why Standard Compute is interesting here.&lt;/p&gt;

&lt;p&gt;Instead of paying per token and constantly wondering whether a sleepy-looking agent is secretly expensive, you get flat monthly pricing through an OpenAI-compatible API with routing across models like GPT-5, Claude Opus, and Grok.&lt;/p&gt;

&lt;p&gt;If you are building persistent OpenClaw automations, that changes the optimization target.&lt;/p&gt;

&lt;p&gt;You can optimize for reliability and output quality instead of constantly asking, "is this heartbeat worth the money?"&lt;/p&gt;

&lt;p&gt;That does not remove the need for good architecture.&lt;/p&gt;

&lt;p&gt;You should still shorten sessions, use handoff files, and move deterministic work into scripts.&lt;/p&gt;

&lt;p&gt;But it does remove a lot of the token anxiety that makes people underbuild useful agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  My take
&lt;/h2&gt;

&lt;p&gt;The main lesson from that Reddit thread was simple:&lt;/p&gt;

&lt;p&gt;The problem usually is not that your OpenClaw agent is too smart.&lt;/p&gt;

&lt;p&gt;It is that the session shape is wrong.&lt;/p&gt;

&lt;p&gt;If you keep one giant thread alive, heartbeats turn context into a recurring tax.&lt;/p&gt;

&lt;p&gt;If you switch to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;longer heartbeat intervals&lt;/li&gt;
&lt;li&gt;bounded sessions&lt;/li&gt;
&lt;li&gt;handoff files&lt;/li&gt;
&lt;li&gt;scripts and cron for repetitive work&lt;/li&gt;
&lt;li&gt;model tiering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then the bill usually gets more predictable very quickly.&lt;/p&gt;

&lt;p&gt;And if you are tired of per-token pricing shaping every design decision, use infrastructure that is built for always-on agent workloads instead of fighting the meter all night.&lt;/p&gt;

&lt;p&gt;That is the real fix: better architecture first, better pricing model second.&lt;/p&gt;

&lt;p&gt;Both matter.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>devops</category>
      <category>openai</category>
    </item>
  </channel>
</rss>
