今日已更新 166 条资讯 | 累计 40611 条内容
关于我们

标签:#Performance

找到 236 篇相关文章

AI 资讯

Three PHP-FPM failure modes and how to actually diagnose them

Tuning guides talk about throughput. Nobody pages you about throughput. They page you about symptoms, and the useful skill is mapping a symptom back to a cause before you spend money on hardware. Three failure modes account for most of what I find on inherited servers. Each has a distinct signature. The 502 nobody can reproduce Server has 8GB. PHP-FPM is set to 100 workers. Each worker uses 60MB under load. That's 6GB of PHP, plus MariaDB, plus Nginx, plus the OS. Under normal traffic you never approach 100 workers, so it looks fine for months. Then a marketing email goes out, concurrency spikes, and the kernel runs out of memory. The OOM killer picks a process and terminates it, usually the biggest one, which is a PHP-FPM worker holding an in-flight request. User gets a 502. The application log has nothing, because the process died before it could write anything. Nginx logs recv() failed (104: Connection reset by peer) . Ten minutes later everything looks normal. sudo dmesg -T | grep -i "killed process" sudo journalctl -k | grep -i oom Hits there mean you don't have a mystery. You have a pm.max_children value nobody checked against real memory. The site that degrades all day and resets overnight TTFB is 180ms at 8am. By 4pm it's 900ms. Nobody deployed. Overnight it's fast again because something restarted PHP-FPM. That's OPcache running out of room. When the cache fills, it stops caching new scripts or wipes and rebuilds, and every miss pays full parse-and-compile again. It degrades gradually, which is why it goes unnoticed for months. The counters are oom_restarts and hash_restarts from opcache_get_status() . Here's the part that trips people up. OPcache state is per SAPI. Run that function from the CLI and you're reading the CLI cache, which is empty, separate, and tells you nothing about your site. You have to ask through PHP-FPM. <?php // drop in webroot, lock to your IP, delete when done $allowed = [ '203.0.113.42' ]; if ( ! in_array ( $_SERVER [ 'REMOTE_ADDR'

2026-09-07 原文 →
AI 资讯

Why Adding an Index Won't Fix Your Slow COUNT(*) in PostgreSQL

COUNT(*) looks like a trivial operation: SELECT COUNT ( * ) FROM orders ; The query asks for a single number, but that doesn't mean PostgreSQL can produce it with a constant-time read from some internal counter. When we need an exact count, PostgreSQL has to determine how many rows are actually part of the visible result set for that query. On large tables, that work can become a meaningful chunk of total execution time. And the problem doesn't just go away by throwing an index at it. The useful question isn't "do I have an index?" It's: How many rows does PostgreSQL actually need to examine to compute this count — and can that work be reduced? Why COUNT(*) Can Be Expensive in PostgreSQL PostgreSQL uses MVCC — Multi-Version Concurrency Control — to manage concurrent access to data. That's what lets multiple transactions work at the same time while each sees a consistent view of the database. But it also means row visibility depends on the snapshot the query is running under. That's why PostgreSQL can't answer: SELECT COUNT ( * ) FROM orders ; by simply reading an exact counter stored somewhere in the table's metadata. To return an exact result, it has to process the rows — or an index structure representing those rows — and determine which ones are part of the visible result. On a small table, that cost is invisible. On a table with millions of rows, the amount of work starts to matter. Which leads to an important distinction: returning a single row from COUNT(*) does not mean processing a single row. How to Analyze a COUNT with EXPLAIN ANALYZE Before reaching for an index, it's worth looking at what PostgreSQL is actually doing. Say we have this query: SELECT COUNT ( * ) FROM orders WHERE status = 'completed' ; We can analyze it with: EXPLAIN ( ANALYZE , BUFFERS ) SELECT COUNT ( * ) FROM orders WHERE status = 'completed' ; The goal isn't to hunt for an Index Scan by default. Worth checking instead: the scan type estimated rows vs. actual rows processed rows discard

