<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Anass Ez-zouaine — Senior Backend Engineer · Software Architect · AI Engineer — Architecture</title><description>Architecture posts from Anass Ez-zouaine — Senior Backend Engineer · Software Architect · AI Engineer.</description><link>https://ansezz.com/</link><item><title>Caching for speed: Redis and semantic layers in RAG</title><link>https://ansezz.com/blog/redis-semantic-caching-rag/</link><guid isPermaLink="true">https://ansezz.com/blog/redis-semantic-caching-rag/</guid><description>Stop paying for the same LLM call twice. Two-tier caching — exact-match Redis keys plus semantic vector lookups via RedisVL — that cuts RAG latency from seconds to milliseconds and slashes API spend by up to 80%. With tenant isolation, TTL tiers, and the precision metrics that keep it honest.</description><pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You finally shipped your RAG pipeline. It works. The retrieval is accurate. The LLM is snappy. But then you look at your cloud bill and your P99 latency. Every single query — even &quot;what are your shipping times?&quot; asked for the tenth time — triggers a full chain of embedding, vector search, and an expensive LLM call.&lt;/p&gt;
&lt;p&gt;At scale, this is a disaster. You are essentially paying for the same computation over and over again. Your users are waiting two seconds for answers that should take twenty milliseconds. Your &quot;denial of wallet&quot; risk is through the roof.&lt;/p&gt;
&lt;p&gt;The solution isn&apos;t a bigger model or a faster vector DB. It&apos;s a smarter cache. I&apos;m talking about semantic caching with Redis. It cuts latency from hundreds of milliseconds to single digits and slashes your API costs by up to 80 percent.&lt;/p&gt;
&lt;p&gt;Here is how I build these systems to handle production traffic.&lt;/p&gt;
&lt;h2&gt;The two-tier cache architecture&lt;/h2&gt;
&lt;p&gt;Standard caching relies on exact matches. If a user asks &quot;How do I reset my password?&quot; and another asks &quot;how do i reset my password&quot;, they might hit the same key if you normalize the string. But if the second user asks &quot;Can you help me change my password?&quot;, a traditional cache fails.&lt;/p&gt;
&lt;p&gt;In a modern RAG stack, I use a two-tier approach.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Exact cache&lt;/strong&gt; — a simple key-value store in Redis. I normalize the query (lowercase, trim, strip punctuation) and hash it. It&apos;s your first line of defense. It costs almost nothing and has zero false positives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic cache&lt;/strong&gt; — if the exact cache misses, I embed the query and look for &quot;near enough&quot; matches in a Redis vector index. If I find a previous question with a similarity score of 0.95 or higher, I serve that cached response instead of hitting the LLM.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This architecture ensures that you never do the heavy lifting twice for the same intent.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/redis-semantic-caching-rag/architecture.webp&quot; alt=&quot;Two-tier cache architecture — exact match, semantic match, LLM fallback&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Why Redis is the king of semantic caching&lt;/h2&gt;
&lt;p&gt;Most developers think of Redis as just a key-value store. But with the &lt;a href=&quot;https://redis.io/blog/how-to-cache-semantic-search/&quot;&gt;Redis Vector Library (RedisVL)&lt;/a&gt;, it becomes a high-performance vector database.&lt;/p&gt;
&lt;p&gt;Why use Redis for this instead of your main vector DB like Pinecone or Weaviate?&lt;/p&gt;
&lt;p&gt;Latency.&lt;/p&gt;
&lt;p&gt;Your main vector DB is likely optimized for searching through millions of document chunks. Your semantic cache is much smaller — it only stores recent queries and answers. By co-locating this cache in Redis, which likely already sits in your application tier, you reduce network hops.&lt;/p&gt;
&lt;p&gt;I typically see vector lookups in Redis finish in under 5ms. Compare that to an embedding API call that takes 100ms and an LLM generation that takes 1500ms. The math is simple.&lt;/p&gt;
&lt;h2&gt;Implementing the semantic layer&lt;/h2&gt;
&lt;p&gt;The trick to a good semantic cache is the similarity threshold. Too low, and you give users wrong answers (the &quot;semantic trap&quot;). Too high, and you never get a cache hit.&lt;/p&gt;
&lt;p&gt;I usually start with a distance threshold of 0.1 for cosine distance, which translates to roughly 90 percent similarity. You can implement this quickly using the RedisVL extensions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from redisvl.extensions.llmcache import SemanticCache

# Initialize the cache with a conservative threshold
llm_cache = SemanticCache(
    name=&quot;production_rag_cache&quot;,
    redis_url=&quot;redis://localhost:6379&quot;,
    distance_threshold=0.1,
)

# Check for a hit
query = &quot;how do i update my billing info?&quot;
hit = llm_cache.check(prompt=query)

if hit:
    return hit[0][&quot;response&quot;]

