<?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: Rafał Fuchs</title>
    <description>The latest articles on DEV Community by Rafał Fuchs (@eraefi).</description>
    <link>https://dev.to/eraefi</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3870435%2Faf525286-1831-4e08-b966-1bd87531f5ce.jpg</url>
      <title>DEV Community: Rafał Fuchs</title>
      <link>https://dev.to/eraefi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eraefi"/>
    <language>en</language>
    <item>
      <title>How to check your brand's visibility in ChatGPT, step by step</title>
      <dc:creator>Rafał Fuchs</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:53:51 +0000</pubDate>
      <link>https://dev.to/eraefi/how-to-check-your-brands-visibility-in-chatgpt-step-by-step-4lal</link>
      <guid>https://dev.to/eraefi/how-to-check-your-brands-visibility-in-chatgpt-step-by-step-4lal</guid>
      <description>&lt;p&gt;When a prospective buyer asks ChatGPT about suppliers in your category, the model names specific companies - usually three to six of them. You are either on that list or you are not. This guide shows how to check that yourself in about 30 minutes: which questions to ask, how to avoid distorting the results, and how to record them so they can be compared over time. If the result disappoints, see how to build &lt;a href="https://www.aivisible.pl/en/chatgpt-visibility" rel="noopener noreferrer"&gt;visibility in ChatGPT&lt;/a&gt; systematically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start: ChatGPT is two different mechanisms
&lt;/h2&gt;

&lt;p&gt;The classic ChatGPT answers from training data, so its knowledge of brands can be many months old. ChatGPT Search - triggered automatically or via the globe icon - searches the live web and cites pages. Your brand can be visible in one mechanism and absent from the other, so check both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: turn off personalisation and work in a clean context
&lt;/h2&gt;

&lt;p&gt;ChatGPT remembers earlier conversations and adapts its answers. If you have previously asked about your own company, the model will name it more often and your results will be distorted. Use Temporary Chat, or sign out and ask in a private window. Ask every question in a new thread.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most common mistake is asking "do you know company X?" in a normal chat. The model will almost always answer yes, politely - that is not a visibility test. A visibility test asks for recommendations in a category without naming the company.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: ask buying questions, not reputation questions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Which companies do you recommend for [your service category]?&lt;/li&gt;
&lt;li&gt;I am choosing a supplier of [service] for a B2B company in [industry]. Who should we consider?&lt;/li&gt;
&lt;li&gt;Build a shortlist of five companies offering [category], with a short rationale for each.&lt;/li&gt;
&lt;li&gt;Which [category] company would suit [company size / budget / specific constraint]?&lt;/li&gt;
&lt;li&gt;How do the leading suppliers of [category] differ from one another?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask every question twice: once in the classic chat and once with search enabled. Note the differences - this is often the moment teams discover that the model "knows" the brand from training, but cites only competitors when searching live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: assess the quality of the description, not just presence
&lt;/h2&gt;

&lt;p&gt;If the brand does appear, look at how it is described. Possible problems include the wrong category, an outdated offer, or a mention with no supporting rationale. Each case should be documented against the complete answer and assessed in the context of the question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: record the results and calculate Share of Model
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;ChatGPT (training)&lt;/th&gt;
&lt;th&gt;ChatGPT Search&lt;/th&gt;
&lt;th&gt;Competitor A&lt;/th&gt;
&lt;th&gt;How the brand is described&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Recommended [category] companies&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes (2x)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shortlist of five [category] companies&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;wrong service category&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supplier for [industry]&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no rationale given&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Share of Model is the percentage of answers containing your brand. Measure across a fixed set of at least ten questions and repeat monthly - a single measurement is a snapshot, and only a series shows the trend and the effect of the changes you made.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do when ChatGPT does not recommend you
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Sharpen the category, target audience and service scope on your home page - models do not recommend companies they cannot classify.&lt;/li&gt;
&lt;li&gt;Publish citable content: comparisons, FAQs and case studies with numbers - this is what ChatGPT Search builds answers from.&lt;/li&gt;
&lt;li&gt;Work on presence in industry rankings and round-ups, which are common sources for recommendations.&lt;/li&gt;
&lt;li&gt;Implement schema.org structured data so the model understands unambiguously what your company is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ChatGPT is only one AI answer surface. The equivalent instructions for Perplexity are in the guide to &lt;a href="https://www.aivisible.pl/en/blog/how-to-check-brand-visibility-in-perplexity" rel="noopener noreferrer"&gt;checking brand visibility in Perplexity&lt;/a&gt;, and the full measurement rules are in the &lt;a href="https://www.aivisible.pl/en/methodology" rel="noopener noreferrer"&gt;methodology&lt;/a&gt;. The scope of a paid Snapshot or audit is matched to the category and the hypothesis.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I check visibility in ChatGPT for free?
&lt;/h3&gt;

&lt;p&gt;Yes. The free version of ChatGPT is enough - it has access both to answers from training data and to web search. The key is the method: a clean context, buying questions and a fixed set of prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does ChatGPT name my company sometimes and not others?
&lt;/h3&gt;

&lt;p&gt;Model answers are non-deterministic - the same question can produce different results. That is why a single test settles nothing. Measure across a set of at least ten questions and look at the percentage of appearances (Share of Model), not at individual answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does it take to improve visibility in ChatGPT?
&lt;/h3&gt;

&lt;p&gt;In ChatGPT Search, the effects of site changes can appear within weeks, because the mechanism reads the live web. In the classic ChatGPT, changes happen more slowly, alongside model data updates. A typical horizon for measurable improvement is two to four months of systematic work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is ChatGPT visibility the same as SEO?
&lt;/h3&gt;

&lt;p&gt;No, although the two overlap. SEO optimises positions in Google results, whereas ChatGPT visibility depends on whether the model can classify the brand, cite it and justify a recommendation. Good SEO helps, but it is not sufficient on its own.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.aivisible.pl/en/blog/how-to-check-brand-visibility-in-chatgpt" rel="noopener noreferrer"&gt;AiVisible&lt;/a&gt;. AiVisible measures how often companies and products appear in ChatGPT, Gemini, Claude and Perplexity answers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>ai</category>
      <category>seo</category>
      <category>marketing</category>
    </item>
    <item>
      <title>AI visibility monitoring tools: a 2026 review and comparison</title>
      <dc:creator>Rafał Fuchs</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:53:41 +0000</pubDate>
      <link>https://dev.to/eraefi/ai-visibility-monitoring-tools-a-2026-review-and-comparison-5c12</link>
      <guid>https://dev.to/eraefi/ai-visibility-monitoring-tools-a-2026-review-and-comparison-5c12</guid>
      <description>&lt;p&gt;2025 was the year the AI visibility tooling market exploded. In 2023 there was no dedicated tool for tracking share of voice in ChatGPT or Perplexity; today more than 30 products compete in the category. The problem is no longer a lack of choice - it is finding your way through the thicket to a tool that matches your needs and budget.&lt;/p&gt;

&lt;p&gt;This guide compares tools by model coverage, data quality, language support, price and fit. If you want to establish the scope first, use our &lt;a href="https://www.aivisible.pl/en/check-your-competitors" rel="noopener noreferrer"&gt;benchmark qualification&lt;/a&gt;. Full measurement is part of a Snapshot or an audit.&lt;/p&gt;

&lt;p&gt;Before paying for a tool, assess your category fit free of charge. A paid Snapshot or audit documents the starting point and the gaps. For continuous measurement, compare &lt;a href="https://www.aivisible.pl/en/pricing" rel="noopener noreferrer"&gt;Continuous AI Visibility in the pricing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should an AI visibility tool monitor?
&lt;/h2&gt;