2026-09-07 原文 →
AI 资讯

Why XopProtector Is a Lightweight Alternative to Commercial Android App Protection

Android App Protection Shouldn't Come at the Cost of Performance: The Lightweight Approach of XopProtector Android application protection has always involved a difficult trade-off. Stronger protection often means: Larger APK size Longer protection/build time Higher runtime overhead Slower application startup For large Android applications, these costs can become especially noticeable. XopProtector takes a different approach: strong protection with a focus on build efficiency, small APK overhead, and fast runtime startup. 300MB APK Protection in Under 5 Minutes For large Android projects, protection time is an important part of the development workflow. If protecting a 300MB APK takes 10–20 minutes or longer, it can significantly slow down: CI/CD pipelines Regression testing Beta releases Production builds Daily development XopProtector is designed to minimize unnecessary processing and optimize the protection pipeline for DEX, native libraries, and protected runtime data. In our testing environment, a 300MB-class APK can be protected within 5 minutes . This makes APK protection much more practical for frequent builds and automated CI/CD workflows. Actual protection time depends on hardware, APK structure, number of DEX files, native libraries, and the selected protection configuration. Small APK Size Overhead Protection should not mean dramatically increasing the APK size. Some protection solutions introduce significant additional runtime components or duplicated protected data, which can result in noticeable APK growth. XopProtector focuses on keeping the protection runtime lightweight and minimizing unnecessary additional data. The goal is simple: Original APK ↓ XopProtector ↓ Protected APK Protection ↑ Security ↑ APK overhead ↓ Build time ↓ Runtime overhead ↓ For large applications, keeping the size overhead low can be just as important as the protection itself. Fast Startup After Protection Build time is only one part of the equation. What users ultimately exper

2026-09-07 原文 →
AI 资讯

Why I Rewrote Four Services in Go

I had four small services. Each one was a Model Context Protocol adapter — a thin wrapper that lets an AI agent call out to some external thing. One talked to Replicate for image generation. One talked to a Nostr-friendly social poster. One was a Git-aware research helper. One was a Tavily-powered web search. They were all written in Python. They all ran on Knative on a small Kubernetes cluster. They all worked. And they were all just slightly too slow to use. A six-second cold start is fine for nothing. It is the precisely wrong amount of time — slow enough to be noticed, fast enough to feel almost loaded. An AI agent waiting six seconds for a single tool call does not know it is waiting for a cold start; it just knows the tool is sluggish. The user does not know either. The user just thinks the agent is broken. And six seconds was a good day. Some of the services took longer. So I rewrote them in Go. This is what that cost me, and what the measurements actually were before and after. The actual problem Cold starts on serverless platforms are an old problem with a well-known shape. The platform spins your container up only when traffic arrives, so the first request after an idle period pays the full startup tax — image pull (or warm cache hit), container start, language runtime initialisation, application bootstrap. For Python, application bootstrap is where the bill arrives. The interpreter has to start. import statements run. The dependency tree gets walked. If you have ever wondered why a hello world Flask app feels so much heavier than a hello world Go binary, this is why. Python is doing real work before your code runs. Go has already started. On a small Kubernetes cluster — small as in I am paying for it personally — you do not keep a fleet of warm replicas around. You scale-to-zero. You scale-to-zero because that is the entire point of using serverless on small infrastructure. The trade-off is that every idle service eats a cold start the next time it is inv

2026-09-06 原文 →
AI 资讯

It Fit in Memory and Was Still Unusable — Do the Bandwidth Arithmetic First