# If miss, run full RAG and then store
# response = run_rag_pipeline(query)
# llm_cache.store(prompt=query, response=response)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This simple wrapper handles the embedding of the incoming query, the vector search in Redis, and the logic for returning the most relevant cached response.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/redis-semantic-caching-rag/code.webp&quot; alt=&quot;Python semantic-cache snippet on a developer workstation&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Avoid the semantic trap: context and versioning&lt;/h2&gt;
&lt;p&gt;Semantic caching is powerful but dangerous if you aren&apos;t careful. If your underlying data changes, your cache might still be serving old, incorrect information.&lt;/p&gt;
&lt;p&gt;I always include a &lt;code&gt;context_version&lt;/code&gt; in my cache keys or metadata. If I re-index my product catalog or update my documentation, I bump the version. The cache immediately starts missing for old entries, forcing a refresh with the new data.&lt;/p&gt;
&lt;p&gt;Another trap is tenant isolation. If User A asks &quot;what is my balance?&quot;, you absolutely cannot serve that cached response to User B. I solve this by partitioning the cache:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use namespaces&lt;/strong&gt; — &lt;code&gt;cache:tenant_id:query_hash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Include metadata&lt;/strong&gt; — add &lt;code&gt;tenant_id&lt;/code&gt; to the vector index filters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This ensures that semantic matches only happen within the correct security boundary. For more on building secure, multi-tenant systems, check out my thoughts on &lt;a href=&quot;https://ansezz.com/blog/laravel-multi-tenancy/&quot;&gt;Laravel multi-tenancy&lt;/a&gt; which shares similar isolation principles.&lt;/p&gt;
&lt;h2&gt;Managing TTL and staleness&lt;/h2&gt;
&lt;p&gt;In a standard cache, you just set an expiry of 3600 seconds and forget it. With a semantic cache, I prefer a tiered TTL strategy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exact matches&lt;/strong&gt; — 1 hour TTL. If the user asks the exact same thing, they probably want the exact same answer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic matches&lt;/strong&gt; — 4 hour TTL. These are more expensive to generate, so we want to keep them longer, but we also include a &quot;last validated&quot; timestamp.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proactive invalidation&lt;/strong&gt; — if my Shopify store updates a product price, I trigger a Redis worker to purge all cache entries related to that product ID.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This hybrid approach keeps the system responsive without serving stale data. I&apos;ve written about similar &lt;a href=&quot;https://ansezz.com/blog/event-driven-pubsub/&quot;&gt;event-driven patterns here&lt;/a&gt; if you want to dive deeper into how to handle these updates at scale.&lt;/p&gt;
&lt;h2&gt;Measuring success: precision and recall&lt;/h2&gt;
&lt;p&gt;Don&apos;t just turn on the cache and walk away. You need to monitor two specific metrics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cache hit rate&lt;/strong&gt; — what percentage of queries are being handled by Redis? I aim for 30–50 percent for general FAQ-style bots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic precision&lt;/strong&gt; — are the cached answers actually correct?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I log every semantic hit along with its similarity score. Once a week, I sample hits with scores between 0.85 and 0.92 and manually review them. If I see too many &quot;near misses&quot; that are actually different questions, I tighten the threshold.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/redis-semantic-caching-rag/dashboard.webp&quot; alt=&quot;Cache analytics dashboard — hit rate, latency, LLM cost, precision&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Final takeaways for senior engineers&lt;/h2&gt;
&lt;p&gt;Implementing Redis as a semantic layer isn&apos;t just about speed. It&apos;s about making your AI systems sustainable. If you are serious about moving from a prototype to a production-ready SaaS, caching is not optional.&lt;/p&gt;
&lt;p&gt;Here is your checklist for next week:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;redisvl&lt;/code&gt; and set up a basic vector index in your dev environment.&lt;/li&gt;
&lt;li&gt;Implement a two-tier lookup (exact then semantic).&lt;/li&gt;
&lt;li&gt;Set your distance threshold conservatively (start at 0.05 or 0.1).&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;tenant_id&lt;/code&gt; or &lt;code&gt;context_version&lt;/code&gt; to your metadata to avoid cross-talk.&lt;/li&gt;
&lt;li&gt;Monitor your hit rate and watch your API bill drop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building in public means sharing the war stories, not just the successes. For more technical deep dives into modern architecture, I suggest looking at &lt;a href=&quot;https://ansezz.com/blog/7-rag-mistakes-production/&quot;&gt;7 RAG mistakes in production&lt;/a&gt; to see what else might be slowing you down.&lt;/p&gt;
&lt;p&gt;What is the one query in your system that keeps hitting your LLM unnecessarily? Drop a note via &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;contact&lt;/a&gt; — let&apos;s figure out if a semantic cache would have caught it. 🤘&lt;/p&gt;
</content:encoded><category>architecture</category><category>redis</category><category>caching</category><category>ai</category><category>rag</category><category>semantic-cache</category><category>redisvl</category><category>vector-search</category><category>performance</category><category>infrastructure</category></item><item><title>Scaling on demand: smart auto-scaling for modern AI apps</title><link>https://ansezz.com/blog/smart-auto-scaling-ai/</link><guid isPermaLink="true">https://ansezz.com/blog/smart-auto-scaling-ai/</guid><description>CPU autoscaling is a lie for GPU workloads. Why queue depth, KV-cache pressure, and TTFT beat CPU as scaling triggers — KEDA-driven patterns, ARIMA forecasting, and composite metrics that scale your AI SaaS before users hit the spinner.</description><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Your AI application is lagging, users are complaining, but your cloud dashboard says everything is fine. Your CPU usage is hovering at a comfortable 20 percent while your inference requests are timing out.&lt;/p&gt;
&lt;p&gt;This is the classic scaling trap for AI engineers. Traditional auto-scaling is built for web servers where CPU and memory are the primary bottlenecks. In the world of large language models and vector databases, those metrics are practically useless.&lt;/p&gt;
&lt;p&gt;If you wait for your CPU to hit 80 percent before spinning up a new pod, your service will be dead in the water long before the second instance even starts its boot sequence. GPU-bound workloads require a completely different playbook.&lt;/p&gt;
&lt;p&gt;To build a resilient, cost-effective AI SaaS, you need to move beyond reactive hardware metrics. You need to scale on intent, queue pressure, and the specific physics of GPU memory.&lt;/p&gt;
&lt;h2&gt;Why the CPU lie is killing your UX&lt;/h2&gt;
&lt;p&gt;Most horizontal pod autoscalers (HPA) are configured to watch CPU utilization by default. For a Laravel or Node.js API, this works great. The work is linear — more requests equal more CPU cycles.&lt;/p&gt;
&lt;p&gt;AI models are different. The CPU handles the &quot;boring&quot; stuff like tokenization, request routing, and managing HTTP headers. The heavy lifting happens on the GPU.&lt;/p&gt;
&lt;p&gt;I have seen production clusters where the GPU is pinned at 100 percent while the CPU sits idle. Kubernetes sees the low CPU usage and thinks the pod is healthy. It might even try to pack &lt;em&gt;more&lt;/em&gt; pods onto that node, leading to a catastrophic failure.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/smart-auto-scaling-ai/cpu-lie.webp&quot; alt=&quot;CPU usage tells you nothing about GPU saturation&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;GPU utilization vs occupancy: the hardware layer&lt;/h2&gt;
&lt;p&gt;When you finally switch to monitoring GPUs, you encounter two confusing metrics: utilization and occupancy.&lt;/p&gt;
&lt;p&gt;GPU utilization is essentially a duty cycle. It tells you the percentage of time the GPU was active over a sample period. It is a lagging indicator. By the time it hits 90 percent, your request queue has likely been building for 30 seconds.&lt;/p&gt;
&lt;p&gt;Occupancy is more granular. It measures how many &quot;warps&quot; or hardware slots are filled within the streaming multiprocessors (SM). You can have high utilization but low occupancy if your batch size is too small.&lt;/p&gt;
&lt;p&gt;For scaling, utilization is the baseline, but it isn&apos;t the truth. You need to look at what is happening before the request even hits the silicon.&lt;/p&gt;
&lt;h2&gt;Queue depth: your best leading indicator&lt;/h2&gt;
&lt;p&gt;If you want to stop fires before they start, monitor your queue depth. In vLLM or SGLang, this is the number of requests waiting for a slot in the inference engine.&lt;/p&gt;
&lt;p&gt;Queue depth is a direct predictor of latency. If you know your model can handle 16 concurrent requests before P99 latency starts to climb, set your scaling trigger at 12.&lt;/p&gt;
&lt;p&gt;Scaling on queue depth lets you provision capacity while the current hardware is still performing within SLO. It gives you that 60-second head start you need to pull a fresh container and load a 20GB model weights file into memory.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/smart-auto-scaling-ai/queue-depth.webp&quot; alt=&quot;Queue depth predicts latency before users feel it&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Token velocity and the KV cache&lt;/h2&gt;
&lt;p&gt;In generative AI, not all requests are created equal. A 10-token summary request is light. A 4,000-token RAG retrieval analysis is a heavyweight.&lt;/p&gt;
&lt;p&gt;This is where token velocity and KV-cache usage come in. The KV cache is the memory on the GPU that stores the context of current conversations. If your KV cache is 95 percent full, the next long request will trigger an eviction or a &quot;swap to CPU&quot; event.&lt;/p&gt;
&lt;p&gt;Latency will skyrocket. Your P99 will look like a mountain range.&lt;/p&gt;
&lt;p&gt;I recommend scaling based on a combination of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Token velocity&lt;/strong&gt; — total tokens per second across all active instances.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KV-cache pressure&lt;/strong&gt; — the percentage of available cache blocks currently occupied.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When the cache is full, it doesn&apos;t matter how low your GPU utilization is. You cannot fit more work onto that chip. You must scale.&lt;/p&gt;
&lt;h2&gt;Predictive scaling with ARIMA&lt;/h2&gt;
&lt;p&gt;Reactive scaling is always playing catch-up. Even with fast boot times, there is a delay. For enterprise apps with predictable traffic patterns, I use ARIMA (Auto-Regressive Integrated Moving Average) models to forecast load.&lt;/p&gt;
&lt;p&gt;If I know traffic historically spikes at 9:00 am every Monday, I don&apos;t wait for the queue to grow. I use a time-series forecast to spin up the &quot;base load&quot; pods at 8:55 am.&lt;/p&gt;
&lt;p&gt;This turns your infrastructure into a proactive system rather than a reactive one. You pay for what you use, but you ensure the capacity is there before the first user clicks &quot;Generate.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/smart-auto-scaling-ai/predictive.webp&quot; alt=&quot;ARIMA forecast lifting capacity before the 9am spike&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Practical steps for your stack&lt;/h2&gt;
&lt;p&gt;Implementing this doesn&apos;t have to be a nightmare. Here is how I structure it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use KEDA&lt;/strong&gt; — the Kubernetes Event-Driven Autoscaler is the gold standard. It lets you scale based on Prometheus metrics like queue depth or P99 latency instead of just CPU.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set TTFT SLOs&lt;/strong&gt; — measure time-to-first-token (TTFT). This is the most critical metric for user perception. If TTFT P99 exceeds 500ms, you need more replicas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blur the lines&lt;/strong&gt; — don&apos;t rely on a single metric. Create a composite score of GPU utilization, queue depth, and cache pressure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix your RAG&lt;/strong&gt; — sometimes the scaling issue is actually a retrieval issue. If your vector search is slow, the inference engine waits longer, hogging the GPU. Check out these &lt;a href=&quot;https://ansezz.com/blog/7-rag-mistakes-production/&quot;&gt;common RAG mistakes&lt;/a&gt; to ensure your bottleneck isn&apos;t upstream.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimize the frontend&lt;/strong&gt; — for Shopify apps or custom SaaS, ensure your &lt;a href=&quot;https://ansezz.com/blog/agentic-workflows-vibe-coding/&quot;&gt;agentic workflows&lt;/a&gt; handle retries gracefully when the infrastructure is scaling up.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scaling AI isn&apos;t about having the biggest GPUs. It is about having the smartest triggers. By moving to service-level metrics, you save money on idle compute and save your users from the dreaded &quot;thinking...&quot; spinner.&lt;/p&gt;
&lt;p&gt;Are you still scaling on CPU, or have you made the jump to queue-based triggers yet? Drop a note via &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;contact&lt;/a&gt; — I love this conversation. 🤘&lt;/p&gt;
</content:encoded><category>architecture</category><category>auto-scaling</category><category>ai</category><category>llm</category><category>kubernetes</category><category>keda</category><category>gpu</category><category>kv-cache</category><category>queue-depth</category><category>ttft</category><category>infrastructure</category></item><item><title>GPU-aware load balancing: managing AI compute like a pro</title><link>https://ansezz.com/blog/gpu-aware-load-balancing/</link><guid isPermaLink="true">https://ansezz.com/blog/gpu-aware-load-balancing/</guid><description>Round-robin is a relic when LLM requests span 50 tokens to 50,000. Prefill vs decode disaggregation, KV-cache-aware routing, prefix matching, and the four metrics that matter — how to route AI traffic so your P99 stops bleeding.</description><pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You just scaled your RAG application to a hundred concurrent users. Suddenly, your latency spikes. Some users get their answers in two seconds, while others are staring at a loading spinner for thirty. You check your load balancer and it says everything is fine. CPU is at 40%. RAM is stable. But your GPUs are screaming, and your P99 latency is in the gutter.&lt;/p&gt;
&lt;p&gt;The problem is that you are treating your AI models like traditional web servers. Sending a 4,000-token prompt to the same GPU that is currently generating a 50-token summary is a recipe for disaster. Round-robin routing is a relic of the past when it comes to LLM inference. If you don&apos;t account for the unique way GPUs handle compute and memory, you aren&apos;t just wasting money. You are killing your user experience.&lt;/p&gt;
&lt;p&gt;The solution isn&apos;t just &quot;more GPUs.&quot; It is building a load balancer that actually understands what is happening inside the model. We need to talk about GPU-aware routing, prefill vs decode disaggregation, and why your KV cache is the most valuable asset in your stack.&lt;/p&gt;
&lt;h2&gt;Why round-robin is a trap for LLMs&lt;/h2&gt;
&lt;p&gt;In traditional software development, a request is a request. Whether it&apos;s a &lt;code&gt;GET /users&lt;/code&gt; or a &lt;code&gt;POST /orders&lt;/code&gt;, the variance in resource consumption is usually predictable and small. Standard load balancers like Nginx or HAProxy work great here. They look at basic health checks and send traffic to the next available worker.&lt;/p&gt;
&lt;p&gt;AI is different. A single request to an LLM has a massive variance in &quot;weight.&quot; One user might ask &quot;what is 2+2?&quot; while another uploads a 50-page PDF and asks for a deep analysis. If your load balancer sends both to the same GPU, the heavy request will hog the compute resources, forcing the light request to wait in a queue.&lt;/p&gt;
&lt;p&gt;This is why CPU-based metrics are useless. A GPU can be at 100% utilization while performing very different types of work. Some work is compute-bound, meaning it needs raw processing power. Other work is memory-bound, meaning it is limited by how fast data can move in and out of VRAM. To solve this, we have to look deeper into the inference lifecycle.&lt;/p&gt;
&lt;h2&gt;Prefill vs decode: the performance gap&lt;/h2&gt;
&lt;p&gt;LLM inference happens in two distinct phases. Understanding the difference between them is the &quot;aha!&quot; moment for GPU load balancing.&lt;/p&gt;
&lt;p&gt;The first phase is &lt;strong&gt;prefill&lt;/strong&gt;. This is when the model reads your entire prompt and processes all the tokens at once. It is a heavy, compute-intensive task that builds something called the &lt;strong&gt;KV cache&lt;/strong&gt; (key-value cache). Prefill loves big batches and high-performance tensor cores. It is where the &quot;heavy lifting&quot; happens.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/gpu-aware-load-balancing/prefill-vs-decode.webp&quot; alt=&quot;Prefill pool vs decode pool — disaggregated GPU fleet&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The second phase is &lt;strong&gt;decode&lt;/strong&gt;. This is where the model generates the response one token at a time. Each new token only needs to look at the previously generated tokens and the KV cache. This phase is surprisingly light on compute but incredibly heavy on memory bandwidth. It is slow and long-lived.&lt;/p&gt;
&lt;p&gt;When you mix these two on the same GPU without a smart scheduler, the &quot;prefill&quot; of a new request will often pause the &quot;decode&quot; of existing requests. This causes the jittery, stuttering text generation that users hate. By using GPU-aware load balancing, we can prioritize these phases differently across our fleet.&lt;/p&gt;
&lt;h2&gt;Metrics for the real world&lt;/h2&gt;
&lt;p&gt;To build a better router, you need to stop looking at CPU and start looking at these four metrics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Token queue depth&lt;/strong&gt; — how many tokens are waiting to be processed? This is a much more accurate representation of &quot;load&quot; than simple request counts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KV cache utilization&lt;/strong&gt; — GPUs have a limited amount of VRAM. The KV cache stores the &quot;memory&quot; of ongoing conversations. If a GPU&apos;s VRAM is 90% full of KV cache, it literally cannot accept a large new prompt, even if it&apos;s currently &quot;idle.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time to first token (TTFT)&lt;/strong&gt; — this measures the latency of the prefill phase. If your TTFT is climbing, your prefill pool is congested.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inter-token latency (ITL)&lt;/strong&gt; — this measures the speed of the decode phase. If this is high, your GPUs are likely memory-bandwidth constrained.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I often recommend using tools like &lt;a href=&quot;https://github.com/vllm-project/vllm&quot;&gt;vLLM&lt;/a&gt; because they expose these metrics out of the box. You can pipe these into a custom gateway that makes routing decisions based on real-time VRAM availability rather than just &quot;is the server up?&quot;&lt;/p&gt;
&lt;h2&gt;The prefix-aware hack: SkyWalker-style routing&lt;/h2&gt;
&lt;p&gt;Here is a secret — the most expensive part of a RAG request is often re-processing the same system prompt or long context over and over again. If you send five consecutive questions about the same document to five different GPUs, each GPU has to perform the &quot;prefill&quot; phase for that document from scratch.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/gpu-aware-load-balancing/prefix-routing.webp&quot; alt=&quot;Prefix matching — routing prompts to GPUs with warm KV cache&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;prefix-aware routing&lt;/strong&gt; (sometimes called SkyWalker-style routing) comes in. Instead of routing randomly, your load balancer tokenizes the start of the prompt and looks for a GPU that already has that specific content in its KV cache.&lt;/p&gt;
&lt;p&gt;By matching the &quot;prefix&quot; of a prompt to a specific GPU, you can skip the prefill phase entirely for large chunks of text. This cuts latency from hundreds of milliseconds to almost zero. It is the single most effective way to optimize costs in production RAG systems. I&apos;ve written before about &lt;a href=&quot;https://ansezz.com/blog/7-rag-mistakes-production/&quot;&gt;common RAG mistakes&lt;/a&gt;, and ignoring cache locality is definitely one of them.&lt;/p&gt;
&lt;h2&gt;Splitting the fleet into specialized pools&lt;/h2&gt;
&lt;p&gt;As you scale, you should stop treating every GPU as a generalist. A senior move is to create &lt;strong&gt;disaggregated inference fleets&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I like to split my GPUs into two pools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The prefill pool&lt;/strong&gt; — high-compute GPUs (like H100s) optimized for processing massive amounts of context quickly. These nodes handle the initial prompt and then &quot;hand off&quot; the state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The decode pool&lt;/strong&gt; — memory-optimized GPUs (like A100s or even cheaper L40s) that focus on churning out tokens for existing requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This separation lets you scale based on your specific workload. If your users are uploading huge documents but only asking for short summaries, you scale your prefill pool. If they are having long, chatty conversations, you scale your decode pool.&lt;/p&gt;
&lt;p&gt;This is the same logic we use in &lt;a href=&quot;https://ansezz.com/blog/coolify-docker-saas-hosting/&quot;&gt;modern DevOps with Coolify&lt;/a&gt;. You wouldn&apos;t put your heavy database on the same tiny instance as your frontend — why would you mix your heavy prefill work with your light decode work?&lt;/p&gt;
&lt;h2&gt;Implementing your first GPU-aware router&lt;/h2&gt;
&lt;p&gt;You don&apos;t need to build a custom engine from scratch to start doing this. Here is the practical path I follow when setting this up for a new SaaS:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Centralize your metrics&lt;/strong&gt; — use Prometheus to scrape vLLM or TGI metrics from every GPU node.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a smart gateway&lt;/strong&gt; — implement a middleware in Go or Rust (or even a heavy-duty Lua script in OpenResty) that queries these metrics before choosing a target.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prioritize KV cache&lt;/strong&gt; — check if the &lt;code&gt;conversation_id&lt;/code&gt; has been seen by a specific node recently. If it has, and that node isn&apos;t at 100% KV utilization, send it there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set hard limits&lt;/strong&gt; — if a GPU reaches 85% VRAM usage, take it out of the rotation for new prompts until some sessions finish.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/gpu-aware-load-balancing/metrics-dashboard.webp&quot; alt=&quot;SaaS metrics dashboard tracking TTFT, ITL, KV utilization&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Managing AI compute is about moving from &quot;black box&quot; infrastructure to &quot;context-aware&quot; infrastructure. When your load balancer knows the difference between a 10-token greeting and a 10,000-token context window, your costs go down and your users stay happy.&lt;/p&gt;
&lt;p&gt;It&apos;s easy to get lost in the hype of &quot;agentic systems&quot; and &lt;a href=&quot;https://ansezz.com/blog/mcp-context-aware-agents/&quot;&gt;context-aware agents&lt;/a&gt;, but none of that matters if your underlying infrastructure is buckling under the weight of unoptimized routing.&lt;/p&gt;
&lt;p&gt;If you are still using round-robin for your AI models, what is the biggest bottleneck you are seeing in your P99 latency right now? Drop a note via &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;contact&lt;/a&gt; — I love this conversation. 🤘&lt;/p&gt;
</content:encoded><category>architecture</category><category>gpu</category><category>load-balancing</category><category>ai</category><category>llm</category><category>inference</category><category>vllm</category><category>kv-cache</category><category>prefill</category><category>decode</category><category>infrastructure</category></item><item><title>Circuit breakers: preventing cascading failures in your vector DB</title><link>https://ansezz.com/blog/circuit-breakers-vector-db/</link><guid isPermaLink="true">https://ansezz.com/blog/circuit-breakers-vector-db/</guid><description>A slow vector DB kills SaaS faster than a dead one. The circuit-breaker pattern for AI infrastructure — closed/open/half-open states, fallback tiers, semantic caches, LLM-only mode, and Laravel-friendly wiring to keep production from melting under one bad dependency.</description><pubDate>Sat, 23 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You built a beautiful RAG pipeline. It works perfectly on your machine with a few hundred vectors. Then you launch. Traffic spikes. Suddenly your managed vector database starts sweating. A single similarity search that used to take 50ms is now taking 5 seconds. Your web workers are all tied up waiting for responses that aren&apos;t coming. The database isn&apos;t technically down — but it is slow enough to kill your entire application. Your users see spinning loaders until the request finally times out. This is a classic cascading failure, and it is the fastest way to drain your &quot;innovation budget&quot; and your users&apos; patience.&lt;/p&gt;
&lt;p&gt;The problem is that we often treat external APIs and databases as if they are always healthy. We write code that assumes the vector DB will return results. When it doesn&apos;t, we wait. And while we wait, we hold onto memory and CPU cycles. The solution is an old-school electrical engineering concept applied to software: the circuit breaker.&lt;/p&gt;
&lt;p&gt;In this guide I want to show you how to wrap your AI infrastructure in protective logic so a slow dependency doesn&apos;t take your whole SaaS down with it.&lt;/p&gt;
&lt;h2&gt;Understanding the three states&lt;/h2&gt;
&lt;p&gt;The circuit breaker pattern is a state machine that sits between your application code and your external service. It monitors every call you make. It has three specific states that dictate how it handles traffic.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/circuit-breakers-vector-db/states.webp&quot; alt=&quot;Closed, open, and half-open circuit breaker states&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Closed — the healthy state.&lt;/strong&gt;
In the closed state, the circuit is complete. Requests flow through to your vector database or LLM provider normally. The breaker is silently watching. It keeps a count of how many requests failed or took too long. As long as the failure rate stays below your threshold, it stays closed. This is the &quot;everything is fine&quot; mode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open — the fail-fast state.&lt;/strong&gt;
Once the failure threshold is hit — let&apos;s say 50% of requests failed in the last 30 seconds — the breaker &quot;trips&quot; and moves to the open state. Now, every time your application tries to call the vector DB, the breaker immediately throws an error or returns a fallback response without even attempting the network call. This gives your database room to breathe and recover. It also ensures your application doesn&apos;t waste time waiting on a service that is clearly struggling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Half-open — the recovery test.&lt;/strong&gt;
After a cooldown period, the breaker moves to the half-open state. It allows a small number of &quot;test&quot; requests to pass through. If these test calls succeed, the breaker assumes the service is healthy again and moves back to the closed state. If they fail, it immediately goes back to open for another cooldown cycle. This is a controlled way to probe the system before fully re-engaging.&lt;/p&gt;
&lt;h2&gt;Why your RAG pipeline needs this&lt;/h2&gt;
&lt;p&gt;RAG pipelines are particularly vulnerable because they usually involve multiple high-latency network hops. You have to embed the query, search the vector DB, and then call the LLM. If any of these pieces fail or slow down, the whole experience breaks.&lt;/p&gt;
&lt;p&gt;Most developers make the mistake of only handling hard errors like a &lt;code&gt;404&lt;/code&gt; or a &lt;code&gt;500&lt;/code&gt; status code. But in production, &quot;slow&quot; is often more dangerous than &quot;down.&quot; A slow vector DB creates a bottleneck that backs up your entire request queue. By the time you realize there is a problem, your server is out of memory because it is holding open thousands of connections.&lt;/p&gt;
&lt;p&gt;If you have read my previous post on &lt;a href=&quot;https://ansezz.com/blog/7-rag-mistakes-production/&quot;&gt;7 RAG mistakes in production&lt;/a&gt;, you know that reliability is the difference between a demo and a product. The circuit breaker is your insurance policy against these types of outages.&lt;/p&gt;
&lt;h2&gt;Implementing fallback strategies&lt;/h2&gt;
&lt;p&gt;Tripping the breaker shouldn&apos;t always mean showing an error message to the user. The best AI systems use fallbacks to maintain a level of service even when parts of the stack are failing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/circuit-breakers-vector-db/fallback-flow.webp&quot; alt=&quot;Architecture flow showing primary path with hot/cold/cache fallbacks&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hot and cold tiers.&lt;/strong&gt;
You can think of your vector DB as your &quot;hot&quot; knowledge tier. If it fails, you should have a &quot;cold&quot; fallback. Maybe you fall back to a standard keyword search in your primary Postgres or MySQL database. The results might not be as contextually relevant as a vector search, but a &quot;decent&quot; answer is always better than a &quot;timed out&quot; error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cached responses.&lt;/strong&gt;
Another great strategy is semantic caching. If the circuit is open, you can check a Redis cache for similar queries that were answered recently. Even if you can&apos;t generate a fresh answer, you might be able to serve a cached one. This keeps the user moving while your backend recovers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLM-only mode.&lt;/strong&gt;
If your retrieval step is what&apos;s failing, you can still send the user&apos;s prompt to the LLM with a note that external knowledge is currently unavailable. The LLM can then answer based on its general training data. It is a degraded experience, but it is still functional. Transparency here is key — tell the user that the &quot;live&quot; data isn&apos;t available so they know to verify the response.&lt;/p&gt;
&lt;h2&gt;Building it in Laravel&lt;/h2&gt;
&lt;p&gt;Since I spend a lot of time in the Laravel ecosystem, I often use tools that make this easy to implement. You don&apos;t need to write the state machine from scratch. Packages like &lt;code&gt;spatie/resilience&lt;/code&gt; or even building a custom wrapper around the &lt;code&gt;illuminate/http&lt;/code&gt; client can get the job done.&lt;/p&gt;
&lt;p&gt;The goal is to wrap your API calls in a block that understands these states. Here is a simplified look at how that logic looks in practice.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/circuit-breakers-vector-db/code.webp&quot; alt=&quot;Circuit breaker wrapper around a vector-db client&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When you call your vector DB client, you wrap it in the breaker. If the call fails multiple times, the breaker trips. In the &lt;code&gt;catch&lt;/code&gt; block, you handle the &lt;code&gt;CircuitBreakerOpenException&lt;/code&gt; by returning your fallback data. This keeps your controllers clean and your architecture robust.&lt;/p&gt;
&lt;p&gt;You can also integrate this with your &lt;a href=&quot;https://ansezz.com/blog/coolify-docker-saas-hosting/&quot;&gt;SaaS hosting on Coolify&lt;/a&gt; to ensure that your containers don&apos;t get killed by health checks just because an external API is slow. The breaker prevents the resource bloat that usually triggers those health-check failures.&lt;/p&gt;
&lt;h2&gt;Live telemetry and smart routing&lt;/h2&gt;
&lt;p&gt;Senior engineers don&apos;t just set a circuit breaker and walk away. They monitor it. You need live telemetry to see how often your circuits are tripping. Tools like Prometheus or even simple logs piped to a dashboard can tell you a lot.&lt;/p&gt;
&lt;p&gt;If you see that your primary vector DB in &lt;code&gt;us-east-1&lt;/code&gt; is constantly tripping but your secondary in &lt;code&gt;eu-west-1&lt;/code&gt; is healthy, you can implement smart routing. Your circuit breaker can act as a signal to your load balancer or your internal router to shift traffic to the healthy region.&lt;/p&gt;
&lt;p&gt;This kind of &lt;a href=&quot;https://ansezz.com/blog/event-driven-pubsub/&quot;&gt;event-driven architecture&lt;/a&gt; makes your system self-healing. It doesn&apos;t wait for a human to wake up at 3am to fix a database. It detects the failure, trips the breaker, uses the fallback, and tries to recover automatically.&lt;/p&gt;
&lt;h2&gt;Practical steps to get started&lt;/h2&gt;
&lt;p&gt;If you are ready to harden your AI infrastructure, start here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identify your weakest links&lt;/strong&gt; — list every external call in your RAG pipeline. Usually it is the embedding API and the vector DB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Define your thresholds&lt;/strong&gt; — how many slow requests are you willing to tolerate? Start with a 50% failure rate over 30 seconds and a 2-second timeout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choose your fallbacks&lt;/strong&gt; — decide what happens when the breaker is open. Do you show an error, use a cache, or switch to keyword search?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrap your clients&lt;/strong&gt; — use a library to wrap your HTTP or database calls. Don&apos;t try to build the state machine logic yourself unless you have a very specific use case.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor the trips&lt;/strong&gt; — set up an alert when a circuit stays open for more than a few minutes. This usually indicates a major provider outage that needs your attention.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The goal is to fail gracefully. Every system has issues, but the ones that survive are the ones that don&apos;t let a small fire in a dependency burn down the whole house.&lt;/p&gt;
&lt;p&gt;Have you ever had a slow dependency take down your entire application, or are you still relying on long timeouts and luck? Drop a note via &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;contact&lt;/a&gt; — I love this conversation. 🤘&lt;/p&gt;
</content:encoded><category>architecture</category><category>circuit-breakers</category><category>ai</category><category>rag</category><category>resilience</category><category>vector-db</category><category>fallback</category><category>laravel</category><category>observability</category><category>infrastructure</category></item><item><title>Message queues: handling the heavy lifting of document processing</title><link>https://ansezz.com/blog/message-queues-document-processing/</link><guid isPermaLink="true">https://ansezz.com/blog/message-queues-document-processing/</guid><description>Stop running embeddings inside the request-response cycle. A production-grade document ingestion pipeline — staged workers, exponential backoff, dead-letter quarantines, batched embeddings, and queue-depth autoscaling that keeps your AI app from melting under a 500-page PDF.</description><pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you are running your document embeddings inside your request-response cycle, you are playing with fire. I have seen too many junior devs build a beautiful RAG application that falls over the second a user uploads a 50MB PDF. The browser spins, the Nginx timeout hits, and the database locks up while your worker tries to chunk 500 pages of legal jargon in real time.&lt;/p&gt;
&lt;p&gt;This is the classic &quot;heavy lifting&quot; problem in AI engineering. Document processing — OCR, text extraction, semantic chunking, and embedding — is slow, unpredictable, and resource-heavy. Trying to force it into a synchronous web request is a recipe for a bad user experience and a fragile system.&lt;/p&gt;
&lt;p&gt;The solution is decoupling. I&apos;m talking about message queues. In this guide, I&apos;ll walk you through why async work belongs in a queue and how to build a production-grade ingestion pipeline that doesn&apos;t melt your server.&lt;/p&gt;
&lt;h2&gt;The synchronous trap&lt;/h2&gt;
&lt;p&gt;Imagine a user uploads a document to your SaaS. Your code receives the file, sends it to an extraction API, waits for the response, loops through the text to create chunks, sends each chunk to an embedding model, and finally saves it to pgvector.&lt;/p&gt;
&lt;p&gt;If any of those steps take more than 30 seconds, the connection drops. If the embedding API has a momentary blip, the whole process fails, and the user has to start over. Worse, while your server is busy doing this heavy work, it&apos;s not responding to other users.&lt;/p&gt;
&lt;p&gt;This is where we apply the first rule of senior engineering: if it takes more than 100ms, consider making it async. By moving this work to a message queue, you give your users immediate feedback (&quot;we&apos;re processing your file!&quot;) while the heavy lifting happens safely in the background.&lt;/p&gt;
&lt;h2&gt;The anatomy of a document pipeline&lt;/h2&gt;
&lt;p&gt;A robust RAG pipeline isn&apos;t just one big function. It&apos;s a series of decoupled stages. I like to break it down into modular steps, each triggered by a message in a queue. This lets you scale different parts of the system independently.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/message-queues-document-processing/pipeline-stages.webp&quot; alt=&quot;Pipeline stages — ingestion, parsing, chunking, embedding&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here is how I usually structure it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ingestion &amp;amp; discovery&lt;/strong&gt; — a user uploads a file. You save it to S3 and push a small message to the queue containing the &lt;code&gt;file_path&lt;/code&gt; and &lt;code&gt;tenant_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parsing &amp;amp; normalization&lt;/strong&gt; — a worker picks up the message, downloads the file, and runs it through a parser like pdfplumber or an OCR service. It emits the raw text to the next queue.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chunking&lt;/strong&gt; — this worker takes the text and splits it into semantic sections. Doing this in its own stage means you can easily swap chunking strategies (e.g., recursive character vs semantic) without re-running the heavy parsing step.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedding &amp;amp; indexing&lt;/strong&gt; — the final stage batches the chunks, hits your embedding API (like OpenAI or a local model), and pushes the vectors into your vector DB.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This stage-based approach is exactly what I discuss in my post on &lt;a href=&quot;https://ansezz.com/blog/7-rag-mistakes-production/&quot;&gt;7 RAG mistakes to avoid in production&lt;/a&gt;. It provides backpressure control — if your vector DB slows down, the &quot;index&quot; queue grows, but the &quot;parsing&quot; workers keep humming along.&lt;/p&gt;
&lt;h2&gt;Retries and the beauty of dead letters&lt;/h2&gt;
&lt;p&gt;In the real world, things break. APIs time out. PDFs are malformed. Workers crash.&lt;/p&gt;
&lt;p&gt;When you use a message queue like Redis (with BullMQ or Laravel Queues) or SQS, you get retries for free. If a worker fails, the message goes back onto the queue to be tried again after a short delay. Exponential backoff is your best friend here — don&apos;t hammer a failing API every 5 seconds. Wait 10, then 60, then 300.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/message-queues-document-processing/dlq-retries.webp&quot; alt=&quot;Retry strategy and dead-letter quarantine flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But what happens when a document simply &lt;em&gt;cannot&lt;/em&gt; be processed? Maybe it&apos;s a password-protected PDF or a corrupted file. You don&apos;t want it retrying forever and clogging up your workers.&lt;/p&gt;
&lt;p&gt;This is where a &lt;strong&gt;Dead Letter Queue (DLQ)&lt;/strong&gt; comes in. After a certain number of failed attempts, the message is moved to the DLQ. This acts as a &quot;quarantine&quot; zone. I can then inspect these failed jobs, fix the underlying issue, and manually re-queue them. It&apos;s a safety net that keeps your main production line moving.&lt;/p&gt;
&lt;h2&gt;Batching for efficiency&lt;/h2&gt;
&lt;p&gt;If you are processing 10,000 chunks, you do not want to make 10,000 individual API calls to your embedding provider. That&apos;s slow and expensive.&lt;/p&gt;
&lt;p&gt;Most embedding APIs and vector databases perform much better with batches. A good worker pattern involves pulling multiple messages from the queue (or aggregating them in memory) and sending them as a single bulk request.&lt;/p&gt;
&lt;p&gt;In a Laravel environment, I often use job batching to track the progress of a large document. I can see exactly when 95% of a PDF is processed and update a progress bar for the user. If you&apos;re interested in how this fits into a larger architecture, check out my thoughts on &lt;a href=&quot;https://ansezz.com/blog/event-driven-pubsub/&quot;&gt;event-driven pub/sub systems&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Event-driven prefetching&lt;/h2&gt;
&lt;p&gt;Here is a &quot;senior&quot; tip — queues aren&apos;t just for ingestion. You can use them for &lt;strong&gt;prefetching&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If a user is chatting with an AI agent and the conversation is heading toward a specific topic, you can fire off a background job to fetch related documents and warm up the cache before the user even asks the next question. This makes your AI feel lightning fast because the context is already &quot;ready&quot; when the retrieval step hits.&lt;/p&gt;
&lt;p&gt;By using an event bus, you can decouple the chat interface from these optimization tasks. The chat app just emits a &lt;code&gt;user_asked_question&lt;/code&gt; event, and a background worker decides whether it should pre-fetch more data or update the semantic cache.&lt;/p&gt;
&lt;h2&gt;Monitoring the heart of your app&lt;/h2&gt;
&lt;p&gt;Once you move to a queue-based system, your most important metric is no longer just &quot;request latency.&quot; You need to watch your &lt;strong&gt;queue depth&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/message-queues-document-processing/monitoring.webp&quot; alt=&quot;Queue-depth dashboard with worker autoscaler&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If the queue depth is growing faster than your workers can clear it, you have a bottleneck. This is where tools like Docker and Coolify make life easy — I can spin up five more worker containers to handle a sudden surge in document uploads. You can read more about how I manage this infra in my &lt;a href=&quot;https://ansezz.com/blog/coolify-docker-saas-hosting/&quot;&gt;Coolify and Docker guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Practical takeaways for your pipeline&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Never store large files in the queue&lt;/strong&gt; — only pass references (like an S3 key). Keep messages small for better performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make tasks idempotent&lt;/strong&gt; — assume a message might be processed twice. Use &lt;code&gt;upsert&lt;/code&gt; instead of &lt;code&gt;insert&lt;/code&gt; in your vector DB to avoid duplicates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use structured logging&lt;/strong&gt; — every worker log should include the &lt;code&gt;doc_id&lt;/code&gt; and &lt;code&gt;tenant_id&lt;/code&gt;. Searching for &quot;why did this file fail?&quot; is impossible without it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale on queue depth&lt;/strong&gt; — set up your autoscaler to add workers based on how many messages are waiting, not just CPU usage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate worker pools&lt;/strong&gt; — have one set of workers for &quot;fast&quot; tasks (like metadata updates) and another for &quot;slow&quot; tasks (like OCR/embedding). Don&apos;t let a huge PDF upload block a simple name change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building a document pipeline is about respecting the time it takes to process data. By moving that work into a queue, you build a system that is resilient, scalable, and — most importantly — provides a smooth experience for your users.&lt;/p&gt;
&lt;p&gt;How are you currently handling long-running AI tasks? Are you still fighting with request timeouts, or have you embraced the queue? Drop a note via &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;contact&lt;/a&gt; — I love this conversation. 🤘&lt;/p&gt;
</content:encoded><category>architecture</category><category>message-queues</category><category>ai</category><category>rag</category><category>document-processing</category><category>redis</category><category>bullmq</category><category>laravel-queues</category><category>dlq</category><category>batching</category><category>infrastructure</category></item><item><title>Rate limiting: protecting your AI wallet</title><link>https://ansezz.com/blog/rate-limiting-ai-wallet/</link><guid isPermaLink="true">https://ansezz.com/blog/rate-limiting-ai-wallet/</guid><description>One runaway agent loop = $5,000 OpenAI bill. Why request-per-second limits lie for LLM apps, how to architect hierarchical token-bucket limits across global / tenant / user layers, and adaptive throttling patterns that protect margins without breaking UX.</description><pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One runaway agent loop is all it takes to wake up to a $5,000 OpenAI bill.&lt;/p&gt;
&lt;p&gt;If you&apos;re building AI-powered SaaS or RAG systems, your biggest threat isn&apos;t a server crash. It&apos;s a &quot;denial of wallet&quot; attack. A buggy client, a malicious user, or even your own experimental agent can spam your API endpoints and burn through your tokens (and credits) in minutes.&lt;/p&gt;
&lt;p&gt;Traditional web apps care about requests per second to keep the CPU from melting. In the world of LLMs, we care about tokens per minute to keep the bank account from draining. Standard rate limiting isn&apos;t enough anymore. You need an architecture that understands cost, context, and the &quot;noisy neighbor&quot; problem before a single prompt even hits your vector DB.&lt;/p&gt;
&lt;h2&gt;Why requests per second (QPS) is a lie for AI&lt;/h2&gt;
&lt;p&gt;In a standard Laravel or Node app, a request is a request. Sure, some take longer than others, but they generally consume similar resources. In AI engineering, one request might be a 50-token greeting, while another is a 128,000-token context dump for a RAG pipeline.&lt;/p&gt;
&lt;p&gt;If you only limit requests per second, a single user can stay within their &quot;10 requests per minute&quot; limit while still costing you 100× more than everyone else combined. This is where the &lt;a href=&quot;https://ansezz.com/blog/laravel-multi-tenancy/&quot;&gt;noisy neighbor problem&lt;/a&gt; becomes a financial crisis.&lt;/p&gt;
&lt;p&gt;You aren&apos;t just protecting your infrastructure. You&apos;re protecting your margins. To do this effectively, we have to move from counting &quot;pings&quot; to counting &quot;value.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/rate-limiting-ai-wallet/dashboard.webp&quot; alt=&quot;Token usage dashboard showing skewed cost per user&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The anatomy of a denial of wallet (DoW) attack&lt;/h2&gt;
&lt;p&gt;A denial of wallet attack is the AI equivalent of a DDoS. The goal isn&apos;t necessarily to take your site down. It&apos;s to exhaust your API quotas or financial budget until your service stops functioning — or you&apos;re forced to pay a massive bill.&lt;/p&gt;
&lt;p&gt;I&apos;ve seen this happen in three ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The agentic loop&lt;/strong&gt; — an autonomous agent gets stuck in a logic loop, calling your tool-use functions repeatedly without a &quot;max steps&quot; ceiling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The scrapers&lt;/strong&gt; — malicious bots trying to exfiltrate your entire knowledge base by querying every possible permutation of your RAG system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The dev mistake&lt;/strong&gt; — a frontend developer accidentally puts an LLM-powered &quot;autocomplete&quot; on a search bar that triggers on every keystroke.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Without token-aware rate limiting, your provider (like OpenAI or Anthropic) will eventually hit you with a &lt;code&gt;429&lt;/code&gt; error. But by that time, the damage to your wallet is already done.&lt;/p&gt;
&lt;h2&gt;Solving the noisy neighbor with hierarchical limits&lt;/h2&gt;
&lt;p&gt;To solve this, I implement a three-layer rate limiting strategy at the API gateway level. This ensures that even if one tenant goes rogue, the rest of the platform stays healthy.&lt;/p&gt;
&lt;h3&gt;1. The global provider layer&lt;/h3&gt;
&lt;p&gt;This is your final line of defense. If your OpenAI quota is 500,000 tokens per minute (TPM), set your internal global limit to 450,000. This leaves a safety buffer and prevents you from actually hitting the provider&apos;s hard ceiling, which can sometimes lead to temporary account bans or throttled priority.&lt;/p&gt;
&lt;h3&gt;2. The tenant layer&lt;/h3&gt;
&lt;p&gt;Every customer gets their own bucket. I usually tie this to their subscription tier. A &quot;Pro&quot; user might get 50,000 TPM, while a &quot;Free&quot; user is capped at 2,000. This ensures no single company can eat up your entire global quota.&lt;/p&gt;
&lt;h3&gt;3. The user/session layer&lt;/h3&gt;
&lt;p&gt;Inside a single tenant, you still need limits. You don&apos;t want one single employee at a customer&apos;s company hogging all the tokens allocated to that entire organization. I set these at about 20% of the total tenant capacity.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/rate-limiting-ai-wallet/architecture.webp&quot; alt=&quot;Hierarchical rate-limit architecture — global, tenant, user buckets&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Implementation: the token bucket algorithm&lt;/h2&gt;
&lt;p&gt;For most of my builds, I use a &quot;token bucket&quot; or &quot;leaky bucket&quot; algorithm backed by Redis. It&apos;s the gold standard for handling bursty traffic while maintaining a steady flow.&lt;/p&gt;
&lt;p&gt;Here is the logic: each user has a &quot;bucket&quot; of tokens. Every time they send a prompt, we estimate the total cost (input tokens + expected &lt;code&gt;max_tokens&lt;/code&gt;). If the bucket has enough, they proceed and the tokens are deducted. The bucket refills at a constant rate over time.&lt;/p&gt;
&lt;p&gt;If you&apos;re &lt;a href=&quot;https://ansezz.com/about/&quot;&gt;modernizing your stack&lt;/a&gt; or building a SaaS on the LEMP stack, you can implement this efficiently in Laravel using middleware and a fast storage layer like Redis.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// A simplified token-bucket check in Laravel middleware
public function handle($request, Closure $next)
{
    $tenantId = $request-&amp;gt;user()-&amp;gt;tenant_id;
    $estimatedTokens = $this-&amp;gt;tokenizer-&amp;gt;estimate($request-&amp;gt;input(&apos;prompt&apos;));

    if (!$this-&amp;gt;limiter-&amp;gt;consume(&quot;tenant:{$tenantId}:tokens&quot;, $estimatedTokens)) {
        return response()-&amp;gt;json([&apos;error&apos; =&amp;gt; &apos;token budget exceeded&apos;], 429);
    }

    return $next($request);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/rate-limiting-ai-wallet/code.webp&quot; alt=&quot;Laravel middleware token-bucket snippet&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Token-budget routing and adaptive throttling&lt;/h2&gt;
&lt;p&gt;What happens when a user hits their limit? Most devs just throw a &lt;code&gt;429&lt;/code&gt; error. But as a senior engineer, I prefer a more graceful degradation. We call this &lt;strong&gt;adaptive throttling&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead of a hard &quot;no,&quot; you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Degrade the model&lt;/strong&gt; — switch the request from GPT-4o to a cheaper, faster model like GPT-4o-mini.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Truncate the context&lt;/strong&gt; — if the user is over budget, strip out some of the retrieved RAG documents to lower the input token count.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queue the request&lt;/strong&gt; — for non-interactive tasks (like background summarization), move the request to a message queue and process it when the token bucket refills.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This keeps the user experience intact while protecting your margins. It&apos;s about being smart, not just being a gatekeeper.&lt;/p&gt;
&lt;h2&gt;The RAG context: limiting the &quot;hidden&quot; calls&lt;/h2&gt;
&lt;p&gt;In a RAG (retrieval-augmented generation) system, one user query often triggers multiple backend actions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One embedding call for the query.&lt;/li&gt;
&lt;li&gt;One search query to the vector database.&lt;/li&gt;
&lt;li&gt;One (or more) LLM calls for the final answer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you only rate limit the final LLM call, your vector database might still get hammered by search queries. You need to treat the entire &quot;RAG flow&quot; as a single unit of work with its own combined budget. I cover some of these &lt;a href=&quot;https://ansezz.com/blog/7-rag-mistakes-production/&quot;&gt;common RAG production mistakes&lt;/a&gt; frequently, but rate limiting the &quot;flow&quot; is often the most overlooked fix.&lt;/p&gt;
&lt;h2&gt;Practical steps to protect your system today&lt;/h2&gt;
&lt;p&gt;If you&apos;re launching an AI feature this week, do these three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Set a hard daily spend cap&lt;/strong&gt; — most API providers let you set a maximum dollar amount per month. Set it. It&apos;s your parachute.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enforce &lt;code&gt;max_tokens&lt;/code&gt;&lt;/strong&gt; — never let a user request an uncapped response. Always set a sane default for &lt;code&gt;max_tokens&lt;/code&gt; in every API call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement per-request timeout&lt;/strong&gt; — if an LLM call takes longer than 30 seconds, kill it. Slow calls are often the symptom of a system that is about to spiral out of control.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Rate limiting isn&apos;t just a &quot;security&quot; feature. In the AI era, it&apos;s a core part of your business model. You can&apos;t scale a product that allows a single user to run up a thousand-dollar bill in their first hour.&lt;/p&gt;
&lt;p&gt;Build for fairness. Build for cost. Build for the &quot;noisy neighbor.&quot;&lt;/p&gt;
&lt;p&gt;Have you ever seen a &quot;denial of wallet&quot; happen in the wild, or are you still running on a wing and a prayer with global provider limits? Drop a note via &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;contact&lt;/a&gt; — I love this conversation. 🤘&lt;/p&gt;
</content:encoded><category>architecture</category><category>rate-limiting</category><category>ai</category><category>llm</category><category>rag</category><category>multi-tenancy</category><category>redis</category><category>laravel</category><category>denial-of-wallet</category><category>api-gateway</category></item><item><title>API Gateway: the front door of your AI stack</title><link>https://ansezz.com/blog/api-gateway-ai-stack/</link><guid isPermaLink="true">https://ansezz.com/blog/api-gateway-ai-stack/</guid><description>Stop exposing LLM providers directly to the frontend. The gateway pattern for AI apps — JWT-scoped tenant isolation, model aliases, denial-of-wallet rate limiting, streaming-safe timeouts, and the wallet-saving guardrails every senior engineer needs.</description><pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Stop exposing your models to the wild.&lt;/p&gt;
&lt;p&gt;If you are building a production AI app, sending requests directly from your frontend to a RAG orchestrator or — god forbid — straight to an LLM provider is a liability. It is slow. It is insecure. And it is the fastest way to wake up to a five-figure bill you didn&apos;t plan for.&lt;/p&gt;
&lt;p&gt;I have spent over a decade building software, and if there is one thing I have learned, it is that engineering for &quot;it works&quot; is not the same as engineering for &quot;it scales.&quot; In the world of AI, scale isn&apos;t just about traffic. It is about cost, latency, and data safety.&lt;/p&gt;
&lt;p&gt;Imagine a &quot;denial of wallet&quot; attack where a malicious script spams your completions endpoint. Without a gatekeeper, your API keys are just sitting ducks. Or worse, imagine a multi-tenant app where one user&apos;s prompt accidentally retrieves another user&apos;s private data from your vector DB.&lt;/p&gt;
&lt;p&gt;This is where the API gateway comes in. It is the first line of defense and the brain of your infrastructure. It handles the boring but critical stuff so your RAG logic can stay focused on actually being smart.&lt;/p&gt;
&lt;h2&gt;The gatekeeper pattern&lt;/h2&gt;
&lt;p&gt;At its core, an API gateway is a reverse proxy that sits between your users and your backend services. But for an AI stack, it does more than just forward traffic. It acts as a centralized brain for auth, routing, and rate limiting.&lt;/p&gt;
&lt;p&gt;When a request hits your gateway, it goes through a gauntlet of checks before it ever touches a model. This &quot;gatekeeper&quot; ensures that every millisecond of GPU time or every cent of token cost is intentional.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/api-gateway-ai-stack/gateway-stack.webp&quot; alt=&quot;Gateway responsibilities — auth, routing, rate limiting, observability&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Authentication and tenant isolation&lt;/h2&gt;
&lt;p&gt;In a typical SaaS, authentication is about knowing who the user is. In an AI-powered SaaS, it is about data sovereignty.&lt;/p&gt;
&lt;p&gt;If you are building a RAG system, your biggest risk is cross-tenant data leakage. If you want to avoid &lt;a href=&quot;https://ansezz.com/blog/7-rag-mistakes-production/&quot;&gt;common RAG mistakes&lt;/a&gt;, you must handle identity at the very edge.&lt;/p&gt;
&lt;p&gt;I prefer using JWTs (JSON Web Tokens) with custom claims. When a request hits the gateway, I validate the token and extract the &lt;code&gt;tenant_id&lt;/code&gt;. That ID is then injected into the headers of the request before it is passed to the RAG orchestrator.&lt;/p&gt;
&lt;p&gt;This means the orchestrator doesn&apos;t have to &quot;guess&quot; who the user is. It receives a verified &lt;code&gt;x-tenant-id&lt;/code&gt; header and uses it to apply metadata filters on the vector database. The user only &quot;sees&quot; data they are allowed to see. No tenant ID? No query. Period.&lt;/p&gt;
&lt;h2&gt;Smart routing for model flexibility&lt;/h2&gt;
&lt;p&gt;The AI world moves fast. Today you are using GPT-4o. Tomorrow, Claude 3.5 Sonnet might be the better play. Next week, you might want to test a fine-tuned Llama 3 model running on your own infrastructure via &lt;a href=&quot;https://ansezz.com/blog/coolify-docker-saas-hosting/&quot;&gt;Docker and Coolify&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If your model logic is hardcoded into your frontend or a single monolithic backend, switching models is a nightmare. An API gateway solves this with smart routing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/api-gateway-ai-stack/routing-flow.webp&quot; alt=&quot;Smart routing — model aliases, tier-based routing, failover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I use the gateway to create &quot;model aliases.&quot; Instead of the frontend calling a specific model, it calls a generic endpoint like &lt;code&gt;/v1/chat/completions&lt;/code&gt;. The gateway then decides where to send that request based on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;User tier&lt;/strong&gt; — free users get routed to a cheaper, faster model like GPT-4o-mini. Pro users get the heavy hitters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versioning&lt;/strong&gt; — run an A/B test by routing 10% of traffic to a new model version without changing a single line of client-side code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failover&lt;/strong&gt; — if OpenAI is having an outage, the gateway can automatically reroute traffic to an Anthropic backup.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This level of abstraction is what separates a weekend project from a resilient SaaS product.&lt;/p&gt;
&lt;h2&gt;Rate limiting: protecting the wallet&lt;/h2&gt;
&lt;p&gt;We used to rate limit to protect our CPUs. Now, we rate limit to protect our bank accounts.&lt;/p&gt;
&lt;p&gt;AI requests are asymmetric. A user sends a 50-word prompt, and the model might generate a 1,000-word response. The cost difference is massive.&lt;/p&gt;
&lt;p&gt;A good API gateway implementation allows for tiered rate limiting. Set global limits to prevent your entire system from being overwhelmed, but also set per-tenant or per-user limits.&lt;/p&gt;
&lt;p&gt;I usually implement this using Redis. The gateway checks the user&apos;s quota in real time. If they have exceeded their daily token limit or their requests-per-minute (RPM) cap, the gateway returns a &lt;code&gt;429 Too Many Requests&lt;/code&gt; immediately.&lt;/p&gt;
&lt;p&gt;This saves your backend from doing expensive work that you won&apos;t get paid for. It also stops &quot;noisy neighbors&quot; — one user scripting an automated tool that hogs all your capacity and makes the app slow for everyone else.&lt;/p&gt;
&lt;h2&gt;Handling the AI-specific quirks&lt;/h2&gt;
&lt;p&gt;Gateways for AI need to handle two things differently than traditional web apps: streaming and long-running requests.&lt;/p&gt;
&lt;h3&gt;Streaming support&lt;/h3&gt;
&lt;p&gt;Most modern AI apps use Server-Sent Events (SSE) to stream responses word by word. Some older gateways or load balancers try to &quot;buffer&quot; the entire response before sending it to the client. This kills the user experience.&lt;/p&gt;
&lt;p&gt;Make sure your gateway (whether you are using &lt;a href=&quot;https://konghq.com/&quot;&gt;Kong&lt;/a&gt;, &lt;a href=&quot;https://tyk.io/&quot;&gt;Tyk&lt;/a&gt;, or a custom &lt;a href=&quot;https://ansezz.com/blog/laravel-multi-tenancy/&quot;&gt;Laravel solution&lt;/a&gt;) is configured to disable buffering for AI routes. The data should flow through the gateway like water through a pipe, not like a bucket that needs to be filled.&lt;/p&gt;
&lt;h3&gt;Extended timeouts&lt;/h3&gt;
&lt;p&gt;Traditional APIs expect a response in 1–2 seconds. A complex RAG query involving multiple vector searches and a large model generation might take 30 seconds or more.&lt;/p&gt;
&lt;p&gt;You need to adjust your gateway&apos;s &quot;upstream timeout&quot; settings. If you keep the default 5-second timeout, your users will see constant &lt;code&gt;504 Gateway Timeout&lt;/code&gt; errors even when your models are working perfectly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/api-gateway-ai-stack/clean-code.webp&quot; alt=&quot;Clean code under load — gateway protecting model traffic&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Practical steps for your stack&lt;/h2&gt;
&lt;p&gt;You don&apos;t need a massive team to set this up. Here is how I usually approach it depending on the project size:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;For startups&lt;/strong&gt; — use a cloud-native gateway like AWS API Gateway or Azure API Management. They are serverless, scale automatically, and integrate directly with Cognito or Entra ID for auth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For self-hosters&lt;/strong&gt; — Kong is the gold standard. It has a great ecosystem of plugins for rate limiting and auth. If you are comfortable with PHP, a thin Laravel app acting as a gateway works surprisingly well for custom logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For Shopify devs&lt;/strong&gt; — if you are building &lt;a href=&quot;https://ansezz.com/blog/agentic-commerce-shopify/&quot;&gt;agentic commerce tools&lt;/a&gt;, use the gateway to handle the specific Shopify HMAC validation before passing the request to your AI agents.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The API gateway isn&apos;t just a piece of infrastructure. It is a design philosophy. It says that your AI logic is too valuable — and too expensive — to be left unprotected.&lt;/p&gt;
&lt;p&gt;By centralizing auth, routing, and rate limiting, you make your system more modular. You can swap models, change pricing tiers, and update security policies without touching the core code that makes your AI &quot;smart.&quot;&lt;/p&gt;
&lt;p&gt;Are you still letting your frontend talk directly to your LLM providers? If so, what is the one thing stopping you from putting a gateway in front of it?&lt;/p&gt;
&lt;p&gt;Stay sharp.
— a senior dev&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Actionable takeaways&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Centralize auth&lt;/strong&gt; — never let your RAG orchestrator handle raw user authentication. Do it at the gateway.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inject tenant context&lt;/strong&gt; — use the gateway to verify the user and inject a &lt;code&gt;tenant_id&lt;/code&gt; header to enforce data isolation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement global + per-user limits&lt;/strong&gt; — protect your wallet from both malicious attacks and accidental bugs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure for streaming&lt;/strong&gt; — ensure your gateway doesn&apos;t buffer responses, or your &quot;typing&quot; effect will break.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use model aliases&lt;/strong&gt; — route to &lt;code&gt;/chat/pro&lt;/code&gt; instead of a specific model name to keep your stack flexible.&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>architecture</category><category>api-gateway</category><category>ai</category><category>rag</category><category>security</category><category>rate-limiting</category><category>multi-tenancy</category><category>streaming</category><category>infrastructure</category></item><item><title>Scaling with RabbitMQ: why message brokers matter</title><link>https://ansezz.com/blog/scaling-with-rabbitmq/</link><guid isPermaLink="true">https://ansezz.com/blog/scaling-with-rabbitmq/</guid><description>Synchronous controllers are how monoliths die. RabbitMQ basics, exchanges and queues, the strangler pattern for going async, idempotent workers, and the Laravel queue setup I use to absorb 100k-row spikes without breaking the login page.</description><pubDate>Sat, 16 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The monolith is screaming. Every time a user hits the &quot;checkout&quot; button, your server has to generate a PDF, send a welcome email, update the inventory, and ping three different third-party APIs. Your request/response cycle is hanging by a thread. If any of those external services take more than two seconds to respond, your user sees a 504 gateway timeout.&lt;/p&gt;
&lt;p&gt;It starts with a small delay. Then it becomes a bottleneck. Before you know it, you are throwing more RAM at a problem that cannot be solved by bigger hardware. This is the &quot;monolith wall.&quot; When everything is synchronous, a single failure in a secondary task brings down the entire user experience.&lt;/p&gt;
&lt;p&gt;I have been in these trenches. I have watched dashboards turn red during a marketing spike because the database was too busy processing background reports to handle new signups. The solution isn&apos;t just &quot;faster code.&quot; It is a change in how your services talk to each other. It is about &lt;strong&gt;decoupling&lt;/strong&gt;. It is about &lt;strong&gt;RabbitMQ&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Why your request path is too crowded&lt;/h2&gt;
&lt;p&gt;In a standard web application, we often fall into the trap of doing too much inside the controller. A user makes a request, and we feel the need to finish every related task before sending back a &quot;200 OK.&quot; This is fine for a side project with ten users. For a scaling SaaS, it is a recipe for disaster.&lt;/p&gt;
&lt;p&gt;Think of it like a coffee shop. If the person taking your order also has to grind the beans, froth the milk, and hand-draw the logo on the cup before taking the next order, the line will wrap around the block. The shop fails because the cashier is &quot;tightly coupled&quot; to the barista&apos;s work.&lt;/p&gt;
&lt;p&gt;To scale, you need a system where the cashier takes the order, writes it on a slip, and hands it off. They are immediately free for the next customer. The work happens &quot;in the background.&quot; That slip of paper is your message. The counter where they put the slips is your message broker.&lt;/p&gt;
&lt;h2&gt;The RabbitMQ magic: more than just a queue&lt;/h2&gt;
&lt;p&gt;RabbitMQ is an open-source message broker that acts as the &quot;middleware&quot; for your architecture. It doesn&apos;t just store messages — it routes them with surgical precision.&lt;/p&gt;
&lt;p&gt;At its core, RabbitMQ uses a few key concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Producers&lt;/strong&gt; — your web applications or APIs that create a task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exchanges&lt;/strong&gt; — the &quot;post office&quot; that decides which queue a message should go to based on rules.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queues&lt;/strong&gt; — the temporary storage where messages sit until they are processed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consumers&lt;/strong&gt; — the background workers (often running in &lt;a href=&quot;https://ansezz.com/work/&quot;&gt;Docker containers&lt;/a&gt;) that actually do the heavy lifting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By putting RabbitMQ in the middle, your web tier only needs to do one thing: tell RabbitMQ that a task needs to be done. This takes milliseconds. The user gets an instant confirmation, while the heavy work happens whenever your workers are ready.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/scaling-with-rabbitmq/architecture-diagram.webp&quot; alt=&quot;Architecture diagram of producers, exchanges, queues, and consumers&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Smoothing out the spikes&lt;/h2&gt;
&lt;p&gt;One of the biggest pains in &lt;a href=&quot;https://ansezz.com/&quot;&gt;custom web development&lt;/a&gt; is handling &quot;noisy neighbors&quot; or sudden traffic bursts. If a large enterprise client uploads a 100,000-row CSV for processing, you don&apos;t want that to slow down the login page for everyone else.&lt;/p&gt;
&lt;p&gt;With RabbitMQ, those 100,000 rows become 100,000 individual messages in a queue. Your workers will chew through them at a steady pace. If the queue gets too long, you don&apos;t need to scale your entire application — you just spin up more worker instances.&lt;/p&gt;
&lt;p&gt;This is called &lt;strong&gt;horizontal scaling&lt;/strong&gt;. Since the workers are decoupled from the web server, you can scale them independently based on the specific load. If you use modern tools like Laravel and Vue, you can easily manage these background jobs using built-in queue drivers that talk directly to RabbitMQ.&lt;/p&gt;
&lt;h2&gt;How to move from sync to async&lt;/h2&gt;
&lt;p&gt;You don&apos;t have to rewrite your entire codebase overnight. I usually recommend the &quot;strangler pattern.&quot; Pick one slow, non-critical process. Maybe it is the &quot;forgot password&quot; email or an image resize task.&lt;/p&gt;
&lt;p&gt;Here is a simplified look at how you might dispatch a job in a modern PHP environment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// instead of sending the email directly
// $emailService-&amp;gt;sendWelcome($user);

// we dispatch a job to RabbitMQ
ProcessWelcomeEmail::dispatch($user)-&amp;gt;onQueue(&apos;high-priority&apos;);

// the user gets a response instantly
return response()-&amp;gt;json([
    &apos;message&apos; =&amp;gt; &apos;welcome! check your inbox soon.&apos;,
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, even if your email provider (like SendGrid or Mailgun) is having a bad day, your application stays up. The message stays safely in the RabbitMQ queue until the service is back online.&lt;/p&gt;
&lt;h2&gt;Building for the future&lt;/h2&gt;
&lt;p&gt;Moving to a message-broker-first mindset is the first step toward a microservices architecture. Once your monolith starts publishing &quot;events&quot; (like &lt;code&gt;order.placed&lt;/code&gt; or &lt;code&gt;user.registered&lt;/code&gt;), other services can start listening to those events without you ever changing the original code.&lt;/p&gt;
&lt;p&gt;It creates a system that is resilient, observable, and significantly easier to debug. You can look at the RabbitMQ management UI and see exactly how many tasks are pending and how fast they are being processed. No more guessing why the server is slow.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/scaling-with-rabbitmq/monitoring-dashboard.webp&quot; alt=&quot;Monitoring dashboard mockup of RabbitMQ queue depth and throughput&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Already running on GCP? The same patterns apply with &lt;a href=&quot;https://ansezz.com/blog/event-driven-pubsub/&quot;&gt;Google Pub/Sub&lt;/a&gt; — pick the broker that matches your hosting stack, not the trend cycle.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Key takeaways for your next build&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Don&apos;t block the user.&lt;/strong&gt; If a task takes more than 100ms, it probably belongs in a queue.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decouple early.&lt;/strong&gt; Use RabbitMQ to separate your &quot;thinking&quot; (web tier) from your &quot;doing&quot; (workers).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Idempotency is key.&lt;/strong&gt; Since messages can sometimes be delivered twice, make sure your workers can handle the same task more than once without causing errors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor your queues.&lt;/strong&gt; A massive queue is a leading indicator that you need more workers or that a service is failing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scaling a SaaS isn&apos;t about working harder. It is about working smarter by giving your data room to breathe. RabbitMQ is that breathing room.&lt;/p&gt;
&lt;p&gt;What is the slowest part of your application right now? Could it be a background job instead? &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;Tell me&lt;/a&gt; — I bet we can move it off the request path.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/scaling-with-rabbitmq/final-visual.webp&quot; alt=&quot;Pop-art final visual of a calm web tier while workers chew through background jobs&quot; /&gt;&lt;/p&gt;
</content:encoded><category>architecture</category><category>rabbitmq</category><category>message-broker</category><category>queues</category><category>decoupling</category><category>scaling</category><category>laravel</category><category>async</category><category>architecture</category></item><item><title>Mastering event-driven architecture with Google Pub/Sub</title><link>https://ansezz.com/blog/event-driven-pubsub/</link><guid isPermaLink="true">https://ansezz.com/blog/event-driven-pubsub/</guid><description>Decouple your services or drown in latency. Topics, fan-out, push vs pull, dead-letter queues, idempotent consumers, and the Laravel integration I run on Google Cloud — a practical EDA blueprint from a senior engineer.</description><pubDate>Sat, 02 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Building a modern web application usually starts simple. You have a request and you send a response. But as your business grows, that simple flow starts to feel heavy. Maybe you need to send a welcome email, update a CRM, and trigger a data warehouse sync all at once. If you do this synchronously, your users are stuck staring at a loading spinner. If one service fails, the whole request dies. Your system becomes a house of cards.&lt;/p&gt;
&lt;p&gt;This is the problem of tight coupling. Your application logic is tangled like old headphones in a pocket. Every new feature adds more risk and more latency. You want to scale, but your monolithic approach is holding you back. You need a way to let your services talk without being glued together.&lt;/p&gt;
&lt;p&gt;The solution is &lt;strong&gt;event-driven architecture&lt;/strong&gt; (EDA). And in the Google Cloud world, the heart of that architecture is &lt;strong&gt;Google Pub/Sub&lt;/strong&gt;. It is a globally distributed messaging service that decouples the services that produce events from the services that consume them. It allows you to build systems that are truly scalable, resilient, and ready for the future of AI and big data.&lt;/p&gt;
&lt;h2&gt;Understanding topics and subscriptions&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/event-driven-pubsub/architecture-diagram.webp&quot; alt=&quot;Architecture diagram of a Pub/Sub topic with multiple subscriptions fanning out&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At its core, Google Pub/Sub is built on two main concepts: &lt;strong&gt;topics&lt;/strong&gt; and &lt;strong&gt;subscriptions&lt;/strong&gt;. I like to think of a topic as a radio station. It broadcasts information out into the void. It doesn&apos;t care who is listening or what they do with the music. It just plays the hits.&lt;/p&gt;
&lt;p&gt;On the other side, you have subscriptions. These are the listeners. A subscription represents a stream of messages from a specific topic. The beauty of this system is the decoupling. The service sending the message (the publisher) only needs to know about the topic. It doesn&apos;t need to know if there are ten consumers or zero.&lt;/p&gt;
&lt;p&gt;In a typical &lt;a href=&quot;https://ansezz.com/&quot;&gt;software development&lt;/a&gt; workflow, this is a game changer. When a user signs up on your site, you publish a &lt;code&gt;UserSignedUp&lt;/code&gt; event to a topic. Your main app is done. It returns a success message to the user immediately. Meanwhile, various subscribers pick up that event and do their jobs in the background.&lt;/p&gt;
&lt;h2&gt;The power of fan-out&lt;/h2&gt;
&lt;p&gt;One of the most effective patterns in Google Pub/Sub is the fan-out. This is where you publish a single message to a topic, but multiple subscriptions receive a copy of that message.&lt;/p&gt;
&lt;p&gt;Imagine you are running an e-commerce store. When an order is placed, you might have three different services that need to act:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An inventory service to update stock levels.&lt;/li&gt;
&lt;li&gt;A shipping service to generate a label.&lt;/li&gt;
&lt;li&gt;An analytics service to track revenue.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Instead of your checkout service calling three different APIs, it sends one message to an &lt;code&gt;order-events&lt;/code&gt; topic. Three separate subscriptions (one for inventory, one for shipping, one for analytics) each get their own copy of that order message. They process it at their own pace. If the analytics service is down for maintenance, it doesn&apos;t stop the shipping label from being created. The messages just wait in the queue until the service is back online.&lt;/p&gt;
&lt;h2&gt;Pull vs push delivery&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/event-driven-pubsub/dashboard-mockup.webp&quot; alt=&quot;Dashboard mockup comparing pull and push subscription metrics&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When you set up a subscription, you have to decide how you want to receive messages. Google Pub/Sub gives you two main options: &lt;strong&gt;pull&lt;/strong&gt; and &lt;strong&gt;push&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Push subscriptions&lt;/strong&gt; are great for serverless architectures. Google Cloud will literally &quot;push&quot; the message to a webhook URL you provide. This is perfect for &lt;a href=&quot;https://ansezz.com/work/&quot;&gt;cloud infrastructure&lt;/a&gt; built on Cloud Run or Cloud Functions. It scales automatically and you only pay for what you use. However, you have to make sure your endpoint can handle the sudden spikes in traffic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pull subscriptions&lt;/strong&gt; work differently. Your consumer service asks Google Pub/Sub for messages when it is ready. This gives you much more control over backpressure. If your worker is busy, it doesn&apos;t ask for more work. This is the preferred method for long-running services or when you are using tools like Laravel&apos;s queue workers. Pull delivery is generally more robust for heavy processing tasks where you want to fine-tune concurrency.&lt;/p&gt;
&lt;h2&gt;Building resilient systems with DLQs&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/event-driven-pubsub/dlq-illustration.webp&quot; alt=&quot;Pop-art illustration of a dead-letter queue catching poison messages&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In a distributed system, things will fail. A database might time out or an external API might be down. If a message can&apos;t be processed, you don&apos;t want to lose it. This is where &lt;strong&gt;Dead Letter Queues&lt;/strong&gt; (DLQs) come in.&lt;/p&gt;
&lt;p&gt;A DLQ is just another topic where Google Pub/Sub sends messages that have failed to be acknowledged after a certain number of attempts. Instead of retrying forever and clogging up your main pipeline, the &quot;poison&quot; message is moved aside.&lt;/p&gt;
&lt;p&gt;I always recommend setting up a DLQ for every critical subscription. It acts as a safety net. You can then build a separate dashboard or a small script to inspect these failed messages, fix the underlying issue, and replay them. It is a professional approach to error handling that prevents data loss and keeps your system moving.&lt;/p&gt;
&lt;h2&gt;Integrating Google Pub/Sub with Laravel&lt;/h2&gt;
&lt;p&gt;For those of us in the &lt;a href=&quot;https://ansezz.com/&quot;&gt;PHP and Laravel&lt;/a&gt; ecosystem, integrating Google Pub/Sub is incredibly smooth. While Laravel comes with great support for Redis and SQS, using a package like &lt;code&gt;google/cloud-pubsub&lt;/code&gt; allows you to tap into GCP&apos;s global scale.&lt;/p&gt;
&lt;p&gt;You can treat Google Pub/Sub as a custom queue driver. Here is a quick look at how you might publish a message in a typical service class:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;use Google\Cloud\PubSub\PubSubClient;

$pubsub = new PubSubClient([
    &apos;projectId&apos; =&amp;gt; &apos;your-gcp-project-id&apos;,
]);

$topic = $pubsub-&amp;gt;topic(&apos;user-events&apos;);

$topic-&amp;gt;publish([
    &apos;data&apos; =&amp;gt; json_encode([
        &apos;user_id&apos; =&amp;gt; 123,
        &apos;action&apos; =&amp;gt; &apos;signup&apos;,
    ]),
    &apos;attributes&apos; =&amp;gt; [
        &apos;event_type&apos; =&amp;gt; &apos;UserSignedUp&apos;,
        &apos;priority&apos; =&amp;gt; &apos;high&apos;,
    ],
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using attributes, you can even filter messages at the subscription level. This means a subscriber can choose to only listen for messages where &lt;code&gt;event_type&lt;/code&gt; is &lt;code&gt;UserSignedUp&lt;/code&gt;. This saves compute power and money because your worker never even sees the messages it doesn&apos;t care about.&lt;/p&gt;
&lt;h2&gt;Monitoring and cost management&lt;/h2&gt;
&lt;p&gt;Monitoring is not an afterthought. It is a requirement. Google Cloud provides deep integration with Cloud Monitoring for Google Pub/Sub. You should keep a close eye on your &quot;unacked message count.&quot; If this number is climbing, it means your subscribers can&apos;t keep up with the producers.&lt;/p&gt;
&lt;p&gt;Cost is another factor to watch. Google Pub/Sub is very cheap for low volumes, but as you scale to millions of messages, those bytes add up. Use batching on the publisher side to reduce the number of API calls. Also, be mindful of message retention. If you don&apos;t need to keep messages for seven days, shorten the retention period to save on storage costs.&lt;/p&gt;
&lt;h2&gt;Wrap up and takeaways&lt;/h2&gt;
&lt;p&gt;Moving to an event-driven architecture with Google Pub/Sub is a major step toward building senior-level systems. It gives you the flexibility to grow your application without it becoming a tangled mess. It is the backbone of many high-performance &lt;a href=&quot;https://ansezz.com/&quot;&gt;web applications&lt;/a&gt; I build for clients today.&lt;/p&gt;
&lt;p&gt;Here are the key takeaways for your next project:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start by identifying &quot;facts&quot;&lt;/strong&gt; in your system (e.g., &lt;code&gt;OrderPlaced&lt;/code&gt;) and turn them into events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the fan-out pattern&lt;/strong&gt; to keep your services decoupled and focused on one task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always implement a Dead Letter Queue&lt;/strong&gt; to handle failures gracefully.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use message attributes for efficient filtering&lt;/strong&gt; at the subscription level.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Design your consumers to be idempotent.&lt;/strong&gt; If they receive the same message twice, it doesn&apos;t cause errors or double-charges.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Building these kinds of systems takes a bit more planning upfront, but the payoff in stability and scalability is worth every second.&lt;/p&gt;
&lt;p&gt;Are you still using synchronous API calls for everything, or have you started moving toward an event-driven flow? &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;Let me know what&apos;s stopping you&lt;/a&gt; from making the switch.&lt;/p&gt;
</content:encoded><category>architecture</category><category>pub-sub</category><category>event-driven</category><category>gcp</category><category>google-cloud</category><category>laravel</category><category>messaging</category><category>scalability</category><category>architecture</category></item><item><title>Vibe coding and the architectural shift to agentic workflows</title><link>https://ansezz.com/blog/agentic-workflows-vibe-coding/</link><guid isPermaLink="true">https://ansezz.com/blog/agentic-workflows-vibe-coding/</guid><description>MCP, agentic loops, and intent-based engineering. How vibe coding becomes a real architecture pattern when AI stops being a chat sidebar and starts owning stateful loops against your tools. The practical Laravel + MCP stack I run today.</description><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve spent the last decade building Laravel applications, managing Docker clusters, and fine-tuning Shopify stores. For most of that time, &quot;coding&quot; meant one thing: translating a business requirement into a specific syntax that a machine could execute. It was a manual, linear process of writing line by line, debugging stack traces, and managing state.&lt;/p&gt;
&lt;p&gt;But recently, the ground has shifted. We&apos;re moving away from the era of &quot;writing code&quot; and into the era of &quot;orchestrating intent.&quot;&lt;/p&gt;
&lt;p&gt;This transition — often playfully called &lt;strong&gt;vibe coding&lt;/strong&gt; — is more than just a meme. It represents a fundamental architectural shift in how we build software, moving from sequential instruction to agentic loops powered by protocols like &lt;strong&gt;MCP&lt;/strong&gt; (Model Context Protocol).&lt;/p&gt;
&lt;h2&gt;The friction of the manual syntax&lt;/h2&gt;
&lt;p&gt;The traditional development lifecycle is riddled with invisible friction. You have an idea (the &quot;vibe&quot;), you break it down into tasks, and then you spend 80% of your time fighting with syntax, configuration, and boilerplate.&lt;/p&gt;
&lt;p&gt;In a standard &lt;strong&gt;Laravel&lt;/strong&gt; environment, even a simple feature — say, an automated reporting tool — requires you to set up routes, controllers, service classes, and database migrations. You are the compiler. You are the architect. You are the labor.&lt;/p&gt;
&lt;p&gt;The problem is that our human cognitive load is being consumed by the &quot;how&quot; rather than the &quot;what.&quot; We get stuck in the weeds of &lt;strong&gt;PHP&lt;/strong&gt; version compatibility or &lt;strong&gt;Docker&lt;/strong&gt; networking issues, losing sight of the actual user value. This manual micromanagement doesn&apos;t scale as fast as the demands of modern business.&lt;/p&gt;
&lt;h2&gt;The agitation of the &quot;black box&quot; assistant&lt;/h2&gt;
&lt;p&gt;When AI first entered the scene with basic autocomplete, it felt like a shortcut. But it wasn&apos;t a solution. We ended up with what I call &quot;the Copilot paradox&quot;: the AI suggests code, but you still have to copy-paste it, test it, find the error, and feed it back to the AI.&lt;/p&gt;
&lt;p&gt;It&apos;s a broken feedback loop. The AI is a &quot;black box&quot; that doesn&apos;t actually know your system. It doesn&apos;t know your database schema, your &lt;strong&gt;MCP&lt;/strong&gt; servers, or your deployment status on &lt;strong&gt;Coolify&lt;/strong&gt;. You are still the manual bridge between the AI&apos;s logic and your local environment.&lt;/p&gt;
&lt;p&gt;This creates a new kind of fatigue. Instead of writing code, you&apos;re now a high-speed code reviewer, constantly context-switching between your editor and a chat interface. This isn&apos;t &quot;vibe coding&quot; — it&apos;s just accelerated manual labor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/agentic-workflows-vibe-coding/architecture-diagram.webp&quot; alt=&quot;Architecture diagram showing the broken loop between developer, AI, and tools&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The solution: agentic workflows and MCP&lt;/h2&gt;
&lt;p&gt;True &lt;strong&gt;vibe coding&lt;/strong&gt; isn&apos;t about being lazy; it&apos;s about shifting your role to that of a high-level system architect. This becomes possible through &lt;strong&gt;agentic workflows&lt;/strong&gt; — systems that don&apos;t just &quot;complete text&quot; but &quot;execute tasks in loops.&quot;&lt;/p&gt;
&lt;p&gt;The breakthrough here is the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; by Anthropic. MCP acts as the &quot;USB port&quot; for AI. Instead of you manually giving the AI context, the AI uses an MCP client to talk directly to your tools — your &lt;strong&gt;PostgreSQL&lt;/strong&gt; database, your &lt;strong&gt;Slack&lt;/strong&gt; channels, or your &lt;strong&gt;GitHub&lt;/strong&gt; repositories.&lt;/p&gt;
&lt;h3&gt;The shift from chains to loops&lt;/h3&gt;
&lt;p&gt;In a traditional chain, you give a prompt and get a result. In an agentic loop, the architecture looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Intent.&lt;/strong&gt; You describe the outcome (&quot;build a Laravel dashboard for my Shopify sales&quot;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reasoning.&lt;/strong&gt; The AI (like &lt;strong&gt;Claude&lt;/strong&gt;) determines it needs to see the schema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Action.&lt;/strong&gt; It uses an &lt;strong&gt;MCP&lt;/strong&gt; tool to query the database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observation.&lt;/strong&gt; It sees a missing table and decides to create a migration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correction.&lt;/strong&gt; If the migration fails, it reads the error and fixes it itself.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I call this &quot;intent-based engineering.&quot; You aren&apos;t writing the migration — you are approving the architectural decision.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/agentic-workflows-vibe-coding/agentic-loop.webp&quot; alt=&quot;Bento grid visual of an agentic loop with intent, reasoning, action, observation, correction&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Implementing the agentic stack&lt;/h2&gt;
&lt;p&gt;As an engineer who values quality, I don&apos;t just let the &quot;vibe&quot; take over without guardrails. Here is how I&apos;m currently structuring my agentic stack using &lt;strong&gt;Laravel&lt;/strong&gt; and &lt;strong&gt;AI&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;1. Defined MCP servers&lt;/h3&gt;
&lt;p&gt;I build small, dedicated &lt;strong&gt;MCP&lt;/strong&gt; servers that expose only the necessary tools to the AI. This keeps the context window clean and the security tight.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Conceptual MCP tool definition in a PHP environment
public function defineTools(): array
{
    return [
        &apos;get_database_schema&apos; =&amp;gt; [
            &apos;description&apos; =&amp;gt; &apos;Retrieves the structure of the Laravel application tables.&apos;,
            &apos;parameters&apos; =&amp;gt; [],
        ],
        &apos;run_artisan_command&apos; =&amp;gt; [
            &apos;description&apos; =&amp;gt; &apos;Executes an artisan command safely.&apos;,
            &apos;parameters&apos; =&amp;gt; [&apos;command&apos; =&amp;gt; &apos;string&apos;],
        ],
    ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Stateful loops&lt;/h3&gt;
&lt;p&gt;Instead of one-off chats, I use tools like &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;Claude Code&lt;/strong&gt;, or &lt;strong&gt;Windsurf&lt;/strong&gt; that maintain a stateful connection to my local file system. This allows the agent to &quot;see&quot; the impact of its changes in real-time, just like a human developer would.&lt;/p&gt;
&lt;h3&gt;3. The human-in-the-loop (HITL)&lt;/h3&gt;
&lt;p&gt;The most important part of the architecture is the review gate. Even with agentic loops, the human architect must sign off on the &quot;plan&quot; before the &quot;action&quot; phase. This ensures the &lt;strong&gt;PHP&lt;/strong&gt; logic follows clean architecture principles rather than just &quot;making it work.&quot;&lt;/p&gt;
&lt;h2&gt;The takeaway for the modern founder&lt;/h2&gt;
&lt;p&gt;If you&apos;re a founder or a CTO, the takeaway is simple: stop hiring for syntax and start hiring for system design. The technical barrier is collapsing, but the architectural stakes are higher than ever.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embrace the vibe.&lt;/strong&gt; Focus on the intent and the user experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invest in infrastructure.&lt;/strong&gt; Build the &lt;strong&gt;MCP&lt;/strong&gt; connections and the data pipelines that allow AI to be effective.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Think in loops.&lt;/strong&gt; Design your internal processes so that AI can iterate autonomously, reducing your bottleneck role.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At &lt;a href=&quot;https://ansezz.com/&quot;&gt;Ansezz&lt;/a&gt;, I&apos;m not just building apps anymore — I&apos;m building agent-ready ecosystems. Whether it&apos;s a complex &lt;strong&gt;Shopify&lt;/strong&gt; integration or a custom &lt;strong&gt;SaaS&lt;/strong&gt;, I ensure the architecture is ready for the agentic future.&lt;/p&gt;
&lt;p&gt;The code might be generated, but the vision is entirely yours.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Are you ready to stop writing code and start orchestrating your intent?&lt;/strong&gt; &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;Get in touch&lt;/a&gt; — let&apos;s design your agent stack together.&lt;/p&gt;
</content:encoded><category>architecture</category><category>vibe-coding</category><category>agentic-ai</category><category>mcp</category><category>anthropic</category><category>claude</category><category>laravel</category><category>architecture</category></item><item><title>From monolith to micro-services: a senior dev&apos;s guide to pragmatic scaling</title><link>https://ansezz.com/blog/monolith-to-microservices/</link><guid isPermaLink="true">https://ansezz.com/blog/monolith-to-microservices/</guid><description>Skip the big-bang rewrite. The strangler fig pattern, anti-corruption layers, Docker-first migration, and GKE/Coolify operations — how I peel services off a Laravel monolith one endpoint at a time without breaking revenue.</description><pubDate>Sun, 22 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Your monolith is a ticking time bomb and every feature you add makes the explosion more inevitable.&lt;/p&gt;
&lt;p&gt;I have seen it happen a dozen times. A startup begins with a clean Laravel or Rails app. It is fast. It is easy. It is productive. Then the team grows. The code base swells. Suddenly, a simple change to the checkout logic breaks the authentication system. Deployments that used to take five minutes now take forty. You are not scaling your business anymore — you are managing technical debt.&lt;/p&gt;
&lt;p&gt;This is the point where most developers start dreaming of micro-services. They imagine a world where every service is isolated and deployments are instant. But the reality is often a nightmare. If you do it wrong, you end up with a distributed monolith. You get all the complexity of networking with none of the benefits of isolation.&lt;/p&gt;
&lt;p&gt;The solution is not a &quot;big bang&quot; rewrite. It is pragmatic scaling. I use the &lt;strong&gt;strangler fig pattern&lt;/strong&gt; to move from monoliths to micro-services without losing my mind or my job.&lt;/p&gt;
&lt;h2&gt;The problem with the big bang&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/monolith-to-microservices/strangler-fig.webp&quot; alt=&quot;Strangler fig pattern diagram — new services wrap the legacy monolith&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When a monolith becomes too heavy, the immediate reaction is to want to scrap it. I have seen companies spend two years on a rewrite only to ship a product that has half the features of the original. The business dies while the engineers play with new toys.&lt;/p&gt;
&lt;p&gt;The monolith is not your enemy. It is just a phase. The real problem is coupling. When every part of your app knows too much about every other part, you cannot move. You are stuck in a web of dependencies. If you try to jump straight into micro-services, you will likely just port those dependencies into a network layer. Now, instead of a function call failing, you have a 500 error across a network socket.&lt;/p&gt;
&lt;p&gt;I prefer a slower, more deliberate approach. I focus on high-value extractions. I look for the parts of the app that hurt the most. Is the image processing service slowing down the web server? Is the reporting engine locking up the database? Those are your first candidates for micro-services.&lt;/p&gt;
&lt;h2&gt;The strangler fig pattern in practice&lt;/h2&gt;
&lt;p&gt;I named this approach after a tree that grows around another tree. It starts as a small vine and eventually replaces the host entirely. In software, this means building new features as services while the old monolith remains.&lt;/p&gt;
&lt;p&gt;The process starts with an API gateway or a load balancer. I use &lt;a href=&quot;https://www.nginx.com/&quot;&gt;Nginx&lt;/a&gt; or &lt;a href=&quot;https://cloud.google.com/armor&quot;&gt;Cloud Armor&lt;/a&gt; on &lt;a href=&quot;https://cloud.google.com/&quot;&gt;Google Cloud&lt;/a&gt; to route traffic. If a request comes for &lt;code&gt;/api/v1/orders&lt;/code&gt;, it goes to the new service. Everything else goes to the old monolith.&lt;/p&gt;
&lt;p&gt;This allows me to test the new service in production with real traffic while the monolith acts as a safety net. If the new service fails, I just flip the routing back. I do not have to migrate everything at once. I can migrate one endpoint at a time.&lt;/p&gt;
&lt;h2&gt;Containerization with Docker&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/monolith-to-microservices/docker-snippet.webp&quot; alt=&quot;Annotated Dockerfile snippet for a Laravel micro-service&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You cannot do micro-services without &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt;. I treat every service as a black box. The monolith might be running on an old version of PHP, while the new service is a lean Go binary or a modern Laravel instance. Docker makes this possible.&lt;/p&gt;
&lt;p&gt;I start by containerizing the monolith. Even if it stays as a monolith for another year, putting it in a container forces me to define its environment. It makes the infrastructure reproducible.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# a simplified example of a service container
FROM php:8.3-fpm

WORKDIR /app
COPY . /app

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    libpq-dev \
    &amp;amp;&amp;amp; docker-php-ext-install pdo_pgsql

EXPOSE 9000
CMD [&quot;php-fpm&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the monolith is containerized, I can deploy it to a platform like &lt;a href=&quot;https://cloud.google.com/kubernetes-engine&quot;&gt;Google Kubernetes Engine (GKE)&lt;/a&gt;. This is where the real power of micro-services comes in. I can scale the order service to fifty instances during a sale while keeping the blog service at two.&lt;/p&gt;
&lt;h2&gt;Communication and the anti-corruption layer&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/monolith-to-microservices/routing-diagram.webp&quot; alt=&quot;Routing diagram showing API gateway dispatching between monolith and new services&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The hardest part of micro-services is not the code. It is the data. Your monolith has a single database. Your micro-services should each have their own. But how do they talk?&lt;/p&gt;
&lt;p&gt;I use an &lt;strong&gt;anti-corruption layer (ACL)&lt;/strong&gt;. When I extract a service, I do not let it reach back into the monolith&apos;s database. That would be cheating. Instead, I create an interface. If the new service needs user data, it asks the monolith via a private API or a message queue like &lt;a href=&quot;https://cloud.google.com/pubsub&quot;&gt;Google Pub/Sub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This keeps the new service clean. It does not care about the messy database schema of the legacy app. It only cares about the data it receives through the ACL. Eventually, when the user logic is also migrated, I just update the ACL to point to the new user service.&lt;/p&gt;
&lt;h2&gt;Cloud infrastructure and DevOps&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/monolith-to-microservices/cloud-infrastructure.webp&quot; alt=&quot;Cloud infrastructure overview — GKE, Pub/Sub, managed databases&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Scaling a monolith usually means buying a bigger server. Scaling micro-services means managing a fleet. I rely heavily on cloud-native tools to manage the complexity.&lt;/p&gt;
&lt;p&gt;I use &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt; to manage my infrastructure as code. This ensures that my staging and production environments are identical. If I need a new database for a service, I define it in code. I do not click around in a dashboard.&lt;/p&gt;
&lt;p&gt;On the DevOps side, I use tools like &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; or &lt;a href=&quot;https://coolify.io/&quot;&gt;Coolify&lt;/a&gt; for deployments. Every service has its own pipeline. If I update the checkout service, I only deploy the checkout service. I do not have to worry about the rest of the system.&lt;/p&gt;
&lt;h2&gt;The hidden costs of micro-services&lt;/h2&gt;
&lt;p&gt;I would be lying if I said this was all sunshine and rainbows. Micro-services come with a &quot;complexity tax.&quot; You now have to deal with distributed logging, service discovery, and eventual consistency.&lt;/p&gt;
&lt;p&gt;I tell my clients that they should only move to micro-services when the pain of the monolith is greater than the cost of the complexity tax. If your team is three people and your app is simple, stay in the monolith. You will move faster.&lt;/p&gt;
&lt;p&gt;But if you are hitting walls every day and your developers are afraid to touch the code, it is time to start strangling.&lt;/p&gt;
&lt;h2&gt;Pragmatic takeaways for your next move&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start with an API gateway&lt;/strong&gt; to handle routing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Containerize your monolith first&lt;/strong&gt; to normalize the environment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the strangler fig pattern&lt;/strong&gt; to migrate one domain at a time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build an anti-corruption layer&lt;/strong&gt; to keep new services clean.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invest in infrastructure as code&lt;/strong&gt; early on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Only split when the monolith starts to hurt&lt;/strong&gt; your productivity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Migration is a marathon, not a sprint. I have spent months on a single extraction just to make sure it was perfect. The goal is not to have micro-services. The goal is to have a system that can grow with your business.&lt;/p&gt;
&lt;p&gt;Have you ever tried a &quot;big bang&quot; rewrite only to regret it six months later? &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;Tell me about it&lt;/a&gt; — I collect these stories for a reason.&lt;/p&gt;
</content:encoded><category>architecture</category><category>monolith</category><category>micro-services</category><category>scaling</category><category>strangler-fig</category><category>docker</category><category>kubernetes</category><category>devops</category><category>laravel</category></item><item><title>AI integration vs traditional development: which is better for your business in 2026?</title><link>https://ansezz.com/blog/ai-vs-traditional-development/</link><guid isPermaLink="true">https://ansezz.com/blog/ai-vs-traditional-development/</guid><description>Speed, control, or a hybrid path? When AI-assisted development pays off, when traditional engineering is non-negotiable, and the hybrid workflow I recommend most often to founders and tech leads.</description><pubDate>Sun, 25 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most teams are asking the wrong question.&lt;/p&gt;
&lt;p&gt;The real problem is not &quot;AI or traditional development?&quot; — it is what kind of speed, control, and risk your business can actually afford.&lt;/p&gt;
&lt;p&gt;I see this mistake a lot. Teams chase AI because it feels faster. Or they reject it because it feels messy. Then they end up with the same problem from both directions. Rushed systems with weak foundations, or polished systems that ship too late.&lt;/p&gt;
&lt;p&gt;The better move is to understand where each approach wins, where it breaks, and where a hybrid model gives you the best return.&lt;/p&gt;
&lt;p&gt;Both approaches work. They just solve different problems.&lt;/p&gt;
&lt;h2&gt;AI-powered development: the speed revolution&lt;/h2&gt;
&lt;p&gt;AI integration changes how I build software. Instead of manually writing every repetitive piece, I can use tools that understand context, generate scaffolding, speed up testing, and remove a lot of the drag from delivery.&lt;/p&gt;
&lt;h3&gt;The core advantage: speed&lt;/h3&gt;
&lt;p&gt;This is where AI shines.&lt;/p&gt;
&lt;p&gt;For standard workflows, admin panels, CRUD-heavy systems, internal tools, and first-pass prototypes, AI can cut a serious amount of time. What used to take weeks can often be reduced to days if the scope is clear and the review process is tight.&lt;/p&gt;
&lt;p&gt;That speed usually comes from a few places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automated code generation.&lt;/strong&gt; Prompts turn into usable boilerplate and feature drafts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faster testing.&lt;/strong&gt; AI can draft test cases and edge-case coverage quickly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging support.&lt;/strong&gt; It helps narrow down likely failures faster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation help.&lt;/strong&gt; It can turn rough implementation details into clean internal docs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/ai-vs-traditional-development/workflow.webp&quot; alt=&quot;Architecture visual showing AI, traditional, and hybrid engineering workflow&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Who benefits most from AI development&lt;/h3&gt;
&lt;p&gt;I would lean toward AI-heavy workflows when speed matters more than perfect customization on day one.&lt;/p&gt;
&lt;p&gt;That usually means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Startups trying to reach product-market fit before the runway gets tight.&lt;/li&gt;
&lt;li&gt;Small teams that need leverage more than headcount.&lt;/li&gt;
&lt;li&gt;Businesses shipping standard features that already follow familiar patterns.&lt;/li&gt;
&lt;li&gt;Teams where non-technical stakeholders want to contribute to discovery and prototyping.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In those cases, AI acts like a power tool. It does not replace the builder. It just makes the first cut much faster.&lt;/p&gt;
&lt;h3&gt;The trade-offs to consider&lt;/h3&gt;
&lt;p&gt;This is where a lot of teams get burned.&lt;/p&gt;
&lt;p&gt;AI is fast at common patterns. It is weaker at deep product nuance, strange business rules, and systems that need careful long-term architecture. If you skip review, you can ship something that looks finished but behaves like a prototype wearing a production costume.&lt;/p&gt;
&lt;p&gt;That means I would not treat AI output as truth. I would treat it as a draft.&lt;/p&gt;
&lt;h2&gt;Traditional development: the control champion&lt;/h2&gt;
&lt;p&gt;Traditional development is slower, but it gives me tighter control over how the system is shaped.&lt;/p&gt;
&lt;p&gt;This is the path I trust most when the business rules are complex, the architecture matters, or the cost of failure is high. Every part of the system is designed with intent instead of inferred from a prompt.&lt;/p&gt;
&lt;h3&gt;The core advantage: control&lt;/h3&gt;
&lt;p&gt;Traditional development is better when the software needs precision.&lt;/p&gt;
&lt;p&gt;That matters for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complex enterprise systems&lt;/strong&gt; — lots of moving parts and layered business logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regulated industries&lt;/strong&gt; — where auditability and traceability matter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mission-critical applications&lt;/strong&gt; — where downtime or bad behavior is expensive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom architectures&lt;/strong&gt; — where the product does not fit common patterns.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The predictability factor&lt;/h3&gt;
&lt;p&gt;One underrated benefit of traditional development is predictability.&lt;/p&gt;
&lt;p&gt;Manual design, explicit code reviews, architecture decisions, and planned testing give me a clearer picture of trade-offs. It is like building with blueprints instead of assembling furniture from a photo.&lt;/p&gt;
&lt;p&gt;That slower process often saves time later because fewer assumptions make it into production.&lt;/p&gt;
&lt;h3&gt;The time investment reality&lt;/h3&gt;
&lt;p&gt;The downside is obvious.&lt;/p&gt;
&lt;p&gt;Manual coding, reviews, debugging, refactoring, and testing take time. You need stronger engineering talent, and you need the discipline to keep standards high when deadlines start squeezing the team.&lt;/p&gt;
&lt;p&gt;Traditional development gives more control, but you pay for it in time and cost.&lt;/p&gt;
&lt;h2&gt;Head-to-head comparison&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;AI-Powered Development&lt;/th&gt;
&lt;th&gt;Traditional Development&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Development speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30–50% faster completion&lt;/td&gt;
&lt;td&gt;Standard industry timelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lower long-term expenses&lt;/td&gt;
&lt;td&gt;Higher labor costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Team requirements&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mixed skill levels acceptable&lt;/td&gt;
&lt;td&gt;Requires senior expertise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Customization level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited by AI training data&lt;/td&gt;
&lt;td&gt;Unlimited customization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Quality assurance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automated testing and fixes&lt;/td&gt;
&lt;td&gt;Manual review processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Risk management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Variable based on AI reliability&lt;/td&gt;
&lt;td&gt;Predictable risk factors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rapid scaling through automation&lt;/td&gt;
&lt;td&gt;Scales with team growth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Making the right choice for your business&lt;/h2&gt;
&lt;h3&gt;Choose AI integration when&lt;/h3&gt;
&lt;p&gt;Choose AI when your bottleneck is delivery speed and the work is close to known patterns.&lt;/p&gt;
&lt;p&gt;That usually applies when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your market window is tight.&lt;/li&gt;
&lt;li&gt;You are building standard business apps like portals, dashboards, e-commerce flows, or content systems.&lt;/li&gt;
&lt;li&gt;Your team wants quick prototypes before committing engineering time.&lt;/li&gt;
&lt;li&gt;Your budget is better spent on iteration than on deep custom engineering from day one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Choose traditional development when&lt;/h3&gt;
&lt;p&gt;Choose traditional development when the cost of being wrong is higher than the cost of being slower.&lt;/p&gt;
&lt;p&gt;That usually means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The app needs a unique architecture.&lt;/li&gt;
&lt;li&gt;Compliance and audit trails are mandatory.&lt;/li&gt;
&lt;li&gt;Reliability matters more than release velocity.&lt;/li&gt;
&lt;li&gt;Your team wants direct ownership of code quality and system design.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The hybrid strategy: best of both worlds&lt;/h3&gt;
&lt;p&gt;This is the option I recommend most often.&lt;/p&gt;
&lt;p&gt;The strongest teams do not treat this like a religion. They use AI where speed helps and switch to traditional engineering where judgment matters.&lt;/p&gt;
&lt;p&gt;A practical hybrid setup looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate boilerplate and first drafts with AI, then review and reshape manually.&lt;/li&gt;
&lt;li&gt;Use AI for prototyping, then rebuild critical paths carefully.&lt;/li&gt;
&lt;li&gt;Automate repetitive testing tasks, but keep human review for logic and architecture.&lt;/li&gt;
&lt;li&gt;Use AI to accelerate docs and support material, while keeping final technical decisions human-led.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The hybrid model works because it treats AI like a junior accelerator, not like an autopilot.&lt;/p&gt;
&lt;h2&gt;Implementation guidelines&lt;/h2&gt;
&lt;h3&gt;Starting with AI integration&lt;/h3&gt;
&lt;p&gt;If I were introducing AI into an existing team, I would start small.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Begin with low-risk features.&lt;/li&gt;
&lt;li&gt;Define a review process for all AI-generated code.&lt;/li&gt;
&lt;li&gt;Choose tools that fit the current workflow.&lt;/li&gt;
&lt;li&gt;Train the team on prompting, verification, and code quality checks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Maintaining traditional excellence&lt;/h3&gt;
&lt;p&gt;If the team stays mostly traditional, I would protect the basics.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Invest in strong senior review.&lt;/li&gt;
&lt;li&gt;Keep documentation current.&lt;/li&gt;
&lt;li&gt;Use clear architecture standards.&lt;/li&gt;
&lt;li&gt;Avoid rushing complex work into fragile implementations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Building hybrid capabilities&lt;/h3&gt;
&lt;p&gt;If the goal is balance, then the workflow matters more than the tools.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identify which tasks are repetitive and safe to automate.&lt;/li&gt;
&lt;li&gt;Keep humans responsible for architecture and business logic.&lt;/li&gt;
&lt;li&gt;Add quality gates before merge and deployment.&lt;/li&gt;
&lt;li&gt;Measure outcomes, not just speed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The future-ready approach&lt;/h2&gt;
&lt;p&gt;The teams that will win in 2026 are not the ones that blindly choose AI or reject it.&lt;/p&gt;
&lt;p&gt;They are the ones that know where speed is enough, where control is non-negotiable, and where a hybrid model gives them leverage without chaos.&lt;/p&gt;
&lt;p&gt;That is the real solution.&lt;/p&gt;
&lt;p&gt;Use AI to remove friction. Use traditional engineering to protect the parts that matter. Combine both when the business needs speed and reliability at the same time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ansezz.com/blog/ai-vs-traditional-development/workspace.webp&quot; alt=&quot;Final workspace visual of a senior developer desk in pop-art comic style&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Your development strategy should match your business goals, not the trend cycle. If you had to choose today, which matters more for your next product: speed, control, or a hybrid path? &lt;a href=&quot;https://ansezz.com/contact/&quot;&gt;Reach out&lt;/a&gt; — I&apos;d love to hear which side you&apos;re leaning toward.&lt;/p&gt;
</content:encoded><category>architecture</category><category>ai</category><category>strategy</category><category>hybrid</category><category>business</category><category>decision-making</category><category>productivity</category></item></channel></rss>