&lt;p&gt;Before the tools themselves, it is worth setting the assessment criteria. A good AI visibility tool should monitor at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share of Voice - what percentage of AI answers to your keywords name your brand. This is the core AI visibility metric.&lt;/li&gt;
&lt;li&gt;Citation frequency rate - how often your site is cited as a source, particularly in Perplexity and ChatGPT Search where links are visible.&lt;/li&gt;
&lt;li&gt;Sentiment analysis - whether mentions are favourable, neutral or unfavourable. You can be cited often but in a negative context.&lt;/li&gt;
&lt;li&gt;Competitor comparison - your share of voice alone says little. What counts is relative: if you are at 30% and the market leader at 70%, there is room to improve.&lt;/li&gt;
&lt;li&gt;Model coverage - the tool should monitor at least ChatGPT, Perplexity, Google AI Overviews and Gemini. Monitoring one model gives an incomplete picture.&lt;/li&gt;
&lt;li&gt;Alerts and trends - notifications when share of voice falls significantly or a new competitor appears in AI answers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;AI models&lt;/th&gt;
&lt;th&gt;Price from&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Profound&lt;/td&gt;
&lt;td&gt;ChatGPT, Perplexity, Gemini, Claude, Bing AI&lt;/td&gt;
&lt;td&gt;USD 49/month&lt;/td&gt;
&lt;td&gt;Agencies, enterprise B2B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Otterly.AI&lt;/td&gt;
&lt;td&gt;ChatGPT, Perplexity, Gemini&lt;/td&gt;
&lt;td&gt;USD 29/month&lt;/td&gt;
&lt;td&gt;Small companies, PR teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AthenaHQ&lt;/td&gt;
&lt;td&gt;ChatGPT, Perplexity, Google AI&lt;/td&gt;
&lt;td&gt;USD 199/month&lt;/td&gt;
&lt;td&gt;Enterprise, larger budgets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZipTie.dev&lt;/td&gt;
&lt;td&gt;ChatGPT, Google AI Overviews&lt;/td&gt;
&lt;td&gt;USD 49/month&lt;/td&gt;
&lt;td&gt;SEO teams focused on Google AIO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peec AI&lt;/td&gt;
&lt;td&gt;ChatGPT, Perplexity, Gemini&lt;/td&gt;
&lt;td&gt;USD 39/month&lt;/td&gt;
&lt;td&gt;Mid-market B2B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brandlight&lt;/td&gt;
&lt;td&gt;ChatGPT, Perplexity, Bing&lt;/td&gt;
&lt;td&gt;USD 79/month&lt;/td&gt;
&lt;td&gt;Brand management teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ahrefs Brand Radar&lt;/td&gt;
&lt;td&gt;AI answers and LLM mentions&lt;/td&gt;
&lt;td&gt;Free plan available&lt;/td&gt;
&lt;td&gt;First check, small teams&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Profound - the most mature enterprise option
&lt;/h2&gt;

&lt;p&gt;Profound is currently the most comprehensive tool on the market. It monitors five AI systems at once - ChatGPT, Perplexity, Gemini, Claude and Bing AI - and offers detailed share-of-voice reports broken down by query category, with competitor comparison. Its prompt-testing module stands out: you can add your own questions that the model asks regularly, and measure the answers.&lt;/p&gt;

&lt;p&gt;Strengths: the widest monitoring coverage, a good API, exportable reports and support for European languages. Weaknesses: price - the enterprise plan starts at USD 199 a month, which can be a barrier for smaller companies. The interface is extensive and takes learning. For agencies managing several clients, the agency plan is justified.&lt;/p&gt;

&lt;h2&gt;
  
  
  Otterly.AI - the most accessible for small companies
&lt;/h2&gt;

&lt;p&gt;Otterly.AI focuses on simplicity and affordability. The entry plan at USD 29 a month covers brand monitoring in ChatGPT, Perplexity and Gemini, email alerts on significant changes and a basic dashboard with trends. The interface is intuitive and can be running in 15 minutes.&lt;/p&gt;

&lt;p&gt;Limitations: no advanced competitor analysis - it tracks your own brand only. For companies just starting to measure AI visibility, it is a good starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  AthenaHQ - for enterprise with demanding requirements
&lt;/h2&gt;

&lt;p&gt;AthenaHQ is designed for large organisations with dedicated marketing teams. What distinguishes it is deep analysis of the reasons for citation - the tool tries to explain why AI names particular brands, analysing credibility and topical-fit factors.&lt;/p&gt;

&lt;p&gt;The USD 199 monthly entry price is a barrier. AthenaHQ suits environments where AI visibility is already an established board-level metric and detailed reports for stakeholders are needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  ZipTie.dev - the Google AI Overviews specialist
&lt;/h2&gt;

&lt;p&gt;ZipTie occupies a distinctive niche: it specialises in monitoring Google AI Overviews. If your priority is visibility in Google results with an AI summary, ZipTie is the dedicated tool. It tracks whether your page is cited in an AI Overview for given keywords, and shows which passage is cited.&lt;/p&gt;

&lt;p&gt;The limitation: ZipTie monitors only ChatGPT and Google AIO - not Perplexity, Gemini or Claude. For companies focused mainly on Google presence, where AI Overviews are increasingly common, it is a good complement to standard SEO tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language coverage matters more than the feature list
&lt;/h2&gt;

&lt;p&gt;Most tools were built for English-language queries first. If your buyers search in another language, check three things before subscribing: whether the tool handles queries in that language accurately, whether its source coverage includes local publications, and whether the reporting distinguishes results by language. A tool with excellent English coverage can produce misleading numbers for a market it barely indexes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A recommendation for B2B teams: start with a low-cost tool such as Otterly.AI or the free Ahrefs Brand Radar plan. After three months, when you have a data baseline and know which metrics matter to you, assess an upgrade to Profound or AthenaHQ. Do not start with an enterprise tool - before you know what you are measuring, expensive tooling wastes budget.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to choose - a decision framework
&lt;/h2&gt;

&lt;p&gt;The choice should follow from a few specific questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you need monitoring in a language other than English? Check source coverage for that market before committing.&lt;/li&gt;
&lt;li&gt;What is your budget? Under USD 50 a month: Otterly.AI, Peec AI or ZipTie. Above that: Profound or AthenaHQ.&lt;/li&gt;
&lt;li&gt;Is Google AIO your priority? ZipTie, or Profound with the Google module.&lt;/li&gt;
&lt;li&gt;Do you run an agency managing several clients? Profound's agency plan or an Otterly.AI team plan.&lt;/li&gt;
&lt;li&gt;Do you need an API to integrate with your own dashboard? Profound or AthenaHQ.&lt;/li&gt;
&lt;li&gt;Are you just starting and want to understand what AI visibility is? Otterly.AI's entry plan has the gentlest learning curve.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The alternative: manual monitoring with no tooling
&lt;/h2&gt;

&lt;p&gt;If budget is tight, you can build a simple monitoring system by hand. It will not replace dedicated tooling, but it gives a baseline at no cost:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a spreadsheet listing 20 to 30 category questions related to your offer&lt;/li&gt;
&lt;li&gt;Once a month, ask each question in ChatGPT (without browsing), Perplexity and Gemini&lt;/li&gt;
&lt;li&gt;Record whether your brand appears, whether competitor X appears, and the context of the mention&lt;/li&gt;
&lt;li&gt;Calculate a simple share of voice: answers containing your brand divided by all queries, times 100&lt;/li&gt;
&lt;li&gt;After three months you have a trend - rising, falling or flat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The drawbacks of the manual approach: it is labour-intensive at around three to four hours a month, it does not provide real-time alerts, and it has no competitor comparison. But before you invest in a tool, it is a good way to verify whether AI visibility is a problem for your company at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Newer entrants: Nightwatch, Keyword.com and Scrunch
&lt;/h2&gt;

&lt;p&gt;The AI visibility tooling market changes quickly. Several new products have gained traction among SEO teams:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Specialisation&lt;/th&gt;
&lt;th&gt;AI models&lt;/th&gt;
&lt;th&gt;Price from&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nightwatch&lt;/td&gt;
&lt;td&gt;Conventional SEO plus AI visibility in one&lt;/td&gt;
&lt;td&gt;ChatGPT, Claude&lt;/td&gt;
&lt;td&gt;USD 39/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword.com&lt;/td&gt;
&lt;td&gt;SEO rank tracking with an AI extension&lt;/td&gt;
&lt;td&gt;ChatGPT, Google AIO&lt;/td&gt;
&lt;td&gt;USD 69/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scrunch AI&lt;/td&gt;
&lt;td&gt;Citation and brand mention monitoring in AI&lt;/td&gt;
&lt;td&gt;ChatGPT, Perplexity, Gemini&lt;/td&gt;
&lt;td&gt;USD 49/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rankscale&lt;/td&gt;
&lt;td&gt;AI search visibility monitoring&lt;/td&gt;
&lt;td&gt;ChatGPT Search, Perplexity&lt;/td&gt;
&lt;td&gt;USD 59/month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nightwatch is particularly interesting for SEO managers who want to track conventional Google positions and AI visibility in one dashboard. Combining both metrics in one place shows the correlation: whether a rise in SEO position translates into improved AI share of voice. Keyword.com is a good choice for teams already using it for rank tracking who want to add AI tracking without changing workflow.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The market has grown past 40 products in this category. Choose tools with at least six months of market history - many start-ups here have closed or been acquired. Also check whether the tool monitors ChatGPT Search, not only the classic ChatGPT; that has been a key feature since 2025.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What data does an AI visibility monitoring tool collect?
&lt;/h3&gt;

