<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yousra S</title>
    <description>The latest articles on DEV Community by Yousra S (@yousrasd).</description>
    <link>https://dev.to/yousrasd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3103571%2F98b09758-0091-42b7-bb76-3078a546b428.jpeg</url>
      <title>DEV Community: Yousra S</title>
      <link>https://dev.to/yousrasd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yousrasd"/>
    <language>en</language>
    <item>
      <title>Weekend Challenge: Earth Day Edition - Cleaning our Earth</title>
      <dc:creator>Yousra S</dc:creator>
      <pubDate>Mon, 20 Apr 2026 01:00:46 +0000</pubDate>
      <link>https://dev.to/yousrasd/weekend-challenge-earth-day-edition-cleaning-our-earth-o1n</link>
      <guid>https://dev.to/yousrasd/weekend-challenge-earth-day-edition-cleaning-our-earth-o1n</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-04-16"&gt;Weekend Challenge: Earth Day Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Have you ever walked through a street or a park, spotted a plastic bag tangled in a bush, a few bottles scattered on the grass and felt that small but familiar pang of helplessness? You might pick one up. But then you wonder: what about the rest? What if others could see this too? What if we could work on it together?&lt;/p&gt;

&lt;p&gt;Keeping our planet clean is one step closer to taking better care of this beautiful home we all share. This is what Terrae helps us do.&lt;/p&gt;

&lt;p&gt;Terrae is an Android app that lets people report and clean up trash together. You spot litter, you open the app, take a photo, and in seconds Google Gemini AI has analyzed the severity and pinned it to a shared map. Someone nearby can claim the spot, take an after-photo to prove it's clean, and earn points and badges. &lt;/p&gt;

&lt;p&gt;Core features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Report&lt;/strong&gt;: Photo a spot that has trash → Gemini 2.5 Flash rates severity (LOW / MEDIUM / HIGH), generates a description, and pins it to the map&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Map&lt;/strong&gt;: Real-time Google Maps markers color-coded by severity, with a peek-able bottom sheet showing nearby reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed&lt;/strong&gt;: Image-first list of reports sorted by distance
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4-Gate Clean Validation&lt;/strong&gt;: the app enforces four gates before awarding points when requesting to clean a place:

&lt;ol&gt;
&lt;li&gt;You can't clean your own report&lt;/li&gt;
&lt;li&gt;You must be within 100m of the trash location&lt;/li&gt;
&lt;li&gt;You must take an after-photo as proof&lt;/li&gt;
&lt;li&gt;Gemini AI must confirm the area actually looks clean
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Points + Badges&lt;/strong&gt;: Reporting earns 5–20 pts, cleaning earns 10–50 pts (scaled by severity). Badges: First Report, First Clean, Earth Warden (5 cleans), Green Guardian (10 cleans)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profile&lt;/strong&gt;: Points counter, stats, badge grid, share CTA&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;flow when user cleans a place&lt;/th&gt;
&lt;th&gt;flow when reporting a place&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/yZRNPYIzHcM"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;
&lt;/td&gt;
&lt;td&gt;  &lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/hqrxT3FIkf4"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Pictures of the different app screens:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Map Screen&lt;/th&gt;
&lt;th&gt;Feed Screen&lt;/th&gt;
&lt;th&gt;Profile Screen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckqvgaoyn2qlyaanemb1.jpg" alt=" "&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexan62ifluzhnkfix9wy.jpg" alt=" "&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hwimgt8773k7if9uj8q.jpg" alt=" "&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yousrasd" rel="noopener noreferrer"&gt;
        yousrasd
      &lt;/a&gt; / &lt;a href="https://github.com/yousrasd/Terrae" rel="noopener noreferrer"&gt;
        Terrae
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An Android app to keep our Earth clean by bringing people together to remove trash they find
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Terrae&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Terrae is an Android app for reporting trash hotspots and coordinating cleanup. Users can capture or select a photo, let Gemini classify the severity, publish the report to a shared map and feed, and earn points and badges by contributing cleanups.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What the app does&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Report trash&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Start from the map&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Camera&lt;/strong&gt; or &lt;strong&gt;Gallery&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Run Gemini image analysis&lt;/li&gt;
&lt;li&gt;Confirm and publish the report&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browse nearby reports&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;View reports on the map with severity-colored markers&lt;/li&gt;
&lt;li&gt;Browse the same data in the feed&lt;/li&gt;
&lt;li&gt;Open a report detail view from map/feed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track cleanup progress&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Open a report&lt;/li&gt;
&lt;li&gt;Tap &lt;strong&gt;I'll Clean This&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Confirm readiness from a bottom sheet before camera capture&lt;/li&gt;
&lt;li&gt;Validate cleanup with proximity checks, photo proof, and Gemini verification&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Earn rewards&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Reporting and cleaning award points&lt;/li&gt;
&lt;li&gt;User profile tracks reports, cleans, and badges&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Core product flows&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Report flow&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Map -&amp;gt; Report -&amp;gt; Source picker -&amp;gt; Camera/Gallery -&amp;gt; AI analysis…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yousrasd/Terrae" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Stack
&lt;/h3&gt;

