<?xml version="1.0"?><feed xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns:idx="urn:atom-extension:indexing" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr"><!--
Content-type: Preventing XSRF in IE.

--><generator uri="https://bazqux.com">BazQux Reader</generator><id>tag:google.com,2005:reader/feed/http://007unlicensedtotest.blogspot.com/feeds/posts/default</id><title>blogs</title><subtitle type="html">blogs</subtitle><link rel="self" href="https://bazqux.com/feed/d45a6ead98c5f8f9f99f?no_branding"></link><gr:continuation>11162620002411</gr:continuation><updated>2026-09-28T21:39:52Z</updated><entry gr:crawl-timestamp-msec="1790620052074"><id gr:original-id="tag:blogger.com,1999:blog-5408914917304971972.post-7683279152327502684">tag:google.com,2005:reader/item/0000052600000131</id><title type="html">Distance from fixes impacts value of same testing</title><published>2026-09-28T18:27:32Z</published><updated>2026-09-28T18:27:32Z</updated><link rel="alternate" href="https://visible-quality.blogspot.com/2026/09/distance-from-fixes-impacts-value-of.html" type="text/html"></link><summary type="html">&lt;p&gt;Same tester, writing same test automation - yet the impact this tester makes on quality is entirely different. Sound like potential for framing the test automation investment in a way that gives up relevant parts of its value. So let me explain. &lt;/p&gt;
&lt;p&gt;A colleague has been offered three different client projects, and in all three she will work on test automation. This made it clear: the choice where the client(s) place her make a difference on the results the client will experience out of test automation. &lt;/p&gt;
&lt;p&gt;The three options are: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Same team with developers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Testing services team&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Client side team&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Imagine you had full control over the choice of where you place your new pair of hands on testing / test automation, what is the impact of the setup? The bottom line: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you are close to fixes, your testing does not just verify - it also impacts what flows forward in the overall efforts. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the same team with developers: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For timely feedback, timely fixes! Likelyhood of timely feedback is up by knowing exactly what change is going on right now. &lt;/li&gt;
&lt;li&gt;Significant impact on quality: Fast fixing, test results guide development, quality improves continuously (making testing also easier)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the testing services team: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The handoff frequency is by design, but often attention of tester is split to multiple teams&lt;/li&gt;
&lt;li&gt;Fixes find their way to the next iterations&lt;/li&gt;
&lt;li&gt;Medium impact on quality: Fixes with delay, test results with delay, slower improvement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the client team: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixes made separately, and some feedback delayed to change framing for users and automation efficiency&lt;/li&gt;
&lt;li&gt;Inclined to prioritize for later, future&lt;/li&gt;
&lt;li&gt;Requires client reaction to improve quality, protects the knowledge client expects to have&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center&quot;&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; width=&quot;320&quot; alt=&quot;Growing feedback distance&quot; src=&quot;https://drive.google.com/thumbnail?id=1Zmsf95hzep_LF0wC0lu1gweD6dVhWFP2&amp;amp;sz=w1000&quot;&gt;&lt;/p&gt;
&lt;p&gt;For years, I have been pitching this: &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When you invest in quality, push the investment into automation directly where the system is being built (or broken). On client side, ask for ability to run this test automation.&lt;/strong&gt;&lt;/p&gt;</summary><author><name>Maaret Pyhäjärvi</name></author><source gr:stream-id="feed/https://visible-quality.blogspot.com/feeds/posts/default"><id>tag:google.com,2005:reader/feed/https://visible-quality.blogspot.com/feeds/posts/default</id><title type="html">A Seasoned Tester&apos;s Crystal Ball</title><link rel="alternate" href="https://visible-quality.blogspot.com/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790611675000"><id gr:original-id="http://emnaayadi.com/?p=4745">tag:google.com,2005:reader/item/000006a000000079</id><category term="AI"></category><category term="Exploratory Testing"></category><category term="testing"></category><category term="exploratorytesting"></category><category term="testing in production"></category><title type="html">Explore with Emna: Can You Fix It in Production?</title><published>2026-09-28T16:07:55Z</published><updated>2026-09-28T16:07:55Z</updated><link rel="alternate" href="https://emnaayadi.com/2026/09/28/bug-in-prod/" type="text/html"></link><summary type="html">&lt;p&gt;As testers, we’re trained to question everything.&lt;/p&gt;



&lt;p&gt;And thankfully… this week, my tester instincts saved me from a very awkward situation.&lt;/p&gt;



&lt;p&gt;I had just submitted a conference proposal. The form was complete, my abstract was uploaded, and my introduction video was linked.&lt;/p&gt;



&lt;p&gt;Done. Submitted. Forgotten.&lt;/p&gt;



&lt;p&gt;A few hours later, curiosity kicked in.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;“What if I click the link one more time?”&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;The video wasn’t working. &lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f631.png&quot; alt=&quot;😱&quot; style=&quot;height: 1em&quot;&gt;&lt;/p&gt;



&lt;p&gt;At that moment, my brain immediately jumped into incident mode.&lt;/p&gt;



&lt;p&gt;The obvious solution was to contact the conference organizers:&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;“Hi, sorry… could you update the video link in my submission?”&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;Not the most exciting email to send once a proposal has already been submitted.&lt;/p&gt;



&lt;p&gt;But before reaching out, I decided to investigate.&lt;/p&gt;



&lt;p&gt;And that’s where today’s adventure began.&lt;/p&gt;



&lt;p&gt;Thanks to Copilot, I discovered a feature in Google Drive that I had never paid attention to before: &lt;strong&gt;Upload a new version&lt;/strong&gt;.&lt;/p&gt;



&lt;p&gt;Wait a second…&lt;/p&gt;



&lt;p&gt;Does that mean I can replace the file &lt;em&gt;without changing the URL&lt;/em&gt;?&lt;/p&gt;



&lt;p&gt;A few clicks later:&lt;/p&gt;



&lt;p&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png&quot; alt=&quot;✅&quot; style=&quot;height: 1em&quot;&gt; Same link&lt;br&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png&quot; alt=&quot;✅&quot; style=&quot;height: 1em&quot;&gt; New video&lt;br&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png&quot; alt=&quot;✅&quot; style=&quot;height: 1em&quot;&gt; No modification to the submission form&lt;br&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png&quot; alt=&quot;✅&quot; style=&quot;height: 1em&quot;&gt; No email to the organizers&lt;br&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png&quot; alt=&quot;✅&quot; style=&quot;height: 1em&quot;&gt; Crisis avoided&lt;/p&gt;



&lt;div style=&quot;aspect-ratio: unset&quot; id=&quot;article-u_cKK8tDbCVe-AtcxssE5ppZHx4-google-drive-production-fix&quot;&gt;&lt;img width=&quot;808&quot; height=&quot;640&quot; data-attachment-id=&quot;4747&quot; data-permalink=&quot;https://emnaayadi.com/2026/09/28/bug-in-prod/drive-feature/&quot; data-orig-file=&quot;https://emnaayadi.com/wp-content/uploads/2026/09/drive-feature.png&quot; data-orig-size=&quot;808,640&quot; data-comments-opened=&quot;1&quot; data-image-title=&quot;drive feature&quot; data-image-description data-image-caption data-large-file=&quot;https://emnaayadi.com/wp-content/uploads/2026/09/drive-feature.png?w=50&quot; data-cover-min-height=&quot;801&quot; alt=&quot;Google Drive upload new version feature used to fix a broken conference submission video link without changing the URL
Afficher plus de lignes&quot; data-object-fit=&quot;cover&quot; data-orig-srcset=&quot;https://emnaayadi.com/wp-content/uploads/2026/09/drive-feature.png 808w, https://emnaayadi.com/wp-content/uploads/2026/09/drive-feature.png?w=150&amp;amp;h=119 150w, https://emnaayadi.com/wp-content/uploads/2026/09/drive-feature.png?w=300&amp;amp;h=238 300w, https://emnaayadi.com/wp-content/uploads/2026/09/drive-feature.png?w=768&amp;amp;h=608 768w&quot; src=&quot;https://emnaayadi.com/wp-content/uploads/2026/09/drive-feature.png&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;



&lt;p&gt;I felt like I was fixing a bug directly in production when I saw it working.&lt;/p&gt;



&lt;p&gt;And honestly… it felt amazing. &lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f602.png&quot; alt=&quot;😂&quot; style=&quot;height: 1em&quot;&gt;&lt;/p&gt;



&lt;p&gt;What I loved most about this story isn’t the feature itself.&lt;/p&gt;



&lt;p&gt;It’s the reminder that the first answer isn’t always the best answer.&lt;/p&gt;



&lt;p&gt;As testers, we often explore systems looking for risks, assumptions, and unexpected behaviors. Sometimes that mindset helps us far beyond testing.&lt;/p&gt;



&lt;p&gt;Instead of immediately escalating the issue, I stayed curious.&lt;/p&gt;



&lt;p&gt;Instead of assuming the link was lost forever, I explored.&lt;/p&gt;



&lt;p&gt;And I discovered a simple solution that was there all along.&lt;/p&gt;



&lt;h2&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f31f.png&quot; alt=&quot;🌟&quot; style=&quot;height: 1em&quot;&gt; Takeaways&lt;/h2&gt;



&lt;ul&gt;
&lt;li&gt;Never stop validating, even after you’ve clicked “Submit”.&lt;/li&gt;



&lt;li&gt;Keep your critical thinking with you everywhere, not only during testing sessions.&lt;/li&gt;



&lt;li&gt;Curiosity is one of the most powerful tools in a tester’s toolbox.&lt;/li&gt;



&lt;li&gt;Before asking someone else to solve your problem, take a few minutes to explore the system yourself.&lt;/li&gt;



&lt;li&gt;Sometimes the best fix is already available, hidden behind a feature you’ve never noticed before.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Today, Google Drive surprised me.&lt;/p&gt;



&lt;p&gt;And today, my tester mindset saved me.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Stay curious. Keep exploring. You never know where the next lesson is hiding.&lt;/strong&gt;&lt;/p&gt;</summary><author><name>Emna Ayadi</name></author><source gr:stream-id="feed/https://emnaayadi.com/feed/"><id>tag:google.com,2005:reader/feed/https://emnaayadi.com/feed/</id><title type="html">Emna Ayadi</title><link rel="alternate" href="https://emnaayadi.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790599958000"><id gr:original-id="https://medium.com/p/8e34e744fcc6">tag:google.com,2005:reader/item/0000089f0000006b</id><title type="html">STOP Heuristic when reviewing AI suggested fixes</title><published>2026-09-28T12:52:38Z</published><updated>2026-09-28T12:52:38Z</updated><link rel="alternate" href="https://fishouthebox.medium.com/stop-heuristic-when-reviewing-ai-suggested-fixes-8e34e744fcc6?source=rss-43716d3850af------2" type="text/html"></link><summary type="html">&lt;p&gt;I wanted to give my team a heuristic to use when reviewing AI suggested fixes and I came up with STOP.&lt;/p&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/360/0*fHnmXqZz9de1EKe0.jpg&quot;&gt;&lt;figcaption&gt;A red stop sign&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Experience lately has taught me that AI suggested fixes can be inaccurate, it can invent problems that don’t exist and has no context on wider business impact. I can go into examples in more detail another time— for now here is what STOP means.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;S — See the problem&lt;/strong&gt;&lt;br&gt;Is there actually a problem? &lt;br&gt;&lt;strong&gt;T — Trace the cause&lt;/strong&gt;&lt;br&gt;Do we know what’s causing it?&lt;br&gt;&lt;strong&gt;O — Observe the impact&lt;/strong&gt;&lt;br&gt;What else could this change affect?&lt;br&gt;&lt;strong&gt;P — Prove the fix&lt;/strong&gt;&lt;br&gt;How do we know this change will solve it?&lt;/p&gt;&lt;p&gt;I hope by sharing this it inspires you to use this or think of new heuristics in this AI world.&lt;/p&gt;</summary><author><name>Melissa Fisher</name></author><source gr:stream-id="feed/https://medium.com/feed/@fishouthebox"><id>tag:google.com,2005:reader/feed/https://medium.com/feed/@fishouthebox</id><title type="html">Stories by Melissa Fisher on Medium</title><link rel="alternate" href="https://medium.com/@fishouthebox?source=rss-43716d3850af------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790599958000"><id gr:original-id="https://medium.com/p/8e34e744fcc6">tag:google.com,2005:reader/item/00000a2b00000053</id><title type="html">STOP Heuristic when reviewing AI suggested fixes</title><published>2026-09-28T12:52:38Z</published><updated>2026-09-28T12:52:38Z</updated><link rel="alternate" href="https://fishouthebox.medium.com/stop-heuristic-when-reviewing-ai-suggested-fixes-8e34e744fcc6?source=rss-43716d3850af------2" type="text/html"></link><summary type="html">&lt;p&gt;I wanted to give my team a heuristic to use when reviewing AI suggested fixes and I came up with STOP.&lt;/p&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/360/0*fHnmXqZz9de1EKe0.jpg&quot;&gt;&lt;figcaption&gt;A red stop sign&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Experience lately has taught me that AI suggested fixes can be inaccurate, it can invent problems that don’t exist and has no context on wider business impact. I can go into examples in more detail another time— for now here is what STOP means.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;S — See the problem&lt;/strong&gt;&lt;br&gt;Is there actually a problem? &lt;br&gt;&lt;strong&gt;T — Trace the cause&lt;/strong&gt;&lt;br&gt;Do we know what’s causing it?&lt;br&gt;&lt;strong&gt;O — Observe the impact&lt;/strong&gt;&lt;br&gt;What else could this change affect?&lt;br&gt;&lt;strong&gt;P — Prove the fix&lt;/strong&gt;&lt;br&gt;How do we know this change will solve it?&lt;/p&gt;&lt;p&gt;I hope by sharing this it inspires you to use this or think of new heuristics in this AI world.&lt;/p&gt;</summary><author><name>Melissa Fisher</name></author><source gr:stream-id="feed/https://fishouthebox.medium.com/feed"><id>tag:google.com,2005:reader/feed/https://fishouthebox.medium.com/feed</id><title type="html">Stories by Melissa Fisher on Medium</title><link rel="alternate" href="https://medium.com/@fishouthebox?source=rss-43716d3850af------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790589600000"><id gr:original-id="https://scrolltest.com/?p=7855">tag:google.com,2005:reader/item/0000044400000344</id><category term="Playwright tutorial Java"></category><category term="Testing"></category><title type="html">Playwright Hooks and Lifecycle: beforeAll, afterEach, and test.step() Mastery</title><published>2026-09-28T10:00:00Z</published><updated>2026-09-28T10:00:00Z</updated><link rel="alternate" href="https://scrolltest.com/playwright-hooks-lifecycle-beforeall-aftereach-step/" type="text/html"></link><summary type="html">&lt;p&gt;Playwright Hooks and Lifecycle: beforeAll, afterEach, and test.step() Mastery.&lt;/p&gt;
&lt;p&gt;The post &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com/playwright-hooks-lifecycle-beforeall-aftereach-step/&quot;&gt;Playwright Hooks and Lifecycle: beforeAll, afterEach, and test.step() Mastery&lt;/a&gt; appeared first on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com&quot;&gt;Software Testing &amp;amp; Automation&lt;/a&gt;.&lt;/p&gt;</summary><author><name>Pramod Dutta</name></author><source gr:stream-id="feed/https://scrolltest.com/feed/"><id>tag:google.com,2005:reader/feed/https://scrolltest.com/feed/</id><title type="html">Software Testing &amp; Automation</title><link rel="alternate" href="https://scrolltest.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790575200000"><id gr:original-id="https://testingil.com/?p=9886">tag:google.com,2005:reader/item/000002570000011b</id><category term="AI Quality"></category><title type="html">The Test Was Wrong. Rewriting.</title><published>2026-09-28T06:00:00Z</published><updated>2026-09-28T06:00:00Z</updated><link rel="alternate" href="https://testingil.com/2026/09/ai-coding-agent-says-the-test-was-wrong.html" type="text/html"></link><summary type="html">&lt;p&gt;Did you ever see this line when you let your code agent run wild?&lt;/p&gt;



&lt;p&gt;“The test was wrong. Rewriting.”&lt;/p&gt;



&lt;p&gt;Can the genie really make mistakes?&lt;br&gt;Ok, enough jokes.&lt;/p&gt;



&lt;p&gt;If you’re surprised, this may be the first time you’re generating tests. It happens a lot – seven times in a single build in one of my checks.&lt;/p&gt;



&lt;p&gt;Let’s walk through a couple of scenarios, and see how we got to this place.&lt;/p&gt;



&lt;p&gt;For our purpose, let’s assume that we generated both code and tests. But the analysis is true for when the code agent generates just the test.&lt;/p&gt;



&lt;p&gt;How did we get here? &lt;/p&gt;



&lt;p&gt;The genie generates test code. This code is just lines of text in a file – unproven until it runs. It can be proven wrong syntactically, and by some heuristics, logically. Meaning, knowing it’s wrong without running it.&lt;/p&gt;



&lt;p&gt;So if the genie runs this check only (we don’t really know unless it decides to tell us), it’s a guess. Not 50-50, but still not a real proof.&lt;/p&gt;



&lt;p&gt;Our genie is nothing but a truth chaser. Rightly so, it runs the test. If the generated test fails, we have a fork in the road.&lt;/p&gt;



&lt;ol&gt;
&lt;li&gt;The code is right, and the test is wrong.&lt;/li&gt;



&lt;li&gt;The test is wrong, and the code is right.&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;Nah, it’s not that simple. Both can be wrong. It can also be:&lt;/p&gt;



&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;The genie understands the test doesn’t even run the code. Or,&lt;/li&gt;



&lt;li&gt;The genie realizes the test isn’t designed correctly to check what it needs to.&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;Happened to me, where a test that should have recreated a race condition turned out to never run the race at all.&lt;/p&gt;



&lt;p&gt;I want you to understand that at least in one of the stations, the genie made a mistake.&lt;/p&gt;



&lt;ol&gt;
&lt;li&gt;Creating the code&lt;/li&gt;



&lt;li&gt;Creating the test&lt;/li&gt;



&lt;li&gt;Validating the test vs the code&lt;/li&gt;



&lt;li&gt;Running the test&lt;/li&gt;



&lt;li&gt;Evaluating the test for its purpose&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;#4 is the easiest to spot. Why?&lt;/p&gt;



&lt;p&gt;Because it’s not reasoning. It’s a deterministic run – pass or fail. So a failure means something is definitely wrong.&lt;/p&gt;



&lt;p&gt;In all other stages, whatever the genie does with the code is&lt;br&gt;a) Hidden from us&lt;br&gt;b) Non-deterministic, meaning we got the result today, but maybe if we ran this yesterday we’d get a different answer, and&lt;br&gt;c) It actually caught something. Or thinks it did.&lt;/p&gt;



&lt;p&gt;Everything except the actual run is opaque and not repeatable. Not what we’d call proof.&lt;br&gt;A crime may have happened here. Or not.&lt;/p&gt;



&lt;hr&gt;



&lt;p&gt;This is not an anti-genie rant.&lt;/p&gt;



&lt;p&gt;Let me be honest. I don’t – I can’t – read everything it logs. I run the agent, and look at the end results. Tests passing? Cool. I usually don’t look back at the logs.&lt;/p&gt;



&lt;p&gt;Heck, the race condition issue? I asked Claude to go through the build logs to find where tests were found wrong.&lt;/p&gt;



&lt;p&gt;I use coding agents all the time. And looking at me, you’d say – I trust them. Because we were taught that trust looks exactly like this.&lt;/p&gt;



&lt;p&gt;But, this is not trust. It’s a bet. Lots of them. The app does work mostly, and when I find something I ask for a fix. And if the agent finds something, it fixes it.&lt;/p&gt;



&lt;p&gt;But remember the options? The genie can make mistakes. Also in fixes. And in new code. And in replacement tests.&lt;/p&gt;



&lt;p&gt;Who says the fix is the correct one?&lt;/p&gt;



&lt;p&gt;My app, even in production, does not carry the risks of fully scaled apps. Imagine hundreds of developers, each with their own agent making those bets on your finance apps. Or law practices. Or online election management.&lt;/p&gt;



&lt;p&gt;Are you scared? I know I am.&lt;/p&gt;



&lt;p&gt;The way out is chunking the tasks to be smaller and manageable. Smaller pieces of code produce smaller logs. Ones we can read better.&lt;/p&gt;



&lt;p&gt;Reviewability – if it’s not a word, it should be – is now a delivery capability.&lt;/p&gt;



&lt;hr&gt;