&lt;p&gt;Primarily: model answers to a defined set of control prompts, the presence and position of the brand in those answers (Share of Model), the sources and links cited, mention sentiment, and comparison with competitors over time. Better tools also track which of your pages are cited, which indicates what content AI treats as credible.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is brand visibility in prompts measured?
&lt;/h3&gt;

&lt;p&gt;Through two metrics: Share of Model (what percentage of answers to a prompt set name your brand) and shortlist position (whether AI names you as the first, third or last recommendation). Measurement requires a fixed set of 20 to 120 control prompts asked regularly of the same models - a single test proves nothing, because AI answers are non-deterministic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do any tools monitor visibility in languages other than English?
&lt;/h3&gt;

&lt;p&gt;Coverage varies considerably. Profound supports European languages, with accuracy strongest in English. Otterly.AI and AthenaHQ work mainly in English - you can set prompts in another language, but the interface and analysis are English. Check source coverage for your market before subscribing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often do tools check AI answers?
&lt;/h3&gt;

&lt;p&gt;It depends on the tool and plan. Otterly.AI checks daily on the base plan. Profound can check several times a day on higher plans. AthenaHQ offers near-real-time monitoring at enterprise level. More frequent checks cost more, because models charge per API query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do AI visibility tools replace SEO tools such as Semrush or Ahrefs?
&lt;/h3&gt;

&lt;p&gt;No - they are complementary, not substitutes. Semrush and Ahrefs measure Google positions and analyse backlinks. AI visibility tools measure share of voice in ChatGPT, Perplexity and similar. Different metrics, different channels. Companies serious about online visibility need both categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are free plans enough to start with?
&lt;/h3&gt;

&lt;p&gt;Most tools offer a trial of 7 to 14 days rather than a permanent free plan. Ahrefs Brand Radar is an exception with a free tier. A two-week trial is enough to judge whether a tool meets your needs. Continuous monitoring requires a paid plan.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.aivisible.pl/en/blog/ai-visibility-monitoring-tools-compared" rel="noopener noreferrer"&gt;AiVisible&lt;/a&gt;. AiVisible measures how often companies and products appear in ChatGPT, Gemini, Claude and Perplexity answers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>tooling</category>
      <category>llm</category>
    </item>
    <item>
      <title>llms.txt: what it is, why it matters and how to implement it</title>
      <dc:creator>Rafał Fuchs</dc:creator>
      <pubDate>Sat, 08 Aug 2026 07:33:12 +0000</pubDate>
      <link>https://dev.to/eraefi/llmstxt-what-it-is-why-it-matters-and-how-to-implement-it-2c67</link>
      <guid>https://dev.to/eraefi/llmstxt-what-it-is-why-it-matters-and-how-to-implement-it-2c67</guid>
      <description>&lt;p&gt;In December 2024, Jeremy Howard, the creator of fast.ai, proposed a simple standard: an llms.txt file placed in a domain's root directory, telling AI systems about the structure and content of the site. The standard is supported by Anthropic (Claude), Perplexity and a dozen or so other AI tools. Implementation cost: a few minutes. Potential effect: better understanding of your site by AI systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is llms.txt?
&lt;/h2&gt;

&lt;p&gt;llms.txt is a Markdown-format text file placed at yourdomain.com/llms.txt. Its job is to convey key information about the site to AI systems in a structured, easily processed form. The analogy: robots.txt tells search engines what they may index; llms.txt tells AI systems what is worth knowing about your site.&lt;/p&gt;

&lt;p&gt;The standard is backed by platforms including Reddit, Medium, Cloudflare, Akamai and Creative Commons. It is not mandatory - AI systems manage without it. But its presence signals that your company understands AI mechanisms and wants to shape actively what AI knows about it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;llms.txt does not replace good content on the site. An AI system will still index your domain and draw on the content of your pages. llms.txt is a pointer, not a substitute for valuable content.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What does an llms.txt file contain?
&lt;/h2&gt;

&lt;p&gt;The file format is simple and human-readable. It consists of a few elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A title and company description - one sentence explaining what the company does and for whom&lt;/li&gt;
&lt;li&gt;Links to key sections with descriptions - service pages, blog, about, case studies&lt;/li&gt;
&lt;li&gt;Optional key facts - the data you want AI to know about your company&lt;/li&gt;
&lt;li&gt;Optional guidance on content use - if you have preferences about citation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  An example llms.txt for a B2B company
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; An example llms.txt for a B2B service company:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# AiVisible&lt;/span&gt;
&lt;span class="gt"&gt;
&amp;gt; AiVisible helps B2B companies measure and improve their visibility in AI answers.&lt;/span&gt;

&lt;span class="gu"&gt;## Services&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;AI Visibility Snapshot&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.aivisible.pl/en/ai-visibility-snapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: a focused measurement of one category and market
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Strategic AI Visibility Audit&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.aivisible.pl/en/ai-visibility-audit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: complete answers, benchmark, sources and a roadmap
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;AI Recommendation Sprint&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.aivisible.pl/en/ai-visibility-implementation-sprint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: a backlog of recommendations and experiments
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Continuous AI Visibility&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.aivisible.pl/en/continuous-ai-visibility&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: ongoing measurement and further experiments

&lt;span class="gu"&gt;## Knowledge&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Blog&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.aivisible.pl/en/blog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: articles on AI Visibility and GEO
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Methodology&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.aivisible.pl/en/methodology&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: the measurement protocol and its limits

&lt;span class="gu"&gt;## Key facts&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; We work with B2B companies
&lt;span class="p"&gt;-&lt;/span&gt; The free stage qualifies the category and is not an audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to implement llms.txt, step by step
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create the llms.txt file - open a text editor and write the content in Markdown, as in the example above. Save it as llms.txt.&lt;/li&gt;
&lt;li&gt;Place the file in the domain root - it has to be reachable at yourdomain.com/llms.txt. In Next.js put it in the /public folder. In WordPress, in the server root directory.&lt;/li&gt;
&lt;li&gt;Check availability - open yourdomain.com/llms.txt in a browser. You should see the text file with its content.&lt;/li&gt;
&lt;li&gt;Optionally add llms-full.txt - the standard also allows an extended version containing the full content of key pages, for models that want to fetch everything at once.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to implement llms.txt in Next.js
&lt;/h2&gt;

&lt;p&gt;In Next.js the simplest approach is to place the file in the /public folder, where it is served automatically as a static asset. Alternatively you can generate llms.txt dynamically through a route handler at app/llms.txt/route.ts, which lets you update the content without rebuilding the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which AI systems read llms.txt?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model or tool&lt;/th&gt;
&lt;th&gt;llms.txt support&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude (Anthropic)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Direct support for the standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perplexity&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Used when indexing sites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChatGPT (browsing)&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Not officially, but indexes the file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;td&gt;Not officially&lt;/td&gt;
&lt;td&gt;The standard is not confirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bing Copilot&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No official support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Does llms.txt actually work?
&lt;/h2&gt;

&lt;p&gt;The standard is too new for hard data on its effect on AI visibility. Early experiments suggest that AI systems officially supporting it - Claude and Perplexity - do use llms.txt to build context about a site. For models that do not support it formally, the file is simply ignored and does no harm.&lt;/p&gt;

&lt;p&gt;Our conclusion: llms.txt is worth implementing on a cost-to-potential-benefit basis. Implementation takes 15 to 20 minutes. The risk is zero. The potential gain is better understanding of your site by the AI systems that support the standard. As the standard matures and adoption grows, companies that implemented it early will have nothing to catch up on.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is llms.txt mandatory?
&lt;/h3&gt;

&lt;p&gt;No, but it is an emerging standard that helps AI systems understand the structure and offer of your site correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where should the llms.txt file go?
&lt;/h3&gt;