&lt;p&gt;Kotlin + Jetpack Compose for the UI, Firebase (Firestore, Storage, anonymous Auth) for the backend, Google Maps Compose for the shared map, Gemini 2.5 Flash via OkHttp for the AI brain and Copilot CLI for agentic development.&lt;/p&gt;

&lt;p&gt;Architecture is MVVM with a use case layer: domain interfaces → use cases → single ViewModel → Compose screens. Hilt wires everything together. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Workflow
&lt;/h3&gt;

&lt;p&gt;I used &lt;strong&gt;GitHub Copilot agent mode with three custom agents&lt;/strong&gt;, each with a specific role, all grounded by a shared &lt;code&gt;copilot-instructions.md&lt;/code&gt; that encoded the full architecture, data models, screen structure, and coding conventions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;android-implementor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Implements each story: writes the Kotlin/Compose code, write unit and component tests, verifies the build compiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maestro-qa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;After each story ships, writes and runs Maestro UI test flows against a real physical device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;doc-agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Invoked once at the end to generate project documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pipeline per story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plan story → android-implementor builds it → I review + validate →
maestro-qa writes &amp;amp; runs tests → tests pass → next story
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I started by using Copilot Cli's plan mode to break the project into 16 stories with a clear dependency graph. From there, I fed each story to the dev agent. It would implement, confirm the build succeeded, and present what it built. I'd review, test on my physical device, and give the go-ahead. Then the QA agent automated the verification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I stayed in the loop as the decision-maker at every handoff.&lt;/strong&gt; The agents knew the architecture (from &lt;code&gt;copilot-instructions.md&lt;/code&gt;), respected the patterns, and flagged when something didn't fit. I orchestrated the flow and validated every step.&lt;/p&gt;

&lt;p&gt;It was bit challenging at times. One challenge was keeping the agents aligned with the architecture. Early on, they would drift and generate inconsistent patterns, so I had to reinforce strict rules inside copilot-instructions.md.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Google Gemini&lt;/strong&gt;&lt;br&gt;
Gemini 2.5 Flash does two critical jobs in Terrae through two dedicated agents:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ScanAgent&lt;/strong&gt; — When you report a trash spot, Gemini looks at your photo and returns a structured JSON response with severity (LOW/MEDIUM/HIGH), a human-readable description, and a confidence score. That severity drives the map marker color and the points reward. A stray bottle cap is LOW (5 pts to report, 10 to clean). A scattered pile of bags and debris is HIGH (20 pts to report, 50 to clean).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CleanAgent&lt;/strong&gt; — When you claim to have cleaned a spot, Gemini looks at your after-photo and decides whether the area actually looks clean. It returns &lt;code&gt;isClean&lt;/code&gt;, &lt;code&gt;confidence&lt;/code&gt;, and &lt;code&gt;reason&lt;/code&gt;. The threshold is 0.6 — below that, the clean is rejected and the user is told why.&lt;/p&gt;