Originally published on hexisteme notes . "Will it fit on our hardware?" is the wrong first question. It's the one everyone asks, because it's free to answer — the thing either loads or it doesn't. Throughput costs you a measurement. So the capacity gate passes, and it feels like the decision is made. The measurement Mac Mini M4, 24GB unified memory, ~120GB/s memory bandwidth. A 27B model, IQ4_XS quantized, 15GB on disk. Capacity gate: pass. Metal's recommendedMaxWorkingSet is 17.76GB, the model is 15GB, ollama ps reports 100% GPU resident. No swap, no spillover. By every "does it fit" criterion this is a clean win. Generation: 5.6 tokens/second. That's not a usable interactive worker. It's barely a usable batch worker. And nothing about the capacity check hinted at it. The arithmetic that would have told me in advance Autoregressive generation reads the entire model's weights once per token. So: ceiling ≈ memory bandwidth ÷ bytes touched per operation = 120 GB/s ÷ 15 GB = 8 tokens/second Measured 5.6 against a ceiling of 8. Ratio 0.70. That ratio is the whole verdict. When measured throughput is a large fraction of the arithmetic ceiling, you are bandwidth-bound , and you now know something concrete: the bottleneck is not your configuration, not memory pressure, not thermal throttling. It's how fast bytes move. Rule of thumb I now use: ratio ≥ 0.5 → bandwidth-bound, and size-reduction fixes are dead. Why "just quantize harder" doesn't work The natural move when capacity is tight is to shrink. Lower quantization, smaller batch, heavier compression. It's the reflex, and in a bandwidth-bound regime it's close to useless. I was considering Q3_K_M at 13.8GB. Run the same division: 120 ÷ 13.8 = 8.7 tokens/second (up from 8) Under 9% more throughput. For a real drop in output quality, because quantization error doesn't scale linearly with size the way bandwidth does — you give up more than you get, every time, in this regime. I killed that plan without downloading anythin

2026-09-06 原文 →
AI 资讯

19 OOM kills in 9 days: diagnosing a shared-hosting WordPress before the rebuild

Nineteen OOM kills in nine days. Ten WordPress apps on a 32GB shared box. One of them a client site that took a CPU spike on 14 July during a paid campaign burst, and pushed the whole tenant into the wall. This post is the diagnosis before the rebuild. What we actually found when we stopped guessing. Two layers of Cloudflare, one page cache plugin, one preloader being silently challenged, and a language subpath that was cold every time it mattered. I'm writing it partly for anyone who runs multi-tenant WordPress on Cloudways or similar, and partly as a reminder for future-me. There's a checklist at the end. Steal it. The client is anonymized throughout. Every number is real. The stack Traffic hits two Cloudflare layers before it reaches origin. Both are Cloudflare, but they're different zones on different accounts, and they own different things. [ visitor ] ↓ [ Upstream Cloudflare zone (managed by a third party) ] ← DNS, SSL, HTML edge cache ↓ [ Cloudflare Enterprise add-on sold by Cloudways ] ← WAF, bot, rate limit, AI crawler block ↓ [ Cloudways origin: nginx + PHP-FPM ] ↓ [ WordPress + WPML + Elementor + FlyingPress ] Two Cloudflares isn't a mistake. The domain has been on Cloudflare via an upstream party since before the site moved to Cloudways. When Cloudways later offered a Cloudflare Enterprise add-on for its security stack, we kept both. We manage the Cloudways side. We don't own the upstream zone, which shapes what we can and can't do without a request going out. The trap is that both layers can cache HTML, and both can serve security challenges. If nobody writes down which layer does what, they fight. Our ownership split ended up like this: Layer Owns Upstream Cloudflare (third party) DNS, SSL, HTML edge cache, purge lifecycle Cloudways CF Enterprise add-on WAF, bot management, rate limiting, AI crawler blocking, ScrapeShield, Browser Integrity Check FlyingPress Origin page cache, Cloudflare integration pointed at the upstream zone, purge rules Cloudways "

2026-09-06 原文 →
AI 资讯

What actually happens in a database index (and why half of them do nothing)