&lt;p&gt;The file should sit in the domain root, for example aivisible.pl/llms.txt, in the same way as robots.txt.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.aivisible.pl/en/blog/llms-txt-what-it-is-and-how-to-implement-it" rel="noopener noreferrer"&gt;AiVisible&lt;/a&gt;. AiVisible measures how often companies and products appear in ChatGPT, Gemini, Claude and Perplexity answers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
      <category>llm</category>
    </item>
    <item>
      <title>How to check whether AI recommends your company, step by step</title>
      <dc:creator>Rafał Fuchs</dc:creator>
      <pubDate>Sat, 08 Aug 2026 07:33:04 +0000</pubDate>
      <link>https://dev.to/eraefi/how-to-check-whether-ai-recommends-your-company-step-by-step-2767</link>
      <guid>https://dev.to/eraefi/how-to-check-whether-ai-recommends-your-company-step-by-step-2767</guid>
      <description>&lt;p&gt;The owner of a consultancy checked one morning what ChatGPT says about their category. They typed in a question a client might ask. The answer named four specific companies. Theirs was not among them. Two of the four were firms they had never heard of in ten years of work. That same day they commissioned an audit of their presence in AI.&lt;/p&gt;

&lt;p&gt;Checking individual answers needs no specialist tooling. A comparable audit, however, needs a protocol, complete records and coding rules. You can start with a free &lt;a href="https://www.aivisible.pl/en/contact?path=category" rel="noopener noreferrer"&gt;category-fit assessment&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: identify the questions your buyers ask
&lt;/h2&gt;

&lt;p&gt;Before opening any AI system, you have to know what to look for. Your buyer does not ask "do you recommend company XYZ?" They ask about the problem they need to solve. So ask yourself: what does my ideal customer type into ChatGPT when looking for the solution I offer?&lt;/p&gt;

&lt;p&gt;Example buyer questions by category:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEO agency: "Which SEO agency is good for an online shop?", "How do I find a good SEO specialist?"&lt;/li&gt;
&lt;li&gt;IT company: "Which development firms do you recommend for building mobile apps?", "Which IT company specialises in e-commerce?"&lt;/li&gt;
&lt;li&gt;Accountancy practice: "How do I choose an accountant for a start-up?", "Who handles bookkeeping for technology companies?"&lt;/li&gt;
&lt;li&gt;Law firm: "Who is good on employment law for employers?", "Which firm is recommended for corporate work?"&lt;/li&gt;
&lt;li&gt;Training provider: "Where can I learn project management?", "Best Excel training for companies"&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A useful technique: ask three current clients what they typed into Google or an AI system before they found you. Those are real phrases, not guesses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: test in ChatGPT
&lt;/h2&gt;

&lt;p&gt;ChatGPT is the starting point because it has the widest reach. A free account is enough, and the current default model has web access.&lt;/p&gt;

&lt;p&gt;Start a new conversation - do not continue an old one, so earlier context cannot influence the result. Enter your test questions and observe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does your company appear by name?&lt;/li&gt;
&lt;li&gt;If so, in which position and how is it described?&lt;/li&gt;
&lt;li&gt;Who else appears? That is your competition in AI.&lt;/li&gt;
&lt;li&gt;Which attributes does AI ascribe to the recommended companies?&lt;/li&gt;
&lt;li&gt;Does AI give any sources or links?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test the same question three times - ChatGPT gives different answers on each run. If your company appears in one of three attempts, that signals weak presence. If it appears every time, that is a good sign.&lt;/p&gt;

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

&lt;p&gt;Perplexity deserves separate measurement because it shows sources alongside the answer. Do not assume, though, that its user profile matches your B2B audience. Confirm that in conversations with clients and in your buying-journey data.&lt;/p&gt;

&lt;p&gt;In Perplexity, pay attention to one extra element: the sources. Click through the cited pages - those are the pages Perplexity treats as authoritative in your category. If your site is not among them, that is a concrete thing to fix.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reddit accounts for a substantial share of citations in Perplexity. If your company is active in relevant subreddits or comparable communities, Perplexity is more likely to notice you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: Google AI Overviews and Gemini
&lt;/h2&gt;

&lt;p&gt;Google AI Overviews - the AI answer box at the top of results - appears on a large share of queries. Go to Google and enter your test phrases. If an AI Overview appears, check whether your company is mentioned and whether your page is among the sources.&lt;/p&gt;

&lt;p&gt;An important difference: 92% of Google AI Overviews citations come from pages in the organic top ten. Conventional SEO therefore matters more here than in ChatGPT. If you do not rank, you will not be cited by Google AI. But 47% of citations come from outside the top five, so content quality - not only position - counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: compare yourself with competitors
&lt;/h2&gt;

&lt;p&gt;Establishing that you are absent is only half the information. The more important question is who appears instead of you. Note the companies AI names in answer to your questions. That is your real competition in the AI channel, even if you outrank them in Google.&lt;/p&gt;

&lt;p&gt;For each competitor, ask what they have that you do not. Typical answers: a stronger LinkedIn presence, an active blog with specific articles, mentions in industry publications, a Wikipedia entry, or activity on forums and Reddit.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to interpret the results
&lt;/h2&gt;

&lt;p&gt;Once the tests are done, assess where you stand:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test result&lt;/th&gt;
&lt;th&gt;Assessment&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Appear in over 70% of answers, in positions 1-3&lt;/td&gt;
&lt;td&gt;Good visibility&lt;/td&gt;
&lt;td&gt;Maintain and monitor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Appear in 30-70% of answers&lt;/td&gt;
&lt;td&gt;Average visibility&lt;/td&gt;
&lt;td&gt;Optimise content and mentions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Appear in under 30% of answers&lt;/td&gt;
&lt;td&gt;Weak visibility&lt;/td&gt;
&lt;td&gt;A full AI Visibility strategy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Do not appear at all&lt;/td&gt;
&lt;td&gt;No visibility&lt;/td&gt;
&lt;td&gt;Diagnosis and urgent action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI describes you unfavourably or inaccurately&lt;/td&gt;
&lt;td&gt;Reputation problem&lt;/td&gt;
&lt;td&gt;Immediate intervention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The limits of manual testing
&lt;/h2&gt;

&lt;p&gt;Manual tests are a good starting point, but they have limits worth knowing. First, AI generates different answers each time - what you see is a sample, not a stable result. Second, different locations and accounts can produce different results. Third, you cannot see how you compare against all competitors - only against whatever AI happened to generate.&lt;/p&gt;

&lt;p&gt;A systematic diagnosis covers hundreds of test queries, a comparison with three to five direct competitors, analysis across several models at once, and an assessment of the site content itself for citability. A manual test tells you whether you have a problem. A full diagnosis tells you why, and what specifically to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do after the diagnosis
&lt;/h2&gt;

&lt;p&gt;If the tests revealed a problem, do not be too alarmed. Most companies have no AI Visibility strategy at all, which means competition is still low. Companies that start now have a genuine chance of dominating AI answers in their niche within three to six months.&lt;/p&gt;

&lt;p&gt;Three immediate steps after the diagnosis: (1) improve site content for citability - specific definitions, data and a question-and-answer structure; (2) start building brand mentions on external platforms such as industry publications, LinkedIn and forums; (3) monitor regularly, because AI changes and results from a month ago may already be out of date.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I check my company's visibility in ChatGPT?
&lt;/h3&gt;

&lt;p&gt;Open ChatGPT and ask the questions a prospective buyer might type, such as "Which [your category] companies do you recommend?" or "Which [service] provider should we choose?" Check whether your company appears. Repeat five to ten times - AI gives different results on each query.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I check brand visibility in Perplexity?
&lt;/h3&gt;

&lt;p&gt;Open Perplexity and search category phrases. Perplexity shows its sources, so you can check whether your page is cited. Ask comparison questions such as "What are the best X companies?" and observe which brands appear in the answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many questions do I need to measure AI visibility?
&lt;/h3&gt;

&lt;p&gt;The minimum is 20 to 30 questions per model, because results vary and a single test is too small a sample. A systematic diagnosis covers at least 50 queries per model, across several question variants, with a benchmark of three to five direct competitors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I improve my company's AI visibility myself?
&lt;/h3&gt;

&lt;p&gt;Yes, but it requires systematic work. The basic steps are tidying up your content, your credible sources and your measurement. A free AiVisible category-fit assessment will help you choose the right paid scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does it mean if AI does not recommend me?
&lt;/h3&gt;