&lt;p&gt;Building that capability into a team – smaller pieces, logs someone actually reads, verdicts that get checked – is hard to do on your own. That’s where I come in – &lt;strong&gt;Mentoring and training on AI quality.&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;Built on your codebase and your quality goals. Mentoring for the ongoing work with your team, training when the ground needs covering.&lt;br&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testingil.com/mentoring-training&quot; rel=&quot;noopener&quot;&gt;Tell me what you’re dealing with&lt;/a&gt;. A couple of lines is enough.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;The post &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testingil.com/2026/09/ai-coding-agent-says-the-test-was-wrong.html&quot;&gt;The Test Was Wrong. Rewriting.&lt;/a&gt; first appeared on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testingil.com&quot;&gt;TestinGil&lt;/a&gt;.</summary><author><name>Gil Zilberfeld</name></author><source gr:stream-id="feed/http://www.everydayunittesting.com/feed"><id>tag:google.com,2005:reader/feed/http://www.everydayunittesting.com/feed</id><title type="html">Everyday Unit Testing</title><link rel="alternate" href="https://testingil.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790546400000"><id gr:original-id="https://scrolltest.com/?p=7854">tag:google.com,2005:reader/item/0000044400000343</id><category term="API Testing"></category><category term="Testing"></category><title type="html">API Versioning Testing: How to Test v1, v2, and Deprecated Endpoints Simultaneously</title><published>2026-09-27T22:00:00Z</published><updated>2026-09-27T22:00:00Z</updated><link rel="alternate" href="https://scrolltest.com/api-versioning-testing-v1-v2-deprecated-endpoints/" type="text/html"></link><summary type="html">&lt;p&gt;API Versioning Testing: How to Test v1, v2, and Deprecated Endpoints Simultaneously.&lt;/p&gt;
&lt;p&gt;The post &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com/api-versioning-testing-v1-v2-deprecated-endpoints/&quot;&gt;API Versioning Testing: How to Test v1, v2, and Deprecated Endpoints Simultaneously&lt;/a&gt; appeared first on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com&quot;&gt;Software Testing &amp;amp; Automation&lt;/a&gt;.&lt;/p&gt;</summary><author><name>Pramod Dutta</name></author><source gr:stream-id="feed/https://scrolltest.com/feed/"><id>tag:google.com,2005:reader/feed/https://scrolltest.com/feed/</id><title type="html">Software Testing &amp; Automation</title><link rel="alternate" href="https://scrolltest.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790530062000"><id gr:original-id="https://qualityeng.substack.com/p/being-heard-and-understood">tag:google.com,2005:reader/item/00000b1e00000089</id><title type="html">Being heard is not the same as being understood</title><published>2026-09-27T17:27:42Z</published><updated>2026-09-27T17:27:42Z</updated><link rel="alternate" href="https://qualityeng.substack.com/p/being-heard-and-understood" type="text/html"></link><summary type="html">&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!-PkB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!-PkB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png 424w, https://substackcdn.com/image/fetch/$s_!-PkB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png 848w, https://substackcdn.com/image/fetch/$s_!-PkB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png 1272w, https://substackcdn.com/image/fetch/$s_!-PkB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;1456&quot; height=&quot;891&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/cfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:null,&amp;quot;imageSize&amp;quot;:null,&amp;quot;height&amp;quot;:891,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:null,&amp;quot;bytes&amp;quot;:371349,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:false,&amp;quot;topImage&amp;quot;:true,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://qualityeng.substack.com/i/217221720?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:null,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!-PkB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png 424w, https://substackcdn.com/image/fetch/$s_!-PkB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png 848w, https://substackcdn.com/image/fetch/$s_!-PkB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png 1272w, https://substackcdn.com/image/fetch/$s_!-PkB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!-PkB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd44a46-091b-47b2-a71f-86a2e344f0e9_2714x1660.png&quot; fetchpriority=&quot;high&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Imagine you’re working on a team and migrating an existing system to a new platform, but you’re worried the new platform doesn’t seem as performant as the old one. It doesn’t seem as scalable, and it’s built for quite a different purpose than your existing system.&lt;/p&gt;&lt;p&gt;You share your concerns with the team, they all listen, but they decides to proceed.&lt;/p&gt;&lt;p&gt;A few months later, the system begins to struggle during an unusually high-demand event. Users are complaining about service unavailable errors and features not loading. Some are pointing out that competitors seem to be performing fine.&lt;/p&gt;&lt;p&gt;Then a member of your team turns to you and says, “I knew you thought it might slow down. I didn’t realise you meant it could become unavailable.”&lt;/p&gt;&lt;p&gt;We often treat communication as successful once we’ve said the thing and someone else has heard it, but being &lt;em&gt;heard&lt;/em&gt; is not the same as being &lt;em&gt;understood&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;The thing is, people interpret warnings through their own context, assumptions, and priorities. Ceri Newton-Sargunar wrote &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.linkedin.com/posts/ceri-newton-sargunar-2b81b91a_somewhere-behind-every-massive-public-failure-share-7503476417012264960-dXzA/&quot;&gt;an excellent post&lt;/a&gt; about why technical warnings get ignored, but one point in particular stood out to me: &lt;em&gt;never assume you’re understood&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Technically, the feedback loop existed. Someone raised a concern, people listened, and a decision was made. And we assume the feedback loop worked. But what if what they heard wasn’t actually what you meant?&lt;/p&gt;&lt;p&gt;And I think this matters to Quality Engineering. We spend a lot of time trying to surface uncertainty and risk early enough for teams to do something about it. But what if we’ve surfaced it and people have understood something different from what we meant? &lt;/p&gt;&lt;p&gt;I think there is a responsibility on both sides here. If I’m raising a concern, I need to think about whether I’ve explained it in a way you can understand. And if you’re receiving it, you need to check that what you heard was actually what I meant.&lt;/p&gt;&lt;p&gt;So what do we do? Well, there’s an old saying I came across years ago: &lt;em&gt;“When talking to farmers, use farmers’ words” &lt;/em&gt;which is to use language the other person understands.&lt;/p&gt;&lt;p&gt;Saying “this won’t scale” might be perfectly clear between engineers, but what does it actually mean for the people making the decision? Could users experience slower responses? Could parts of the service become unavailable? What happens at peak demand and beyond it?&lt;/p&gt;&lt;p&gt;For those receiving the concern, there’s a responsibility to explain back what they think they’ve heard before responding: “I think what you’re saying is…”. Then give the other person the chance to correct or clarify it.&lt;/p&gt;&lt;p&gt;This is called &lt;em&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://charlesduhigg.com/wp-content/uploads/2025/03/How-to-Have-Hard-Conversations.pdf&quot;&gt;looping for understanding&lt;/a&gt;, &lt;/em&gt;and what I like about it is that you’re essentially creating another small feedback loop inside the conversation. You’re not just checking that someone spoke, but that what they shared is what you understood.&lt;/p&gt;&lt;p&gt;So as Quality Engineers, if we want concerns to influence decisions, we need to check not only that people are heard, but that they were understood too.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;p&gt;If this made you think about what happens before someone raises a concern, you might also like &lt;em&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/p/speaking-up-is-only-half-the-story&quot;&gt;Speaking up is only half the story&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;p&gt;Thanks for reading. If you found this useful, you can support the newsletter by becoming a paid subscriber and help me keep exploring practical ideas around Quality Engineering, feedback and how teams make better decisions.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://qualityeng.substack.com/subscribe?&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Upgrade&amp;quot;,&amp;quot;language&amp;quot;:&amp;quot;en&amp;quot;}&quot; data-component-name=&quot;SubscribeWidgetToDOM&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;input tabindex=&quot;-1&quot; type=&quot;email&quot; name=&quot;article-Bz16xpNQZ4MOtzPK6H9XV1REUns-email&quot; placeholder=&quot;Type your email…&quot;&gt;&lt;/div&gt;&lt;/div&gt;</summary><author><name>Jit Gosai</name></author><source gr:stream-id="feed/https://qualityeng.substack.com/feed"><id>tag:google.com,2005:reader/feed/https://qualityeng.substack.com/feed</id><title type="html">Quality Engineering Newsletter</title><link rel="alternate" href="https://qualityeng.substack.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790523106238"><id gr:original-id="https://www.thegreenreport.blog/?t=1790523717">tag:google.com,2005:reader/item/00000861000000aa</id><category term="desktop"></category><category term="c#"></category><category term=".net framework"></category><category term="flaui"></category><category term="playwright"></category><category term="strategy"></category><title type="html">Testing the Handoff: When Your Desktop App Opens a Browser</title><published>2026-09-27T15:31:46Z</published><updated>2026-09-27T15:31:46Z</updated><link rel="alternate" href="https://www.thegreenreport.blog/articles/testing-the-handoff-when-your-desktop-app-opens-a-browser/testing-the-handoff-when-your-desktop-app-opens-a-browser.html" type="text/html"></link><summary type="html">Plenty of desktop applications rely on a browser for at least one part of the user journey. A &amp;quot;View Report&amp;quot; button opens a dashboard, a &amp;quot;Pay Now&amp;quot; button opens a payment...</summary><author><name></name></author><source gr:stream-id="feed/https://www.thegreenreport.blog/resources/xml/feeds.xml"><id>tag:google.com,2005:reader/feed/https://www.thegreenreport.blog/resources/xml/feeds.xml</id><title type="html">The Green Report | Quality Assurance Blog</title><link rel="alternate" href="https://www.thegreenreport.blog/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790522904000"><id gr:original-id="https://chroniclesofapragmaticprogrammer.substack.com/p/engenharia-de-cobranca-modelando">tag:google.com,2005:reader/item/00000a270000006c</id><title type="html">Engenharia de Cobrança: Modelando Dados no DynamoDB</title><published>2026-09-27T15:28:24Z</published><updated>2026-09-27T15:28:24Z</updated><link rel="alternate" href="https://chroniclesofapragmaticprogrammer.substack.com/p/engenharia-de-cobranca-modelando" type="text/html"></link><link rel="enclosure" href="https://api.substack.com/feed/podcast/217683389/4d713713b1eded0bf8139340734a3b09.mp3" type="audio/mpeg"></link><media:content url="https://api.substack.com/feed/podcast/217683389/4d713713b1eded0bf8139340734a3b09.mp3" type="audio/mpeg"></media:content><summary type="html">&lt;p&gt;Este episódio é a versão em áudio de &lt;strong&gt;Engenharia de Cobrança: Modelando Dados no DynamoDB&lt;/strong&gt;, &lt;em&gt;Gerado por IA.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;Obrigado por ler Crônicas de um Programador Pragmático! Inscreva-se gratuitamente para receber novas publicações e apoiar meu trabalho.&lt;/p&gt;&lt;p&gt;Segunda-feira, 8h40. O canal de atendimento ao cliente está pegando fogo… o clássico pesadelo matinal de qualquer operação! Será que tudo está perdido? Não… por enquanto não… 👀&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/subscribe?&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Inscreva-se&amp;quot;,&amp;quot;language&amp;quot;:&amp;quot;pt-br&amp;quot;}&quot; data-component-name=&quot;SubscribeWidgetToDOM&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;input tabindex=&quot;-1&quot; type=&quot;email&quot; name=&quot;article-8beHto9RfoRpslj1sbXu78LT1FU-email&quot; placeholder=&quot;Digite seu e-mail…&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;A conversa percorre as escolhas que separam um modelo que responde o negócio de um que só grava entidade: por que a lista de perguntas vem antes da tabela, por que dias de atraso nunca deveriam estar gravados, por que CPF não pode ir na chave de partição, e por que um índice de agenda sobrecarregado é capaz de impedir o registro de um pagamento às 00h07.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;No episódio:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;A reunião que ninguém quer marcar — e por que o ORM não pode representar o negócio nela&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Grave o fato, derive o estado: a diferença entre a data de nascimento e a idade&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Num BaaS, a chave de partição é uma decisão de isolamento&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Índice esparso: sair da régua é arrancar o post-it da geladeira&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;O engarrafamento do estacionamento que tranca a rua da ambulância&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Onde o dinheiro realmente vaza — e por que não é armazenamento&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;O artigo completo traz os exemplos o catálogo dos doze padrões de acesso, a anatomia da chave passo a passo e a conta de custo dos dois modelos lado a lado: &lt;strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca?r=2615ny&quot;&gt;Engenharia de Cobrança: Modelando Dados no DynamoDB&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Se você opera cobrança, crédito ou pagamentos em DynamoDB, me conta nos comentários: qual foi a pergunta que a sua tabela não soube responder?&lt;/p&gt;&lt;p data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/p/engenharia-de-cobranca-modelando/comments&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Deixe um comentário&amp;quot;,&amp;quot;action&amp;quot;:null,&amp;quot;class&amp;quot;:null}&quot; data-component-name=&quot;ButtonCreateButton&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/engenharia-de-cobranca-modelando/comments&quot;&gt;&lt;span&gt;Deixe um comentário&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;clear: both&quot;&gt;&lt;/p&gt;&lt;p data-bqr-info=&quot;attachment&quot;&gt;&lt;audio style=&quot;display: block; width: 100%&quot; controls preload=&quot;none&quot; playsinline=&quot;true&quot; webkit-playsinline=&quot;true&quot; tabindex=&quot;-1&quot;&gt;&lt;source src=&quot;https://api.substack.com/feed/podcast/217683389/4d713713b1eded0bf8139340734a3b09.mp3&quot; type=&quot;audio/mpeg&quot;&gt;&lt;/audio&gt;&lt;span class=&quot;downloadLink&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://api.substack.com/feed/podcast/217683389/4d713713b1eded0bf8139340734a3b09.mp3&quot; title=&quot;https://api.substack.com/feed/podcast/217683389/4d713713b1eded0bf8139340734a3b09.mp3&quot;&gt;4d713713b1eded0bf8139340734a3b09.mp3&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</summary><author><name>Rafael Miguel</name></author><source gr:stream-id="feed/https://chroniclesofapragmaticprogrammer.substack.com/feed"><id>tag:google.com,2005:reader/feed/https://chroniclesofapragmaticprogrammer.substack.com/feed</id><title type="html">Chronicles of a Pragmatic Programmer</title><link rel="alternate" href="https://chroniclesofapragmaticprogrammer.substack.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790522354000"><id gr:original-id="https://medium.com/p/188b4900d144">tag:google.com,2005:reader/item/0000089f0000006a</id><title type="html">Quality Engineering is a philosophy, Not a Phase</title><published>2026-09-27T15:19:14Z</published><updated>2026-09-27T15:19:14Z</updated><link rel="alternate" href="https://fishouthebox.medium.com/quality-engineering-is-a-philosophy-not-a-phase-188b4900d144?source=rss-43716d3850af------2" type="text/html"></link><summary type="html">&lt;p&gt;I have a goal at the moment to make more of my work visible. As part of that I want to describe how I work, and how that has evolved over time.&lt;/p&gt;&lt;p&gt;Instead of influencing the outcome in a phase, I am influencing the outcome throughout all the stages of delivery — from planning through to control and continuously improving. Or, if you prefer:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Plan → Design → Build → Deploy → Monitor&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Whichever way you want to look at it.&lt;/p&gt;&lt;p&gt;The way that I would describe how I work is &lt;strong&gt;a philosophy and a way of working&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;I’ll call it Quality Engineering.&lt;/p&gt;&lt;p&gt;It’s how I influence the outcome throughout delivery by having conversations with the team. The goal is to substantially reduce the likelihood and impact of problems in the first place.&lt;/p&gt;&lt;p&gt;Some examples of what it might look like:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Improve a requirement to be testable.&lt;/li&gt;&lt;li&gt;Challenge an assumption or identify a risk early&lt;/li&gt;&lt;li&gt;Change the design&lt;/li&gt;&lt;li&gt;Simplify the solution&lt;/li&gt;&lt;li&gt;Automate a repetitive check&lt;/li&gt;&lt;li&gt;Improve deployment practices&lt;/li&gt;&lt;li&gt;Change how we monitor the system&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It is a way of approaching software development that encourages teams to think about quality early, understand risk, consider the bigger picture, and focus on outcomes rather than simply outputs.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;It is fundamentally a strategic approach.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;One that can help an organisation achieve its desired outcomes, but also one that I’ve found has had a significant impact on how I have developed my own career.&lt;/p&gt;&lt;p&gt;Some important principles when I think of this are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Quality is engineered, not inspected &lt;/strong&gt;— Testing is SO important, however, testing does not equal quality. Quality is influenced by decisions we make throughout delivery.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Be proactive, not reactive &lt;/strong&gt;— To prevent or limit the impact of problems we need to work throughout the delivery life cycle. We have more opportunities when we take this approach.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Optimise for risk, not coverag&lt;/strong&gt;e — Coverage doesn’t tell you much if it doesn’t coverage the things that matter.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Think in systems, not features&lt;/strong&gt; — Bigger picture always! Try and zoom out always — think about the architecture, stakeholders and users, documentation, processes and so much more.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Think about impact, not only defects&lt;/strong&gt; — Does the defect even matter?&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Quality is a continuous feedback loop&lt;/strong&gt; —We’re not always going to get it right. We don’t know everything upfront. Users behave in ways we don’t expect. Our assumptions may be wrong or systems fail in which we didn’t predict. That’s all OK. What matters is what we do with that information.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The more I work this way, the more I see Quality Engineering as less about &lt;strong&gt;testing what has been built&lt;/strong&gt; and more about &lt;strong&gt;influencing what gets built in the first place&lt;/strong&gt;.&lt;/p&gt;</summary><author><name>Melissa Fisher</name></author><source gr:stream-id="feed/https://medium.com/feed/@fishouthebox"><id>tag:google.com,2005:reader/feed/https://medium.com/feed/@fishouthebox</id><title type="html">Stories by Melissa Fisher on Medium</title><link rel="alternate" href="https://medium.com/@fishouthebox?source=rss-43716d3850af------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790522354000"><id gr:original-id="https://medium.com/p/188b4900d144">tag:google.com,2005:reader/item/00000a2b00000052</id><title type="html">Quality Engineering is a philosophy, Not a Phase</title><published>2026-09-27T15:19:14Z</published><updated>2026-09-27T15:19:14Z</updated><link rel="alternate" href="https://fishouthebox.medium.com/quality-engineering-is-a-philosophy-not-a-phase-188b4900d144?source=rss-43716d3850af------2" type="text/html"></link><summary type="html">&lt;p&gt;I have a goal at the moment to make more of my work visible. As part of that I want to describe how I work, and how that has evolved over time.&lt;/p&gt;&lt;p&gt;Instead of influencing the outcome in a phase, I am influencing the outcome throughout all the stages of delivery — from planning through to control and continuously improving. Or, if you prefer:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Plan → Design → Build → Deploy → Monitor&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Whichever way you want to look at it.&lt;/p&gt;&lt;p&gt;The way that I would describe how I work is &lt;strong&gt;a philosophy and a way of working&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;I’ll call it Quality Engineering.&lt;/p&gt;&lt;p&gt;It’s how I influence the outcome throughout delivery by having conversations with the team. The goal is to substantially reduce the likelihood and impact of problems in the first place.&lt;/p&gt;&lt;p&gt;Some examples of what it might look like:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Improve a requirement to be testable.&lt;/li&gt;&lt;li&gt;Challenge an assumption or identify a risk early&lt;/li&gt;&lt;li&gt;Change the design&lt;/li&gt;&lt;li&gt;Simplify the solution&lt;/li&gt;&lt;li&gt;Automate a repetitive check&lt;/li&gt;&lt;li&gt;Improve deployment practices&lt;/li&gt;&lt;li&gt;Change how we monitor the system&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It is a way of approaching software development that encourages teams to think about quality early, understand risk, consider the bigger picture, and focus on outcomes rather than simply outputs.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;It is fundamentally a strategic approach.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;One that can help an organisation achieve its desired outcomes, but also one that I’ve found has had a significant impact on how I have developed my own career.&lt;/p&gt;&lt;p&gt;Some important principles when I think of this are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Quality is engineered, not inspected &lt;/strong&gt;— Testing is SO important, however, testing does not equal quality. Quality is influenced by decisions we make throughout delivery.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Be proactive, not reactive &lt;/strong&gt;— To prevent or limit the impact of problems we need to work throughout the delivery life cycle. We have more opportunities when we take this approach.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Optimise for risk, not coverag&lt;/strong&gt;e — Coverage doesn’t tell you much if it doesn’t coverage the things that matter.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Think in systems, not features&lt;/strong&gt; — Bigger picture always! Try and zoom out always — think about the architecture, stakeholders and users, documentation, processes and so much more.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Think about impact, not only defects&lt;/strong&gt; — Does the defect even matter?&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Quality is a continuous feedback loop&lt;/strong&gt; —We’re not always going to get it right. We don’t know everything upfront. Users behave in ways we don’t expect. Our assumptions may be wrong or systems fail in which we didn’t predict. That’s all OK. What matters is what we do with that information.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The more I work this way, the more I see Quality Engineering as less about &lt;strong&gt;testing what has been built&lt;/strong&gt; and more about &lt;strong&gt;influencing what gets built in the first place&lt;/strong&gt;.&lt;/p&gt;</summary><author><name>Melissa Fisher</name></author><source gr:stream-id="feed/https://fishouthebox.medium.com/feed"><id>tag:google.com,2005:reader/feed/https://fishouthebox.medium.com/feed</id><title type="html">Stories by Melissa Fisher on Medium</title><link rel="alternate" href="https://medium.com/@fishouthebox?source=rss-43716d3850af------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790503200000"><id gr:original-id="https://scrolltest.com/?p=7853">tag:google.com,2005:reader/item/0000044400000342</id><category term="Playwright tutorial Java"></category><category term="Testing"></category><title type="html">Playwright Screenshot Comparison: Visual Testing Beyond toHaveScreenshot</title><published>2026-09-27T10:00:00Z</published><updated>2026-09-27T10:00:00Z</updated><link rel="alternate" href="https://scrolltest.com/playwright-screenshot-comparison-visual-testing-advanced/" type="text/html"></link><summary type="html">&lt;p&gt;Playwright Screenshot Comparison: Visual Testing Beyond toHaveScreenshot.&lt;/p&gt;
&lt;p&gt;The post &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com/playwright-screenshot-comparison-visual-testing-advanced/&quot;&gt;Playwright Screenshot Comparison: Visual Testing Beyond toHaveScreenshot&lt;/a&gt; appeared first on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com&quot;&gt;Software Testing &amp;amp; Automation&lt;/a&gt;.&lt;/p&gt;</summary><author><name>Pramod Dutta</name></author><source gr:stream-id="feed/https://scrolltest.com/feed/"><id>tag:google.com,2005:reader/feed/https://scrolltest.com/feed/</id><title type="html">Software Testing &amp; Automation</title><link rel="alternate" href="https://scrolltest.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790502886000"><id gr:original-id="https://qualityeng.substack.com/p/uoc-talk-resources">tag:google.com,2005:reader/item/00000b1e00000088</id><title type="html">Navigating uncertainty in complex software systems - Resources</title><published>2026-09-27T09:54:46Z</published><updated>2026-09-27T09:54:46Z</updated><link rel="alternate" href="https://qualityeng.substack.com/p/uoc-talk-resources" type="text/html"></link><summary type="html">&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!P3vE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!P3vE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png 424w, https://substackcdn.com/image/fetch/$s_!P3vE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png 848w, https://substackcdn.com/image/fetch/$s_!P3vE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png 1272w, https://substackcdn.com/image/fetch/$s_!P3vE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;1456&quot; height=&quot;816&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:null,&amp;quot;imageSize&amp;quot;:null,&amp;quot;height&amp;quot;:816,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:null,&amp;quot;bytes&amp;quot;:809580,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:false,&amp;quot;topImage&amp;quot;:true,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://qualityeng.substack.com/i/217646158?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:null,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!P3vE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png 424w, https://substackcdn.com/image/fetch/$s_!P3vE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png 848w, https://substackcdn.com/image/fetch/$s_!P3vE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png 1272w, https://substackcdn.com/image/fetch/$s_!P3vE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!P3vE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F096e9046-b11a-401e-9c7d-000323710af7_2900x1626.png&quot; fetchpriority=&quot;high&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Thanks for coming to my talk.&lt;/p&gt;&lt;p&gt;I’ve pulled together the slides, articles, research and other resources that helped shape it below, along with some ways to explore the ideas further.&lt;/p&gt;&lt;h2&gt;On this page&lt;/h2&gt;&lt;p&gt;🎤 &lt;strong&gt;Talk and slides&lt;/strong&gt;&lt;br&gt;🧠 &lt;strong&gt;Explore the ideas from the talk&lt;/strong&gt;&lt;br&gt;👋 &lt;strong&gt;How I can help&lt;/strong&gt;&lt;br&gt;📬 &lt;strong&gt;Quality Engineering Newsletter&lt;/strong&gt;&lt;br&gt;📚 &lt;strong&gt;Full further reading&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h1&gt;🎤 Talk and slides&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;Navigating uncertainty in complex software systems&lt;/strong&gt; explores how change introduces uncertainty, why we can find that uncertainty uncomfortable, and how complexity makes it increasingly difficult to predict how the systems we build will behave.&lt;/p&gt;&lt;p&gt;It looks at how &lt;strong&gt;curiosity, humility and empathy&lt;/strong&gt; can help us work through uncertainty, before reframing testing as &lt;strong&gt;uncertainty reduction&lt;/strong&gt; and exploring how &lt;strong&gt;probing, sensing and responding&lt;/strong&gt; can help us work through complex problems.&lt;/p&gt;&lt;h3&gt;The original talk write-up&lt;/h3&gt;&lt;p&gt;The talk was originally called &lt;strong&gt;Uncertainty of Change&lt;/strong&gt;, which is why the full write-up still uses that title.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Uncertainty of Change – Full write-up of the talk with slides&lt;/strong&gt;&lt;br&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/p/uncertainty-of-change&quot;&gt;Read it here&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Navigating uncertainty in complex software systems – Slides&lt;/strong&gt;&lt;br&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://prezi.com/view/otgV9YAtClUQkIZ7vGEc/&quot;&gt;View them here&lt;/a&gt;&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h1&gt;🧠 Explore the ideas from the talk&lt;/h1&gt;&lt;p&gt;If there was a particular part of the talk you’d like to explore further, these are some good places to start.&lt;/p&gt;&lt;h3&gt;Curiosity, humility and empathy&lt;/h3&gt;&lt;p&gt;The three mindsets I talk about for helping us become more aware of uncertainty and work through it.&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/p/the-three-mindsets-of-a-qe&quot;&gt;The Three Mindsets of a Quality Engineer&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Complexity and Cynefin&lt;/h3&gt;&lt;p&gt;I use a small part of the Cynefin framework to explore the difference between complicated and complex problems, and why uncertainty is unavoidable in the systems we build.&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://thecynefin.co/about-us/about-cynefin-framework/&quot;&gt;Overview of the Cynefin Framework&lt;/a&gt;&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://hbr.org/2007/11/a-leaders-framework-for-decision-making&quot;&gt;A Leader’s Framework for Decision-Making – Harvard Business Review&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Testing as uncertainty reduction&lt;/h3&gt;&lt;p&gt;Rather than thinking only about how we can do more testing, what happens if we ask what is creating uncertainty and what we could do to reduce it?&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The Testing Unknown Unknowns Matrix&lt;/strong&gt; is one practical way of exploring that.&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/p/the-testing-unknown-unknowns&quot;&gt;The Testing Unknown Unknowns Matrix&lt;/a&gt;&lt;/p&gt;&lt;p&gt;You may also find these useful:&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/p/what-is-quality-engineering&quot;&gt;What is Quality Engineering?&lt;/a&gt;&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://open.substack.com/pub/qualityeng/p/qe-how-quality-is-created-maintained-lost&quot;&gt;How Quality is Created, Maintained, and Lost&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Probing, sensing and responding&lt;/h3&gt;&lt;p&gt;Towards the end of the talk I use &lt;strong&gt;probe, sense and respond&lt;/strong&gt; as a way of putting some structure around working through uncertainty in complex situations.&lt;/p&gt;&lt;p&gt;If you’d like to explore that idea in more depth:&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://thecynefin.co/about-us/about-cynefin-framework/&quot;&gt;Overview of the Cynefin Framework&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Why uncertainty feels uncomfortable&lt;/h3&gt;&lt;p&gt;These resources explore some of the psychology and biology behind our responses to uncertainty.&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.health.harvard.edu/staying-healthy/understanding-the-stress-response&quot;&gt;Understanding the stress response&lt;/a&gt;&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4635443/&quot;&gt;Neuroscience of Uncertainty&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;The stories from the talk&lt;/h3&gt;&lt;p&gt;If you want to dig further into the examples I use around change and adaptation, you’ll find the sources for the Luddites, Sears, Blockbuster, Nokia and the development of the iPhone in the &lt;strong&gt;full further reading&lt;/strong&gt; section below.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h1&gt;👋 How I can help&lt;/h1&gt;&lt;p&gt;A lot of the work I do is about helping teams and organisations explore the kinds of problems I talk about in this session.&lt;/p&gt;&lt;p&gt;That might mean understanding where uncertainty and risk are appearing in the way teams work, looking at whether feedback is arriving early enough to influence decisions, or helping people think more broadly about Quality Engineering than testing at the end of the development process.&lt;/p&gt;&lt;p&gt;I’m currently available for:&lt;/p&gt;&lt;h3&gt;🎤 Talks and training&lt;/h3&gt;&lt;p&gt;Conference talks, internal talks and training around Quality Engineering, uncertainty, complexity, feedback, change safety, psychological safety and building a culture of quality.&lt;/p&gt;&lt;h3&gt;🧑‍🤝‍🧑 Workshops&lt;/h3&gt;&lt;p&gt;Interactive sessions that help teams explore their own systems and ways of working, including areas such as Designing for Feedback, Software Feedback Rings, Change Safety, Quality Engineering and Psychological Safety.&lt;/p&gt;&lt;h3&gt;🧭 Quality Engineering advisory&lt;/h3&gt;&lt;p&gt;Working with engineering leaders and teams to understand how quality is currently being created, where risk or uncertainty may be appearing too late, and what changes might help teams learn and make decisions earlier.&lt;/p&gt;&lt;p&gt;👉 &lt;strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/p/work-with-me&quot;&gt;Find out more about working with me&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;And if you’re at the conference and something here sounds relevant to a problem you’re working through, come and have a chat with me.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h1&gt;📬 Quality Engineering Newsletter&lt;/h1&gt;&lt;p&gt;If you’re interested in these ideas, I write about them regularly in the &lt;strong&gt;Quality Engineering Newsletter (QEN)&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;The newsletter explores Quality Engineering much more broadly than testing, including uncertainty, feedback, complexity, systems thinking and how teams can make better decisions about quality.&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/&quot;&gt;Subscribe to the Quality Engineering Newsletter&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;🎁 Conference offer&lt;/h3&gt;&lt;p&gt;If you’d like to go a little deeper, I’ve also put together a conference discount for a paid QEN subscription. (Code: UOC30)&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/UOC30&quot;&gt;30% off for 1 year&lt;/a&gt; &lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h1&gt;📚 Full further reading&lt;/h1&gt;&lt;p&gt;These are some of the articles, research and case studies that informed different parts of the talk.&lt;/p&gt;&lt;h2&gt;The psychology of uncertainty and change&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.health.harvard.edu/staying-healthy/understanding-the-stress-response&quot;&gt;Understanding the stress response&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.hopkinsmedicine.org/health/conditions-and-diseases/adrenal-glands&quot;&gt;The role of adrenal glands&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.frontiersin.org/research-topics/295/decision-making-under-uncertainty#articles&quot;&gt;Different varieties of uncertainty in human decision-making&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.psychologytoday.com/gb/blog/off-the-couch/202103/curiosity-can-help-you-cope-with-uncertainty&quot;&gt;Curiosity Can Help You Cope With Uncertainty&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4635443/&quot;&gt;Neuroscience of Uncertainty&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Complexity and decision-making&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://thecynefin.co/about-us/about-cynefin-framework/&quot;&gt;Overview of the Cynefin Framework&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://hbr.org/2007/11/a-leaders-framework-for-decision-making&quot;&gt;A Leader’s Framework for Decision-Making – Harvard Business Review&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.mindtools.com/atddimk/the-cynefin-framework&quot;&gt;MindTools guide to Cynefin&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://xkcd.com/2347/&quot;&gt;XKCD: Dependency&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://getlighthouse.com/blog/developing-leaders-team-grows-big/&quot;&gt;Lines of Communication in Large Teams&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Change, failure and adaptation&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;The Luddites&lt;/strong&gt;&lt;br&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.historic-uk.com/HistoryUK/HistoryofBritain/The-Luddites/&quot;&gt;The Luddites and resistance to technological change&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Sears&lt;/strong&gt;&lt;br&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.linkedin.com/pulse/fight-flight-freeze-human-responses-business-strategy-nixon&quot;&gt;The fight, flight or freeze response in business strategy&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Blockbuster and Netflix&lt;/strong&gt;&lt;br&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.todayifoundout.com/index.php/2022/12/the-truth-about-blockbuster-turning-down-purchasing-netflix-and-what-seemingly-everyone-gets-wrong/&quot;&gt;The story behind Blockbuster’s decision not to buy Netflix&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Nokia and Microsoft&lt;/strong&gt;&lt;br&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://slidebean.com/story/microsoft-acquired-selling-nokia&quot;&gt;Why Nokia sold to Microsoft&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The technologies behind the iPhone&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.bbc.co.uk/sounds/play/p04hyzm5&quot;&gt;BBC Sounds: iPhones – 50 Things That Made the Modern Economy&lt;/a&gt;&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.bbc.co.uk/news/business-38320198&quot;&gt;BBC News: The hidden technologies behind the iPhone&lt;/a&gt;&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h1&gt;💬 Continue the conversation&lt;/h1&gt;&lt;p&gt;If the talk has made you think about something differently, or there’s something you’d like to explore further, I’d be interested to hear from you.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt; is probably the easiest place to connect with me and continue the conversation.&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.linkedin.com/in/jiteshgosai/&quot;&gt;Connect with me on LinkedIn&lt;/a&gt;&lt;/p&gt;&lt;p&gt;I also share shorter thoughts, ideas and things I’m exploring between QEN articles through &lt;strong&gt;Substack Notes&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;👉 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityeng.substack.com/notes&quot;&gt;Follow QEN on Substack Notes&lt;/a&gt;&lt;/p&gt;&lt;p&gt;And if you want to get in touch about talks, workshops or other work, you can email me at &lt;a href=&quot;mailto:jiteshgosai+qen@gmail.com&quot;&gt;jiteshgosai+qen@gmail.com&lt;/a&gt;&lt;/p&gt;</summary><author><name>Jit Gosai</name></author><source gr:stream-id="feed/https://qualityeng.substack.com/feed"><id>tag:google.com,2005:reader/feed/https://qualityeng.substack.com/feed</id><title type="html">Quality Engineering Newsletter</title><link rel="alternate" href="https://qualityeng.substack.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790460000000"><id gr:original-id="https://scrolltest.com/?p=7852">tag:google.com,2005:reader/item/0000044400000341</id><category term="Playwright tutorial Java"></category><category term="Testing"></category><title type="html">Playwright Codegen Advanced: Device Emulation, Auth State, and VS Code Integration</title><published>2026-09-26T22:00:00Z</published><updated>2026-09-26T22:00:00Z</updated><link rel="alternate" href="https://scrolltest.com/playwright-codegen-advanced-device-auth-vscode/" type="text/html"></link><summary type="html">&lt;p&gt;Playwright Codegen Advanced: Device Emulation, Auth State, and VS Code Integration.&lt;/p&gt;
&lt;p&gt;The post &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com/playwright-codegen-advanced-device-auth-vscode/&quot;&gt;Playwright Codegen Advanced: Device Emulation, Auth State, and VS Code Integration&lt;/a&gt; appeared first on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com&quot;&gt;Software Testing &amp;amp; Automation&lt;/a&gt;.&lt;/p&gt;</summary><author><name>Pramod Dutta</name></author><source gr:stream-id="feed/https://scrolltest.com/feed/"><id>tag:google.com,2005:reader/feed/https://scrolltest.com/feed/</id><title type="html">Software Testing &amp; Automation</title><link rel="alternate" href="https://scrolltest.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790416800000"><id gr:original-id="https://scrolltest.com/?p=7851">tag:google.com,2005:reader/item/0000044400000340</id><category term="Career Guide"></category><category term="Testing"></category><title type="html">QA Release Management: The Checklist From Code Freeze to Production Deploy</title><published>2026-09-26T10:00:00Z</published><updated>2026-09-26T10:00:00Z</updated><link rel="alternate" href="https://scrolltest.com/qa-release-management-checklist-code-freeze-production/" type="text/html"></link><summary type="html">&lt;p&gt;QA Release Management: The Checklist From Code Freeze to Production Deploy.&lt;/p&gt;
&lt;p&gt;The post &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com/qa-release-management-checklist-code-freeze-production/&quot;&gt;QA Release Management: The Checklist From Code Freeze to Production Deploy&lt;/a&gt; appeared first on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com&quot;&gt;Software Testing &amp;amp; Automation&lt;/a&gt;.&lt;/p&gt;</summary><author><name>Pramod Dutta</name></author><source gr:stream-id="feed/https://scrolltest.com/feed/"><id>tag:google.com,2005:reader/feed/https://scrolltest.com/feed/</id><title type="html">Software Testing &amp; Automation</title><link rel="alternate" href="https://scrolltest.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790383518000"><id gr:original-id="6ab70149702ae3e126e7021c">tag:google.com,2005:reader/item/0000038300000420</id><title type="html">do we still enjoy software engineering in the age of AI?</title><published>2026-09-26T00:45:18Z</published><updated>2026-09-26T00:45:18Z</updated><link rel="alternate" href="https://shubs.io/do-we-still-enjoy-software-engineering-in-the-age-of-ai/" type="text/html"></link><summary type="html">&lt;p&gt;A few weeks ago, one of the software engineers I manage sent me a message about how they have been struggling to find satisfaction in their work ever since AI has eroded much of the creativity and problem solving required in programming. &lt;/p&gt;&lt;p&gt;Their message had considerable vulnerability, and I was grateful that they gave me the opportunity to respond as both a manager and a friend who has been experiencing the same revolution as them.&lt;/p&gt;&lt;p&gt;I spent some time contemplating whether or not I should even publish this blog post, as some friends of mine rightly pointed out that there is currently greater chaos in the world than just how satisfied one is with their career in the age of AI. I decided to publish regardless as I know there are a lot of engineers that are still really feeling this and the chaos in the world doesn&amp;apos;t change that.&lt;/p&gt;&lt;p&gt;Their message, and my response, can be found below:&lt;/p&gt;&lt;blockquote&gt;Hey shubs. I know you&amp;apos;re off sick today so no need to respond to this today.&lt;br&gt;&lt;br&gt;I&amp;apos;ve been finding it hard to get used to the new development workflow using AI. I think the issue is less from a technical perspective and more from a job satisfaction(?) perspective I suppose.&lt;br&gt;&lt;br&gt;Beforehand;&lt;br&gt;- When I pushed out code I fully understood the code. I&amp;apos;d always have read it and imagined what comments a reviewer might post to save their time. I felt like I could take a level of responsibility for what I ship and its quality.&lt;br&gt;- I enjoyed the process of building things and felt like what I built was my own creation/ the work of the team.&lt;br&gt;- I feel like I had a general understanding of the codebase, what other people were working on, and the direction the team was going. Over time I felt like my understanding of the codebase got better.&lt;br&gt;&lt;br&gt;It also frustrates me working with AI. On the surface level I find their AI phrases to be a bit grating (but I&amp;apos;m sure some prompting/ some plugin could mostly fix that), but other things are frustrating in a different way. Whenever I&amp;apos;ve setup a memory to fix something it usually ignores the memory later on and does it anyway. Obviously people make mistakes too, but it&amp;apos;s frustrating to work with a system that can&amp;apos;t learn and grow. And it never feels worth investing deeply in tweaking the tooling or a model because it all tends to change every 6 months.&lt;br&gt;&lt;br&gt;Not really sure how to work though this stuff? Like it&amp;apos;s not the end of the world, but I find myself getting off topic or procrastinating the things that need to get done more.&lt;br&gt;&lt;br&gt;Obviously I recognise that a developer using these tools can normally produce more/better output in less time overall, and they do a lot of stuff that&amp;apos;s really cool and useful, but it&amp;apos;s also kind of turning the job into role which I don&amp;apos;t enjoy as much.&lt;br&gt;&lt;br&gt;I&amp;apos;ve used AI a bit for my [redacted] side project to refactor the whole thing significantly, and that felt fine/good. So I don&amp;apos;t think I have an issue with not writing code by hand. I think what bothers me is more the losing touch with the project and feeling like I don&amp;apos;t get what&amp;apos;s going on overall? I suppose taking a break and missing the in person meeting doesn&amp;apos;t help with all that.&lt;br&gt;&lt;br&gt;A part of me feels like as the time for development goes down the proportion of the time/effort spent on team co-ordination and planning work has to go up, and so it&amp;apos;s natural for people to feel like they&amp;apos;re less in touch with what&amp;apos;s going on across the team?&lt;br&gt;&lt;br&gt;Anyway I thought I should let you know how I was feeling about it. I&amp;apos;m not expecting you to swoop in and change anything because &amp;apos;making me feel good about developing with AI isn&amp;apos;t a managers job, and I&amp;apos;m not even sure what could be done anyway. I also think some of this is just because I got back from a really awesome trip to Europe, and it&amp;apos;s hard to adjust back to normal work.&lt;/blockquote&gt;&lt;p&gt;I spent a few days thinking about their message, and responded with the following:&lt;/p&gt;&lt;blockquote&gt;Hey, I&amp;apos;m still off sick but wanted to respond to you because I know you&amp;apos;re reaching out with some vulnerability here that I relate to quite a bit.&lt;br&gt;&lt;br&gt;AI has taken a lot of satisfaction away from our roles as engineers, and hackers as well. In many ways, it&amp;apos;s forced us to go from critical thinking skills more towards management skills. Basically, it&amp;apos;s forced all of us to become middle managers – which honestly is not what a lot of people want to do or enjoy.&lt;br&gt;&lt;br&gt;In terms of what the world was like pre-AI, I cannot argue with you there. I also had a much deeper understanding of the research or engineering I was doing, and post-AI, I feel like a lot of this has been taken away from me too. It&amp;apos;s been pretty hard adjusting to the fact that AI is better than me at research or engineering for the most part, when both of us spent the last fifteen years perfecting our skills in those areas.&lt;br&gt;&lt;br&gt;It&amp;apos;s been hard to stay on top of all the changes in AI, I am also often left feeling overwhelmed with the pace of change. It&amp;apos;s not perfect in its current state, but it is rapidly improving as time goes on. I think this frustration about it not learning and growing is valid, but you might have more luck shoving things into CLAUDE.md / AGENTS.md as that&amp;apos;s forced into each subsequent prompt. Memories have been pretty weak for me too.&lt;br&gt;&lt;br&gt;I think the reason you&amp;apos;re not enjoying the job as much is because we are getting the outcomes quickly, but you&amp;apos;re not actually doing that much critical thinking in terms of learning / executing how you used to as an engineer.&lt;br&gt;&lt;br&gt;The only way I have been able to convince myself that this is still a worthwhile endeavour is focusing my learning and skill development on how to be the best with AI, and better than other people with AI – instead of the code itself. We&amp;apos;re all forced to be middle managers now whether we agree with it or not, if we want outcomes as fast as possible. &lt;br&gt;&lt;br&gt;I&amp;apos;m not saying it&amp;apos;s grim, there are pros and cons here, but with all technology there are going to be massive shifts in how we work, and I believe we are going through a revolution right now that we&amp;apos;ve never seen before. It&amp;apos;s not easy to keep up or even adjust to.&lt;br&gt;&lt;br&gt;Do you have any ideas on how we could make this role more enjoyable for you in the AI age? Is there something that we could be doing to support you more to make this role still worthwhile for you, while we achieve the outcomes we want for the business? Is there anything I can do to help with it as well?&lt;br&gt;&lt;br&gt;Part of what you&amp;apos;re feeling is probably on us. We are at this crossroads with Assetnote where we are going all in for [redacted] and [redacted], and a lot of focus and momentum in our planning has been lost to this. I don&amp;apos;t think it&amp;apos;s a bad thing entirely as such a huge strategy shift has required a bit more fluidness in how we operate, but it&amp;apos;s not as organised or well executed as I would like it to be.&lt;br&gt;&lt;br&gt;We can try and make the team present their work as the development pace goes faster to try and revive that pride in their work, and showing people how they did their best towards something (AI or not), and I do really want to build this sort of culture.&lt;br&gt;&lt;br&gt;I am grateful you feel comfortable enough to come to me about this. These last few months, I have felt depressed about how good AI has become at source code auditing, a skill I spent over 15 years on. I have started baking recently, to find joy in things that I don&amp;apos;t think AI will beat me at for a while. I have been rapidly improving and practicing that instead, while I still face reality where I use AI to achieve things I could never before.&lt;br&gt;&lt;br&gt;The big takeaway I had from this AI age is that we should be using it to solve problems that are _even more difficult_ than ever before. Increasing the ambition that we have for what we are building or in my case researching, has been the most rewarding thing to do. AI is solving previously unsolved maths conjectures, and finding zero-days in software that are hard for humans to even comprehend. I did write about this in detail here: &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://shubs.io/frontier-class-vulnerabilities-it-gets-worse-before-it-maybe-gets-better/&quot; rel=&quot;noreferrer&quot;&gt;https://shubs.io/frontier-class-vulnerabilities-it-gets-worse-before-it-maybe-gets-better/&lt;/a&gt;&lt;br&gt;&lt;br&gt;You might get some inspiration from it, even if you apply it in the context of software engineering.&lt;br&gt;&lt;br&gt;Coming back from such a good holiday is always hard. For me, it always starts with optimism, and then reality hits about how terrible everything really is, and a slow depression tends to kick in for the remainder of my work time. What you&amp;apos;re describing is something that is felt by everyone after a break and it is very relatable. I have been incredibly easy and flexible with our engineers, but it is with a lot of intention, empathy and understanding, it can be mistaken for laziness as a manager, but it&amp;apos;s not really that.&lt;br&gt;&lt;br&gt;I deeply care about the work that everyone is doing, but everyone is an adult and is dealing with many different things (neurodivergence, adjusting to AI, holidays, emotional changes, mental health challenges) - FWIW, our team is made up of misfits, but they are the best misfits in the world, and absolutely perfect for Assetnote, because the management team themselves are misfits too and know how to operate with diverse people and skills to get the outcomes we need for the business. This extends out to the research team too.&lt;br&gt;&lt;br&gt;I am not going to push you while you&amp;apos;re still adjusting. Just take the time you need. My co-founder Michael will not push either. It&amp;apos;s just not our style. We trust you, and always have operated with this level of high trust in this job, that&amp;apos;s super rare in most other roles. We know that we will get to the outcomes we need, and the right way to do that is not by pushing our engineers but rather treating them as humans who are going through their own set of problems in life. We will be patient. Just reach out to me if you need me to get involved in anything and I will be there.&lt;br&gt;&lt;br&gt;BTW, not to be overly optimistic, but there is a real superpower being someone that has done deep engineering work pre-AI, or as I like to call it, artisanal programming. A lot of people now will completely miss this part of their lives, and will be very much so worse off as engineers. Our deep understanding will continue being a superpower for a long time, and others will struggle to get to the same level of comprehension.&lt;/blockquote&gt;&lt;p&gt;Reflecting on this conversation, I concluded that there is an elevated need for people&amp;apos;s pride in their work, human connection, and communication with our engineers, as the ways in which they are working have changed so much. These are all cultural elements that I look forward to building in our team.&lt;/p&gt;</summary><author><name>Shubham Shah</name></author><source gr:stream-id="feed/https://shubs.io/rss/"><id>tag:google.com,2005:reader/feed/https://shubs.io/rss/</id><title type="html">Shubham Shah</title><link rel="alternate" href="https://shubs.io/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790380800000"><id gr:original-id="https://www.rocketpoweredjetpants.com/2026/09/bookmarks-near-the-end-of-books/">tag:google.com,2005:reader/item/0000047a00000056</id><title type="html">Bookmarks Near the End of Books</title><published>2026-09-26T00:00:00Z</published><updated>2026-09-26T00:00:00Z</updated><link rel="alternate" href="https://www.rocketpoweredjetpants.com/2026/09/bookmarks-near-the-end-of-books/" type="text/html"></link><summary type="html">&lt;p&gt;Many years ago, my son would want me to sit with him as he fell asleep
every night. Often, it would take him half an hour or more to do so,
so I’d be sitting quietly in the dark just doing nothing. I didn’t
mind. I knew that the time would come when he wouldn’t need me there
as he fell asleep, and then I’d look back with a sense of nostalgia
for those quiet times.&lt;/p&gt;
&lt;p&gt;Sure enough, the bed time routine is far more cursory now. I’m not
needed in the same way any more. That’s to be expected: my son is
growing up and becoming more independent.&lt;/p&gt;
&lt;p&gt;One of the other great pleasures of his childhood was reading a book
with him before bed. It started when he was tiny, and couldn’t read by
himself. We’d read books with bright pictures and simple stories, and
I’d try and do a different voice for each character. As he grew older,
the books also changed.&lt;/p&gt;
&lt;p&gt;After a few years, we started reading books so long we couldn’t finish
them in one sitting at night, so instead we’d read a chapter
together. I’d place a bookmark when we finished, so we’d be able to
pick up the stories again the next evening. If I wasn’t around, my
wife would be able to read the story too, so each night the bookmark
crept ever closer to the end of the back cover.&lt;/p&gt;
&lt;p&gt;I always knew that time would come to an end too. There’d be a day
when reading a book at bedtime was something my son would do on his
own. That’s to be expected: my son is growing up and becoming more
independent.&lt;/p&gt;
&lt;p&gt;I know this, yet still, it leaves me with a sense of wistful sorrow
that there are books with my bookmark in them sitting on his shelf,
waiting for an evening routine that won’t come again.&lt;/p&gt;
&lt;p&gt;I hope that maybe one day, we’ll sit together and finish those books,
not because he needs me to read them, but because he’d like to.&lt;/p&gt;</summary><author><name></name></author><source gr:stream-id="feed/https://blog.rocketpoweredjetpants.com/feeds/posts/default"><id>tag:google.com,2005:reader/feed/https://blog.rocketpoweredjetpants.com/feeds/posts/default</id><title type="html">Rocket Powered</title><link rel="alternate" href="https://www.rocketpoweredjetpants.com/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790373600000"><id gr:original-id="https://scrolltest.com/?p=7850">tag:google.com,2005:reader/item/000004440000033f</id><category term="Playwright tutorial Java"></category><category term="Testing"></category><title type="html">Playwright Tag Annotations: Organize Tests with @smoke, @critical, @regression</title><published>2026-09-25T22:00:00Z</published><updated>2026-09-25T22:00:00Z</updated><link rel="alternate" href="https://scrolltest.com/playwright-tag-annotation-smoke-critical-regression/" type="text/html"></link><summary type="html">&lt;p&gt;Playwright Tag Annotations: Organize Tests with @smoke, @critical, @regression.&lt;/p&gt;
&lt;p&gt;The post &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com/playwright-tag-annotation-smoke-critical-regression/&quot;&gt;Playwright Tag Annotations: Organize Tests with @smoke, @critical, @regression&lt;/a&gt; appeared first on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; rel=&quot;nofollow&quot; href=&quot;https://scrolltest.com&quot;&gt;Software Testing &amp;amp; Automation&lt;/a&gt;.&lt;/p&gt;</summary><author><name>Pramod Dutta</name></author><source gr:stream-id="feed/https://scrolltest.com/feed/"><id>tag:google.com,2005:reader/feed/https://scrolltest.com/feed/</id><title type="html">Software Testing &amp; Automation</title><link rel="alternate" href="https://scrolltest.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1790347698000"><id gr:original-id="https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca">tag:google.com,2005:reader/item/00000a270000006b</id><title type="html">Engenharia de Cobrança: Modelando Dados no DynamoDB</title><published>2026-09-25T14:48:18Z</published><updated>2026-09-25T14:48:18Z</updated><link rel="alternate" href="https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca" type="text/html"></link><summary type="html">&lt;p&gt;Segunda-feira, 8h40. No canal compartilhado com um dos clientes da plataforma, um varejista que vende no crediário&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-1&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-1&quot;&gt;1&lt;/a&gt; e terceiriza a cobrança, a head de cobrança escreve três perguntas seguidas:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Quantos contratos viraram 31+&lt;/em&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-2&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-2&quot;&gt;2&lt;/a&gt;&lt;em&gt; no fim de semana? Quanto disso tem promessa de pagamento&lt;/em&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-3&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-3&quot;&gt;3&lt;/a&gt;&lt;em&gt;&lt;strong&gt;&lt;sup&gt; &lt;/sup&gt;&lt;/strong&gt;para esta semana? E por que o cliente do protocolo 88213 recebeu três SMS no domingo?&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;São perguntas absolutamente normais para quem opera uma carteira&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-4&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-4&quot;&gt;4&lt;/a&gt;. E o time descobre, ao vivo, que nenhuma delas tem resposta barata.&lt;/p&gt;&lt;p&gt;A primeira exige um Scan na tabela inteira com filtro por dias de atraso, e o campo &lt;code&gt;daysPastDue&lt;/code&gt; foi atualizado pela última vez na madrugada de sexta, porque o job de recálculo falhou no sábado. A segunda depende de um array &lt;code&gt;promises&lt;/code&gt; dentro do item do contrato, que ninguém consegue consultar sem ler todos os contratos. A terceira nem está no banco: tentativas de contato viraram linhas de log no CloudWatch, com retenção de trinta dias e sem nada que identifique o devedor.&lt;/p&gt;&lt;p&gt;Nenhuma das três falhas é bug de código. São decisões de modelagem que nunca foram tomadas. Alguém escolheu uma chave de partição num refinamento, alguém adicionou um array “só por enquanto”, alguém decidiu que contato “é log”. Cada escolha respondeu à pergunta de engenharia do momento, &lt;em&gt;como eu gravo isso?&lt;/em&gt;, e ignorou a pergunta do negócio: &lt;strong&gt;quem vai perguntar o quê sobre esse dado, com que frequência, e o que acontece se a resposta vier errada?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Em dezembro do ano passado escrevi &lt;em&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/um-breve-mergulho-dynamodb&quot;&gt;Um breve mergulho no DynamoDB&lt;/a&gt;&lt;/em&gt;, sobre a mecânica: partição e ordenação, particionamento vertical, a conta de RCU e WCU. Este texto sobe um andar. Ele não é sobre como a chave funciona; é sobre como um tech lead ou arquiteto decide qual chave existe, qual índice merece nascer, qual atributo deve ser gravado e qual deve ser calculado, e quanto cada uma dessas decisões custa em dólar, em latência e em risco jurídico.&lt;/p&gt;&lt;p&gt;O fio condutor é um motor de cobrança oferecido como BaaS&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-5&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-5&quot;&gt;5&lt;/a&gt;: uma plataforma que atende dezenas de instituições (SCDs&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-6&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-6&quot;&gt;6&lt;/a&gt;, varejistas com crediário próprio, fintechs de consignado&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-7&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-7&quot;&gt;7&lt;/a&gt;), cada uma com sua carteira, sua política de encargos e sua régua&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-8&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-8&quot;&gt;8&lt;/a&gt;. &lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;Vou chamá-la de &lt;/mark&gt;&lt;em&gt;&lt;strong&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;Lastro&lt;/mark&gt;&lt;/strong&gt;&lt;/em&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;. Ela não existe, mas cada problema descrito aqui existe.&lt;/mark&gt;&lt;/p&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Antes de começar&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Assumo que você conhece PK, SK, GSI e o modelo de cobrança por capacidade; este texto parte de onde o de dezembro parou. Os preços citados são da lista pública da AWS em us-east-1 (setembro de 2026). A região de São Paulo é mais cara. Use os números como ordem de grandeza, não como orçamento. Os exemplos de código estão em TypeScript com o AWS SDK v3. Termos de negócio marcados com um número pequeno têm explicação, com analogia, nas notas ao final.&lt;/p&gt;&lt;/div&gt;&lt;div data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/subscribe?&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Inscreva-se&amp;quot;,&amp;quot;language&amp;quot;:&amp;quot;pt-br&amp;quot;}&quot; data-component-name=&quot;SubscribeWidgetToDOM&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Obrigado por ler Crônicas de um Programador Pragmático! Inscreva-se gratuitamente para receber novas publicações e apoiar meu trabalho.&lt;/p&gt;&lt;/div&gt;&lt;input tabindex=&quot;-1&quot; type=&quot;email&quot; name=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-email&quot; placeholder=&quot;Digite seu e-mail…&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2&gt;&lt;strong&gt;1. Comece pela reunião que ninguém quer marcar&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;O guia de design NoSQL da AWS é direto num ponto que engenheiros formados no relacional subestimam: você não deveria começar o schema no DynamoDB antes de saber quais perguntas ele precisa responder. No relacional, você normaliza e deixa as perguntas para depois; o otimizador se vira. No DynamoDB, as estruturas são desenhadas sob medida para os casos de uso do negócio, e consultar fora deles é caro e lento.&lt;/p&gt;&lt;p&gt;A consequência organizacional é mais importante que a técnica. Se o schema é derivado das perguntas, então &lt;strong&gt;a lista de perguntas é o artefato de arquitetura mais importante do projeto&lt;/strong&gt;. E ela não pode ser escrita só por engenheiros.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Imagine assim&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Um banco relacional é um supermercado: tudo está em algum corredor, e você encontra qualquer coisa, desde que aceite andar. O DynamoDB é a cozinha de um restaurante com a &lt;em&gt;mise en place&lt;/em&gt; pronta: cada ingrediente já está cortado e posicionado para os pratos do cardápio, e o prato sai em segundos.&lt;/p&gt;&lt;p&gt;Mas, se o cliente pedir algo que não está no cardápio, o cozinheiro precisa ir ao estoque e revirar prateleira por prateleira. Isso é o Scan. A lista de padrões de acesso é o cardápio, e cardápio não se define só na cozinha: define-se com quem conhece os clientes.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Em cobrança, as perguntas vêm de cinco atores com necessidades radicalmente diferentes: &lt;strong&gt;a mesa de cobrança&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-9&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-9&quot;&gt;9&lt;/a&gt; (o operador buscando um devedor, a supervisora olhando a carteira por faixa de atraso), &lt;strong&gt;a régua automática&lt;/strong&gt; (o scheduler que acorda e precisa saber o que fazer hoje), &lt;strong&gt;o próprio devedor&lt;/strong&gt; (no app ou no portal de autoatendimento, querendo ver o que deve e fechar um acordo), &lt;strong&gt;a conciliação&lt;/strong&gt; (o webhook de liquidação&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-10&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-10&quot;&gt;10&lt;/a&gt; de Pix e boleto que precisa achar a cobrança em milissegundos) e &lt;strong&gt;o compliance&lt;/strong&gt; (auditoria, ouvidoria, relatório regulatório).&lt;/p&gt;&lt;p&gt;A documentação pede que você entenda três propriedades de cada padrão de acesso: o tamanho do dado lido e gravado, a forma como ele precisa chegar e a velocidade, especialmente nos picos. Eu acrescento duas colunas que mudam tudo em cobrança: &lt;strong&gt;quem pergunta&lt;/strong&gt; e &lt;strong&gt;qual é o custo de negócio se a resposta atrasar ou vier errada&lt;/strong&gt;. O catálogo da Lastro ficou assim:&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;markdown&amp;quot;,&amp;quot;nodeId&amp;quot;:&amp;quot;f9bc75ec-a4c0-46ed-994a-d8accf48947c&amp;quot;}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;ID    QUESTION                                          ASKED BY            VELOCITY / SHAPE
AP01  Everything about one debtor (360 view)            operator, app       ~40 req/s, one debtor, &amp;lt; 50 ms
AP02  Open installments of a contract, oldest first     app, agreements     ~120 req/s, &amp;lt; 30 ms
AP03  Contracts with a dunning action due today         dunning scheduler   daily burst, ~2M items in &amp;lt; 20 min
AP04  Promises due today (and broken yesterday)         dunning scheduler   daily, ~80k items
AP05  Portfolio by delinquency bucket, largest first    supervisor desk     ~5 req/s, paginated, &amp;lt; 200 ms
AP06  Find the charge behind a Pix txid / boleto number settlement webhook  peaks ~800 req/s on paydays
AP07  Was this settlement already applied?              settlement webhook  every webhook, MUST BE EXACT
AP08  How many contacts did this debtor get today?      contact gateway     every attempt, MUST BE EXACT
AP09  Full audit trail of one installment               compliance, ombuds  rare, &amp;lt; 2 s
AP10  Contracts eligible for credit bureau reporting    dunning scheduler   daily
AP11  Monthly aging and roll rate of the portfolio      finance, BI         monthly  -&amp;gt; NOT IN DYNAMODB
AP12  Regulatory and accounting reports                 compliance          monthly  -&amp;gt; NOT IN DYNAMODB&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Três leituras dessa lista valem mais que qualquer diagrama.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;A primeira está nas duas últimas linhas.&lt;/strong&gt; A pergunta mais valiosa do workshop é “qual destas perguntas &lt;em&gt;não&lt;/em&gt; vamos responder aqui?”. Aging, roll rate&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-11&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-11&quot;&gt;11&lt;/a&gt; e relatórios regulatórios são perguntas analíticas, agregações sobre a carteira inteira. DynamoDB responde perguntas com chave; agregação sobre tudo é Scan, e Scan é o jeito mais caro de descobrir que você escolheu o banco errado para aquela pergunta. Essas perguntas vão para o S3 via export e são respondidas com Athena ou no lakehouse. Declarar isso no workshop evita que, seis meses depois, alguém crie um GSI sobre &lt;code&gt;status&lt;/code&gt; (quatro valores possíveis, cardinalidade de catástrofe) para alimentar um dashboard.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;A segunda: velocidade não é média, é forma.&lt;/strong&gt; AP03 não é “25 requisições por segundo”; é dois milhões de itens numa janela curta e quase nada no resto do dia. AP06 não é uniforme: ele explode no quinto dia útil, no dia do pagamento do INSS&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-12&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-12&quot;&gt;12&lt;/a&gt;, na véspera de feriado. São esses picos, não a média, que vão definir sharding e capacidade.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;A terceira: as linhas marcadas como exatas.&lt;/strong&gt; AP07 e AP08 não toleram leitura eventualmente consistente. Uma liquidação aplicada duas vezes é dinheiro errado no saldo do cliente; um contato a mais pode ser uma ligação que a lei proíbe. Isso já diz, antes de qualquer desenho, que essas perguntas não podem ser respondidas por GSI, que só oferece leitura eventualmente consistente. Voltaremos a isso na seção 4.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Ferramenta para o workshop&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;A AWS mantém o NoSQL Workbench, que permite desenhar o modelo com dados de exemplo e executar as queries antes de existir uma linha de código. Use na reunião, com a supervisora de cobrança presente: ver o próprio “devedor de teste” aparecer inteiro numa única query convence mais do que qualquer slide.&lt;/p&gt;&lt;/blockquote&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Pense comigo&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Se a gerente de cobrança não estava na sala quando vocês listaram os padrões de acesso, quem representou o negócio?&lt;/p&gt;&lt;p&gt;Muito provavelmente, o ORM. E o ORM só conhece uma pergunta: “me dá a entidade por ID”.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h4&gt;Pontos altos&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Em DynamoDB, a lista de perguntas vem antes da tabela. Ela é o cardápio que organiza a cozinha, e se escreve junto com o negócio.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Para cada pergunta, anote quatro coisas: quem pergunta, em que formato a resposta precisa chegar, qual é o pico de uso (não a média) e o que acontece se a resposta vier errada.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Decida em voz alta o que a tabela não vai responder. Relatórios que olham a carteira inteira vão para o S3, não para um Scan.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Perguntas que não podem errar nunca, como pagamento aplicado em dobro ou contato acima do limite legal, não podem depender de índice secundário.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca?utm_source=substack&amp;amp;utm_medium=email&amp;amp;utm_content=share&amp;amp;action=share&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Compartilhar&amp;quot;,&amp;quot;action&amp;quot;:null,&amp;quot;class&amp;quot;:null}&quot; data-component-name=&quot;ButtonCreateButton&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca?utm_source=substack&amp;amp;utm_medium=email&amp;amp;utm_content=share&amp;amp;action=share&quot;&gt;&lt;span&gt;Compartilhar&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;2. Cada atributo é uma afirmação sobre o mundo&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Um item no DynamoDB não tem schema imposto além da chave. Isso é liberdade para o código e perigo para o negócio: nada impede que o mesmo conceito exista com três nomes, em três formatos, em itens diferentes. A disciplina que o banco não impõe precisa vir do modelo.&lt;/p&gt;&lt;p&gt;A regra que uso: &lt;strong&gt;cada atributo deve ser uma afirmação verificável sobre a realidade do negócio, com significado único, unidade explícita e dono claro&lt;/strong&gt;. Vamos aos dois itens centrais da Lastro, o cabeçalho do contrato e a parcela.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;json&amp;quot;,&amp;quot;nodeId&amp;quot;:&amp;quot;ad4f21ff-d5ba-4094-83c7-45cc5c8cb0a1&amp;quot;}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// CONTRACT header: one per contract, inside the debtor&amp;apos;s item collection
{
  &amp;quot;PK&amp;quot;: &amp;quot;T#t042#D#9f2c1a7e4b0d3e61&amp;quot;,
  &amp;quot;SK&amp;quot;: &amp;quot;CT#C-2026-004512&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;CONTRACT&amp;quot;,
  &amp;quot;tenantId&amp;quot;: &amp;quot;t042&amp;quot;,
  &amp;quot;contractId&amp;quot;: &amp;quot;C-2026-004512&amp;quot;,
  &amp;quot;productCode&amp;quot;: &amp;quot;CREDIARIO_12X&amp;quot;,
  &amp;quot;currency&amp;quot;: &amp;quot;BRL&amp;quot;,
  &amp;quot;chargesPolicy&amp;quot;: &amp;quot;CP#v3&amp;quot;,
  &amp;quot;status&amp;quot;: &amp;quot;IN_COLLECTION&amp;quot;,
  &amp;quot;statusReason&amp;quot;: &amp;quot;OLDEST_INSTALLMENT_OVERDUE&amp;quot;,
  &amp;quot;oldestOverdueDueDate&amp;quot;: &amp;quot;2026-08-10&amp;quot;,
  &amp;quot;bucket&amp;quot;: &amp;quot;D031_060&amp;quot;,
  &amp;quot;outstandingCents&amp;quot;: 292740,
  &amp;quot;nextActionDate&amp;quot;: &amp;quot;2026-09-24&amp;quot;,
  &amp;quot;nextAction&amp;quot;: &amp;quot;SMS_D45&amp;quot;,
  &amp;quot;shard&amp;quot;: 7,
  &amp;quot;version&amp;quot;: 22,
  &amp;quot;updatedAt&amp;quot;: &amp;quot;2026-09-23T03:12:44Z&amp;quot;
}

