<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Noor Yasser</title>
    <description>The latest articles on DEV Community by Noor Yasser (@nooryasserx).</description>
    <link>https://dev.to/nooryasserx</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4059533%2Fbd025ca9-efbf-490b-a591-314719d65e38.png</url>
      <title>DEV Community: Noor Yasser</title>
      <link>https://dev.to/nooryasserx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nooryasserx"/>
    <language>en</language>
    <item>
      <title>PHP2Shell: Patch the PHP Runtime, Not Just Your App</title>
      <dc:creator>Noor Yasser</dc:creator>
      <pubDate>Sun, 25 Oct 2026 16:49:30 +0000</pubDate>
      <link>https://dev.to/nooryasserx/php2shell-patch-the-php-runtime-not-just-your-app-1p01</link>
      <guid>https://dev.to/nooryasserx/php2shell-patch-the-php-runtime-not-just-your-app-1p01</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://nooryasser.com/articles/php2shell-cve-2026-109618-109619-patch-guide/" rel="noopener noreferrer"&gt;nooryasser.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PHP2Shell is the name used for two critical PHP runtime vulnerabilities: &lt;strong&gt;CVE-2026-109618&lt;/strong&gt; and &lt;strong&gt;CVE-2026-109619&lt;/strong&gt;. Both sit below application code, inside PHP's request lifecycle and memory handling.&lt;/p&gt;

&lt;p&gt;If you run PHP in production, the main action is simple: &lt;strong&gt;patch the PHP runtime and replace every running worker&lt;/strong&gt;. Updating Laravel, WordPress, Drupal, Composer dependencies, Nginx, or a WAF does not patch the affected engine.&lt;/p&gt;

&lt;p&gt;This article explains where the bugs live, which releases fix them, and how to prove that your production fleet actually changed. The vulnerability mechanics and versions come from the primary advisories. The rollout and investigation steps are defensive engineering recommendations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these bugs are different from an application vulnerability
&lt;/h2&gt;

&lt;p&gt;A PHP web request crosses several layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the web server or proxy accepts the request;&lt;/li&gt;
&lt;li&gt;PHP allocates request state and initializes extensions;&lt;/li&gt;
&lt;li&gt;application and framework code run;&lt;/li&gt;
&lt;li&gt;PHP cleans up request memory;&lt;/li&gt;
&lt;li&gt;a persistent worker may stay alive for the next request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;PHP2Shell breaks assumptions inside that native lifecycle.&lt;/p&gt;

&lt;p&gt;A framework login check is too late to protect a bug that can execute while PHP is preparing or cleaning request state. Input validation and authentication still matter, but they are not substitutes for a fixed runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVE-2026-109618: unsafe recovery from memory exhaustion
&lt;/h2&gt;

&lt;p&gt;The advisory describes an abrupt &lt;code&gt;longjmp()&lt;/code&gt; exit after an allocation failure. Some internal request data may still be incomplete, but cleanup later treats it as valid. What should have been a clean memory-limit error can therefore become memory corruption.&lt;/p&gt;

&lt;p&gt;The fix changes the failure model. PHP now uses a deferred soft limit handled at a VM safe point, plus a higher hard limit that terminates the process immediately.&lt;/p&gt;

&lt;p&gt;This matters operationally after patching. A hard-limit termination may skip shutdown callbacks and can look like exit status 137. Do not classify every post-update worker restart as an attack. Use stderr, container events, kernel signals, and platform telemetry to identify the actual cause.&lt;/p&gt;

&lt;p&gt;Also avoid putting the only copy of important business state in a shutdown callback. Durable work needs a recoverable state transition before the worker can disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVE-2026-109619: request state outlives its memory
&lt;/h2&gt;

&lt;p&gt;The second bug is a use-after-free pattern.&lt;/p&gt;

&lt;p&gt;Request-owned state can remain referenced after the request heap has been released. If an interruption skips a reset, the next request handled by the same persistent worker may consume stale state before normal initialization replaces it.&lt;/p&gt;

&lt;p&gt;The published research identifies concrete PHP internals, including session ID state, unserializer bookkeeping, and fatal-error backtrace state. These are engine globals, not vulnerable columns in your application database.&lt;/p&gt;