Same query. Same table. Same million rows. One day it takes 4 seconds . The next day, 4 milliseconds . Nothing changed in the data. The only thing that changed was one line — you added an index . Four seconds to four milliseconds is a thousand times faster, from one line of SQL. But here's the part nobody tells you: half the indexes people add do nothing. The query stays slow, the writes get slower, and they can't figure out why. By the end of this you'll know what an index actually is — and the one rule that decides whether yours even gets used. Prefer to watch? Full walkthrough with the B-tree lookup animation: With no index: a full table scan You ask the database for one user by email. With no index, what does it do? It reads the first row. Not a match. The second row. Not a match. It keeps going — every single row — until it finds yours or runs out. A million rows, a million checks. SELECT * FROM users WHERE email = 'vlad@stack.dev' ; With no index, that WHERE line has only one way to run: look at all of them. The work grows with the table — ten times the rows, ten times the wait. That's a full table scan , and that's your four seconds. What an index actually is Most people picture an index as a copy of the table, or some kind of cache. It's neither. An index is a sorted map — just the column you search on, kept in order, with a pointer back to the full row. And the shape it's sorted into has a name: a B-tree (the default index in both Postgres and MySQL — technically a B+ tree). At the top, one node — the root . It splits into a few branches . Each branch splits again, down to the leaves , where the pointers to the rows actually live. Every node is sorted. The root doesn't hold your data — it holds signposts . Emails before "M"? Go left. "N" and after? Go right. Each step throws away half the tree, or more. You're never reading rows. You're following signs. The walk: three hops, not a million rows Watch what the lookup actually does: The root — one hop. A branc

2026-09-06 原文 →
AI 资讯

Fifty seconds for half a megabyte: the optimisation that fixed the constant, not the order

A cryptography library had a bottleneck no test could see : encrypting half a megabyte took fifty seconds. Every test passed. They had been passing for months. The cause is a trap that keeps recurring: a correct, well-documented optimisation that fixes the constant and not the order — and whose comment, precisely because it is well written, convinces the reader the problem is already solved. What the code did Quipu renders encrypted data as a sequence of symbols. To do that it converts the whole message into a single huge integer and repeatedly divides it to extract digits, the same way you would convert a base-10 number to base 2 by hand. The code did not divide one digit at a time. It carried a sensible optimisation: divide by the largest power of the base that fits in a machine word, extracting nine digits per pass instead of one. The comment explaining it opened by saying that doing it one at a time would be quadratic , and then described the improvement. All true. And the result was still quadratic: extracting nine digits per pass divides the work by nine; it does not change how the work grows. That sentence — "doing it this way would be quadratic" — reads in the past tense, as if it described the previous state. It described the current one. The measurement, which is the only thing that says so Size Time Factor per doubling 64 KiB 0.79 s — 128 KiB 3.16 s ×4.0 256 KiB 12.6 s ×4.0 512 KiB 50.7 s ×4.0 Exactly four, three times running. That is textbook quadratic: every time the input doubles, the time quadruples. Extrapolating, ten megabytes would have cost about five and a half hours . And here is the point: a correctness test sees none of this . A slow algorithm produces exactly the same bytes as a fast one. The suite stayed green, and would have stayed green forever. The fix is two hundred years old Nothing had to be invented. Divide-and-conquer radix conversion is a classical algorithm: instead of peeling digits off one end, you split the number in half — div

2026-09-06 原文 →
AI 资讯

Stress-Testing dbx: 20 MB on the Disk, 90 Database Paths to Exercise