&lt;p&gt;Both agents use the same model (&lt;code&gt;gemini-2.5-flash&lt;/code&gt;) via OkHttp with base64-encoded images and structured text prompts. Both return JSON that the app parses into typed Kotlin data classes.&lt;/p&gt;

&lt;p&gt;Thanks to Gemini, the app can autonomously judge severity of pictures, and validate that work was actually done.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of GitHub Copilot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I used Copilot's agent mode with three custom agents, each with a defined role, all grounded in a shared copilot-instructions.md that encoded the full architecture, data models, and conventions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;android-implementor — the dev agent, implementing each story in Kotlin/Compose&lt;/li&gt;
&lt;li&gt;maestro-qa — the QA agent, writing and running Maestro UI tests against a real device after each story&lt;/li&gt;
&lt;li&gt;doc-agent — the documentation agent, invoked once at the end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I first started the plan mode through Copilot CLI and came up with solid plan thanks to copilot. Once the plan was defined and stories broken down,&lt;br&gt;
the pipeline per story was then: dev agent implements (and makes sure the build is successful) → I review and validate  → QA agent runs tests → tests pass → next story. I stayed in the loop as the decision-maker at every step.  &lt;/p&gt;

&lt;p&gt;At the end, doc agent writes final doc of the entire project.&lt;/p&gt;

&lt;p&gt;What i liked about this workflow is that, even though I delegated all the work to AI, I was there orchestrating the different flows (dev -&amp;gt; qa-&amp;gt; doc) and validating any AI work. &lt;br&gt;
Copilot CLI made the entire developement workflow fun.  &lt;/p&gt;

&lt;p&gt;You can see the details workflow &lt;a href="https://github.com/yousrasd/Terrae/tree/feat/feed-hide-description/.github" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;That was a fun challenge. Thanks DEV community for such a great theme idea.&lt;/p&gt;

&lt;p&gt;I would love to keep building on it. For example, adding actual account creation, a leaderboard.&lt;br&gt;&lt;br&gt;
And of course expand it to iOS. Right now it's Android only, but the idea works on any phone.  &lt;/p&gt;

&lt;p&gt;This was one of the most fun builds I’ve done in a while, especially experimenting with AI agents in the loop for mobile app developement.&lt;/p&gt;

&lt;p&gt;I'm curious to hear from other developers. How are you integrating AI agents into your daily coding workflow? &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>Avoiding Stale Data: Redis Caching Gotchas</title>
      <dc:creator>Yousra S</dc:creator>
      <pubDate>Mon, 16 Jun 2025 01:33:47 +0000</pubDate>
      <link>https://dev.to/yousrasd/avoiding-stale-data-redis-caching-gotchas-c1h</link>
      <guid>https://dev.to/yousrasd/avoiding-stale-data-redis-caching-gotchas-c1h</guid>
      <description>&lt;p&gt;Caching data with Redis can lead to stale data if the right operations aren't used carefully ⚠️🕰️.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Redis? 🚀
&lt;/h2&gt;

&lt;p&gt;For those unfamiliar, Redis is an open-source, in-memory data structure store used as a database, cache, and message broker. It supports data structures such as strings, hashes, lists, sets, and more.&lt;/p&gt;

&lt;p&gt;Its most appealing feature is speed — Redis keeps data in memory, making it incredibly fast for reads and writes. It's a popular choice for caching data that needs to be accessed quickly and frequently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redis Gotchas ⚠️🐍
&lt;/h2&gt;

&lt;p&gt;One Redis operation, HSET, doesn’t allow you to set an expiry directly. You have to invoke a second operation, EXPIRE, to set a TTL (time-to-live) for the cache.&lt;/p&gt;

&lt;p&gt;Gotcha: Calling HSET then EXPIRE without a transaction could lead to stale data if, for example, the EXPIRE command fails while the HSET succeeds.&lt;/p&gt;

&lt;p&gt;HSET depends on the EXPIRE command to clear the cache and sync it with new data by reinvoking the backend.&lt;/p&gt;