&lt;p&gt;Researchers demonstrated remote code execution and separate process-memory disclosure in tested deployments. That proves serious technical impact. It does &lt;strong&gt;not&lt;/strong&gt; mean every PHP installation has the same reachable exploit chain or that every exposed server was compromised.&lt;/p&gt;

&lt;p&gt;Keep three findings separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerable:&lt;/strong&gt; an affected runtime and reachable configuration existed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attempted exploitation:&lt;/strong&gt; logs or telemetry show crafted traffic consistent with the vulnerable path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirmed compromise:&lt;/strong&gt; host, process, or business evidence shows unauthorized impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fixed upstream releases
&lt;/h2&gt;

&lt;p&gt;The PHP security announcement lists these fixed releases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PHP line&lt;/th&gt;
&lt;th&gt;Fixed release&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.2&lt;/td&gt;
&lt;td&gt;8.2.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.3&lt;/td&gt;
&lt;td&gt;8.3.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.4&lt;/td&gt;
&lt;td&gt;8.4.27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP 8.5&lt;/td&gt;
&lt;td&gt;8.5.12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Older branches need special handling. PHP published minimal source backports for 7.4, 8.0, and 8.1, and some Linux distributions ship their own backported fixes.&lt;/p&gt;

&lt;p&gt;That creates an important versioning trap: a distribution package may keep an older upstream base number while adding the security patch in the package revision.&lt;/p&gt;

&lt;p&gt;For example, do not compare &lt;code&gt;php -v&lt;/code&gt; with 8.3.36 and immediately decide that an Ubuntu package is vulnerable. Check the complete package revision against the security notice for your exact distribution release and repository.&lt;/p&gt;

&lt;p&gt;“The package manager says latest” is not evidence unless the configured repository actually contains a fixed build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inventory the runtime that serves traffic
&lt;/h2&gt;

&lt;p&gt;Start with read-only checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; php
php &lt;span class="nt"&gt;-v&lt;/span&gt;
php &lt;span class="nt"&gt;--ini&lt;/span&gt;
php &lt;span class="nt"&gt;-m&lt;/span&gt;
php &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'printf("PHP=%s SAPI=%s\n", PHP_VERSION, PHP_SAPI);'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands describe the CLI binary you invoked. They do not prove that PHP-FPM, Apache mod_php, CGI, FrankenPHP, LiteSpeed, a container, or a serverless image uses the same runtime.&lt;/p&gt;

&lt;p&gt;For every production service, record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hostname and alternate origin;&lt;/li&gt;
&lt;li&gt;PHP SAPI and complete version;&lt;/li&gt;
&lt;li&gt;package source and vendor advisory;&lt;/li&gt;
&lt;li&gt;FPM socket or Apache module;&lt;/li&gt;
&lt;li&gt;container image digest;&lt;/li&gt;
&lt;li&gt;worker or instance identity;&lt;/li&gt;
&lt;li&gt;extension compatibility;&lt;/li&gt;
&lt;li&gt;restart and verification status.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common failure mode is a patched CLI beside an older FPM service. Another is a new image tag with old Pods still serving traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safe production patch workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Map the real exposure
&lt;/h3&gt;

&lt;p&gt;Include alternate hostnames, preview environments, old virtual hosts, direct-origin access, replicas outside the main load balancer, scheduled jobs, and autoscaling templates.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Select a trusted fixed build
&lt;/h3&gt;

&lt;p&gt;Match both CVEs to either a fixed upstream release or a vendor notice covering the exact package revision. Keep the runtime and native extensions compatible.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Prepare recovery
&lt;/h3&gt;

&lt;p&gt;Back up configuration and application data. Keep a known-good &lt;strong&gt;patched&lt;/strong&gt; artifact available. A rollback that restores the vulnerable runtime is not a safe recovery plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Exercise a canary
&lt;/h3&gt;

&lt;p&gt;Test login, sessions, uploads, imports, checkout, APIs, queues, scheduled jobs, and error handling. Watch error rate, latency, memory, worker churn, and extension compatibility.&lt;/p&gt;

&lt;p&gt;Do not send exploit payloads to production to “prove” the bug.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Replace the workers
&lt;/h3&gt;

&lt;p&gt;Updating files on disk is not enough. Drain and restart or replace every affected PHP worker and replica.&lt;/p&gt;

&lt;p&gt;For an installation that really uses Ubuntu's PHP 8.3 FPM service, the operational sequence might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/sbin/php-fpm8.3 &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart php8.3-fpm
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl is-active php8.3-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An active service only proves that it is running. Verify the package revision and the runtime reached through the actual serving path.&lt;/p&gt;