// INSTALLMENT: one per installment, sorted under its contract
{
  &amp;quot;PK&amp;quot;: &amp;quot;T#t042#D#9f2c1a7e4b0d3e61&amp;quot;,
  &amp;quot;SK&amp;quot;: &amp;quot;CT#C-2026-004512#IN#007&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;INSTALLMENT&amp;quot;,
  &amp;quot;seq&amp;quot;: 7,
  &amp;quot;originalDueDate&amp;quot;: &amp;quot;2026-08-10&amp;quot;,
  &amp;quot;dueDate&amp;quot;: &amp;quot;2026-08-10&amp;quot;,
  &amp;quot;principalCents&amp;quot;: 41230,
  &amp;quot;interestCents&amp;quot;: 7560,
  &amp;quot;fineCents&amp;quot;: 0,
  &amp;quot;lateInterestCents&amp;quot;: 0,
  &amp;quot;paidCents&amp;quot;: 0,
  &amp;quot;status&amp;quot;: &amp;quot;OVERDUE&amp;quot;,
  &amp;quot;version&amp;quot;: 9
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A primeira linha dos dois itens, &lt;code&gt;&amp;quot;PK&amp;quot;: &amp;quot;T#t042#D#9f2c1a7e4b0d3e61&amp;quot;&lt;/code&gt;, parece críptica, e é de propósito. &lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;Por ora, leia assim: &lt;/mark&gt;&lt;em&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;“instituição t042, devedor cujo CPF, embaralhado, virou o código 9f2c1a7e4b0d3e61”&lt;/mark&gt;&lt;/em&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;.&lt;/mark&gt; &lt;strong&gt;Na seção 3 desmonto essa chave peça por peça e mostro como se chega a ela.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Nada nesses dois itens está ali por acaso. Vamos por partes.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Atraso é do contrato; vencimento é da parcela&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;A primeira decisão é puramente de negócio: dias de atraso não pertencem à parcela. No mercado, o atraso de um contrato é medido pela parcela mais antiga em aberto, e é esse número que define faixa, régua, provisão&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-13&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-13&quot;&gt;13&lt;/a&gt; e negativação&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-14&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-14&quot;&gt;14&lt;/a&gt;. Por isso &lt;code&gt;oldestOverdueDueDate&lt;/code&gt; e &lt;code&gt;bucket&lt;/code&gt; moram no cabeçalho do contrato, e a parcela guarda apenas o próprio vencimento e o próprio status.&lt;/p&gt;&lt;p&gt;Se você colocar faixa de atraso em cada parcela, o contrato do João aparece três vezes na carteira da supervisora (uma por parcela vencida) e, cedo ou tarde, alguém soma o saldo em triplo num relatório para a diretoria.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Grave o fato, derive o estado&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Repare no que &lt;em&gt;não&lt;/em&gt; está no item: &lt;code&gt;daysPastDue&lt;/code&gt;. Dias de atraso mudam todo dia, para todo contrato inadimplente, sem que nada tenha acontecido no mundo. Gravar esse número exige uma escrita por contrato por dia. Chamo isso de &lt;strong&gt;escrita sem evento&lt;/strong&gt;, e ela é o vazamento de custo e de risco mais comum que já vi em motores de cobrança.&lt;/p&gt;&lt;p&gt;O fato é &lt;code&gt;oldestOverdueDueDate&lt;/code&gt;. Ele só muda quando algo acontece: um pagamento quita a parcela mais antiga, um acordo reestrutura o contrato. Dias de atraso são derivados na leitura, hoje menos essa data. No exemplo, em 24 de setembro o contrato está com 45 dias, exatamente o gatilho do &lt;code&gt;SMS_D45&lt;/code&gt;. É um cálculo de uma linha que nunca fica desatualizado porque o job da madrugada falhou.&lt;/p&gt;&lt;p&gt;Já &lt;code&gt;bucket&lt;/code&gt; é estado derivado &lt;em&gt;gravado&lt;/em&gt;, e a exceção é deliberada. Ele precisa existir como atributo porque é chave de um índice (a carteira por faixa, que veremos adiante) e muda raramente: no máximo uma vez a cada trinta dias por contrato. A regra geral fica assim: &lt;strong&gt;estado derivado só merece virar atributo quando é necessário numa chave e muda muito menos do que é lido&lt;/strong&gt;. Na seção 5 essa decisão vira dinheiro.&lt;/p&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Imagine assim&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;O seu RG não traz a sua idade. Traz a sua data de nascimento. A idade muda todo ano sem que nada aconteça com você; a data de nascimento nunca muda. Se o documento trouxesse a idade, o país inteiro precisaria reemitir o RG a cada aniversário, e sempre haveria documentos errados circulando.&lt;/p&gt;&lt;p&gt;&lt;code&gt;oldestOverdueDueDate&lt;/code&gt; é a data de nascimento do atraso; &lt;code&gt;daysPastDue&lt;/code&gt; é a idade. E o &lt;code&gt;bucket&lt;/code&gt;? É a série escolar. Dá para calcular pela idade, mas a escola grava a série porque é por ela que organiza as salas (o índice), e ela só muda uma vez por ano.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!RTHu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!RTHu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png 424w, https://substackcdn.com/image/fetch/$s_!RTHu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png 848w, https://substackcdn.com/image/fetch/$s_!RTHu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png 1272w, https://substackcdn.com/image/fetch/$s_!RTHu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;1012&quot; height=&quot;476&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/e3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:559,&amp;quot;width&amp;quot;:1186,&amp;quot;resizeWidth&amp;quot;:1012,&amp;quot;bytes&amp;quot;:81971,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!RTHu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png 424w, https://substackcdn.com/image/fetch/$s_!RTHu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png 848w, https://substackcdn.com/image/fetch/$s_!RTHu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png 1272w, https://substackcdn.com/image/fetch/$s_!RTHu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!RTHu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3e92e19-30ba-4556-97fa-2c8475b2ec8d_1186x559.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Cinco dias sem nenhum evento no negócio: ninguém pagou, ninguém negociou. O modelo A reemite o documento todo dia só para atualizar a idade impressa; o modelo B imprime a data de nascimento uma vez.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;O vencimento original é um fato; o vencimento vigente é uma decisão&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;originalDueDate&lt;/code&gt; nunca muda. &lt;code&gt;dueDate&lt;/code&gt; muda quando há prorrogação&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-15&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-15&quot;&gt;15&lt;/a&gt;. Parece redundante até alguém do risco perguntar quanto da carteira de agosto foi prorrogada e quantas dessas prorrogações acabaram pagas. Se você sobrescreveu, perdeu a resposta. Pense numa passagem aérea remarcada: o bilhete precisa mostrar o voo original e o novo, senão ninguém sabe depois se o passageiro se atrasou ou se a companhia mudou o horário. É o mesmo princípio do artigo sobre modelagem de dados: você guardou o resultado e jogou fora a causa.&lt;/p&gt;&lt;p&gt;E vencimento é data civil, não instante: &lt;code&gt;&amp;quot;2026-08-10&amp;quot;&lt;/code&gt;, nunca &lt;code&gt;&amp;quot;2026-08-10T00:00:00Z&amp;quot;&lt;/code&gt;. Meia-noite UTC é 21h do dia anterior em Brasília; um vencimento gravado como timestamp vai, em algum relatório, aparecer um dia antes. Pior: a regra de mercado que permite pagar sem encargos no próximo dia útil um boleto que vence em feriado é uma regra de calendário, não de relógio. Modele o conceito que o negócio usa.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Dinheiro tem unidade, e encargo tem origem&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Valores em centavos inteiros, com a moeda explícita no contrato. O tipo Number do DynamoDB é decimal de alta precisão, então o problema não está no banco; está no caminho: JSON parseado como double num cliente JavaScript, uma biblioteca que converte para float no meio de um mapper. Inteiro em centavos não tem ambiguidade em nenhuma camada.&lt;/p&gt;&lt;p&gt;A decomposição em &lt;code&gt;principalCents&lt;/code&gt;, &lt;code&gt;interestCents&lt;/code&gt;, &lt;code&gt;fineCents&lt;/code&gt; e &lt;code&gt;lateInterestCents&lt;/code&gt; não é preciosismo. Ela existe porque o negócio precisa provar duas coisas: a ordem de imputação do pagamento&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-16&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-16&quot;&gt;16&lt;/a&gt; (o art. 354 do Código Civil manda imputar primeiro nos juros vencidos e depois no capital, salvo estipulação em contrário) e o respeito ao teto da multa moratória&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-17&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-17&quot;&gt;17&lt;/a&gt; em relações de consumo, de 2% pelo art. 52, §1º, do CDC. Um único &lt;code&gt;amountCents&lt;/code&gt; não prova nenhuma das duas.&lt;/p&gt;&lt;p&gt;Note que multa e juros de mora aparecem zerados na parcela vencida. Não é esquecimento. Encargo de atraso é calculado quando vira fato (na liquidação ou no fechamento de um acordo&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-18&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-18&quot;&gt;18&lt;/a&gt;) a partir da política vigente, referenciada no contrato por &lt;code&gt;chargesPolicy: &amp;quot;CP#v3&amp;quot;&lt;/code&gt;. A política é um item versionado na própria tabela. Quando o tenant mudar a regra em março, os contratos antigos continuam apontando para a versão sob a qual foram assinados. Recalcular encargo todo dia seria, de novo, escrita sem evento.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Regra de negócio é dado versionado, não um &lt;/em&gt;&lt;code&gt;if&lt;/code&gt;&lt;em&gt; que alguém alterou num PR de sexta-feira.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!f6nh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!f6nh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png 424w, https://substackcdn.com/image/fetch/$s_!f6nh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png 848w, https://substackcdn.com/image/fetch/$s_!f6nh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png 1272w, https://substackcdn.com/image/fetch/$s_!f6nh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;1010&quot; height=&quot;451&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/e8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:479,&amp;quot;width&amp;quot;:1072,&amp;quot;resizeWidth&amp;quot;:1010,&amp;quot;bytes&amp;quot;:50156,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!f6nh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png 424w, https://substackcdn.com/image/fetch/$s_!f6nh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png 848w, https://substackcdn.com/image/fetch/$s_!f6nh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png 1272w, https://substackcdn.com/image/fetch/$s_!f6nh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!f6nh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8c8c2ce-69ea-465e-8504-930ed6076186_1072x479.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Cinco dias sem nenhum evento no negócio: ninguém pagou, ninguém negociou. O modelo A reemite o documento todo dia só para atualizar a idade impressa; o modelo B imprime a data de nascimento uma vez.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;O estado não carrega contexto&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;status&lt;/code&gt; e &lt;code&gt;statusReason&lt;/code&gt; são atributos separados, pelo mesmo princípio que defendi no artigo sobre máquinas de estado em crédito: &lt;code&gt;IN_COLLECTION&lt;/code&gt; é um estado; o motivo é atributo. Criar &lt;code&gt;IN_COLLECTION_BY_OVERDUE&lt;/code&gt; e &lt;code&gt;IN_COLLECTION_BY_BROKEN_AGREEMENT&lt;/code&gt; gera explosão combinatória na máquina, nos índices e nos relatórios.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;A régua e o pagamento vão correr um contra o outro&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Às 00h07 o scheduler está reagendando a próxima ação do contrato. Às 00h07 o webhook de Pix está aplicando um pagamento no mesmo contrato. Sem controle de concorrência, o último a escrever vence, e o contrato quitado ganha um &lt;code&gt;nextAction&lt;/code&gt; de SMS. O atributo &lt;code&gt;version&lt;/code&gt;, com toda escrita condicionada a &lt;code&gt;version = :expected&lt;/code&gt;, transforma essa corrida num erro explícito e retentável em vez de um dado silenciosamente errado. Imagine dois atendentes editando a mesma ficha em papel: quem entrega por último apaga, sem saber, o que o outro escreveu. O &lt;code&gt;version&lt;/code&gt; é o número da página no rodapé da ficha: se ele mudou desde que você a pegou, devolva e pegue a versão nova.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Nome de atributo custa bytes, mas legibilidade custa mais&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;O nome do atributo entra no tamanho do item; a AWS documenta isso nas restrições do serviço. Esse fato levou muita gente a nomes como &lt;code&gt;a1&lt;/code&gt;, &lt;code&gt;dd&lt;/code&gt;, &lt;code&gt;pc&lt;/code&gt;. Não faça isso. O item precisa ser legível por quem vai investigar um incidente às 3h da manhã direto no console. O equilíbrio é nome curto e semântico: &lt;code&gt;dueDate&lt;/code&gt;, e não &lt;code&gt;installmentCurrentDueDateLocal&lt;/code&gt; nem &lt;code&gt;dd&lt;/code&gt;. Na seção 5 fica claro que a economia de bytes que importa vem de outro lugar.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Chaves sintéticas estão saindo dos índices&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Até novembro de 2025, um GSI tinha exatamente um atributo de partição e um de ordenação. Isso obrigava a concatenar valores em strings como &lt;code&gt;GSI1PK = &amp;quot;T#t042#ACT#2026-09-24#07&amp;quot;&lt;/code&gt; e, pior, a fazer backfill da tabela inteira toda vez que um índice novo exigia uma chave nova. &lt;em&gt;&lt;strong&gt;Desde então, GSIs aceitam até quatro atributos na partição e quatro na ordenação, usando os atributos naturais do item, sem custo adicional.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;Isso é mudança de modelagem, não só de sintaxe: índices novos podem nascer a partir de atributos que já existem, e o item deixa de carregar strings que só servem ao banco.&lt;/mark&gt; É por isso que &lt;code&gt;tenantId&lt;/code&gt; aparece duas vezes no contrato, dentro da PK e como atributo próprio: é o preço, barato, de índices com chaves naturais. A chave primária da tabela base continua com um atributo de cada, então PK e SK sintéticos continuam existindo ali. Antes de adotar, confira se o seu SDK e o seu mapper já suportam o recurso.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Pense comigo&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Em 2029, uma ação judicial pergunta por que a parcela 7 do contrato &lt;code&gt;C-2026-004512&lt;/code&gt; foi liquidada com R$ 9,75 de multa. Qual atributo responde?&lt;/p&gt;&lt;p&gt;Se a resposta for “a gente recalcula com a regra atual”, você não tem uma resposta. Tem uma estimativa, e estimativa não se defende.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h4&gt;Pontos altos&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;O atraso pertence ao contrato e é medido pela parcela mais antiga em aberto. O vencimento pertence a cada parcela.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Grave a data de nascimento, não a idade: guarde o que muda quando algo acontece e calcule o que muda só porque o dia virou.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Um valor calculado só vira campo gravado quando organiza um índice e muda raramente, como a série escolar.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Vencimento é data do calendário, não hora do relógio. Dinheiro é número inteiro em centavos, separado pela origem: principal, juros, multa.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Regras de encargo são versionadas, e cada contrato aponta para a versão sob a qual foi assinado.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Toda escrita que pode concorrer com outra confere o número da versão. Nomes de campo curtos, mas que qualquer pessoa entenda no console.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;&lt;strong&gt;3. Num BaaS, a chave de partição é uma decisão de isolamento&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;A documentação da AWS descreve um bloco de multi-tenancy em que todos os dados de um tenant ficam na mesma coleção de itens, com o tenant como chave de partição. Funciona muito bem para muitos tenants pequenos e parecidos, e oferece de brinde uma exclusão barata quando o tenant vai embora.&lt;/p&gt;&lt;p&gt;Num BaaS de cobrança, é uma armadilha. O maior tenant da Lastro tem 60% dos contratos. Toda a escrita da régua dele cairia numa única chave, sujeita ao limite físico de throughput por partição (mil unidades de escrita por segundo). As alternativas óbvias também falham. CPF puro como PK mistura tenants: a mesma pessoa pode dever para o varejista e para a SCD, e cada instituição é controladora&lt;strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#n19&quot;&gt;&lt;sup&gt;19&lt;/sup&gt;&lt;/a&gt;&lt;/strong&gt; do próprio dado perante a LGPD, então a coleção do devedor não pode ser compartilhada. Contrato como PK fragmenta o devedor: a visão 360 vira N consultas.&lt;/p&gt;&lt;p&gt;A escolha: &lt;code&gt;PK = &amp;quot;T#&amp;quot; + tenantId + &amp;quot;#D#&amp;quot; + debtorKey&lt;/code&gt;. O tenant garante isolamento; o devedor garante cardinalidade.&lt;/p&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Imagine assim&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Pense na plataforma como um prédio comercial. Usar o tenant como chave é mandar todos os funcionários da maior empresa do prédio para um único elevador: no horário de pico, ninguém sobe. Usar só o CPF é guardar na mesma gaveta os papéis que duas empresas diferentes têm sobre a mesma pessoa, quando cada uma só pode ver os seus.&lt;/p&gt;&lt;p&gt;Tenant mais devedor é cada empresa no seu andar, com um armário por cliente. Ninguém disputa elevador, e nenhum papel vai parar no andar errado.&lt;/p&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;Não coloque CPF na chave&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Minha proposta de dezembro usava o CPF na chave. Hoje eu não faria isso, e a razão é operacional, não teórica: &lt;strong&gt;chaves vazam&lt;/strong&gt;. Elas aparecem nas mensagens de erro que vão para o log, nos registros do stream que alimentam outros sistemas, nos payloads da DLQ e no CloudWatch Contributor Insights, cuja função é justamente listar as chaves mais acessadas. CPF em chave é CPF em todo lugar.&lt;/p&gt;&lt;p&gt;&lt;code&gt;debtorKey&lt;/code&gt; é um HMAC-SHA256 do documento, com um segredo por tenant, truncado:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;import { createHmac } from &amp;quot;node:crypto&amp;quot;;

// Same CPF, different tenants -&amp;gt; different keys. No index needed to find a debtor.
export function debtorKey(tenantSecret: Buffer, document: string): string {
  const normalized = document.replace(/\D/g, &amp;quot;&amp;quot;);
  return createHmac(&amp;quot;sha256&amp;quot;, tenantSecret)
    .update(normalized)
    .digest(&amp;quot;hex&amp;quot;)
    .slice(0, 16);
}&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Três propriedades importam. É determinístico: o operador digita o CPF, o serviço calcula a chave e faz uma Query direta, sem índice por CPF. Não é reversível sem o segredo. E o mesmo CPF gera chaves diferentes em tenants diferentes, o que impede correlacionar carteiras. Funciona como a ficha da chapelaria de um restaurante: o número encontra o seu casaco na hora, mas não revela o seu nome a quem olha a ficha, e outro restaurante daria outro número para a mesma pessoa. O documento em si fica como atributo, criptografado no cliente; o AWS Database Encryption SDK faz criptografia por atributo exatamente para esse cenário.&lt;/p&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;O segredo é parte da chave&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Escolha e guarde esse segredo com o mesmo cuidado com que se escolhe uma chave de partição. Rotacioná-lo significa recalcular a PK de todos os devedores do tenant, ou seja, migrar a tabela. Planeje versionamento do segredo desde o primeiro dia, ou aceite conscientemente que ele não roda.&lt;/p&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;Anatomia da chave, peça por peça&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Com as duas decisões tomadas (tenant mais devedor, e CPF nunca em claro), a chave se monta em quatro passos. Vamos seguir um CPF fictício, &lt;code&gt;123.456.789-09&lt;/code&gt;, de um devedor do tenant &lt;code&gt;t042&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;step 1  normalize the document         &amp;quot;123.456.789-09&amp;quot;  -&amp;gt;  &amp;quot;12345678909&amp;quot;
step 2  HMAC-SHA256 with t042&amp;apos;s secret  -&amp;gt;  &amp;quot;9f2c1a7e4b0d3e61a84c…e07b&amp;quot;   (64 hex chars)
step 3  keep the first 16 characters    -&amp;gt;  &amp;quot;9f2c1a7e4b0d3e61&amp;quot;             (debtorKey)
step 4  add tenant and entity prefixes  -&amp;gt;  &amp;quot;T#&amp;quot; + &amp;quot;t042&amp;quot; + &amp;quot;#D#&amp;quot; + &amp;quot;9f2c1a7e4b0d3e61&amp;quot;

PK = &amp;quot;T#t042#D#9f2c1a7e4b0d3e61&amp;quot;&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!Jkkf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!Jkkf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png 424w, https://substackcdn.com/image/fetch/$s_!Jkkf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png 848w, https://substackcdn.com/image/fetch/$s_!Jkkf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png 1272w, https://substackcdn.com/image/fetch/$s_!Jkkf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;990&quot; height=&quot;295&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/ee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:386,&amp;quot;width&amp;quot;:1295,&amp;quot;resizeWidth&amp;quot;:990,&amp;quot;bytes&amp;quot;:62188,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!Jkkf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png 424w, https://substackcdn.com/image/fetch/$s_!Jkkf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png 848w, https://substackcdn.com/image/fetch/$s_!Jkkf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png 1272w, https://substackcdn.com/image/fetch/$s_!Jkkf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!Jkkf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee8c1b9e-946b-45cb-a910-086f0f06c20b_1295x386.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Toda chave da tabela segue a mesma gramática: prefixo que diz o que vem depois, valor, separador.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Cada pedaço da chave responde a uma pergunta: de qual instituição, de que tipo de coisa, de quem.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Do geral para o específico, como um endereço.&lt;/strong&gt; Você escreve um endereço começando pelo estado, depois a cidade, a rua e o número, e o carteiro vai estreitando a busca a cada pedaço. A chave faz o mesmo: primeiro a instituição, depois o tipo de coisa, depois quem. Essa ordem não é estética. É ela que permite dizer ao IAM “este papel só pode tocar chaves que começam com &lt;code&gt;T#t042#&lt;/code&gt;“, porque prefixo é a única coisa que uma regra de acesso consegue comparar.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Prefixos dizem o que vem depois.&lt;/strong&gt; A mesma tabela guarda famílias de chave diferentes, e o prefixo é o que impede que elas se confundam:&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:&amp;quot;4081392d-0773-4f3d-aef8-9baca62c07b7&amp;quot;}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;T#t042#D#9f2c1a7e4b0d3e61       debtor item collection (profile, contracts, installments...)
T#t042#CHG#&amp;lt;txid&amp;gt;               charge lookup           (section 4)
T#t042#STL#&amp;lt;settlementId&amp;gt;       settlement idempotency  (section 4)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sem o &lt;code&gt;D#&lt;/code&gt;, &lt;code&gt;CHG#&lt;/code&gt; e &lt;code&gt;STL#&lt;/code&gt;, nada impediria que o identificador de uma liquidação coincidisse, por acaso, com o código de um devedor. Com o prefixo, a colisão entre famílias é impossível por construção, e quem abre o console às 3h da manhã sabe o que está olhando sem ler o código.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Por que &lt;/strong&gt;&lt;code&gt;#&lt;/code&gt;&lt;strong&gt; como separador?&lt;/strong&gt; Porque esse caractere nunca aparece nos identificadores do negócio, &lt;em&gt;&lt;strong&gt;e porque é a convenção que a própria documentação da AWS usa nas chaves compostas.&lt;/strong&gt;&lt;/em&gt; Qualquer caractere que não apareça nos valores serviria; o importante é escolher um e nunca mais mudar.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Por que letras tão curtas?&lt;/strong&gt; A chave se repete em todos os itens da coleção e em todos os índices que a projetam, e o tamanho dela entra na conta de cada item. &lt;code&gt;T&lt;/code&gt; e &lt;code&gt;D&lt;/code&gt; bastam, desde que o time tenha o dicionário por escrito, &lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;no README ou no ADR:&lt;/mark&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;T        tenant                 CT       contract
D        debtor                 IN       installment
CHG      charge (Pix / boleto)  AG       agreement
STL      settlement             EV       event (audit trail)
PROFILE  debtor profile         LIMIT    daily contact counter
CONTACT  contact attempt        PROMISE  payment promise&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Uma chave curta só é legível se existir o dicionário. Sem ele, &lt;code&gt;T#t042#D#&lt;/code&gt; vira hieróglifo no primeiro rodízio do time.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Por que o tenant aparece em claro e o devedor não?&lt;/strong&gt; O identificador da instituição não é dado pessoal e precisa estar legível para a regra de acesso e para a operação. Quando o Contributor Insights mostrar uma chave quente, &lt;code&gt;T#t042&lt;/code&gt; diz na hora qual cliente está gerando a carga, enquanto o código do devedor esconde quem é a pessoa. Você sabe qual cliente está pegando fogo sem saber o nome de ninguém.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Por que só 16 caracteres do hash?&lt;/strong&gt; Dezesseis caracteres hexadecimais são 64 bits, um espaço de 18 quintilhões de valores. Para um tenant com 10 milhões de devedores, a chance de dois CPFs diferentes gerarem o mesmo código fica na ordem de uma em algumas centenas de milhares: baixa, mas não zero. Por isso o cadastro do devedor grava o item &lt;code&gt;PROFILE&lt;/code&gt; com o documento criptografado e, ao criar um devedor novo, confere se o documento bate com o que já está lá. Uma colisão improvável vira um erro detectável, e nunca dois devedores misturados na mesma pasta.&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt; Se você preferir não conviver com esse cenário, use 20 caracteres (80 bits): a chance cai para algo desprezível, ao custo de quatro bytes a mais por item.&lt;/mark&gt;&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Isolamento no IAM, não só no código&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Com o prefixo do tenant na chave, o isolamento pode ser imposto pela política de acesso. A condição &lt;code&gt;dynamodb:LeadingKeys&lt;/code&gt; restringe quais valores de partição um papel pode tocar. A camada de API assume um papel com o tenant como tag de sessão, e a política só aceita chaves que comecem com o prefixo daquele tenant:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;{
  &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
  &amp;quot;Action&amp;quot;: [&amp;quot;dynamodb:GetItem&amp;quot;, &amp;quot;dynamodb:Query&amp;quot;, &amp;quot;dynamodb:PutItem&amp;quot;, &amp;quot;dynamodb:UpdateItem&amp;quot;],
  &amp;quot;Resource&amp;quot;: &amp;quot;arn:aws:dynamodb:*:*:table/collections&amp;quot;,
  &amp;quot;Condition&amp;quot;: {
    &amp;quot;ForAllValues:StringLike&amp;quot;: {
      &amp;quot;dynamodb:LeadingKeys&amp;quot;: [&amp;quot;T#${aws:PrincipalTag/tenantId}#*&amp;quot;]
    }
  }
}&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Um bug que monte a chave do tenant errado vira &lt;code&gt;AccessDeniedException&lt;/code&gt;, não vazamento de dados entre instituições. Num BaaS regulado, essa diferença cabe inteira num parecer de segurança.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;O preço dessa escolha: offboarding&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Nenhuma chave é grátis. Com o tenant como partição, apagar um tenant seria uma Query seguida de deletes. Com o devedor como partição, os dados de um tenant estão espalhados por milhões de coleções. Quando um tenant encerra o contrato e pede exclusão, você precisa enumerá-los: um export filtrado, ou um GSI esparso com projeção &lt;code&gt;KEYS_ONLY&lt;/code&gt; sobre os itens &lt;code&gt;PROFILE&lt;/code&gt;, particionado por &lt;code&gt;tenantId&lt;/code&gt;. Registre esse custo no ADR. Trade-off não documentado é dívida com data de vencimento desconhecida.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;A coleção do devedor&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Tudo o que a plataforma sabe sobre um devedor, num tenant, mora na mesma coleção de itens, ordenado pela sort key. Pense numa pasta suspensa de arquivo com abas separadoras em ordem alfabética: puxar a pasta inteira é a visão 360; abrir direto na aba das parcelas é um &lt;code&gt;begins_with&lt;/code&gt;.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!nq3y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!nq3y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png 424w, https://substackcdn.com/image/fetch/$s_!nq3y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png 848w, https://substackcdn.com/image/fetch/$s_!nq3y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png 1272w, https://substackcdn.com/image/fetch/$s_!nq3y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;1126&quot; height=&quot;657&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:null,&amp;quot;imageSize&amp;quot;:null,&amp;quot;height&amp;quot;:657,&amp;quot;width&amp;quot;:1126,&amp;quot;resizeWidth&amp;quot;:null,&amp;quot;bytes&amp;quot;:126585,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:null,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!nq3y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png 424w, https://substackcdn.com/image/fetch/$s_!nq3y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png 848w, https://substackcdn.com/image/fetch/$s_!nq3y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png 1272w, https://substackcdn.com/image/fetch/$s_!nq3y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!nq3y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2704f2-ff0a-4e77-821d-01bbe9475bf3_1126x657.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Ordem lexicográfica: CONTACT &amp;lt; CT &amp;lt; LIMIT &amp;lt; PROFILE &amp;lt; PROMISE. Dentro do contrato: AG &amp;lt; EV &amp;lt; IN.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Os prefixos da sort key não são estética: são o plano de consultas. Cada faixa contígua é uma pergunta de negócio respondida com uma única Query.&lt;/p&gt;&lt;p&gt;A visão 360 (AP01) é uma Query na partição, sem filtro. As parcelas do contrato (AP02) são uma Query com &lt;code&gt;begins_with&lt;/code&gt; no prefixo &lt;code&gt;#IN#&lt;/code&gt;. A trilha de auditoria da parcela 7 (AP09) é outra, no prefixo &lt;code&gt;#EV#007#&lt;/code&gt;. Os prefixos foram escolhidos para que a ordem alfabética coincida com a necessidade de leitura: eventos ficam antes das parcelas porque &lt;code&gt;E&lt;/code&gt; vem antes de &lt;code&gt;I&lt;/code&gt;, e por isso o prefixo das parcelas nunca devolve eventos por acidente.&lt;/p&gt;&lt;p&gt;Um detalhe que já gerou chamado em produção: a sort key é string, então a sequência precisa de zeros à esquerda. Sem eles, &lt;code&gt;IN#10&lt;/code&gt; vem antes de &lt;code&gt;IN#2&lt;/code&gt;, e o app mostra a parcela dez antes da segunda. Ordem lexicográfica é regra de negócio disfarçada de detalhe técnico.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Histórico não mora dentro do item&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;A tentação é óbvia: um array &lt;code&gt;statusHistory&lt;/code&gt; dentro da parcela. O problema técnico é que, numa atualização, o DynamoDB cobra a escrita pelo maior tamanho entre o item antes e depois da mudança. Cada update na parcela passa a pagar pelo histórico inteiro. É como levar o álbum de família completo ao cartório toda vez que você só precisa trocar a foto do documento. Uma parcela com dezoito meses de eventos passa de 1 KB e dobra o custo de toda escrita futura, inclusive a do pagamento. O problema de negócio é que o array não é consultável: a auditoria vira “baixar tudo e procurar”.&lt;/p&gt;&lt;p&gt;A saída é o particionamento vertical que a própria AWS recomenda: eventos como itens irmãos (&lt;code&gt;#EV#&lt;/code&gt;), append-only. Cada evento custa uma escrita pequena, uma única vez, e só é lido quando alguém pergunta.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Cuidado com LSI&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Um índice secundário local só pode ser criado junto com a tabela e, quando existe, impõe um limite de 10 GB a cada coleção de itens. A coleção de um devedor antigo, com anos de contatos e eventos, é exatamente o tipo de coisa que cresce sem ninguém olhar. Prefira GSI, que pode nascer depois e não impõe esse teto.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Pense comigo&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Se o seu maior tenant dobrar de tamanho amanhã, qual chave esquenta primeiro? Se a resposta for “a do tenant”, o seu modelo tem um teto de crescimento que ninguém escreveu em lugar nenhum, e ele vai ser descoberto no pior dia possível.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h4&gt;Pontos altos&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Num BaaS, a chave junta instituição e devedor: cada inquilino no seu andar, cada devedor no seu armário.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Documento pessoal nunca vai na chave. Use um código derivado (HMAC), como a ficha da chapelaria: encontra o casaco sem expor o nome.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;O isolamento entre instituições é garantido pela política de acesso da AWS, e não só pelo cuidado do código.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Toda chave tem um preço. O desta é o trabalho de remover um tenant inteiro; escreva isso no ADR.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;A ordem das abas da pasta é planejada: prefixos pensados para as perguntas e números com zeros à esquerda.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Histórico vira folhas novas na pasta, nunca um anexo que cresce dentro do mesmo item.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;&lt;strong&gt;4. Índices secundários são respostas, não atalhos&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Cada GSI é uma cópia parcial da tabela, com capacidade, custo de escrita e custo de armazenamento próprios. Por isso cada índice deve nascer com o nome da pergunta que responde. Na Lastro, são dois índices de negócio e dois tipos de item de lookup, e a escolha entre um e outro diz muito sobre maturidade de modelagem.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;GSI1: a régua do dia é um índice esparso&lt;/strong&gt;&lt;/h3&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:&amp;quot;38e9ec1e-9312-469c-9c7a-e1d6ce3af773&amp;quot;}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;GSI1  dunning-agenda          sparse: only items that have nextActionDate
  partition key   tenantId (S) + nextActionDate (S) + shard (N)
  sort key        nextAction (S) + contractId (S)
  projection      INCLUDE debtorKey, bucket, outstandingCents, channelPrefs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A AWS descreve o índice esparso como um bloco de construção: só entram no GSI os itens que possuem os atributos de chave. Com chaves de múltiplos atributos vale a mesma regra; se faltar qualquer componente, o item fica de fora. Na Lastro, apenas contratos em cobrança ativa e promessas pendentes têm &lt;code&gt;nextActionDate&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;O efeito é elegante. Quando um pagamento cura o contrato, a mesma transação que baixa a parcela remove &lt;code&gt;nextActionDate&lt;/code&gt; e &lt;code&gt;nextAction&lt;/code&gt;, e o contrato sai da régua. Não existe “job para limpar a régua”. &lt;strong&gt;Sair da régua é remover um atributo.&lt;/strong&gt; A modelagem está dizendo, em estrutura de dados, uma regra de negócio: régua só existe para quem deve.&lt;/p&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Imagine assim&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;A régua é o quadro de post-its na porta da geladeira. Só vai para o quadro o que precisa ser feito, cada post-it com a sua data. Quando a tarefa acaba, você arranca o post-it; ninguém precisa “fazer faxina no quadro” no fim do mês.&lt;/p&gt;&lt;p&gt;A tabela é a casa inteira. O índice esparso é só a porta da geladeira, e é por isso que ele é pequeno, barato e rápido de ler.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Promessas de pagamento usam o mesmo índice, com &lt;code&gt;nextAction = PROMISE_CHECK&lt;/code&gt; na data prometida. A elegibilidade para negativação (AP10) também: &lt;code&gt;BUREAU_REPORT&lt;/code&gt; é só mais uma ação agendada, na data em que o contrato passa a ser elegível pela política do tenant. Um índice, três perguntas, porque as três têm a mesma forma: &lt;em&gt;o que precisa acontecer para este tenant nesta data&lt;/em&gt;. E, como o sort key começa por &lt;code&gt;nextAction&lt;/code&gt;, o worker que só envia SMS consulta só os SMS.&lt;/p&gt;&lt;p&gt;Há ainda um benefício de custo que a documentação destaca: um índice esparso só cobra escrita e armazenamento dos itens que entram nele. A régua indexa os 25% da carteira em cobrança, não os 100%.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!CM1c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!CM1c!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png 424w, https://substackcdn.com/image/fetch/$s_!CM1c!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png 848w, https://substackcdn.com/image/fetch/$s_!CM1c!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png 1272w, https://substackcdn.com/image/fetch/$s_!CM1c!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;958&quot; height=&quot;441&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/b56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:528,&amp;quot;width&amp;quot;:1146,&amp;quot;resizeWidth&amp;quot;:958,&amp;quot;bytes&amp;quot;:110606,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!CM1c!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png 424w, https://substackcdn.com/image/fetch/$s_!CM1c!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png 848w, https://substackcdn.com/image/fetch/$s_!CM1c!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png 1272w, https://substackcdn.com/image/fetch/$s_!CM1c!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!CM1c!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb56cc272-d26b-49c9-a060-0e7a9dfd90aa_1146x528.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;O quadro de post-its da geladeira: só está no índice o que precisa ser feito, e concluir a tarefa é arrancar o post-it....&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;O shard é a unidade de paralelismo do seu batch&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Primeiro, a palavra. &lt;em&gt;Shard&lt;/em&gt;, em inglês, é caco, fragmento, o pedaço de algo que se partiu. Em banco de dados, fazer &lt;em&gt;sharding&lt;/em&gt; é dividir de propósito um volume grande em pedaços menores, guardados em lugares diferentes, para que nenhum pedaço receba trabalho demais sozinho. Na Lastro, o shard é apenas um número de 0 a 15 gravado no contrato, que diz em qual dos dezesseis pedaços do índice aquele contrato mora.&lt;/p&gt;&lt;p&gt;Sem o atributo &lt;code&gt;shard&lt;/code&gt;, a partição do índice seria apenas tenant e data. Toda madrugada, depois de executar as ações do dia, o scheduler reagenda a próxima ação de cada contrato. Mudar &lt;code&gt;nextActionDate&lt;/code&gt; é mudar a chave do índice, e para o GSI isso significa remover o item de uma posição e inseri-lo em outra. No maior tenant, são cerca de 1,2 milhão de contratos convergindo na mesma chave de “amanhã”.&lt;/p&gt;&lt;p&gt;E aqui mora o detalhe que transforma um problema de performance num incidente de negócio. A AWS avisa que throttling de escrita num GSI devolve back-pressure para a tabela base: quando o índice não aguenta, as escritas na tabela falham. Quem mais escreve na tabela base às 00h07? O webhook de liquidação.&lt;/p&gt;&lt;p&gt;O cliente que pagou o Pix à meia-noite para “limpar o nome” gera um webhook que toma throttling, os retries se esgotam, a mensagem cai na DLQ e a baixa só entra às 9h30, no reprocessamento manual. Às 8h, a régua mandou SMS de cobrança para quem já tinha pago. Reclamação no Procon, post no Reclame Aqui, risco de dano moral. Tudo porque um índice de &lt;em&gt;agenda&lt;/em&gt; estava quente.&lt;/p&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Imagine assim&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;É o engarrafamento na saída do estacionamento de um shopping que tranca a rua por onde passa a ambulância. O estacionamento (a régua, que pode esperar) não tem nada a ver com o hospital (o pagamento, que não pode esperar), mas os dois usam a mesma rua: a tabela base.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;A saída é o write sharding com sufixo calculado: &lt;code&gt;shard = hash(contractId) mod N&lt;/code&gt;. O N sai de uma conta simples, o pico de escritas por segundo esperado no índice dividido por mil, com folga para crescimento. O sufixo é calculado, não aleatório, para que qualquer contrato saiba sempre em que shard está.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!i2JI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!i2JI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png 424w, https://substackcdn.com/image/fetch/$s_!i2JI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png 848w, https://substackcdn.com/image/fetch/$s_!i2JI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png 1272w, https://substackcdn.com/image/fetch/$s_!i2JI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;962&quot; height=&quot;474&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/07e1aa20-750b-4966-a7df-784d053be887_1209x596.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:596,&amp;quot;width&amp;quot;:1209,&amp;quot;resizeWidth&amp;quot;:962,&amp;quot;bytes&amp;quot;:91700,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!i2JI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png 424w, https://substackcdn.com/image/fetch/$s_!i2JI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png 848w, https://substackcdn.com/image/fetch/$s_!i2JI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png 1272w, https://substackcdn.com/image/fetch/$s_!i2JI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!i2JI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07e1aa20-750b-4966-a7df-784d053be887_1209x596.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;O sharding que protege o banco é o mesmo que distribui o trabalho: cada shard é uma fila natural para um worker, sem locks nem coordenação.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;O insight que costuma passar despercebido: &lt;strong&gt;o shard não é gambiarra de banco. É exatamente a unidade de paralelismo que o seu batch já queria.&lt;/strong&gt; Às 8h, dezesseis workers fazem, cada um, uma Query paginada no próprio shard. Sem coordenação, sem locks, sem a eterna pergunta “quem pegou este contrato?”. O desenho que protege a partição é o mesmo que distribui o trabalho. Pense nos caixas de um supermercado: com uma fila única, tudo trava; com dezesseis caixas e uma regra fixa para escolher o caixa (o final do número do contrato, digamos), a fila anda, e cada caixa já tem o seu operador.&lt;/p&gt;&lt;p&gt;Sobre a projeção: &lt;code&gt;INCLUDE&lt;/code&gt; apenas com o que o worker precisa para decidir e agir. Cada atributo projetado é armazenamento replicado e custo extra em toda escrita que o altere. Projetar &lt;code&gt;ALL&lt;/code&gt; “por garantia” duplica a tabela.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;GSI2: a carteira da supervisora&lt;/strong&gt;&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;GSI2  portfolio-by-bucket     sparse: only CONTRACT items that have bucket
  partition key   tenantId (S) + bucket (S)
  sort key        outstandingCents (N) + contractId (S)
  projection      INCLUDE debtorKey, oldestOverdueDueDate, productCode, lastContactAt&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Uma Query com &lt;code&gt;ScanIndexForward = false&lt;/code&gt; devolve os maiores saldos primeiro; os dias de atraso são calculados na tela a partir de &lt;code&gt;oldestOverdueDueDate&lt;/code&gt;. É aqui que a decisão da seção 2 aparece com clareza. Se &lt;code&gt;daysPastDue&lt;/code&gt; fosse chave de ordenação, cada contrato inadimplente mudaria de posição no índice todos os dias. Com a faixa na partição, o contrato se move no máximo uma vez por mês. &lt;strong&gt;Nunca coloque numa chave de índice um atributo que muda com o calendário.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;outstandingCents&lt;/code&gt; na ordenação muda com pagamento parcial, o que é raro e até desejável: o contrato desce na lista. A partição tem baixa cardinalidade (poucas faixas por tenant), e a documentação recomenda considerar sharding também em GSIs assim. Aqui o volume de escrita é baixo, então a decisão foi começar sem shard e alarmar as métricas de throttling do índice. Nem todo índice precisa de shard; todo índice precisa de alarme.&lt;/p&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Quando a mesa quer filtrar por tudo&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Se a supervisora pedir filtros livres (produto, cidade, faixa, canal, último contato, score), a resposta não é GSI3, GSI4 e GSI5. É um motor de busca, como o OpenSearch, alimentado pelo stream da tabela. DynamoDB é excelente em perguntas conhecidas e ruim em perguntas abertas. O erro de arquitetura é tentar transformá-lo, índice a índice, no que ele não é.&lt;/p&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;Onde GSI não serve: exatidão&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;GSI só oferece leitura eventualmente consistente. Na maior parte do tempo a propagação é rápida, mas “na maior parte do tempo” não é contrato. O GSI é o quadro de avisos que uma secretária atualiza copiando do livro oficial: quase sempre em dia, mas, quando o assunto é dinheiro, você consulta o livro. No Pix, o QR Code é gerado e pago em segundos. Se o webhook procurar a cobrança pelo &lt;code&gt;txid&lt;/code&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-anchor-19&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-19&quot;&gt;19&lt;/a&gt;num GSI, existe uma janela em que a cobrança recém-criada ainda não chegou ao índice. “Cobrança não encontrada” num webhook de liquidação é dinheiro sem dono.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!Zkxm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!Zkxm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png 424w, https://substackcdn.com/image/fetch/$s_!Zkxm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png 848w, https://substackcdn.com/image/fetch/$s_!Zkxm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png 1272w, https://substackcdn.com/image/fetch/$s_!Zkxm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;968&quot; height=&quot;398&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:456,&amp;quot;width&amp;quot;:1108,&amp;quot;resizeWidth&amp;quot;:968,&amp;quot;bytes&amp;quot;:63849,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!Zkxm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png 424w, https://substackcdn.com/image/fetch/$s_!Zkxm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png 848w, https://substackcdn.com/image/fetch/$s_!Zkxm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png 1272w, https://substackcdn.com/image/fetch/$s_!Zkxm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!Zkxm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F68104cf3-50ac-4374-ac5a-3f0f91304afe_1108x456.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;O índice é o quadro de avisos copiado do livro oficial. Quando o assunto é dinheiro, consulte o livro.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Por isso AP06 e AP07 são respondidos por &lt;strong&gt;itens de lookup na tabela base&lt;/strong&gt;, lidos com consistência forte:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;// Written in the same transaction that creates the charge
PK = &amp;quot;T#t042#CHG#&amp;lt;txid or boleto number&amp;gt;&amp;quot;     SK = &amp;quot;CHG&amp;quot;
     -&amp;gt; debtorKey, contractId, seq, amountCents, expiresAt

// Written in the same transaction that applies the payment
PK = &amp;quot;T#t042#STL#&amp;lt;settlementId&amp;gt;&amp;quot;              SK = &amp;quot;STL&amp;quot;
     -&amp;gt; amountCents, receivedAt, appliedTo&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;span&gt;O item de liquidação &lt;/span&gt;&lt;em&gt;é&lt;/em&gt;&lt;span&gt; a idempotência. Pense no carimbo de PAGO num boleto de papel: se a mesma via voltar ao caixa, o carimbo já está lá, e ninguém recebe duas vezes. O webhook vai chegar duas vezes: o provedor reenviou, o consumer reprocessou, alguém rodou o replay da DLQ. A transação que aplica o pagamento inclui um Put condicionado à inexistência desse item. Na segunda tentativa, a transação inteira é cancelada e o saldo não é baixado duas vezes.&lt;/span&gt;&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;csharp&amp;quot;,&amp;quot;nodeId&amp;quot;:&amp;quot;91b56051-ac34-4916-888b-4e59d58aa3e6&amp;quot;}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;import { TransactWriteCommand } from &amp;quot;@aws-sdk/lib-dynamodb&amp;quot;;

