WebSockets change your operational model

Every client reconnects at once. That is what a deploy means now on one project of mine, since Reverb made WebSockets a first-party Laravel feature this spring and order updates go to the browser instead of polling. The code was the easy part. A classic PHP app is stateless between requests. An FPM worker takes a request, answers, forgets. Capacity planning is requests per second. A WebSocket server is the opposite animal: thousands of open connections that mostly do nothing, but each one holds memory and a file descriptor, and each one is state that dies with the process. Your quiet realtime feature has a thundering herd built in. Plan for reconnect storms, raise descriptor limits, and make the client reconnect with jitter, never on a fixed timer. ...

July 3, 2024 · 2 min · Murat Useinov

A faster sprintf will not fix your p99

Three times faster. That is the number in the benchmark posts about PHP 8.4, where simple sprintf() calls with basic placeholders get turned into plain string operations at compile time. The function call disappears. for ($i = 0; $i < 1000000; $i++) { $s = sprintf('%s-%d', $prefix, $i); } I like this work. I do not like how people read it. A microbenchmark measures one function in a loop, alone, with a warm cache and nothing else to do. Now profile a real request from the same application. In every backend I have profiled the picture has the same shape: most of the wall time is waiting. SQL queries, Redis round trips, an HTTP call to some partner API. The CPU spent inside PHP is a modest slice, and sprintf inside that slice is a rounding error. Three times faster rounding error is still a rounding error. ...

June 18, 2024 · 2 min · Murat Useinov

Symfony 7.1 and the value of boring releases

composer update, the changelog over coffee, two deprecation notices fixed, done before lunch. That was Symfony 7.1 on one project this morning. Nothing in the release will change your architecture, and I mean this as a compliment. There are nice small things. An attribute that maps an uploaded file straight into a controller argument, one more piece of request plumbing gone. A new experimental component for type introspection that libraries will quietly benefit from. A layer of deprecations preparing the next major. That is the whole show. ...

May 31, 2024 · 2 min · Murat Useinov

MySQL 8.4 gives us a place to stand

Half the industry is sitting on MySQL 5.7 right now, unsupported since last autumn, and calling it stability. MySQL 8.4 came out today, and the important word in the announcement is not a feature. It is LTS. First long-term release of the new model: innovation releases every quarter for the brave, an LTS every couple of years for production, bugfixes only after that. People underestimate how much pain the old model caused. 8.0 was a rolling target. A “minor” 8.0.x could change optimizer behavior or deprecate something you used. Staying current meant re-testing, staying behind meant missing security fixes. ...

April 30, 2024 · 2 min · Murat Useinov

Laravel 11 removes files, not decisions

The diff of a Laravel 11 upgrade on one mid-size API this week is mostly deletions. Almost empty app/, configuration in one fluent bootstrap file, the slim skeleton I wrote about in January now real. It feels great. Now look at what survived untouched. The use case class that wraps an order state change in a transaction. The listener that must fire only after commit, because it queues an email about a row that must exist. The cache invalidation that follows every write to a heavily read table. The decision that validation lives in a request class and business rules one layer deeper. None of this is skeleton. None of it got smaller. ...

March 25, 2024 · 2 min · Murat Useinov

One trace through HTTP, queue and database

A request creates a job. The job calls an external API and writes to the database. Something in this chain is slow, and the logs tell four disconnected stories. Grepping a request id across services is our folklore. OpenTelemetry finally makes the boring standard version practical in PHP. The core is small. One trace id for the whole causal chain, a span id for every operation, and one rule: pass the context along. Over HTTP it is a single header: ...

February 8, 2024 · 2 min · Murat Useinov

The shrinking Laravel skeleton

No app/Http/Kernel.php. That is the first thing you notice in the Laravel 11 preview, and the first thing every comment thread asks: where did my middleware go. The skeleton is the loudest change this quarter, louder than any feature. An app/ directory that is almost empty, no folder of middleware stubs, a pile of service providers collapsed into one, config files trimmed. The middleware did not go anywhere. It moved into the framework. The mechanism exists exactly as before, only the file does not, and bootstrap/app.php gets a small fluent API to reconfigure the stack when you actually need to. ...

January 6, 2024 · 2 min · Murat Useinov

AI assistants do not know your production

A DTO with a dozen fields, a framework migration, a slow query incident. Three tasks from the last few months, all done with Copilot and ChatGPT open. Here is where the line runs for me. Task one, boilerplate. The DTO, its validation rules, a test with fixtures. The assistant writes this faster than I type, and the result needs only a quick read. Same for converting an array-shaped legacy structure into typed classes. This is honest time saved, maybe an hour a day. The code is the kind where being generic is correct. ...

December 23, 2023 · 2 min · Murat Useinov

PHP 8.3 and Symfony 7 in one month

public const int TIMEOUT_SECONDS = 30; One week old, and the one thing I waited for from PHP 8.3. Busy November. 8.3 released last week, and yesterday Symfony shipped 6.4 LTS and 7.0 on the same day. Time to plan, not to jump. Typed class constants are a small thing, but constants were the last untyped island in a class, and a child class could silently redefine one to a different type. Also json_validate(), which checks JSON syntax without building the whole tree in memory. Everyone had a helper that did json_decode and threw the result away. Now the helper can retire. ...

November 30, 2023 · 2 min · Murat Useinov

FrankenPHP: the server absorbs PHP

One binary, my code, a Caddyfile. That is the whole deployment of a small internal service I moved to FrankenPHP in staging this week. The classic PHP deployment is a couple: Nginx in front, PHP-FPM behind, FastCGI between them. Two configs, two processes to supervise, one socket to misconfigure. It works, we all know it by heart, and knowing it by heart is the only reason we call it simple. ...

October 27, 2023 · 2 min · Murat Useinov