&lt;p&gt;Restarting Nginx alone does not replace a separate FPM process. Apache mod_php requires replacing the Apache processes that loaded the module.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Verify the whole fleet
&lt;/h3&gt;

&lt;p&gt;For containers, rebuild from a fixed base image, publish an immutable digest, roll it out, and confirm that no old digest remains in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;serving replicas;&lt;/li&gt;
&lt;li&gt;Jobs and CronJobs;&lt;/li&gt;
&lt;li&gt;standby regions;&lt;/li&gt;
&lt;li&gt;deployment templates;&lt;/li&gt;
&lt;li&gt;rollback targets;&lt;/li&gt;
&lt;li&gt;autoscaling definitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A successful image build is not proof that production stopped running the old image.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if you cannot patch immediately?
&lt;/h2&gt;

&lt;p&gt;Temporary containment reduces specific paths; it does not provide a general fix.&lt;/p&gt;

&lt;p&gt;The official guidance discusses measures such as disabling session upload progress before request parsing and, for PHP 8.5, disabling fatal-error backtraces. Administrative pool configuration matters because a setting inside application code may run after vulnerable initialization work.&lt;/p&gt;

&lt;p&gt;Isolation may be the safer short-term choice: restrict access, disable unneeded upload paths at the frontend, or temporarily remove the affected service while the runtime is repaired.&lt;/p&gt;

&lt;p&gt;Treat every workaround as an owned exception with a deadline. Do not let a containment setting become a permanent substitute for upgrading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a WAF is not the patch
&lt;/h2&gt;

&lt;p&gt;Cloudflare reported deploying default protections and observing attack traffic. That is useful defense in depth, but it is not proof that your origin runtime is fixed.&lt;/p&gt;

&lt;p&gt;Check that all traffic actually traverses the protected hostname and that no direct origin, alternate domain, preview environment, or internal path bypasses it.&lt;/p&gt;

&lt;p&gt;Managed cloud does not automatically answer patch ownership either. Some platforms update supported runtimes; container-based services commonly require the customer to rebuild and redeploy. Get evidence for the exact service you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Investigate without overstating the evidence
&lt;/h2&gt;

&lt;p&gt;Amazon Threat Intelligence reported an attempt dated October 17, before public disclosure, followed by broader attempts. Its report also said it was unclear whether those attempts would have succeeded against real systems.&lt;/p&gt;

&lt;p&gt;That distinction matters. Preserve relevant evidence from at least October 17 where available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;web and proxy request metadata;&lt;/li&gt;
&lt;li&gt;PHP worker crashes and restart reasons;&lt;/li&gt;
&lt;li&gt;unexpected child processes;&lt;/li&gt;
&lt;li&gt;file changes;&lt;/li&gt;
&lt;li&gt;outbound connections;&lt;/li&gt;
&lt;li&gt;container and kernel events;&lt;/li&gt;
&lt;li&gt;secrets-access anomalies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An empty Laravel or CMS log does not prove that native request processing was safe. If credible compromise evidence appears, preserve evidence, rebuild from trusted artifacts, rotate affected credentials, and involve incident response.&lt;/p&gt;

&lt;p&gt;Applying the patch prevents the repaired bug from being reused. It does not remove an existing foothold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Completion checklist
&lt;/h2&gt;