A database client supporting 90+ engines sounds like a dependency-management problem disguised as a UI. My late-night question was simpler: how much of that complexity does t8y2/dbx carry before the first connection? The interesting claim is its small footprint—around 20 MB—combined with desktop, CLI, Docker, AI, and MCP Server modes. That is a much different architecture from shipping one heavy client per database vendor. The real test is not today’s +420 stars; it is startup latency, resident memory, and whether an unused adapter stays out of the hot path. Under the Hood The likely execution model is a shared core with database-specific drivers around it. The desktop interface, CLI, Docker image, and MCP endpoint become different front doors to the same connection and query layers. That design has two useful consequences: Connection handling and query behavior can stay consistent across interfaces. New database support does not require duplicating authentication, result formatting, or export logic. The edge case is driver loading. If all 90+ integrations initialize eagerly, startup and memory usage will grow quickly. Lazy loading is therefore more important than the headline database count. A Minimal Measurement Pass After downloading a release binary, I used this deliberately boring check: chmod +x ./dbx /usr/bin/time -v ./dbx --help 2>&1 \ | grep -E 'Elapsed|Maximum resident' For a source checkout, the first useful inspection is: git clone https://github.com/t8y2/dbx.git cd dbx find . -maxdepth 2 \( -name 'go.mod' -o -name 'Cargo.toml' -o -name 'Dockerfile' \) -print This avoids guessing the build system and immediately exposes whether the advertised modes are separate binaries, containers, or wrappers. Trade-offs I Would Watch A compact binary does not guarantee a compact running process. TLS libraries, database drivers, schema introspection, query history, and result grids can dominate memory after startup. MongoDB and Redis also do not fit neatly into a relat

2026-09-05 原文 →
AI 资讯

C# Concurrent Collections: A Practical Guide

Choosing a thread-safe collection is not simply a matter of replacing Dictionary<TKey, TValue> with ConcurrentDictionary<TKey, TValue> . The right choice depends on the operations you need to make atomic, the ratio of reads to writes, whether consumers must block, and whether the data can become immutable after construction. This guide explains how ordinary generic collections fail under concurrent access, then compares the main types in System.Collections.Concurrent with immutable and frozen collections. The goal is to give you enough mechanical detail to defend the choice in code review—not just a catalog of APIs. C# Concurrent Collections: Quick Selection Guide Requirement Start with Concurrent FIFO processing ConcurrentQueue<T> Concurrent LIFO processing ConcurrentStack<T> Concurrent key-based reads and updates ConcurrentDictionary<TKey, TValue> Unordered items produced and consumed by the same workers ConcurrentBag<T> Blocking or bounded producer-consumer flow BlockingCollection<T> Snapshot-style updates System.Collections.Immutable Build-once, read-many lookup data System.Collections.Frozen The table is a starting point, not a substitute for checking which compound operations must be atomic. The sections below explain the mechanics and tradeoffs behind each choice. Why C# Needs Thread-Safe Collections C# 1.0 introduced System.Collections , which includes ArrayList , Hashtable , Stack , Queue , and other collection classes. The problem is that these collections are not type-safe. They store elements as object , which can lead to type-mismatch exceptions and to performance costs from boxing and unboxing. C# 2.0 then introduced the System.Collections.Generic namespace and collection classes such as List<T> , Dictionary<TKey, TValue> , Stack<T> , and Queue<T> . These collections are type-safe, but not thread-safe. Type safety means that when you create a generic collection, you specify the type it stores as a generic type parameter. Reading an element then returns

2026-09-05 原文 →
AI 资讯

# How enabling cross-origin isolation silently broke our multi-threaded WASM image compressor

A production postmortem. We shipped browser-side image compression (Rust → WASM + WebGPU), turned on cross-origin isolation for speed, and watched every format crash with compression worker crashed . Here's the root cause and the fix. The setup We built an image compressor that runs 100% in the browser — Rust compiled to WASM for the codec work, WebGPU for the heavy ML passes (background removal, denoise, watermark). No upload, so users' pixels never leave the device. Privacy is the whole selling point. For the multi-threaded code paths we rely on shared memory + atomics , which in the browser requires crossOriginIsolated . So we served the document with: Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Opener-Policy: same-origin That gives us crossOriginIsolated === true , unlocks SharedArrayBuffer , and lets the *‑threaded WASM builds actually spawn workers. The build uses a nightly toolchain ( nightly-2025-06-01 + -Z build-std ) with: RUSTFLAGS = "--cfg=... +atomics,+bulk-memory --shared-memory --import-memory" and a custom rayon handle pool ( with_turbo_pool ) instead of build_global , so we control worker lifecycle and can abort/self-heal. The incident After flipping COEP to require-corp in production, every format started crashing with the same message: compression worker crashed Not one codec — JPG, PNG, WebP, AVIF, all of them. It was a P0: the core feature was dead for every user. What made it nasty: it only reproduced under real cross-origin isolation . Local dev without COEP was fine. Staging without the header was fine. So the bug hid until it hit production traffic. Root cause The *‑threaded WASM packages spin up nested rayon workers to parallelize the codec. Under COI + COEP require-corp , those nested workers get blocked by Cross-Origin-Resource-Policy / COEP — the spawned worker script is treated as a cross-origin response without the right CORP header, so the browser refuses it. No worker → the rayon pool never initializes → the compression c