&lt;p&gt;It means the model lacks sufficient knowledge about your company, or does not associate you with that service category. The most common causes are an absence of mentions in external sources, site content that does not answer buyer questions directly, or a company too new to appear in the model's training data.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.aivisible.pl/en/blog/how-to-check-whether-ai-recommends-your-company" rel="noopener noreferrer"&gt;AiVisible&lt;/a&gt;. AiVisible measures how often companies and products appear in ChatGPT, Gemini, Claude and Perplexity answers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>marketing</category>
      <category>llm</category>
    </item>
    <item>
      <title>Transaction boundaries in Django: where consistency really ends</title>
      <dc:creator>Rafał Fuchs</dc:creator>
      <pubDate>Fri, 17 Apr 2026 14:50:24 +0000</pubDate>
      <link>https://dev.to/eraefi/transaction-boundaries-in-django-where-consistency-really-ends-4ilh</link>
      <guid>https://dev.to/eraefi/transaction-boundaries-in-django-where-consistency-really-ends-4ilh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;TL;DR: &lt;code&gt;transaction.atomic()&lt;/code&gt; protects SQL work on one connection to one database. It does not protect queues, emails, webhooks, other databases, or third-party APIs. If you design a business process as if one &lt;code&gt;atomic&lt;/code&gt; block covered the whole thing, sooner or later you will ship a half-commit to production: DB state changed, side effects missing (or the other way around). This post walks through where the real consistency boundary is, and what to reach for when you need more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;I'm a senior backend engineer working in Python/Django. More long-form writing at &lt;a href="https://rafalfuchs.dev/en" rel="noopener noreferrer"&gt;rafalfuchs.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The illusion &lt;code&gt;atomic()&lt;/code&gt; creates
&lt;/h2&gt;

&lt;p&gt;Most Django developers learn transactions through a comforting pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&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;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="n"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
    &lt;span class="nf"&gt;send_confirmation_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;publish_to_kafka&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It reads like one unit of work. It feels atomic. It is not.&lt;/p&gt;

&lt;p&gt;Only the first two lines are actually protected by the database transaction. The email and the Kafka publish happen inside the &lt;code&gt;with&lt;/code&gt; block, but they are side effects that do not roll back if the transaction aborts. Worse: if they fire before the commit and the commit then fails, you just notified the world about an order that does not exist.&lt;/p&gt;

&lt;p&gt;This is the core misconception I want to unpack: &lt;strong&gt;SQL commit is not business-process commit&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What &lt;code&gt;atomic()&lt;/code&gt; actually guarantees
&lt;/h2&gt;

&lt;p&gt;Think of &lt;code&gt;atomic&lt;/code&gt; as a &lt;em&gt;local database safety boundary&lt;/em&gt;. Scoped to one connection, one database, one transaction.&lt;/p&gt;

&lt;p&gt;It does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;commit or roll back SQL changes together,&lt;/li&gt;
&lt;li&gt;support nesting via savepoints,&lt;/li&gt;
&lt;li&gt;preserve invariants inside that specific DB transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;include external side effects (HTTP calls, emails, message brokers, cache writes),&lt;/li&gt;
&lt;li&gt;guarantee delivery of any asynchronous message,&lt;/li&gt;
&lt;li&gt;solve multi-database atomicity,&lt;/li&gt;
&lt;li&gt;protect you from a successful commit followed by a crash before your handler returns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point trips up a lot of people. The moment &lt;code&gt;__exit__&lt;/code&gt; on the context manager finishes, your transaction is committed. Anything afterwards is a new world, and the database has no idea whether your Celery task made it into Redis or not.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;code&gt;ATOMIC_REQUESTS&lt;/code&gt;: a sharp tool, not a default
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ATOMIC_REQUESTS = True&lt;/code&gt; wraps every request in a transaction. It feels like a sane default, and for small apps it genuinely reduces accidental partial writes.&lt;/p&gt;

&lt;p&gt;At higher traffic it starts to bite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transactions live longer (the full request lifecycle, not just the write),&lt;/li&gt;
&lt;li&gt;lock contention climbs on hot rows,&lt;/li&gt;
&lt;li&gt;throughput on mixed read/write endpoints drops,&lt;/li&gt;
&lt;li&gt;a slow external call inside a view now holds a DB transaction open for its entire duration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The better architectural question is rarely &lt;em&gt;"can we wrap the whole request?"&lt;/em&gt;. It is &lt;em&gt;"which specific write-critical section genuinely needs a transaction?"&lt;/em&gt;. Reach for explicit &lt;code&gt;with transaction.atomic():&lt;/code&gt; around that section, and let the rest of the request run without holding row locks.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The minimum viable guardrail: &lt;code&gt;transaction.on_commit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If you only take one pattern away from this post, take this one. Never fire a side effect from inside an &lt;code&gt;atomic&lt;/code&gt; block directly. Register it with &lt;code&gt;on_commit&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_invoice_and_enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&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;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;invoice_data&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;on_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;publish_invoice_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;on_commit&lt;/code&gt; holds the callback until the outermost transaction successfully commits. If the transaction rolls back, the callback never runs. No phantom notifications about invoices that no longer exist.&lt;/p&gt;

&lt;p&gt;But note what this still does not give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if the process crashes between commit and &lt;code&gt;on_commit&lt;/code&gt; execution, the callback is lost,&lt;/li&gt;
&lt;li&gt;if the broker is down when the callback fires, the message is gone,&lt;/li&gt;
&lt;li&gt;if the consumer processes the message twice, you get double side effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;on_commit&lt;/code&gt; is a necessary guardrail, not a delivery guarantee. Pair it with retries on the producer side and &lt;strong&gt;idempotent consumers&lt;/strong&gt; on the receiver side, or move to something stronger (see section 6).&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Isolation levels and the race conditions you are not seeing
&lt;/h2&gt;

&lt;p&gt;Django on PostgreSQL defaults to &lt;code&gt;READ COMMITTED&lt;/code&gt;. That means: you see rows that were committed before your statement started. It does not mean: nobody can change a row between your &lt;code&gt;SELECT&lt;/code&gt; and your &lt;code&gt;UPDATE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Classic broken pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# BROKEN under concurrency
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reserve_stock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&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;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available_qty&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Insufficient stock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available_qty&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&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;available_qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two concurrent requests both read &lt;code&gt;available_qty = 5&lt;/code&gt;, both see enough stock for &lt;code&gt;qty = 3&lt;/code&gt;, both subtract, and you have just oversold by 1 unit. The transaction committed successfully. The business invariant is broken.&lt;/p&gt;

&lt;p&gt;Three tools to pick from, in rough order of cost:&lt;/p&gt;

&lt;h3&gt;
  
  
  4a. Pessimistic locking with &lt;code&gt;select_for_update&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reserve_stock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&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;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available_qty&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Insufficient stock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available_qty&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&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;available_qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, correct, and it serializes everyone hitting the same row. Use it for short critical sections on high-value state (stock, balance, seat booking). Keep the locked section small - do not put HTTP calls inside.&lt;/p&gt;

&lt;h3&gt;
  
  
  4b. Optimistic locking with a version column
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;expected_version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;available_qty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;available_qty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;updated&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ConcurrentUpdateError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets readers through without blocking. The loser of a race has to retry. Good fit for read-heavy paths where contention is rare but must be detected.&lt;/p&gt;

&lt;h3&gt;
  
  
  4c. Database constraints as the last line of defence
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;UNIQUE&lt;/code&gt;, &lt;code&gt;CHECK&lt;/code&gt;, &lt;code&gt;FK&lt;/code&gt;, partial indexes. Your application logic will have bugs. The database is the one layer that will reliably catch a duplicate order number or a negative balance. Treat constraints as non-negotiable, not as "optimization for later".&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The moment you cross a process boundary, there is no global transaction
&lt;/h2&gt;

&lt;p&gt;The moment your use case touches Celery, a webhook, an email provider, a second database, or any external API, you are out of the ACID world. There is no protocol that wraps "insert row in Postgres" and "send message to SQS" into one atomic action. Two-phase commit exists on paper. Almost nobody runs it in production for good reasons.&lt;/p&gt;

&lt;p&gt;What you actually have is a distributed system with partial failures. Your options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Acceptable approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Side effect is nice-to-have (analytics event)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;on_commit&lt;/code&gt; + fire-and-forget, accept occasional loss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Side effect must eventually happen&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;on_commit&lt;/code&gt; + retries + idempotent consumer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Side effect must happen exactly-once-ish, and loss is unacceptable&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Outbox pattern&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The outbox pattern is the one I reach for in anything touching billing, inventory, compliance, or audit.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The outbox pattern, concretely
&lt;/h2&gt;