&lt;p&gt;Close the patch task only when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every serving PHP runtime has a documented fix for both CVEs;&lt;/li&gt;
&lt;li&gt;all old workers and container images are replaced;&lt;/li&gt;
&lt;li&gt;normal business flows pass the canary;&lt;/li&gt;
&lt;li&gt;the live SAPI and package revision are verified per instance;&lt;/li&gt;
&lt;li&gt;vulnerable rollback and autoscaling artifacts are removed;&lt;/li&gt;
&lt;li&gt;investigation findings have a separate owner and evidence record.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.php.net/archive/2026.php" rel="noopener noreferrer"&gt;PHP: October 2026 security releases and mitigation guidance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w" rel="noopener noreferrer"&gt;PHP advisory: CVE-2026-109618&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp" rel="noopener noreferrer"&gt;PHP advisory: CVE-2026-109619&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thephp.foundation/blog/2026/10/21/php-security-release/" rel="noopener noreferrer"&gt;PHP Foundation: security release and engineering background&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert.europa.eu/publications/security-advisories/2026-018/" rel="noopener noreferrer"&gt;CERT-EU advisory 2026-018&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ubuntu.com/security/notices/USN-8948-1" rel="noopener noreferrer"&gt;Ubuntu USN-8948-1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.cloudflare.com/php2shell/" rel="noopener noreferrer"&gt;Cloudflare PHP2Shell protections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation/" rel="noopener noreferrer"&gt;Amazon Threat Intelligence report&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Read the complete source-backed guide, including vendor-specific package notes and the full reference list: &lt;strong&gt;&lt;a href="https://nooryasser.com/articles/php2shell-cve-2026-109618-109619-patch-guide/" rel="noopener noreferrer"&gt;PHP2Shell: where the PHP flaws are and how to patch&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>php</category>
      <category>security</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Kubernetes Graceful Shutdown: Drain Traffic Before Your Process Exits</title>
      <dc:creator>Noor Yasser</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:51:41 +0000</pubDate>
      <link>https://dev.to/nooryasserx/kubernetes-graceful-shutdown-drain-traffic-before-your-process-exits-27d5</link>
      <guid>https://dev.to/nooryasserx/kubernetes-graceful-shutdown-drain-traffic-before-your-process-exits-27d5</guid>
      <description>&lt;p&gt;Kubernetes graceful shutdown coordinates SIGTERM handling, traffic draining, rollout capacity, and disruption controls into one production contract.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Original article:&lt;/strong&gt; &lt;a href="https://nooryasser.com/articles/kubernetes-graceful-shutdown-pdb-draining/" rel="noopener noreferrer"&gt;Kubernetes graceful shutdown: drain traffic before processes exit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A reliable Pod must stop accepting new work, give routing components time to converge, finish or safely release in-flight work, close connections, and exit before its single termination budget expires. Kubernetes can send signals and update endpoints, but it cannot complete a transaction, acknowledge a queue message, or make a side effect idempotent for your application.&lt;/p&gt;

&lt;p&gt;This distinction matters during rollouts and node maintenance. A Deployment can look healthy while users still see connection resets, workers duplicate jobs, or containers end with forced SIGKILL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a shutdown contract
&lt;/h2&gt;

&lt;p&gt;Treat shutdown as an application state machine with explicit invariants:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The instance changes from &lt;strong&gt;accepting&lt;/strong&gt; to &lt;strong&gt;draining&lt;/strong&gt; exactly once.&lt;/li&gt;
&lt;li&gt;It rejects or redirects new long-running work after draining starts.&lt;/li&gt;
&lt;li&gt;Work already acknowledged is completed, released with a lease, or made safe to retry.&lt;/li&gt;
&lt;li&gt;Listeners, workers, buffers, and database clients reach a known state before exit.&lt;/li&gt;
&lt;li&gt;Every cleanup step has a deadline shorter than the remaining Pod grace period.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The exact contract depends on the workload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An HTTP API stops accepting new connections and waits for active requests.&lt;/li&gt;
&lt;li&gt;A queue consumer stops polling before it waits for handlers.&lt;/li&gt;
&lt;li&gt;A WebSocket service asks clients to reconnect, then closes within a bounded window.&lt;/li&gt;
&lt;li&gt;A leader releases or expires its lease before exiting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not hide these different semantics behind one generic delay. The shutdown path should also be idempotent: a second signal or callback must observe the existing drain instead of starting another flush or acknowledging the same job twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the real termination timeline
&lt;/h2&gt;

&lt;p&gt;When a Pod gets a deletion timestamp, multiple activities begin close together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The kubelet starts the local termination flow.&lt;/li&gt;
&lt;li&gt;If a preStop hook exists, it runs before the main process receives TERM.&lt;/li&gt;
&lt;li&gt;The control plane marks the Pod as terminating and updates Service endpoint state.&lt;/li&gt;
&lt;li&gt;The application drains work after receiving the signal.&lt;/li&gt;
&lt;li&gt;Anything still running when the grace period expires is forcibly stopped.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important detail is that the grace countdown starts &lt;strong&gt;before&lt;/strong&gt; preStop. A 15-second hook inside a 60-second budget does not leave another 60 seconds for the application; it leaves roughly 45.&lt;/p&gt;