2026-09-04 原文 →
AI 资讯

Stop Timing the Happy Path

The happy path was never the bottleneck. I was timing successes and shipping a miss. Production traffic is full of misses. Would you trust a bench that never fails? An AI rewrite loves the clean try. It wraps a lookup in except KeyError. It logs the miss "for observability." It looks professional. It is also a tiny furnace. Exceptions are not cheap branches. Log formatters are not free either. I learned that the loud way. Cheap generation makes the trap faster. A model will emit a polite miss path before you blink. Technical debt used to wait for a human. Now it arrives as a helpful patch tonight. The debt is not the lookup. The debt is a story about speed with no miss mix in the graph. I needed variants, not vibes. I used MonkeyCode's free model access and free server option to draft those variants. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The model proposes shapes. It does not know your miss rate. If the graph disagrees, the patch dies. The lab I actually rerun This is a pocket harness. It is not a production claim. Steal the file. Change the mix. Keep your own picture. I am not posting a trophy chart from a machine you cannot see. # miss_bench.py # Lab harness. Treat printed rows as local output, not a benchmark paper. from __future__ import annotations import logging import time import tracemalloc from typing import Callable logging . basicConfig ( level = logging . DEBUG ) log = logging . getLogger ( " hot " ) HITS = { f " user: { i } " : i for i in range ( 800 )} KEYS = [ f " user: { i } " for i in range ( 1000 )] # 20% misses on purpose def lookup_except ( key : str ) -> int | None : try : return HITS [ key ] except KeyError : log . debug ( " cache miss key=%s " , key ) return None def lookup_get_quiet ( key : str ) -> int | None : return HITS . get ( key ) def lookup_get_log ( key : str ) -> int | None : value = HITS . get ( key ) if value is None : log . debug ( " cache miss key=%s " , key ) return value def run_mix (

2026-09-04 原文 →
AI 资讯

How Does a Website Become Fast?

You open a website. A blank screen appears. You wait. Then finally, the page loads. But what actually happened during those few seconds? Why does one website feel almost instant while another feels painfully slow? It isn't just about writing “better code.” Website performance is the result of many things working together: DNS + networking + servers + HTML + CSS + JavaScript + images + caching + browser rendering And most performance problems come down to two simple questions: What is the browser waiting for? What is the browser doing unnecessarily? Let's break it down. What Actually Happens When You Open a Website? Suppose you enter: https://example.com Your browser has quite a journey ahead. A simplified version looks like this: URL ↓ DNS Lookup ↓ Connect to Server ↓ HTTP Request ↓ Receive Response ↓ Parse HTML ↓ Download CSS / JS / Images ↓ Build DOM + CSSOM ↓ Layout ↓ Paint ↓ Interactive Page Every step takes time. So the goal of performance optimization isn't simply: “Make the code faster.” It's: Reduce unnecessary waiting and unnecessary work. 1. Send Less Data Imagine your homepage downloads: HTML 250 KB CSS 400 KB JavaScript 4 MB Images 8 MB Fonts 2 MB That's a lot of data just to display a page. Now imagine: HTML 80 KB CSS 100 KB JavaScript 500 KB Images 1 MB Fonts 300 KB The browser has significantly less to download and process. This is why techniques such as: Compression Code splitting Lazy loading Responsive images Removing unused dependencies can have a huge impact. A simple rule: If the user doesn't need it yet, don't make them download it yet. 2. Images Can Be Your Biggest Bottleneck You can optimize your JavaScript perfectly... …and still have a slow website because of images. Consider a: 5 MB hero image That's potentially more expensive than many of your JavaScript files combined. Instead of sending a huge original image: <img src= "hero-original.jpg" /> serve an appropriately sized and compressed image. Modern formats such as: WebP AVIF can reduce