&lt;p&gt;The idea: instead of publishing to a broker from application code, write the message to a regular table in the same transaction as your domain change. A separate worker reads the outbox and publishes. Because the write and the message land in one DB commit, they succeed or fail together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OutboxEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BigAutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&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="n"&gt;aggregate_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;aggregate_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JSONField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&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="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&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="n"&gt;db_index&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;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;fields&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;published_at&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outbox_unpublished_idx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;published_at__isnull&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="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Writing the event
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&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;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;invoice_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;OutboxEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;aggregate_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;aggregate_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;on_commit&lt;/code&gt;, no direct broker call. The invoice row and the outbox row commit together, or neither exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relaying
&lt;/h3&gt;

&lt;p&gt;A separate process (Celery beat, a small dedicated worker, or a CDC tool like Debezium reading the WAL) pulls unpublished rows and publishes them:&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;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;relay_outbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&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;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;OutboxEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skip_locked&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;published_at__isnull&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&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;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;publish_to_broker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_type&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="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;message_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# for consumer dedup
&lt;/span&gt;            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&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;published_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;skip_locked&lt;/code&gt; lets you run multiple relay workers without them fighting over the same rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you gain
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;no lost events on broker outage (they sit in the outbox),&lt;/li&gt;
&lt;li&gt;no phantom events on rollback (they never hit the outbox),&lt;/li&gt;
&lt;li&gt;an auditable history of what was published and when,&lt;/li&gt;
&lt;li&gt;a lag metric (&lt;code&gt;unpublished outbox rows&lt;/code&gt; and &lt;code&gt;oldest unpublished row age&lt;/code&gt;) you can alert on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What you still owe the consumer side
&lt;/h3&gt;

&lt;p&gt;Consumers must be &lt;strong&gt;idempotent&lt;/strong&gt;. Design every handler so that receiving the same message twice is a no-op. The outbox gives you at-least-once delivery, not exactly-once. The message ID (outbox row PK) is your dedup key.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Multiple databases: there is no &lt;code&gt;atomic&lt;/code&gt; across them
&lt;/h2&gt;

&lt;p&gt;Django supports multiple databases. It does not give you a cross-database transaction. This code is a lie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Does NOT make the two writes atomic
&lt;/span&gt;&lt;span class="k"&gt;with&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;atomic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&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;atomic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analytics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
        &lt;span class="n"&gt;AnalyticsEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analytics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the &lt;code&gt;default&lt;/code&gt; commit succeeds and the &lt;code&gt;analytics&lt;/code&gt; commit fails (or the process dies in between), you have inconsistent state across databases with no automatic recovery.&lt;/p&gt;

&lt;p&gt;Practical rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep each business invariant anchored in &lt;strong&gt;one&lt;/strong&gt; database,&lt;/li&gt;
&lt;li&gt;if a flow genuinely spans databases, design it as eventual consistency: commit to the source of truth, then propagate via outbox or CDC,&lt;/li&gt;
&lt;li&gt;accept that "propagate" means "retry forever until it sticks, with alerts if lag grows".&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Decision matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;th&gt;Avoid when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plain &lt;code&gt;atomic&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Single DB write, no external side effects, low business risk&lt;/td&gt;
&lt;td&gt;Any side effect leaves the DB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;atomic&lt;/code&gt; + &lt;code&gt;on_commit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Side effects must run after commit, brief loss is acceptable, basic retry in place&lt;/td&gt;
&lt;td&gt;Loss is genuinely unacceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outbox + idempotent consumers&lt;/td&gt;
&lt;td&gt;Billing, inventory, compliance, audit, anything where partial failure is a incident&lt;/td&gt;
&lt;td&gt;You have no consumers and never will&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saga / compensation&lt;/td&gt;
&lt;td&gt;Long-running workflows across multiple services&lt;/td&gt;
&lt;td&gt;Simple CRUD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not jump to the bottom of the table by default. Outbox has operational cost: another table, another worker, another dashboard, another runbook. Use it where the business cost of a lost event exceeds that.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Production checklist
&lt;/h2&gt;

&lt;p&gt;Before you ship any write path that has side effects, walk through this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is every critical write anchored in a single database?&lt;/li&gt;
&lt;li&gt;Are all external side effects deferred until after commit (either via &lt;code&gt;on_commit&lt;/code&gt; or outbox)?&lt;/li&gt;
&lt;li&gt;Are all message consumers idempotent? Do you have a dedup strategy with a concrete key?&lt;/li&gt;
&lt;li&gt;Do you track outbox lag (oldest unpublished row age) and retry rate, with alerts?&lt;/li&gt;
&lt;li&gt;Do your integration tests include concurrent writers hitting the same row?&lt;/li&gt;
&lt;li&gt;Is there a runbook for recovery from a half-commit? Who runs it at 03:00?&lt;/li&gt;
&lt;li&gt;Do you have constraints in the DB that catch the failure modes your application logic might miss?&lt;/li&gt;
&lt;li&gt;Are long-running operations (HTTP, file I/O) kept out of &lt;code&gt;atomic&lt;/code&gt; blocks?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any answer is "we will add it later", that is the thing that will page you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final verdict
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;transaction.atomic()&lt;/code&gt; gives you &lt;strong&gt;local transactional correctness inside one database&lt;/strong&gt;. It does not give you &lt;strong&gt;global process consistency&lt;/strong&gt;. Those are different layers, solved by different tools.&lt;/p&gt;

&lt;p&gt;Most "committed but broken" production incidents come from conflating them - trusting one &lt;code&gt;with transaction.atomic():&lt;/code&gt; block to cover a process that actually spans three systems. Treat the database transaction as one small, strict boundary, move every side effect to &lt;code&gt;on_commit&lt;/code&gt; at minimum, and reach for the outbox pattern when losing an event is unacceptable.&lt;/p&gt;

&lt;p&gt;Get that separation right and a whole class of weird half-state bugs disappears from your backlog.&lt;/p&gt;







&lt;p&gt;&lt;em&gt;If this was useful, I write more about Django architecture, backend design, and production consistency at &lt;a href="https://rafalfuchs.dev/en/blog" rel="noopener noreferrer"&gt;rafalfuchs.dev/en/blog&lt;/a&gt;. The original version of this post lives &lt;a href="https://rafalfuchs.dev/en/blog/django-transaction-boundaries-where-consistency-really-ends" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>architecture</category>
      <category>python</category>
    </item>
    <item>
      <title>SEO Is Not Enough. What AIO (Generative Engine Optimization) Is and Why Your Company Needs It in 2026</title>
      <dc:creator>Rafał Fuchs</dc:creator>
      <pubDate>Thu, 09 Apr 2026 18:59:28 +0000</pubDate>
      <link>https://dev.to/eraefi/seo-is-not-enough-what-aio-generative-engine-optimization-is-and-why-your-company-needs-it-in-4gf5</link>
      <guid>https://dev.to/eraefi/seo-is-not-enough-what-aio-generative-engine-optimization-is-and-why-your-company-needs-it-in-4gf5</guid>
      <description>&lt;h1&gt;
  
  
  SEO Is Not Enough. What AIO (Generative Engine Optimization) Is and Why Your Company Needs It in 2026
&lt;/h1&gt;

&lt;p&gt;Google CTR is falling as more users consume answers without clicking. This guide explains AIO/GEO vs SEO and how to prepare your company for visibility in ChatGPT and AI assistants.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Decision Problem: Is SEO Alone Still Enough in 2026?
&lt;/h2&gt;

&lt;p&gt;If your growth strategy still assumes that winning means getting the click from Google, you are operating on a distribution model built for the previous decade.&lt;/p&gt;

&lt;p&gt;In 2026, a growing share of user intent gets consumed inside answer interfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google AI Overviews
&lt;/li&gt;
&lt;li&gt;ChatGPT Search
&lt;/li&gt;
&lt;li&gt;Perplexity
&lt;/li&gt;
&lt;li&gt;Claude
&lt;/li&gt;
&lt;li&gt;Gemini
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users get a synthesis and recommendations before they even decide whether to open a link.&lt;/p&gt;

&lt;p&gt;SEO is not dead. But SEO alone is no longer a complete solution.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed in Practice (and Why CTR Is Falling)
&lt;/h2&gt;