&lt;p&gt;Kubernetes EndpointSlices expose terminating, serving, and ready conditions. A terminating endpoint is normally no longer ready for ordinary Service traffic, but external load balancers, Ingress controllers, service meshes, DNS caches, and keep-alive connections may converge at different speeds. Endpoint removal is necessary; it is not proof that no new packet can arrive.&lt;/p&gt;

&lt;p&gt;Official reference: &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination" rel="noopener noreferrer"&gt;Pod termination flow&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/" rel="noopener noreferrer"&gt;EndpointSlice conditions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the process own graceful shutdown
&lt;/h2&gt;

&lt;p&gt;Register signal handling before the application reports readiness. A practical handler follows this order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;set an internal draining flag and make readiness fail;&lt;/li&gt;
&lt;li&gt;stop accepting new connections or queue messages;&lt;/li&gt;
&lt;li&gt;tell long-lived clients to reconnect when the protocol supports it;&lt;/li&gt;
&lt;li&gt;wait for in-flight work until a bounded deadline;&lt;/li&gt;
&lt;li&gt;release leases, flush telemetry, and close database clients;&lt;/li&gt;
&lt;li&gt;exit with a result that distinguishes clean completion from timeout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The container's process 1 must actually receive and forward the stop signal. Shell wrappers that do not use exec, detached child processes, and custom supervisors can swallow or delay SIGTERM. Test the built container image, not only the framework's local development server.&lt;/p&gt;

&lt;p&gt;Readiness is a traffic-admission signal, not a shutdown executor. Changing readiness to false helps remove the Pod from normal routing, but it does not close existing connections or finish current work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use preStop as a small propagation buffer
&lt;/h2&gt;

&lt;p&gt;A short preStop hook can help when routing components need time to observe endpoint changes before the application exits. Keep it deterministic and small because its runtime is charged against terminationGracePeriodSeconds.&lt;/p&gt;

&lt;p&gt;Avoid putting business cleanup into a large shell script that depends on DNS or an unauthenticated admin endpoint. Native application signal handling is easier to test and can apply workload-specific deadlines.&lt;/p&gt;

&lt;p&gt;If the hook only waits for routing convergence, measure that delay. Copying a 20- or 30-second sleep from another manifest is cargo-cult reliability.&lt;/p&gt;

&lt;p&gt;Official reference: &lt;a href="https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/" rel="noopener noreferrer"&gt;Container lifecycle hooks&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculate one explicit grace budget
&lt;/h2&gt;

&lt;p&gt;Size the termination period from measured components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grace budget &amp;gt;=
  routing propagation buffer
  + longest accepted request or job slice
  + connection close time
  + durable flush or lease release
  + safety margin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not use average latency. Use a high percentile, the product deadline, and the retry contract. If requests may run for two minutes, a 30-second grace period cannot make shutdown graceful. Either shorten the request deadline, make the work resumable, transfer ownership, or reserve a larger budget.&lt;/p&gt;

&lt;p&gt;Long grace periods are not free. Terminating Pods continue consuming CPU, memory, connections, and sometimes temporary rollout capacity. Often the better solution is a bounded unit of work and a faster checkpoint rather than an arbitrarily large timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  A PDB does not control your rollout
&lt;/h2&gt;

&lt;p&gt;A PodDisruptionBudget limits concurrent &lt;strong&gt;voluntary disruptions&lt;/strong&gt; performed through the Eviction API. kubectl drain respects it, so a PDB is useful for planned node maintenance and cooperating eviction clients.&lt;/p&gt;

&lt;p&gt;It does not prevent involuntary failures such as node loss or a network partition. It also does not control a Deployment rolling update; rollout capacity is defined by the workload strategy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;What it protects&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;minAvailable / maxUnavailable in a PDB&lt;/td&gt;
&lt;td&gt;Voluntary evictions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maxUnavailable in a Deployment&lt;/td&gt;
&lt;td&gt;Serving capacity removed during rollout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maxSurge&lt;/td&gt;
&lt;td&gt;Temporary replacement capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;minReadySeconds&lt;/td&gt;
&lt;td&gt;A replacement must remain ready before counting as available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;progressDeadlineSeconds&lt;/td&gt;
&lt;td&gt;Detects a stalled rollout; it does not automatically roll back&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A strict PDB cannot compensate for all replicas living on one node, and a perfect shutdown handler cannot help when the scheduler has no capacity for a replacement.&lt;/p&gt;