// Settlement webhook: apply a payment exactly once
await doc.send(new TransactWriteCommand({
  TransactItems: [
    {
      // 1. Idempotency: the settlement id IS the key
      Put: {
        TableName: TABLE,
        Item: {
          PK: `T#${tenantId}#STL#${settlementId}`,
          SK: &amp;quot;STL&amp;quot;,
          type: &amp;quot;SETTLEMENT&amp;quot;,
          amountCents,
          receivedAt,
          appliedTo: `${contractId}#${pad3(seq)}`,
        },
        ConditionExpression: &amp;quot;attribute_not_exists(PK)&amp;quot;,
      },
    },
    {
      // 2. Installment: optimistic lock on version
      Update: {
        TableName: TABLE,
        Key: { PK: debtorPk, SK: `CT#${contractId}#IN#${pad3(seq)}` },
        UpdateExpression:
          &amp;quot;SET paidCents = paidCents + :amt, #status = :newStatus, version = version + :one&amp;quot;,
        ConditionExpression: &amp;quot;version = :expected&amp;quot;,
        ExpressionAttributeNames: { &amp;quot;#status&amp;quot;: &amp;quot;status&amp;quot; },
        ExpressionAttributeValues: {
          &amp;quot;:amt&amp;quot;: amountCents,
          &amp;quot;:newStatus&amp;quot;: installmentTransition.to, // decided by the state machine, not here
          &amp;quot;:one&amp;quot;: 1,
          &amp;quot;:expected&amp;quot;: installmentVersion,
        },
      },
    },
    // 3. Contract header: new balance and delinquency fact.
    //    When cured: REMOVE oldestOverdueDueDate, #bucket, nextActionDate, nextAction
    //    -&amp;gt; the contract leaves the dunning agenda and the bucket portfolio in the same commit.
    { Update: contractUpdate(contract, installmentTransition, expectedContractVersion) },
  ],
}));&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Por que não usar o ClientRequestToken?&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;TransactWriteItems&lt;/code&gt; aceita um token de idempotência, mas ele vale por uma janela de dez minutos. Provedores de pagamento reenviam webhooks por horas; replays de DLQ acontecem dias depois. O token protege o retry do SDK. A idempotência de negócio precisa morar na chave, com a vida útil do próprio fato.&lt;/p&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;Limite de contato: a data na chave garante a regra; o TTL só faz a limpeza&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;No artigo sobre resiliência em sistemas de cobrança, falei do “guarda de trânsito” da régua: um limite diário de contatos por devedor, independentemente do canal. Na Lastro, ele é um item na coleção do devedor, incrementado de forma atômica e condicional:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;import { UpdateCommand } from &amp;quot;@aws-sdk/lib-dynamodb&amp;quot;;

// Contact gateway: atomically claim one of today&amp;apos;s allowed contacts
await doc.send(new UpdateCommand({
  TableName: TABLE,
  Key: { PK: debtorPk, SK: `LIMIT#${localDate}` }, // civil date in America/Sao_Paulo
  UpdateExpression: &amp;quot;SET expiresAt = if_not_exists(expiresAt, :ttl) ADD attempts :one&amp;quot;,
  ConditionExpression: &amp;quot;attribute_not_exists(attempts) OR attempts &amp;lt; :max&amp;quot;,
  ExpressionAttributeValues: {
    &amp;quot;:one&amp;quot;: 1,
    &amp;quot;:max&amp;quot;: policy.maxDailyContacts,
    &amp;quot;:ttl&amp;quot;: toEpochSeconds(addDays(localDate, 8)), // housekeeping only
  },
}));
// ConditionalCheckFailedException =&amp;gt; do NOT contact. It is not &amp;quot;retry later&amp;quot;.&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dois detalhes carregam o negócio. Primeiro, a data na chave é local, no fuso de São Paulo: o limite legal é por dia civil do devedor, não por dia UTC. Segundo, o TTL. &lt;strong&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;Deletes por TTL são gratuitos, mas assíncronos, e a documentação avisa que podem levar mais de 48 horas&lt;/mark&gt;&lt;/strong&gt;. Se a correção dependesse do TTL (um único item &lt;code&gt;LIMIT&lt;/code&gt; que “expira” à meia-noite), &lt;strong&gt;o devedor poderia ficar bloqueado por dois dias, ou o contador poderia zerar tarde demais&lt;/strong&gt;. Com a data na chave, o contador de amanhã é outro item, e o TTL só varre o lixo. &lt;strong&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;TTL é ferramenta de custo, nunca de correção.&lt;/mark&gt;&lt;/strong&gt; O TTL é o caminhão de lixo, que passa sem horário fixo. Ninguém espera o caminhão para saber se o leite venceu: a data está impressa na embalagem. Aqui, a data está na chave.&lt;/p&gt;&lt;p&gt;Cada tentativa também grava um item &lt;code&gt;CONTACT#{timestamp}&lt;/code&gt; com canal, template e resultado. É prova, não log. A pergunta “por que ele recebeu três SMS no domingo?” deixa de ser uma investigação e vira uma Query.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!QBn1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!QBn1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png 424w, https://substackcdn.com/image/fetch/$s_!QBn1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png 848w, https://substackcdn.com/image/fetch/$s_!QBn1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png 1272w, https://substackcdn.com/image/fetch/$s_!QBn1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;1320&quot; height=&quot;668&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:null,&amp;quot;imageSize&amp;quot;:null,&amp;quot;height&amp;quot;:668,&amp;quot;width&amp;quot;:1320,&amp;quot;resizeWidth&amp;quot;:null,&amp;quot;bytes&amp;quot;:124681,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:null,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!QBn1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png 424w, https://substackcdn.com/image/fetch/$s_!QBn1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png 848w, https://substackcdn.com/image/fetch/$s_!QBn1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png 1272w, https://substackcdn.com/image/fetch/$s_!QBn1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!QBn1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7d52dc31-f64b-4c46-b867-b0609460c6a6_1320x668.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Doze perguntas, cinco estruturas na tabela, uma fora dela. Nenhuma resposta depende de Scan.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;A imagem acima é o mapa que deveria estar anexado a todo ADR de modelagem em DynamoDB: pergunta de um lado, estrutura do outro, e nenhuma linha solta.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Pense comigo&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Qual dos seus índices, se ficasse lento amanhã às 00h07, impediria um cliente de pagar?&lt;/p&gt;&lt;p&gt;Se você não sabe responder, abra agora o painel de métricas dos seus GSIs. Não depois do incidente.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h4&gt;Pontos altos&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Cada índice existe para responder uma pergunta com nome e dono.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Índice esparso é o post-it na geladeira: sair da régua é arrancar o post-it, não rodar um job de limpeza.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Um índice sobrecarregado trava as escritas da tabela principal. A régua, que pode esperar, pode impedir o registro de um pagamento, que não pode.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;O shard espalha a carga como os caixas de um supermercado e, de brinde, define quantos workers o batch usa.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Nunca use como chave de índice um valor que muda só porque o calendário andou.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;O que precisa ser exato (pagamento aplicado uma única vez, limite legal de contatos, cobrança recém-criada) fica na tabela principal, lido com consistência forte.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;O TTL é o caminhão de lixo: limpa, mas não decide o que venceu. A regra mora na data da chave.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Quando a operação quer filtrar por qualquer coisa, a resposta é um motor de busca, não mais índices.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;&lt;strong&gt;5. Custo: onde o dinheiro realmente vaza&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Vamos dimensionar a Lastro com números redondos e premissas explícitas: 8 milhões de contratos ativos, em média 10 parcelas por contrato, 25% da carteira em cobrança (2 milhões de contratos), quarenta tenants, o maior deles com 60% do volume.&lt;/p&gt;&lt;p&gt;Primeiro, a verdade que desmonta metade das discussões de revisão: &lt;strong&gt;armazenamento quase nunca é a conta&lt;/strong&gt;. Contratos, parcelas, eventos, contatos e itens de lookup somam algo em torno de 95 GB. A US$ 0,25 por GB-mês na classe Standard, são cerca de US$ 24 por mês. Discutir abreviação de nome de atributo por causa de armazenamento é otimizar o centavo errado.&lt;/p&gt;&lt;p&gt;O dinheiro, e principalmente o risco, vaza em três lugares.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Vazamento 1: escrita sem evento&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Compare dois modelos para o mesmo requisito, “saber o atraso de cada contrato”, a US$ 0,625 por milhão de unidades de escrita (on-demand):&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:&amp;quot;fd7f4c13-dc98-4a54-8b6d-d7583b9a0503&amp;quot;}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;MODEL A: store daysPastDue, recompute nightly, daysPastDue in GSI2 sort key
         contract item at 1.1 KB (embedded promises and history)
  2M delinquent contracts x 30 nights            = 60M updates / month
  base table: 1.1 KB rounds to 2 KB -&amp;gt; 2 WRU     = 120M WRU
  GSI2 key change: delete + insert (~0.4 KB)     = 120M WRU
  total                                          = 240M WRU  ~ US$ 150 / month

MODEL B: store oldestOverdueDueDate, derive days past due at read time
         contract item under 1 KB, bucket changes at most once a month
  ~2M bucket moves / month x (1 base + 2 index)  = 6M WRU    ~ US$ 4 / month&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Imagine assim&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;O modelo A reemite, toda madrugada, o RG de dois milhões de pessoas só para atualizar a idade impressa. O modelo B imprime a data de nascimento uma única vez e deixa cada um fazer a conta quando precisar.&lt;/p&gt;&lt;p&gt;E repare no detalhe cruel do modelo A: quanto mais gente entra na inadimplência, maior fica a fila da gráfica.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Quarenta vezes mais caro. E, sendo honesto, ninguém é demitido por US$ 150 por mês. O custo real do modelo A não está na fatura; está no formato da carga. Ele cria, por design, um pico diário de escrita proporcional à inadimplência, concentrado em poucas chaves de baixa cardinalidade, na mesma janela em que a régua reagenda as ações. No modo on-demand, a tabela acomoda instantaneamente até o dobro do pico anterior; um salto além disso pode gerar throttling enquanto a capacidade se ajusta.&lt;/p&gt;&lt;p&gt;Agora pense no que acontece numa crise de crédito. A inadimplência sobe, o pico noturno do modelo A cresce junto, e o risco de throttling aumenta exatamente quando a cobrança mais importa. &lt;strong&gt;Um bom modelo não aumenta a própria carga com notícia ruim.&lt;/strong&gt; No modelo B, a carga cresce com eventos (pagamentos, contatos, acordos), não com o calendário.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;Vazamento 2: ler o que você não precisa&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;A primeira versão da tela de carteira quase sempre é um Scan com &lt;code&gt;FilterExpression&lt;/code&gt;. O filtro dá a sensação de que você só paga pelo que volta. Não é assim: a capacidade consumida é calculada sobre os itens lidos, não sobre os devolvidos. É uma biblioteca que cobra por cada livro que o bibliotecário tirou da estante procurando o seu, e não pelo único livro que você levou para casa.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:&amp;quot;d41e62ca-6007-485e-ae1d-66c334245e64&amp;quot;}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Eventually consistent Scan over 95 GB
  95 GB / 4 KB x 0.5                             ~ 12.5M RRU  ~ US$ 1.56 per scan