&lt;p&gt;This is not just a UI refresh. The information consumption model is changing.&lt;/p&gt;

&lt;p&gt;In July 2025, Pew Research Center reported that when users see an AI summary in Google, they are less likely to click traditional results and more likely to end the session without visiting a website.&lt;/p&gt;

&lt;p&gt;At the same time, OpenAI expanded ChatGPT Search as a full search interface, making it broadly available on February 5, 2025.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operational Conclusion
&lt;/h3&gt;

&lt;p&gt;A portion of traffic and purchase decision-making is shifting from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“click a result” →&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“ask an assistant and choose a recommendation”&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  SEO vs AIO/GEO: The Architectural Difference
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SEO&lt;/strong&gt; optimizes for indexing, ranking, and clicks.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;AIO/GEO&lt;/strong&gt; optimizes for how models understand and use your data in answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Difference
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SEO → &lt;em&gt;How do we rank higher in SERP?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;AIO/GEO → &lt;em&gt;How do we ensure AI reconstructs our offer correctly and recommends us?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the AI layer, the winner is not always the highest-ranking website—but the one with the clearest semantic structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Classic SEO Underperforms in Generative Search
&lt;/h2&gt;

&lt;p&gt;Modern websites are optimized for humans and front-end frameworks—not for machine understanding.&lt;/p&gt;

&lt;p&gt;For AI models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heavy HTML = harder parsing
&lt;/li&gt;
&lt;li&gt;Dynamic rendering = inconsistent context
&lt;/li&gt;
&lt;li&gt;Weak relationships = ambiguity
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What AI Actually Needs to Answer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What exactly do you sell?
&lt;/li&gt;
&lt;li&gt;Who is it for?
&lt;/li&gt;
&lt;li&gt;What are your packages or pricing?
&lt;/li&gt;
&lt;li&gt;What proves your credibility?
&lt;/li&gt;
&lt;li&gt;When should someone choose you (and when not)?
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SEO answers this indirectly.&lt;br&gt;&lt;br&gt;
AIO/GEO answers this directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What AIO/GEO Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;AIO/GEO is best understood as a &lt;strong&gt;data and knowledge distribution layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s not about more content—it’s about &lt;strong&gt;better structured information&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Layers
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Semantic layer&lt;/strong&gt; (JSON-LD, Schema.org)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM reference layer&lt;/strong&gt; (llms.txt, extended files)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI crawler accessibility layer&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability layer&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes AIO/GEO an &lt;strong&gt;architectural decision&lt;/strong&gt;, not a content task.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. JSON-LD + Schema.org: Stop Forcing AI to Guess
&lt;/h3&gt;

&lt;p&gt;Without structured data, AI guesses meaning.&lt;/p&gt;

&lt;p&gt;With structured data, AI understands relationships:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who
&lt;/li&gt;
&lt;li&gt;what
&lt;/li&gt;
&lt;li&gt;for whom
&lt;/li&gt;
&lt;li&gt;where
&lt;/li&gt;
&lt;li&gt;under what conditions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why AIO starts with a &lt;strong&gt;semantic audit&lt;/strong&gt;, not more blog posts.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. llms.txt and llms-full.txt: Reference Layer
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; acts as a &lt;strong&gt;knowledge capsule&lt;/strong&gt; for AI systems.&lt;/p&gt;

&lt;p&gt;Important:&lt;br&gt;&lt;br&gt;
It’s an emerging convention—not a strict standard like &lt;code&gt;robots.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Extended approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;llms.txt&lt;/code&gt; → summary
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llms-full.txt&lt;/code&gt; → full structured context
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best used when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your frontend is heavy
&lt;/li&gt;
&lt;li&gt;business context is hard to extract
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. AI Crawler Accessibility
&lt;/h3&gt;

&lt;p&gt;AIO fails if bots cannot read your content.&lt;/p&gt;

&lt;h4&gt;
  
  
  Must Be Verified
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;robots.txt for:

&lt;ul&gt;
&lt;li&gt;OAI-SearchBot
&lt;/li&gt;
&lt;li&gt;GPTBot
&lt;/li&gt;
&lt;li&gt;ClaudeBot
&lt;/li&gt;
&lt;li&gt;PerplexityBot
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;rendering consistency
&lt;/li&gt;

&lt;li&gt;metadata (Open Graph, titles, descriptions)
&lt;/li&gt;

&lt;li&gt;response times
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This is &lt;strong&gt;baseline infrastructure&lt;/strong&gt;, not optional.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Observability: Does AI Understand You?
&lt;/h3&gt;

&lt;p&gt;Core problem:&lt;br&gt;&lt;br&gt;
You don’t know how AI sees your company.&lt;/p&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simulate buying-intent prompts
&lt;/li&gt;
&lt;li&gt;analyze answers
&lt;/li&gt;
&lt;li&gt;measure visibility
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns assumptions into &lt;strong&gt;measurable data&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where AIO/GEO Delivers the Highest ROI
&lt;/h2&gt;

&lt;p&gt;Best results appear in &lt;strong&gt;decision-stage queries&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-Impact Segments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;B2B SaaS
&lt;/li&gt;
&lt;li&gt;Expert services / clinics
&lt;/li&gt;
&lt;li&gt;Local high-ticket services
&lt;/li&gt;
&lt;li&gt;Niche solutions
&lt;/li&gt;
&lt;li&gt;Companies with rising CAC from ads
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If users ask AI &lt;em&gt;“what should I choose?”&lt;/em&gt; → AIO becomes critical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trade-Offs: What AIO/GEO Won’t Fix
&lt;/h2&gt;

&lt;p&gt;AIO/GEO will NOT fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;weak offer
&lt;/li&gt;
&lt;li&gt;lack of proof
&lt;/li&gt;
&lt;li&gt;unclear positioning
&lt;/li&gt;
&lt;li&gt;inconsistent messaging
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It improves &lt;strong&gt;representation&lt;/strong&gt;, not &lt;strong&gt;business fundamentals&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Second limitation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no monitoring = lost visibility over time
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How to Roll It Out (Without Rebuilding Everything)
&lt;/h2&gt;

&lt;p&gt;You don’t need a full rebuild.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical Plan
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Run an AI visibility audit
&lt;/li&gt;
&lt;li&gt;Fix semantic structure
&lt;/li&gt;
&lt;li&gt;Add reference layer
&lt;/li&gt;
&lt;li&gt;Verify crawler access
&lt;/li&gt;
&lt;li&gt;Monitor and iterate (2–4 weeks cycles)
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 Start here: &lt;a href="https://www.aivisible.pl/" rel="noopener noreferrer"&gt;AiVisible Audit&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where AiVisible Fits
&lt;/h2&gt;

&lt;p&gt;AiVisible focuses on &lt;strong&gt;visibility in AI-generated answers&lt;/strong&gt;, not just rankings.&lt;/p&gt;

&lt;h3&gt;
  
  
  What It Includes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AI interpretation audit
&lt;/li&gt;
&lt;li&gt;semantic mapping of your offer
&lt;/li&gt;
&lt;li&gt;plug-and-play implementation
&lt;/li&gt;
&lt;li&gt;missed-query analysis
&lt;/li&gt;
&lt;li&gt;iteration plan based on real prompts
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Try: &lt;a href="https://www.aivisible.pl/" rel="noopener noreferrer"&gt;AiVisible Audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The value is not just traffic—it’s &lt;strong&gt;being recommended at decision moment&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Measure AIO/GEO Impact
&lt;/h2&gt;

&lt;p&gt;SEO metrics are not enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;brand share in AI answers
&lt;/li&gt;
&lt;li&gt;correctness of descriptions
&lt;/li&gt;
&lt;li&gt;missed queries vs competitors
&lt;/li&gt;
&lt;li&gt;time to first recommendation
&lt;/li&gt;
&lt;li&gt;lead quality from AI channels
&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;SEO is not gone—but it’s no longer the only growth system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Winners Optimize Both
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SEO → indexing &amp;amp; ranking
&lt;/li&gt;
&lt;li&gt;AIO/GEO → understanding &amp;amp; recommendation
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a &lt;strong&gt;shift in how the web delivers answers&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Challenge
&lt;/h2&gt;

&lt;p&gt;Your old SEO will not win here.&lt;/p&gt;