&lt;p&gt;Official reference: &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/disruptions/" rel="noopener noreferrer"&gt;Disruptions and PodDisruptionBudgets&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;Deployment rolling updates&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A complete starting manifest
&lt;/h2&gt;

&lt;p&gt;The following example keeps a short propagation buffer while leaving most of a 45-second budget for the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="na"&gt;minReadySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;progressDeadlineSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders-api&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RollingUpdate&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders-api&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;45&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.example/orders-api:2026-10-18&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/ready&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
            &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
            &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
          &lt;span class="na"&gt;lifecycle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;preStop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sleep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;policy/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PodDisruptionBudget&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;minAvailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;unhealthyPodEvictionPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AlwaysAllow&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders-api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This manifest is a starting point, not a guarantee. The application still needs bounded SIGTERM handling, retry-safe work, and enough cluster capacity. Also verify that the image contains the shell and sleep; a distroless image often does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observe shutdown as a first-class operation
&lt;/h2&gt;

&lt;p&gt;Useful signals include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;graceful_shutdown_duration_seconds;&lt;/li&gt;
&lt;li&gt;forced_shutdown_total;&lt;/li&gt;
&lt;li&gt;in_flight_requests or active_jobs;&lt;/li&gt;
&lt;li&gt;rejected_during_drain_total;&lt;/li&gt;
&lt;li&gt;connection resets and rollout-time 5xx errors;&lt;/li&gt;
&lt;li&gt;PDB disruptionsAllowed;&lt;/li&gt;
&lt;li&gt;EndpointSlice terminating and serving conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Correlate them with the deployment revision, node-drain events, container exit reason, and resource pressure. Exit code 137 alone is ambiguous: forced termination and memory pressure can both lead to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test failure instead of trusting YAML
&lt;/h2&gt;

&lt;p&gt;Before calling the design reliable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send requests that span the shutdown window and start a rollout.&lt;/li&gt;
&lt;li&gt;Verify that accepted responses finish without resets.&lt;/li&gt;
&lt;li&gt;Drain one node under load and confirm the PDB blocks an unsafe simultaneous eviction.&lt;/li&gt;
&lt;li&gt;Make one Pod unhealthy and verify the selected unhealthy eviction policy.&lt;/li&gt;
&lt;li&gt;Exceed the cleanup deadline deliberately and ensure the timeout is visible.&lt;/li&gt;
&lt;li&gt;Test involuntary cases a PDB cannot prevent: node loss, network interruption, and insufficient replacement capacity.&lt;/li&gt;
&lt;li&gt;For workers, kill the process after receiving a message but before acknowledgement and prove retry does not duplicate side effects.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;Handle SIGTERM in process 1.&lt;/li&gt;
&lt;li&gt;Stop new work before waiting for current work.&lt;/li&gt;
&lt;li&gt;Make cleanup idempotent and deadline-aware.&lt;/li&gt;
&lt;li&gt;Keep preStop short and count it inside the same grace budget.&lt;/li&gt;
&lt;li&gt;Measure routing convergence and request duration.&lt;/li&gt;
&lt;li&gt;Configure rollout capacity separately from the PDB.&lt;/li&gt;
&lt;li&gt;Spread replicas across failure domains.&lt;/li&gt;
&lt;li&gt;Exercise graceful and forced failures under load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A shutdown is successful only when users see no unexplained errors and acknowledged work remains correct.&lt;/p&gt;




&lt;p&gt;The expanded article, including additional decision guidance and references, is available at &lt;a href="https://nooryasser.com/articles/kubernetes-graceful-shutdown-pdb-draining/" rel="noopener noreferrer"&gt;nooryasser.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>architecture</category>
      <category>sre</category>
    </item>
    <item>
      <title>Production RAG Is an Evidence Pipeline, Not a Vector-Database Demo</title>
      <dc:creator>Noor Yasser</dc:creator>
      <pubDate>Fri, 09 Oct 2026 14:26:52 +0000</pubDate>
      <link>https://dev.to/nooryasserx/production-rag-is-an-evidence-pipeline-not-a-vector-database-demo-325o</link>
      <guid>https://dev.to/nooryasserx/production-rag-is-an-evidence-pipeline-not-a-vector-database-demo-325o</guid>
      <description>&lt;p&gt;A production RAG system is not “put documents in a vector database and ask an LLM.” It is an evidence pipeline with two versioned paths: an offline path that parses, chunks, embeds, and indexes authorized content; and an online path that retrieves, reranks, assembles bounded context, and asks the model to answer only from that evidence.&lt;/p&gt;