2026-09-03 原文 →
AI 资讯

Presentation: Instrumentation at Scale: Having Your Performance Cake and Eating It Too

Brian Martin discusses the real-world performance costs of metrics libraries and shares strategies for low-overhead, "fearless" instrumentation. Drawing from his work at IOP Systems, he explores atomic primitives, per-CPU sharding, lock-free histograms, and eBPF integration to help software architects and engineering leaders maintain full system visibility without sacrificing performance. By Brian Martin

2026-09-03 原文 →
AI 资讯

Cloudflare injects a beacon. My CSP said no.

Originally published on indiecore.net . I deployed, opened the console on the live site out of habit, and found this: Loading the script 'https://static.cloudflareinsights.com/beacon.min.js/v3d52…' violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'inline-speculation-rules'". The action has been blocked. I had not added that script. It is in no template, no build output, and no dependency. grep -c cloudflareinsights dist/index.html returns 0. It is not in the page you build Cloudflare Web Analytics has an automatic mode, on by default when a site is added, that injects beacon.min.js into HTML responses at the edge. Your origin never sees it. Your repository never contains it. It also does not inject for everything. I fetched the same URL with curl, then again with a full desktop browser User-Agent, and neither response carried the script. Only a real browser navigation gets it, which is why Lighthouse saw it and my terminal did not. That combination is worth sitting with for a second. The artefact exists in production, is absent from your source, and cannot be reproduced with the tool most of us reach for first when we want to see what a server actually returned. Nothing was tracked The CSP did its job. From the Lighthouse network trace: url : https://static.cloudflareinsights.com/beacon.min.js/v3d52… resourceType : Script statusCode : -1 transferSize : 0 Status −1 with zero bytes transferred means the request never started. The browser matched the URL against script-src , found no permitted source, and refused before opening a connection. No data left anyone's browser. So the console error is the sound of a guard working. It still costs something: a logged error drops the Best Practices category from 100 to 92, and a red line in the console trains you to ignore red lines in the console. The fix everyone reaches for is the wrong one here Search the error and the common answer is to add https://static.cloudflareinsights.com

2026-09-03 原文 →
AI 资讯

Building a multi-region routing system with Cloudflare Workers

We serve customers primarily in Australia, but we are now expanding to the USA. The timeline for launch is less than 2 months. This is now a race against time to design a multi-region routing system that fits all of our needs. Here is the story. Background Almost all of our customers were based in Oceania. We run our Kubernetes Cluster on GCP in Australia. Go microservices, federated GraphQL, gRPC services. 2 products - Tutoring and Schools. All designed for Australia. Then we expanded to the USA, which meant a new Kubernetes Cluster in US Central. The latency for serving US customers from Australia is an extra 200ms-300ms depending on network conditions - unacceptable. This would mean sharding the data by region, or does it? There are definitely ways to keep a unified dataset even across regions - though we did not need to do so. More on this later. What are the requirements If the only requirements were "Americans get served from America", we wouldn't be here discussing this, would we? Logged in users are served from their own region, wherever they happen to be in the world. Logged out users are routed geographically, as we have no other information to infer their actual region. Account Managers and Admins should be able to access both regions from one button, with a single account. Teaching materials opened via links from the Schools product must be shareable across both regions. Geography takes care of the logged out user, but nothing else. Using geography for a logged in user can be actively wrong. They might be travelling or simply using a VPN. Then comes the Admin; we have a lot of admin operations regarding curricula, which will be entirely separate for both clusters. Account Managers need to be able to see and modify information on both clusters. One admin should be able to access both clusters with a single account. We considered showing data of both clusters on one screen, but ruled it out as it may become too ambiguous or confusing, not worth the technic