&lt;p&gt;Find out how ChatGPT perceives your company today:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.aivisible.pl/" rel="noopener noreferrer"&gt;AiVisible Audit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Complete Guide to Django Performance Optimization</title>
      <dc:creator>Rafał Fuchs</dc:creator>
      <pubDate>Thu, 09 Apr 2026 18:56:07 +0000</pubDate>
      <link>https://dev.to/eraefi/a-complete-guide-to-django-performance-optimization-4ig3</link>
      <guid>https://dev.to/eraefi/a-complete-guide-to-django-performance-optimization-4ig3</guid>
      <description>&lt;h1&gt;
  
  
  A Senior-Level Guide to Optimizing Django Applications
&lt;/h1&gt;

&lt;p&gt;A practical, production-focused guide covering ORM pitfalls, SQL performance, caching, API design, architecture, and real-world scaling decisions.&lt;/p&gt;




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

&lt;p&gt;Optimizing a Django application is something every production system eventually faces. Early on, everything feels fast. As users grow, data expands, and business logic becomes more complex, bottlenecks inevitably appear.&lt;/p&gt;

&lt;p&gt;This guide is based on real production issues, code audits, and hands-on experience with systems handling hundreds of thousands of requests per day.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Think About Django Optimization
&lt;/h2&gt;

&lt;p&gt;The most common mistake: &lt;strong&gt;optimizing blindly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Django is a high-level framework. Most performance issues don’t come from Django itself, but from architectural decisions.&lt;/p&gt;

&lt;p&gt;Before touching the code, answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where exactly is time being lost?&lt;/li&gt;
&lt;li&gt;Is the problem:

&lt;ul&gt;
&lt;li&gt;CPU-bound
&lt;/li&gt;
&lt;li&gt;Database-bound
&lt;/li&gt;
&lt;li&gt;I/O-bound
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Is the issue constant or dependent on data size?&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; Never optimize anything you haven’t measured.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Application Profiling
&lt;/h2&gt;

&lt;p&gt;Without profiling, optimization is guesswork.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Django Debug Toolbar (local)&lt;/li&gt;
&lt;li&gt;django-silk / debug toolbar on staging&lt;/li&gt;
&lt;li&gt;SQL query logging&lt;/li&gt;
&lt;li&gt;APM tools:

&lt;ul&gt;
&lt;li&gt;New Relic&lt;/li&gt;
&lt;li&gt;Datadog&lt;/li&gt;
&lt;li&gt;Sentry Performance&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  What to Measure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Number of SQL queries per request&lt;/li&gt;
&lt;li&gt;Execution time of queries&lt;/li&gt;
&lt;li&gt;Serialization time&lt;/li&gt;
&lt;li&gt;View rendering time&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ORM and SQL Query Optimization
&lt;/h2&gt;

&lt;p&gt;Django ORM is powerful—but easy to misuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eliminating the N+1 Problem
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Classic issue in Django apps.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Symptoms
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Looping over objects&lt;/li&gt;
&lt;li&gt;Each related object access triggers a query&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Solutions
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;select_related()&lt;/code&gt; → ForeignKey / OneToOne&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prefetch_related()&lt;/code&gt; → ManyToMany / reverse FK&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Prefetch()&lt;/code&gt; with custom querysets&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Any query inside a loop = performance bug.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Limiting Retrieved Data
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Model.objects.all()&lt;/code&gt; is often excessive.&lt;/p&gt;

&lt;h4&gt;
  
  
  Techniques
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;only()&lt;/code&gt; / &lt;code&gt;defer()&lt;/code&gt; for wide models&lt;/li&gt;
&lt;li&gt;Explicit fields in serializers&lt;/li&gt;
&lt;li&gt;Separate lightweight read models&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Benefits
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Faster serialization&lt;/li&gt;
&lt;li&gt;Lower memory usage&lt;/li&gt;
&lt;li&gt;Reduced latency&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Database Indexes
&lt;/h3&gt;

&lt;p&gt;Missing indexes = silent performance killers.&lt;/p&gt;

&lt;h4&gt;
  
  
  Focus On
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Fields in &lt;code&gt;filter()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fields in &lt;code&gt;order_by()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fields used in joins&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Decision Rules
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Query runs multiple times per second → add index&lt;/li&gt;
&lt;li&gt;Table &amp;gt; 100k rows → regular index audits&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Cache as an Architectural Component
&lt;/h2&gt;

&lt;p&gt;Cache is not an add-on. It’s part of system design.&lt;/p&gt;




&lt;h3&gt;
  
  
  View-Level Cache
&lt;/h3&gt;

&lt;p&gt;Best for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public endpoints&lt;/li&gt;
&lt;li&gt;Rarely changing data&lt;/li&gt;
&lt;li&gt;Dashboards / reports&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Tools
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cache_page&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reverse proxies (Varnish, CDN)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Data-Level Cache
&lt;/h3&gt;

&lt;p&gt;Most flexible approach.&lt;/p&gt;

&lt;h4&gt;
  
  
  Examples
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Query results&lt;/li&gt;
&lt;li&gt;Aggregations&lt;/li&gt;
&lt;li&gt;Expensive computations&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Good Practices
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Keys based on business parameters&lt;/li&gt;
&lt;li&gt;Short TTL + manual invalidation&lt;/li&gt;
&lt;li&gt;Redis as production standard&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  API and Serialization Optimization
&lt;/h2&gt;

&lt;p&gt;APIs often become bottlenecks faster than the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Problems
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deep serialization trees&lt;/li&gt;
&lt;li&gt;“Universal” serializers&lt;/li&gt;
&lt;li&gt;No pagination&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Solutions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Separate serializers (list vs detail)&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;SerializerMethodField&lt;/code&gt; sparingly&lt;/li&gt;
&lt;li&gt;Always paginate collections&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Unpaginated endpoints = bug.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Asynchronous Processing and Background Tasks
&lt;/h2&gt;

&lt;p&gt;Not everything belongs in request–response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Good Async Candidates
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Emails&lt;/li&gt;
&lt;li&gt;Report generation&lt;/li&gt;
&lt;li&gt;External API integrations&lt;/li&gt;
&lt;li&gt;Heavy validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Typical Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Celery + Redis / RabbitMQ&lt;/li&gt;
&lt;li&gt;Django Q&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Idempotency&lt;/li&gt;
&lt;li&gt;Retry-safe logic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Application Architecture and Performance
&lt;/h2&gt;

&lt;p&gt;Performance often loses to “clean-looking code”.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Issues
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fat models&lt;/li&gt;
&lt;li&gt;Business logic in serializers&lt;/li&gt;
&lt;li&gt;No read/write separation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Better Patterns
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Service layer&lt;/li&gt;
&lt;li&gt;CQRS (for larger systems)&lt;/li&gt;
&lt;li&gt;Read models optimized for use cases&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Scaling Django
&lt;/h2&gt;

&lt;p&gt;Django scales well—if designed properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Elements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Stateless backend&lt;/li&gt;
&lt;li&gt;Shared cache (Redis)&lt;/li&gt;
&lt;li&gt;Shared storage (S3, GCS)&lt;/li&gt;
&lt;li&gt;Load balancers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Decision Rules
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;More users → horizontal scaling
&lt;/li&gt;
&lt;li&gt;Slower queries → optimize data, not hardware
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Django Is No Longer Enough
&lt;/h2&gt;

&lt;p&gt;Rare—but possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warning Signs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ultra-low latency (&amp;lt;50 ms)&lt;/li&gt;
&lt;li&gt;Heavy real-time workloads&lt;/li&gt;
&lt;li&gt;CPU-heavy request processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strategy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Extract critical components&lt;/li&gt;
&lt;li&gt;Use microservices only where justified&lt;/li&gt;
&lt;li&gt;Keep Django as business core&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Django optimization is a &lt;strong&gt;process&lt;/strong&gt;, not a one-time task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Principles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Measure before optimizing&lt;/li&gt;
&lt;li&gt;Treat ORM as a tool—not magic&lt;/li&gt;
&lt;li&gt;Cache is foundational&lt;/li&gt;
&lt;li&gt;Architecture &amp;gt; micro-optimizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-designed Django application can handle large-scale systems without changing the technology stack.&lt;/p&gt;

&lt;p&gt;If you found this useful, you can read the full canonical version here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://rafalfuchs.dev/en/blog/django-performance-optimization" rel="noopener noreferrer"&gt;django performace optimization&lt;/a&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