&lt;p&gt;This distinction matters because most real failures happen before generation. The parser drops a table, the chunk loses its heading, the tenant filter is missing, or a deleted source survives in the index. A fluent answer can hide all of those problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  First decide whether RAG is the right tool
&lt;/h2&gt;

&lt;p&gt;Use RAG when answers depend on large, changing, unstructured knowledge and provenance matters: policies, manuals, contracts, support histories, or technical documentation.&lt;/p&gt;

&lt;p&gt;Use something simpler when the problem is simpler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL&lt;/strong&gt; for exact structured facts and transactional state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lexical search&lt;/strong&gt; for known identifiers, error codes, names, or quoted phrases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A deterministic API&lt;/strong&gt; for permissions, account state, and actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tuning&lt;/strong&gt; when the missing capability is behavior or style, not knowledge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-context prompting&lt;/strong&gt; when a small, stable corpus fits safely inside the model context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The router that makes this choice is part of the product. Evaluate it like every other component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Split indexing from query-time work
&lt;/h2&gt;

&lt;p&gt;The offline path should be idempotent and resume-safe:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store the original document and its version.&lt;/li&gt;
&lt;li&gt;Extract structured text and preserve headings, pages, and source locations.&lt;/li&gt;
&lt;li&gt;Create chunks using document structure.&lt;/li&gt;
&lt;li&gt;Generate embeddings with an explicit model and pipeline version.&lt;/li&gt;
&lt;li&gt;Write searchable points and payload indexes.&lt;/li&gt;
&lt;li&gt;Mark the document searchable only after every required artifact is durable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The online path owns the latency budget:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticate the requester and derive tenant and ACL scope.&lt;/li&gt;
&lt;li&gt;Classify or rewrite the query only when justified.&lt;/li&gt;
&lt;li&gt;Run dense and lexical retrieval branches.&lt;/li&gt;
&lt;li&gt;Fuse and rerank a bounded candidate set.&lt;/li&gt;
&lt;li&gt;Hydrate canonical text through an authorized repository.&lt;/li&gt;
&lt;li&gt;Build cited context under a deterministic token budget.&lt;/li&gt;
&lt;li&gt;Generate an answer, validate citations, and abstain when evidence is insufficient.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Join both paths with stable identifiers such as tenant_id, document_id, document_version, chunk_id, source location, checksum, embedding model, and index version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunk by meaning and structure
&lt;/h2&gt;

&lt;p&gt;A single global chunk size is rarely a production strategy. Prefer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;headings and paragraphs for prose;&lt;/li&gt;
&lt;li&gt;function or class boundaries for code;&lt;/li&gt;
&lt;li&gt;rows plus headers for tables;&lt;/li&gt;
&lt;li&gt;clauses for contracts;&lt;/li&gt;
&lt;li&gt;turns for conversations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A chunk should represent one retrieval idea while still carrying enough context to answer a useful sub-question. Large overlap is an expensive substitute for good boundaries because it multiplies embedding cost and returns near-duplicates.&lt;/p&gt;

&lt;p&gt;Parent-child retrieval is useful when narrow chunks search well but the answer needs surrounding explanation: index the child, then hydrate its parent section or immediate neighbors after retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat embeddings as a versioned contract
&lt;/h2&gt;

&lt;p&gt;An embedding belongs to a specific model, input mode, preprocessing pipeline, and output dimension. Changing any of those changes the vector space.&lt;/p&gt;

&lt;p&gt;Store the model, dimensions, and pipeline version with every point. Build a new named vector or collection for migrations, backfill it, compare quality and latency, switch reads gradually, and keep a rollback window.&lt;/p&gt;

&lt;p&gt;Never mark a document indexed until all intended chunks have the expected embedding version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model Qdrant around retrieval and isolation
&lt;/h2&gt;

&lt;p&gt;A Qdrant collection should follow the embedding contract, not the number of business entities. Put filter and provenance fields next to the vector: tenant, document and version IDs, language, content type, ACL labels, timestamps, and source location.&lt;/p&gt;

