yeganeahmadnejad opened a new issue, #13888:
URL: https://github.com/apache/apisix/issues/13888

   ## Description
   
   We use a priority-based upstream (`nodes[].priority`) with active + passive 
health checks to fail over from a primary node to backup nodes when the primary 
becomes unhealthy. We're seeing the primary node continue to receive real 
traffic for minutes after one nginx worker process has already logged that it 
crossed the `unhealthy.http_failures` threshold for that node.
   
   This reproduces consistently on **3.13.0, 3.15.0, and 3.18.0** with an 
identical upstream config — it does not appear to be tied to any specific 
APISIX version.
   
   ## Upstream config (sanitized)
   
   ```yaml
   upstreams:
     - id: upstream-api-service-priority-failover
       type: roundrobin
       scheme: https
       pass_host: node
       checks:
         active:
           type: https
           timeout: 2
           http_path: /internal-health
           https_verify_certificate: false
           healthy:
             interval: 3
             http_statuses: [200, 404]
             successes: 2
           unhealthy:
             interval: 3
             http_statuses: [500, 502, 503, 504]
             http_failures: 2
             tcp_failures: 2
             timeouts: 2
         passive:
           type: https
           healthy:
             http_statuses: [200, 201]
             successes: 2
           unhealthy:
             http_statuses: [500, 502, 503, 504]
             http_failures: 2
             tcp_failures: 2
             timeouts: 2
       nodes:
         - host: primary.example.internal
           port: 443
           weight: 100
           priority: 0
         - host: fallback-a.example.internal
           port: 443
           weight: 50
           priority: -1
         - host: fallback-b.example.internal
           port: 443
           weight: 50
           priority: -1
   ```
   
   ## Reproduction / evidence
   
   Deployment runs with multiple nginx worker processes per pod, and multiple 
pod replicas behind a Kubernetes Service. We added `$pid` to our access log 
format to correlate real requests with the worker process that handled them, 
alongside APISIX's own `[healthcheck]` warn logs (which already include the 
worker's OS pid in the `<pid>#<tid>:` prefix).
   
   Example (all identifiers replaced with fakes, but structure/timing is real):
   
   ```
   2026-08-26T14:37:34Z  [warn] 65#65: [lua] healthcheck.lua:1394: log(): 
[healthcheck]
     (upstream#/upstreams/upstream-api-service-priority-failover)
     unhealthy HTTP increment (2/2) for '10.0.1.100(10.0.1.100:443)', context: 
ngx.timer
   ```
   
   → worker process **65** in pod `api-gateway-abc123def-11111` has just 
crossed the unhealthy threshold for the primary node.
   
   6.5 minutes later, a real request is served **by the same pod**, but by a 
**different worker process (pid 70)**, and is still routed to the same primary 
node:
   
   ```json
   {
     "timestamp": "2026-08-26T14:44:04Z",
     "pod_name": "api-gateway-abc123def-11111",
     "pid": "70",
     "upstream_addr": "10.0.1.100:443",
     "status": "201"
   }
   ```
   
   No further `[healthcheck]` log lines were emitted for this target on this 
pod in between — in particular, no "healthy SUCCESS" recovery line, which 
`lua-resty-healthcheck`'s `incr_counter()` would emit on any real state 
transition back to healthy (an unhealthy→healthy transition doesn't 
short-circuit the way same-state reports do). That suggests worker 65's checker 
state genuinely stayed "unhealthy" the whole time, and worker 70 simply never 
converged to it.
   
   ## Suspected mechanism
   
   `resty.healthcheck`'s `checker:get_target_status()` reads from an 
**in-process, per-worker Lua table** (`self.targets`), not shm directly:
   
   ```lua
   local function get_target(self, ip, port, hostname)
     return ((self.targets[ip] or EMPTY)[port] or EMPTY)[hostname]
   end
   ```
   
   `self.targets[...].internal_health` is populated once at checker init (read 
from shm) and thereafter updated **only via `worker_events` pub/sub** — each 
worker subscribes to health-status-change events raised by whichever worker 
currently holds the active-check "leader" lock. If that event is dropped, 
delayed, or not processed by a given worker, that worker's local cache never 
converges, and it keeps routing traffic to a node that shm — and other workers 
— correctly consider unhealthy.
   
   We additionally observed a related but distinct issue on 3.15/3.18 where 
`healthcheck_manager.fetch_checker()` can return `nil` on a `resource_ver` 
mismatch (composed from `up_conf.resource_version` + `node_ver`), and 
`healthcheck_manager.fetch_node_status()` fails open in that case:
   
   ```lua
   function _M.fetch_node_status(checker, ip, port, hostname)
       if not checker or checker.dead then
           return true  -- treated as healthy
       end
       ...
   end
   ```
   
   We haven't confirmed whether this second path is the actual cause of the 
worker-divergence symptom above, or a separate contributing factor — flagging 
both since they compound the same user-visible problem (traffic reaching a node 
health checks say is down).
   
   ## Environment
   
   - APISIX 3.13.0, 3.15.0, and 3.18.0 (reproduces on all three) — same 
upstream config on all.
   - Multiple nginx worker processes per instance; multiple replicas behind a 
k8s Service.
   - Priority-based upstream nodes (primary + 2 fallback), active (https) + 
passive checks enabled.
   
   ## Expected behavior
   
   Once any worker process's active health checker crosses the unhealthy 
threshold for a node, all worker processes in that instance should stop routing 
traffic to that node until it's confirmed healthy again — health check state 
should not diverge silently between workers within the same process group.
   
   ## Additional notes
   
   We can provide a minimal `docker-compose`-based reproduction (single 
upstream, no plugins, controlled multi-worker `nginx.conf`) if useful — happy 
to follow up with that separately.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to