2026-09-02 原文 →
AI 资讯

Qwen 3.6 vs 3.5: Same 37 tok/s on RTX 4070, +43% on Frontend Generation

The first number I saw on Qwen3.6-35B-A3B was 12 tok/s . I almost hit publish on "Qwen regressed at generation speed" and moved on. The 3.5 baseline on the same RTX 4070 was 34.6 tok/s. A new generation running at a third of the old one would have been a hell of a headline. It was also completely wrong. The culprit was not the model. Another process on the box was sitting on 9-11 GB of VRAM, so the layers that were supposed to live on the GPU were spilling to system RAM. The tell was that my sanity-check run of Qwen3.5 slowed down too. When two independent models degrade together, the model is not the variable. I killed the offending process, re-measured, and got numbers that told a completely different story. Model Generation speed tg128 (tok/s) Runs Qwen3.6-35B-A3B 38.76 ± 0.82 avg of 3 Qwen3.5-35B-A3B 36.7 ± 1.4 avg of 3 (range 34.9-38.6) Both models sit inside the ±1.5 tok/s band on the same RTX 4070. On the tokens-per-second axis, "the new generation" is not a story. Same architecture, same activated-parameter count (3B active out of 35B), same MoE routing pattern. The half-speed regression was a measurement bug, and it lived for about half a day before its own inconsistency killed it. The lesson I keep re-learning: when the number you got is dramatically convenient for your narrative, measure it again before you write anything. The moment I could sell 12 tok/s as a regression, I should have been suspicious. The version of me that ran the second test earned the version of me that got to keep his self-respect. So where did the generation move to? If speed did not change, does the 3.5-to-3.6 bump mean anything? It does. The move lives on a different axis. The official Qwen3.6-35B-A3B model card publishes benchmarks with a very lopsided shape: Benchmark Qwen3.5 Qwen3.6 Lift Terminal-Bench 2.0 40.5 51.5 +27% QwenWebBench (frontend generation) 978 1,397 +43% SWE-bench Pro 44.6 49.5 +11% LiveCodeBench v6 74.6 80.4 +8% SWE-bench Verified 70.0 73.4 +5% AIME26 91.0 92.7

2026-09-02 原文 →
AI 资讯

Why I Built an Image Converter That Never Touches a Server

The problem: every "free" image converter wants your files If you've ever needed to quickly convert a batch of photos to WebP or shrink a folder of PNGs before shipping them to production, you've probably run into the same annoyance I did: most " free online converters " require you to upload your files to a remote server first. That's fine for a random screenshot. It's not fine when the images are: Unreleased product shots under NDA Client assets you're not supposed to redistribute Personal photos you'd rather not hand to a third-party server you know nothing about So I started looking at what the browser can actually do on its own — and it turns out, more than most people assume. What the browser can already do Modern browsers ship with everything needed to decode, resize, re-encode, and compress images entirely client-side: + toBlob() / toDataURL() for re-encoding to JPG, PNG, or WebP The File API for drag-and-drop and batch uploads Web Workers to keep the UI thread responsive during batch conversion JSZip (or similar) to bundle multiple converted files into a single downloadable ZIP None of this requires a backend. No image ever has to leave the user's machine. Why this matters beyond privacy Besides the obvious privacy win, doing conversion in-browser has some nice side effects: No server costs that scale with usage. A traditional image-conversion API has to provision compute for every request. A client-side tool scales for free — the user's own CPU does the work. No upload/download round trip. For large batches, skipping the network entirely is often faster than uploading to a server and waiting for a processed file back. Works offline once loaded. A PWA-style client-side converter keeps working even with a flaky connection. The trade-offs It's not free lunch: Very large batches (hundreds of high-res images) can strain the main thread if you're not careful with Web Workers. WebP/AVIF encoder quality and speed vary by browser engine, so you can't guarantee byte

2026-09-02 原文 →