Supervisor screen opened hourly by 40 tenants
  40 x 24 h x 30 days = 28,800 scans / month     ~ US$ 45,000 / month

Same screen on GSI2, 100 contracts per page (~0.3 KB each)
  30 KB / 4 KB x 0.5                             ~ 4 RRU per page  -&amp;gt; cents per month&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Esse é o único número do artigo que realmente assusta, e ele não vem de um recurso caro do DynamoDB. Vem de uma pergunta sem estrutura para respondê-la.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!dJl8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!dJl8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png 424w, https://substackcdn.com/image/fetch/$s_!dJl8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png 848w, https://substackcdn.com/image/fetch/$s_!dJl8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png 1272w, https://substackcdn.com/image/fetch/$s_!dJl8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;994&quot; height=&quot;383&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/8c786871-2747-4862-a45c-0b415819428f_1143x441.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:441,&amp;quot;width&amp;quot;:1143,&amp;quot;resizeWidth&amp;quot;:994,&amp;quot;bytes&amp;quot;:49457,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/217078847?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!dJl8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png 424w, https://substackcdn.com/image/fetch/$s_!dJl8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png 848w, https://substackcdn.com/image/fetch/$s_!dJl8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png 1272w, https://substackcdn.com/image/fetch/$s_!dJl8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!dJl8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c786871-2747-4862-a45c-0b415819428f_1143x441.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;A biblioteca cobra por cada livro que o bibliotecário tirou da estante, não pelo que você levou para casa....&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;Vazamento 3: fronteiras de tamanho e transações por reflexo&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;Escritas são cobradas em blocos de 1 KB; leituras, em blocos de 4 KB. Um cabeçalho de contrato com 1,02 KB paga duas unidades em toda atualização, para sempre, como na tabela dos Correios, em que um pacote de 1,02 kg paga a tarifa de 2 kg. Por isso o histórico sai do item (seção 3), e por isso a projeção dos índices é enxuta.&lt;/p&gt;&lt;p&gt;Transações custam o dobro: cada item é preparado e confirmado. É o reconhecimento de firma em cartório: vale quando o documento exige, e é desperdício quando uma assinatura simples resolve. Use-as apenas quando a invariante atravessa itens, como na liquidação (item de idempotência, parcela e contrato precisam mudar juntos, ou nenhum muda). O contador de contatos não precisa de transação; um &lt;code&gt;ADD&lt;/code&gt; condicional num único item já é atômico. E lembre que escrita condicional que falha também consome capacidade: cada webhook duplicado custa uma escrita. É um seguro barato, mas entra na conta.&lt;/p&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Custo como teste automatizado&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Use &lt;code&gt;ReturnConsumedCapacity: &amp;quot;INDEXES&amp;quot;&lt;/code&gt; nos testes de integração e declare um orçamento por operação de negócio: “aplicar pagamento consome no máximo 8 WRU”, “abrir a visão 360 consome no máximo 4 RRU”. Quando alguém embutir um array no item ou projetar &lt;code&gt;ALL&lt;/code&gt; num índice, o CI quebra antes da fatura.&lt;/p&gt;&lt;/div&gt;&lt;h3&gt;&lt;strong&gt;Modo de capacidade e classe de tabela&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;O perfil da Lastro (picos em dia de pagamento, lote noturno, vales no resto do dia) favorece on-demand, cujo preço caiu pela metade em novembro de 2024. Provisionado com auto scaling fica em torno de três vezes mais barato por requisição quando a utilização é alta e estável, então revisite a decisão quando o tráfego amadurecer. Antes de picos previsíveis, como o dia do INSS, a AWS permite pré-aquecer a capacidade de tabelas e índices (warm throughput).&lt;/p&gt;&lt;p&gt;Para o histórico, a documentação de fundamentos é clara: uma tabela que mistura dado operacional e histórico não aproveita bem a classe Standard-Infrequent Access. Contratos encerrados, eventos antigos e evidências de contato com mais de alguns meses vão para uma tabela separada em Standard-IA, com armazenamento a US$ 0,10 por GB-mês contra US$ 0,25, e requisições cerca de 25% mais caras. O caminho natural é TTL na tabela quente, stream configurado com imagem antiga, e uma Lambda que reconhece o delete feito pelo próprio serviço e arquiva no destino frio. Tabelas separadas também permitem políticas de backup e de recuperação diferentes para dados de criticidade diferente.&lt;/p&gt;&lt;h3&gt;&lt;strong&gt;A pergunta de unit economics&lt;/strong&gt;&lt;/h3&gt;&lt;div data-callout=&quot;true&quot;&gt;&lt;p&gt;&lt;strong&gt;Unit economics.&lt;/strong&gt;&lt;span&gt; Quanto custa, e quanto rende, uma unidade do negócio; aqui, um contrato ativo por mês. É o que diz se o preço cobrado de cada tenant cobre o custo de atendê-lo, como saber o custo de cada prato antes de fechar o preço do cardápio.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Num BaaS, o número que importa não é a fatura do DynamoDB; é o custo por contrato ativo por mês, porque é ele que entra na precificação para o tenant. Somando escrita (régua, contatos, liquidações, eventos), leitura, armazenamento, PITR e o export mensal, a Lastro fica na casa de US$ 200 por mês em us-east-1. Algo como dois centavos de dólar por mil contratos.&lt;/p&gt;&lt;p&gt;Agora compare com o custo operacional da própria cobrança: 2 milhões de contratos em cobrança, dez tentativas de contato por mês, metade por SMS a R$ 0,05, são R$ 500 mil por mês só em SMS. O banco custa menos do que algumas horas de disparo.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;A conclusão não é “DynamoDB é barato, relaxa”. É que o valor do modelo está nas decisões de negócio que ele torna corretas: o contador que impede o quarto contato economiza mais, em SMS e em processo judicial, do que qualquer otimização de byte.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;&lt;span&gt;Pense comigo&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Você sabe quanto custa, em unidades de escrita, aplicar um pagamento na sua plataforma?&lt;/p&gt;&lt;p&gt;Se não sabe, também não sabe quanto vai custar o próximo quinto dia útil, nem se ele vai caber na capacidade que você tem.&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h4&gt;Pontos altos&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Guardar dado custa pouco. O que custa é escrever sem motivo e ler o que não precisava.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;A carga do banco deve crescer com o que acontece (pagamentos, contatos, acordos), não com a passagem dos dias, nem com o aumento da inadimplência.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Scan com filtro cobra cada livro tirado da estante, não os que você levou.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Transação custa o dobro, como reconhecer firma: use só quando vários itens precisam mudar juntos ou nenhum muda.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;As faixas de 1 KB e 4 KB funcionam como a tabela de peso dos Correios. Ponha no CI um teste que mede o consumo de cada operação de negócio.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;O histórico vai para uma tabela própria, na classe mais barata para dado pouco acessado.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Meça o custo por contrato ativo e compare com o custo de uma decisão de cobrança errada.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;&lt;strong&gt;6. A tabela é o estado; o stream é a história&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;A tabela responde “como está agora”. O resto da plataforma quer saber “o que aconteceu”: negativação, contabilidade, notificações, o motor de busca da mesa, o lakehouse. O DynamoDB Streams, consumido por Lambda com filtros de evento, publica as transições como eventos de domínio (&lt;code&gt;InstallmentOverdue&lt;/code&gt;, &lt;code&gt;PromiseBroken&lt;/code&gt;, &lt;code&gt;AgreementSigned&lt;/code&gt;, &lt;code&gt;SettlementApplied&lt;/code&gt;) em EventBridge ou Kafka.&lt;/p&gt;&lt;p&gt;No single-table design, toda mudança de todo tipo de item passa pelo mesmo stream. A documentação lembra que, com os filtros de evento da Lambda, isso não pesa na fatura; com outros consumidores, pesa. Filtre por &lt;code&gt;type&lt;/code&gt; na borda.&lt;/p&gt;&lt;p&gt;O cuidado de modelagem aqui é sutil: &lt;strong&gt;o stream mostra o antes e o depois, mas não o porquê&lt;/strong&gt;. Ele é a câmera de segurança da portaria: grava que a porta abriu às 14h02, mas não se foi o morador, o entregador ou o síndico. O motivo precisa estar no livro de registro. Se o contrato mudou de &lt;code&gt;IN_COLLECTION&lt;/code&gt; para &lt;code&gt;ACTIVE&lt;/code&gt;, foi pagamento, acordo ou estorno? O consumer não tem como saber, a menos que o item carregue essa informação. É mais um motivo para &lt;code&gt;statusReason&lt;/code&gt; existir: ele não serve só para a tela, serve para que o evento derivado da mudança tenha significado.&lt;/p&gt;&lt;p&gt;E as perguntas analíticas da seção 1 nunca tocam a tabela. O export incremental para o S3 é cobrado por GB de dado da tabela e não consome capacidade de leitura; aging, roll rate e relatórios regulatórios rodam em Athena ou no lakehouse, sobre dados que o time de dados pode consultar livremente sem acordar ninguém de plantão.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h4&gt;Pontos altos&lt;/h4&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;A tabela guarda o agora, o stream conta o que aconteceu, e o S3 atende as análises.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;O stream é a câmera da portaria: mostra que a porta abriu, não por quê. O motivo precisa estar gravado no item.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;BI nunca faz Scan na tabela que atende a operação; usa o export.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;&lt;strong&gt;7. A revisão de design que eu faria no seu PR&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Se você é tech lead ou arquiteto e vai revisar um modelo em DynamoDB, estas são as perguntas que eu levaria para a sala. Nenhuma delas é sobre sintaxe.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Onde está a lista de padrões de acesso, e quem do negócio a validou?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Qual pergunta vocês decidiram não responder na tabela, e para onde ela vai?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Qual atributo muda sem que nada aconteça no negócio? Por que ele está gravado em vez de derivado?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Alguma chave, da tabela ou de índice, contém dado pessoal?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Qual será o maior tenant daqui a dois anos, e qual chave ele esquenta primeiro?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Qual GSI, se sofrer throttling, bloqueia uma escrita crítica na tabela base?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Quais perguntas exigem leitura forte, e por que nenhuma delas depende de GSI?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Onde mora a idempotência de cada comando externo, e qual é a janela real de retry do provedor?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Quanto custa, em unidades de capacidade, cada operação de negócio, e existe teste para isso?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Qual é o plano para histórico, retenção, rotação do segredo de chave e offboarding de tenant?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;&lt;strong&gt;Voltando à segunda-feira, 8h40&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;Com o modelo da Lastro, as três perguntas da head de cobrança têm dono.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;“Quantos contratos viraram 31+ no fim de semana?” Operacionalmente, uma Query na faixa &lt;code&gt;D031_060&lt;/code&gt; do tenant no GSI2, restrita por &lt;code&gt;oldestOverdueDueDate&lt;/code&gt;, que lê a faixa e não a tabela. Analiticamente, a contagem dos eventos de mudança de faixa que o stream já publicou.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;“Quanto disso tem promessa para esta semana?” Consultas no GSI1 com &lt;code&gt;nextAction = PROMISE_CHECK&lt;/code&gt;, uma por dia da semana, em cada shard, pelos mesmos workers que já leem a régua.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;“Por que ele recebeu três SMS no domingo?” Uma Query na coleção do devedor com o prefixo &lt;code&gt;CONTACT#2026-09-20&lt;/code&gt;, ao lado do contador &lt;code&gt;LIMIT#2026-09-20&lt;/code&gt;. Se foram três, a prova está ali, e o limite respeitado (ou violado) também.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;No relacional, o schema descreve o dado, e as perguntas se resolvem depois. No DynamoDB, o schema descreve as perguntas. Em cobrança, as perguntas são o negócio: quem deve, desde quando, quanto, o que foi prometido, o que foi feito e o que a lei permite fazer a seguir. Um modelo que responde a isso com uma Query por pergunta não é só rápido e barato. É auditável, explicável e defensável.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt;Toda chave é uma pergunta. Garanta que alguém do negócio a tenha feito.&lt;/mark&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Se você opera cobrança, crédito ou pagamentos em DynamoDB, me conta nos comentários: qual foi a pergunta que a sua tabela não soube responder?&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/subscribe?&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Inscreva-se&amp;quot;,&amp;quot;language&amp;quot;:&amp;quot;pt-br&amp;quot;}&quot; data-component-name=&quot;SubscribeWidgetToDOM&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Inscreva-se gratuitamente para receber novas publicações e apoiar meu trabalho.&lt;/p&gt;&lt;/div&gt;&lt;input tabindex=&quot;-1&quot; type=&quot;email&quot; name=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-email&quot; placeholder=&quot;Digite seu e-mail…&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-1&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-1&quot; contenteditable=&quot;false&quot;&gt;1&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Crediário.&lt;/strong&gt;&lt;span&gt; Venda parcelada em que a própria loja, ou a financeira ligada a ela, empresta o dinheiro ao cliente. É o velho caderninho do fiado da mercearia, só que com contrato, juros, CPF e, claro, uma operação de cobrança por trás.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-2&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-2&quot; contenteditable=&quot;false&quot;&gt;2&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;31+ e faixas de atraso.&lt;/strong&gt;&lt;span&gt; Jargão para &amp;quot;contratos com 31 dias ou mais de atraso&amp;quot;. O mercado agrupa a inadimplência em faixas (1 a 30, 31 a 60, 61 a 90 e acima de 90 dias), e cada faixa pede um tratamento diferente. Pense num termômetro: 37,5 °C pede atenção, 39 °C pede médico. Com cinco dias de atraso, um lembrete gentil resolve; com 45, a conversa já é sobre acordo; depois de 90, o contrato costuma seguir para negativação e para perda. &amp;quot;Virar 31+&amp;quot; é atravessar a linha da primeira para a segunda faixa: o sinal de que o atraso deixou de ser esquecimento.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-3&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-3&quot; contenteditable=&quot;false&quot;&gt;3&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Promessa de pagamento.&lt;/strong&gt;&lt;span&gt; Quando o devedor diz &amp;quot;pago no dia 26&amp;quot;, a cobrança registra a data, pausa as ações até lá e confere, no dia, se o dinheiro entrou. Se não entrou, é uma promessa quebrada, um dos sinais de risco mais fortes que existem. É o fiado com data marcada.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-4&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-4&quot; contenteditable=&quot;false&quot;&gt;4&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Carteira.&lt;/strong&gt;&lt;span&gt; O conjunto de todos os contratos de crédito que uma instituição tem a receber. Imagine um pomar: cada árvore é um contrato, e a gestora olha a saúde do pomar inteiro (quantas árvores estão doentes, em que estágio, onde a praga avança), não só a de uma fruta.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-5&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-5&quot; contenteditable=&quot;false&quot;&gt;5&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;BaaS e tenant.&lt;/strong&gt;&lt;span&gt; Banking as a Service: uma empresa que oferece a infraestrutura financeira para que outras marcas operem crédito, conta ou cobrança sem construir tudo do zero. Pense numa cozinha industrial compartilhada, as chamadas &lt;/span&gt;&lt;em&gt;dark kitchens&lt;/em&gt;&lt;span&gt;: várias marcas de restaurante usam a mesma cozinha, cada uma com cardápio, clientes e receitas próprios, e o pedido de uma nunca pode sair com a comida da outra. Cada marca cliente da plataforma é um &lt;/span&gt;&lt;em&gt;tenant&lt;/em&gt;&lt;span&gt;, &amp;quot;inquilino&amp;quot; em inglês.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-6&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-6&quot; contenteditable=&quot;false&quot;&gt;6&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;SCD.&lt;/strong&gt;&lt;span&gt; Sociedade de Crédito Direto: um tipo de fintech autorizada pelo Banco Central a emprestar dinheiro usando capital próprio.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-7&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-7&quot; contenteditable=&quot;false&quot;&gt;7&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Consignado.&lt;/strong&gt;&lt;span&gt; Empréstimo cuja parcela é descontada direto do salário ou do benefício do INSS, antes de o dinheiro chegar à conta. Como o pagamento é quase automático, o risco é menor, e os juros também.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-8&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-8&quot; contenteditable=&quot;false&quot;&gt;8&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Régua de cobrança.&lt;/strong&gt;&lt;span&gt; A sequência programada de ações conforme os dias de atraso: lembrete no primeiro dia, SMS no quinto, ligação no décimo quinto, proposta de acordo no quadragésimo quinto, e assim por diante. Funciona como a caderneta de vacinação: cada idade tem a sua vacina, cada dia de atraso tem a sua ação. O &lt;/span&gt;&lt;code&gt;SMS_D45&lt;/code&gt;&lt;span&gt; do exemplo é &amp;quot;o SMS do 45º dia de atraso&amp;quot;.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-9&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-9&quot; contenteditable=&quot;false&quot;&gt;9&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Mesa de cobrança.&lt;/strong&gt;&lt;span&gt; A equipe humana da cobrança: operadores que ligam, negociam e registram acordos, e supervisores que acompanham a carteira e as metas. É o call center da cobrança, com roteiros e indicadores próprios.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-10&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-10&quot; contenteditable=&quot;false&quot;&gt;10&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Liquidação, baixa e conciliação.&lt;/strong&gt;&lt;span&gt; Liquidação é o dinheiro efetivamente entrando. O webhook de liquidação é o aviso automático que o banco ou o provedor de pagamento envia quando isso acontece: o &amp;quot;plim&amp;quot; do caixa. Dar baixa é marcar a parcela como paga. Conciliar é casar cada valor recebido com a cobrança que o originou, como conferir o extrato do cartão com as notas fiscais do mês: nenhum real pode ficar sem dono.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-11&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-11&quot; contenteditable=&quot;false&quot;&gt;11&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Aging e roll rate.&lt;/strong&gt;&lt;span&gt; Aging é a foto da carteira distribuída pelas faixas de atraso num dia específico. Roll rate mede quanto de uma faixa &amp;quot;escorrega&amp;quot; para a seguinte de um mês para o outro, por exemplo, quantos contratos que estavam com 1 a 30 dias chegaram a 31 a 60. Se o aging é a foto de uma escada com pessoas em cada degrau, o roll rate mostra quantas desceram um degrau desde a última foto.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-12&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-12&quot; contenteditable=&quot;false&quot;&gt;12&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Quinto dia útil e pagamento do INSS.&lt;/strong&gt;&lt;span&gt; Datas em que salários (pagos até o quinto dia útil do mês, pela CLT) e benefícios do INSS caem na conta. Com dinheiro na mão, muita gente paga as contas no mesmo dia, e o volume de pagamentos dispara. Para a cobrança, são os dias de Black Friday.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-13&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-13&quot; contenteditable=&quot;false&quot;&gt;13&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Provisão.&lt;/strong&gt;&lt;span&gt; Uma reserva contábil que a instituição separa pensando nas perdas que espera ter; quanto maior o atraso, maior a reserva. É o envelope de &amp;quot;imprevistos&amp;quot; do orçamento doméstico, só que calculado com regras do Banco Central.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-14&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-14&quot; contenteditable=&quot;false&quot;&gt;14&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Negativação.&lt;/strong&gt;&lt;span&gt; A inclusão do CPF do devedor nos birôs de crédito (Serasa, SPC, Boa Vista), que outras empresas consultam antes de conceder crédito. O CDC exige comunicação prévia por escrito ao consumidor, e um erro aqui costuma virar processo. É o &amp;quot;nome no mural&amp;quot; que todo o comércio consulta, e por isso não admite engano: negativar quem já pagou é dano quase certo.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-15&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-15&quot; contenteditable=&quot;false&quot;&gt;15&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Prorrogação.&lt;/strong&gt;&lt;span&gt; Mudar a data de vencimento de uma parcela, por pedido do cliente, por política comercial ou dentro de uma renegociação. Pense numa consulta médica remarcada: o prontuário precisa registrar a data original e a nova, senão ninguém sabe depois se o paciente faltou ou se a clínica mudou a agenda. Quando alguém pergunta &amp;quot;quanto da carteira foi prorrogada?&amp;quot;, quer medir quanta dívida foi empurrada para frente, porque prorrogação em excesso pode esconder inadimplência real: o contrato parece em dia só porque o vencimento mudou de lugar.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-16&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-16&quot; contenteditable=&quot;false&quot;&gt;16&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Imputação do pagamento e principal.&lt;/strong&gt;&lt;span&gt; Principal é o valor emprestado; juros são o preço desse empréstimo. Imputação é a regra que decide qual parte da dívida o dinheiro quita primeiro. Imagine baldes enchidos em sequência: o pagamento enche primeiro o balde dos juros vencidos, e só o que sobra cai no balde do principal. Sem os valores separados no dado, não há como provar em qual balde cada real caiu.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-17&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-17&quot; contenteditable=&quot;false&quot;&gt;17&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Multa moratória e juros de mora.&lt;/strong&gt;&lt;span&gt; Dois encargos diferentes pelo mesmo atraso. A multa é cobrada uma única vez, como uma taxa de religação; em relações de consumo, não pode passar de 2% do valor da prestação. Os juros de mora correm enquanto o atraso durar, como um taxímetro ligado. Guardar os dois separados é o que permite provar que o teto foi respeitado.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-18&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-18&quot; contenteditable=&quot;false&quot;&gt;18&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Acordo.&lt;/strong&gt;&lt;span&gt; Uma renegociação: as parcelas em atraso são substituídas por uma nova condição, geralmente com desconto de encargos, uma entrada e um novo parcelamento. Se o cliente deixa de pagar o acordo, ele &amp;quot;quebra&amp;quot;, e o contrato volta para a régua.&lt;/span&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-2YXf2qyplhEPRUqSUWo_MMN1MUU-footnote-19&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/dynamodb-motor-de-cobranca#footnote-anchor-19&quot; contenteditable=&quot;false&quot;&gt;19&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;txid, nosso número e E2E.&lt;/strong&gt;&lt;span&gt; Os códigos de rastreio do dinheiro. O txid identifica uma cobrança Pix, o QR Code que você escaneia. O nosso número identifica um boleto dentro do banco que o emitiu. O E2E (end-to-end ID) identifica uma transação Pix específica no momento em que ela é liquidada. É como o número de rastreio de uma encomenda: é por ele que se descobre de onde o pacote veio e para quem ele vai.&lt;/span&gt;&lt;/p&gt;&lt;h2&gt;&lt;strong&gt;Fontes e leituras&lt;/strong&gt;&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;AWS, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/data-modeling.html&quot;&gt;Data modeling for DynamoDB tables&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;AWS, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-general-nosql-design.html&quot;&gt;NoSQL design for DynamoDB&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;AWS, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/data-modeling-foundations.html&quot;&gt;Data modeling foundations: single table and multiple table design&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;AWS, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/data-modeling-blocks.html&quot;&gt;Data modeling building blocks: composite sort key, multi-tenancy, sparse index, TTL, vertical partitioning, write sharding&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;AWS, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Constraints.html&quot;&gt;Constraints in Amazon DynamoDB&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;AWS, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html&quot;&gt;Using Global Secondary Indexes in DynamoDB&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;AWS, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://aws.amazon.com/about-aws/whats-new/2025/11/amazon-dynamodb-multi-attribute-composite-keys-global-secondary-indexes&quot;&gt;Multi-attribute composite keys in global secondary indexes (nov/2025)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;AWS, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://aws.amazon.com/dynamodb/pricing/&quot;&gt;Amazon DynamoDB pricing&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Rafael Miguel, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/um-breve-mergulho-dynamodb&quot;&gt;Um breve mergulho no DynamoDB&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Rafael Miguel, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/quando-orientacao-a-objetos-estraga&quot;&gt;Quando Orientação a Objetos Estraga Seu Modelo de Dados&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Rafael Miguel, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/engenharia-de-credito-implementando&quot;&gt;Engenharia de Crédito: Implementando Máquinas de Estado&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Rafael Miguel, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/desafios-de-arquitetura-e-resiliencia&quot;&gt;Desafios de Arquitetura e Resiliência em Sistemas de Cobrança&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;</summary><author><name>Rafael Miguel</name></author><source gr:stream-id="feed/https://chroniclesofapragmaticprogrammer.substack.com/feed"><id>tag:google.com,2005:reader/feed/https://chroniclesofapragmaticprogrammer.substack.com/feed</id><title type="html">Chronicles of a Pragmatic Programmer</title><link rel="alternate" href="https://chroniclesofapragmaticprogrammer.substack.com" type="text/html"></link></source></entry></feed>