[{"data":1,"prerenderedAt":95},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:en":3,"api:\u002Fapi\u002Fnotes:en":8,"api:\u002Fapi\u002Fprojects\u002Fapi-gateway:en":32},{"email":4,"lang":5,"name":6,"role":7},"hello@ewsirdev.example","en","Ewsirdev","Web developer: websites, landing pages and Go services",[9,16,22,27],{"slug":10,"lang":5,"title":11,"date":12,"summary":13,"project":14,"minutes":15},"double-booking","Double booking: a transaction plus a partial UNIQUE index","2026-10-20","How booking-slots protects against two clients booking the same time at once, and how that protection is tested.","booking-slots",1,{"slug":17,"lang":5,"title":18,"date":12,"summary":19,"project":20,"minutes":21},"lease-and-fencing","Lease and fencing: how a queue neither loses a job nor lets a zombie finish it twice","A worker can crash, stall, or wake up after its job was taken by someone else. How the lease and the lease token in job-queue deal with that on SQLite.","job-queue",2,{"slug":23,"lang":5,"title":24,"date":12,"summary":25,"project":26,"minutes":21},"p95-nearest-rank","p95 in an uptime monitor: the nearest-rank method and what it does not hide","How uptime-monitor computes p50\u002Fp95 latency and uptime, why the nearest-rank method was chosen, and where those numbers can mislead.","uptime-monitor",{"slug":28,"lang":5,"title":29,"date":12,"summary":30,"project":31,"minutes":21},"websocket-from-scratch","WebSocket on the standard library: what really happens under new WebSocket()","Why realtime-rooms has its own RFC 6455 layer, which protocol violations it checks, and why a library is still the better choice for production.","realtime-rooms",{"slug":33,"title":34,"type":35,"stack":36,"description":49,"highlights":50,"run":55,"hue":56,"og":57,"shots":58,"case":66,"case_lang":5,"prev":90,"next":92},"api-gateway","Sluice: API gateway","backend",[37,38,39,40,41,42,43,44,45,46,47,48],"Go","stdlib only","Reverse proxy","Circuit breaker","Token bucket","SSE","Prometheus","slog","Vanilla JS","embed","Docker","GitHub Actions","An API gateway on the pure stdlib: host\u002Fprefix routes from a JSON config with hot reload that keeps connections alive, round-robin and least-conn balancing, health checks, a per-node circuit breaker, retries of idempotent requests, rate limiting, hashed API keys, Prometheus metrics and a live SSE dashboard.",[51,52,53,54],"Zero dependencies: stdlib only","Config hot reload without dropping connections","Per-node circuit breaker and retries of idempotent requests","Load test: 4000 requests, 40 clients (a demo run)","cd api-gateway && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fgateway",200,"\u002Fog\u002Fen\u002Fapi-gateway.jpg",{"desktop":59,"desktop_sm":60,"mobile":61,"dw":62,"dh":63,"mw":64,"mh":65},"\u002Fshots\u002Fapi-gateway-desktop.webp","\u002Fshots\u002Fapi-gateway-desktop-sm.webp","\u002Fshots\u002Fapi-gateway-mobile.webp",1280,800,390,844,{"about":67,"features":71,"run":80,"notes":85},[68,69,70],"Sluice is an API gateway (a reverse proxy) on the pure Go standard library, with no external dependencies. Routes by host and prefix are described in a JSON config that reloads on the fly without dropping connections.","Each route points to an upstream with several nodes: round-robin or least-conn balancing, health checks, a circuit breaker per node, and retries of idempotent requests. On top of that come a rate limit, hashed API keys, Prometheus metrics and a live SSE dashboard.","In demo mode the process starts three fake upstreams and a synthetic traffic generator that goes through the real gateway. These are not real services and not real load.",[72,73,74,75,76,77,78,79],"Routing by host and longest prefix; an unknown route gets 404, and the gateway is not an open proxy (the destination comes only from the config)","Hot reload: the state is immutable and swapped with a single atomic.Pointer, the listening socket is untouched, and an invalid config is rejected as a whole","Round-robin and least-conn balancing over healthy nodes only, with a health loop per node","A circuit breaker per node (closed, open, half-open) and retries on another node for idempotent methods; the body is buffered up to 1 MiB","SSRF protection: upstream addresses are checked at load time and again at connect time (DNS rebinding)","Token-bucket rate limit (per IP or key), API keys stored as sha256, Prometheus metrics, and an SSE dashboard in vanilla JS","A load test inside the test suite: 4000 requests, 40 clients, about 12,000 requests per second (a demo measurement on one machine, not a benchmark)","Docker and GitHub Actions (go test -race in CI)",[81,82,83,84],"git clone \u003Crepository on request> api-gateway","cd api-gateway","SEED_DEMO=1 go run .\u002Fcmd\u002Fgateway   # proxy :8087, dashboard http:\u002F\u002F127.0.0.1:8088","go run .\u002Fcmd\u002Fgateway -genkey   # a new API key and its sha256",[86,87,88,89],"The rate limit and circuit breaker state live in the memory of one process, so with several replicas the limits are not shared. There is no TLS on the inbound side, and counters are not kept across restarts.","WebSocket and Upgrade are not supported (501); HTTP\u002F2 to upstreams and gRPC were not tested. SIGHUP works on Unix only; on Windows there is POST \u002Fapi\u002Freload.","Percentiles are computed from a histogram with a x1.25 step, so the error is up to 25% upward. The admin uses a single token, with no roles or audit log.","The demo key demo-key-not-a-secret is public. The load figure comes from one machine and is not a benchmark.",{"slug":31,"title":91},"Rooms: realtime chat and board",{"slug":93,"title":94},"notes-search","Card Index: notes with search",1792697892726]