&lt;p&gt;Create payload indexes for fields used in filters. A field being present in payload does not automatically make filtering efficient.&lt;/p&gt;

&lt;p&gt;For multitenancy, never accept a tenant filter directly from the public request. Derive it from authenticated scope and inject it into every retrieval branch, including dense, sparse, recommendation, and scroll endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieve broadly, rerank narrowly
&lt;/h2&gt;

&lt;p&gt;Dense retrieval catches semantic similarity. Lexical retrieval catches identifiers and exact terms. Hybrid retrieval runs both and fuses ranks to improve candidate coverage.&lt;/p&gt;

&lt;p&gt;Treat fusion as candidate generation, not final context. Rerank only a bounded candidate set because rerankers are more expensive. Skip reranking when the initial candidates already meet the quality target.&lt;/p&gt;

&lt;p&gt;Query rewriting can resolve pronouns or expand abbreviations, but preserve the original query and evaluate the rewrite independently. A generated rewrite can drift from user intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build context as evidence
&lt;/h2&gt;

&lt;p&gt;Context construction should be deterministic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remove duplicate and near-duplicate chunks;&lt;/li&gt;
&lt;li&gt;prefer the newest authorized version;&lt;/li&gt;
&lt;li&gt;merge adjacent fragments only when they share a source;&lt;/li&gt;
&lt;li&gt;cap evidence per document;&lt;/li&gt;
&lt;li&gt;reserve tokens for instructions, user input, and the answer;&lt;/li&gt;
&lt;li&gt;attach stable citation IDs to document versions and locations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat retrieved documents as untrusted data. They may contain prompt-injection strings. Retrieved text must never override system rules or request tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;dense&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sparse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;rerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;deduplicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hydrated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAuthorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chunkId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hydrated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxPerDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;includeSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;documentId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;section&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;generateGroundedAnswer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;requireCitations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those numbers are example budgets, not universal recommendations. Measure them against your corpus, model, and latency objective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluate every gate
&lt;/h2&gt;

&lt;p&gt;A final-answer score is not enough. Measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retrieval recall@k;&lt;/li&gt;
&lt;li&gt;ranking quality with MRR or nDCG;&lt;/li&gt;
&lt;li&gt;tenant and ACL isolation;&lt;/li&gt;
&lt;li&gt;reranker lift;&lt;/li&gt;
&lt;li&gt;evidence precision;&lt;/li&gt;
&lt;li&gt;citation validity;&lt;/li&gt;
&lt;li&gt;supported-claim rate;&lt;/li&gt;
&lt;li&gt;abstention quality;&lt;/li&gt;
&lt;li&gt;p50 and p95 latency;&lt;/li&gt;
&lt;li&gt;token, embedding, and reranking cost;&lt;/li&gt;
&lt;li&gt;queue age and index freshness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run ablations on the same evaluation set: lexical only, dense only, hybrid, hybrid plus reranking, neighbor expansion, and contextualized chunks. Change one component at a time.&lt;/p&gt;

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

&lt;p&gt;Before launch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define which questions go to RAG, SQL, search, or tools;&lt;/li&gt;
&lt;li&gt;version the parser, chunker, embeddings, and prompts;&lt;/li&gt;
&lt;li&gt;index only complete document versions;&lt;/li&gt;
&lt;li&gt;derive authorization filters from trusted identity;&lt;/li&gt;
&lt;li&gt;create payload indexes for real filter fields;&lt;/li&gt;
&lt;li&gt;enforce a deterministic context budget;&lt;/li&gt;
&lt;li&gt;validate every citation;&lt;/li&gt;
&lt;li&gt;reconcile deletions against vector points;&lt;/li&gt;
&lt;li&gt;trace model and component versions;&lt;/li&gt;
&lt;li&gt;maintain permission-aware evaluation data;&lt;/li&gt;
&lt;li&gt;test cross-tenant retrieval and prompt injection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical goal is not a clever demo. It is a system that fails closed, degrades predictably, and can explain which evidence produced every material claim.&lt;/p&gt;

&lt;p&gt;For the full bilingual guide, architecture diagram, and official references, read the canonical article: &lt;a href="https://nooryasser.com/articles/production-rag-pipeline-chunking-qdrant-reranking/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=personal_brand" rel="noopener noreferrer"&gt;Production RAG: from chunks to grounded LLM answers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prepared by Noor Yasser.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>rag</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