&lt;p&gt;➡️ If EXPIRE fails, the cached data will stay forever and become stale. If your app depends on fresh data, this could lead to showing outdated info. 😬&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;

&lt;span class="n"&gt;redis_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mississauga&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Oakville&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wind&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Cloudy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters 💡
&lt;/h2&gt;

&lt;p&gt;When caching, expiration is just as important as storing data. Stale data in a cache can be worse than no cache at all — it tricks your app into thinking it’s working with fresh info. 🚫🕰️&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Use Transactions or Lua Scripts 🔐🐉
&lt;/h2&gt;

&lt;p&gt;To ensure that both commands succeed or fail together, we should wrap them in a pipeline within a transaction (MULTI/EXEC) or use a Lua script.&lt;/p&gt;

&lt;p&gt;A Redis pipeline allows multiple commands to be sent to the server as a single batch, reducing network overhead and minimizing the impact of network &lt;br&gt;
glitches. For example, without pipelining, if two dependent write operations are executed separately, one might reach the Redis server while the other &lt;br&gt;
could be lost due to a network issue. With pipelining, both operations are transmitted together in one network packet, making it more likely &lt;br&gt;
that either both are received or both are dropped — offering a basic form of network-level consistency.&lt;/p&gt;

&lt;p&gt;However, it's important to note that pipelining does not guarantee atomicity — commands are still executed one after the other by Redis, and &lt;br&gt;
failures in individual commands do not roll back others.&lt;/p&gt;

&lt;p&gt;To ensure true atomic execution, Redis transactions should be used. Using a transaction ensures that all operations within the block are executed &lt;br&gt;
sequentially and without interference from other clients. The MULTI command queues the operations, and EXEC executes them all at once. This provides a &lt;br&gt;
degree of isolation: no other client's commands are interleaved during the execution of the transaction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wind&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Cloudy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Heads up: Redis transactions are not fully atomic. If one command fails, others won't be rolled back — partial changes can still persist.&lt;/p&gt;



&lt;h2&gt;
  
  
  True Atomicity with Lua Scripts 🧙‍♂️✨
&lt;/h2&gt;

&lt;p&gt;Redis guarantees atomicity when using Lua scripts. All operations run as one indivisible block — no other commands can run until the script finishes.&lt;/p&gt;

&lt;p&gt;If the script completes, all changes are applied in order.&lt;/p&gt;

&lt;p&gt;If there’s a runtime error, the entire script aborts and no changes are made.&lt;/p&gt;

&lt;p&gt;But be careful! Lua scripts block the entire Redis server while running. If a script takes too long, it can slow down or freeze Redis for other clients. &lt;br&gt;
So keep Lua scripts super fast .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mississauga&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Oakville&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;lua_script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
for i = 1, #KEYS do
    local base = (i - 1) * 5
    local key = KEYS[i]
    redis.call(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HSET&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, key, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, ARGV[base + 1], &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wind&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, ARGV[base + 2], &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, ARGV[base + 3], &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, ARGV[base + 4])
    redis.call(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;EXPIRE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, key, tonumber(ARGV[base + 5]))
end
return #KEYS
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# For each city, pack values: temp=22, wind=5, condition='Cloudy', city=city, expire=10
&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;22&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Cloudy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lua_script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;h2&gt;
  
  
  Final Thoughts  💭
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Always consider atomicity when working with Redis for critical caching.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stale cache is worse than no cache — it can cause hidden bugs and confusing behavior. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Redis is fast and simple, but it can fail silently — wrap multi-step operations when consistency matters!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it for today!&lt;br&gt;&lt;br&gt;
Hope this article helped you avoid some Redis gotchas.&lt;br&gt;&lt;br&gt;
Happy coding and creating! ✨🚀&lt;/p&gt;

&lt;p&gt;👉 You can also read this article on &lt;a href="https://www.coderystack.com/posts/post3-redis-caching" rel="noopener noreferrer"&gt;my website&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>backend</category>
      <category>data</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
