Engineering blog

The first thing I open in a new system is the ugliest part of it. The service nobody volunteers for. The file with a comment from 2019 that still says “temporary”.

Greenfield is easy. Archaeology is the part I’m good at: reading a system and the business around it until I know why it became what it is, finding where it tears under load, rebuilding it piece by piece while the business keeps trading and never feels a thing.

Hands-on Lead Engineer and Architect. Back-end by trade: service boundaries, data flows, the things that have to hold at 3am. I make the calls and I own how they age. AI as leverage, not as a replacement for engineers.

I like complex systems. Which is exactly why I check that instinct at the door. The best thing I ever shipped was the version I talked myself down to.

A single server and a database is enough here

Ten years ago a system design conversation was short. A server, a monolith, Postgres, maybe a load balancer in front. For most products that was the entire architecture, and it held. Now the same conversation opens with API gateways and doesn’t stop: Kubernetes, service meshes, Kafka, Redis, three databases because each one is good at one thing, gRPC, GraphQL, Elasticsearch, Terraform, Prometheus, tracing, sharding, replication, autoscaling. The awkward part is that none of it is junk. Every one of those tools exists because someone hit a real wall: more users, more data, more teams, more regions, more nines of uptime. ...

September 1, 2026 · 1 min · Murat Useinov

Reading PHP 8.6 RFCs as constraints

Accepted, declined, discussion drama. That is how most posts about PHP 8.6 RFCs read, and the vote cycle is in full swing this month. I read RFCs for a different reason. Not “what do I get”, but “what will my current code look like next year”. One RFC is a feature. Ten RFCs are a direction. When several push the same way, toward stricter types, explicit declarations, deprecating some old freedom, the direction tells you which of your habits are becoming legacy while you still type them. Syntax sugar is the least interesting part. Deprecations are the most interesting, because every deprecation is a migration with a countdown attached. ...

August 17, 2026 · 2 min · Murat Useinov

Testing PostgreSQL 19 beta without guessing

A production-like dump, a separate PostgreSQL 19 beta instance, one evening plus machine time. That is the whole method, and it is the only thing a database beta is for: finding out where the new version changes the behavior of your application while the change is still a bug report and not your incident. Restore the dump first. Data size matters. A plan that is fine on a thousand rows goes bad on fifty million. ...

July 8, 2026 · 2 min · Murat Useinov

When version numbers stop talking

Major means breakage possible, minor means safe. For twenty years a MySQL version number carried that promise, and you could plan by reading it. With the move to calendar-style releases the number tells you one thing: when it was built. The promise moved elsewhere, and you have to know where to look. The signals now are the channel and the dates. Is this an LTS or an innovation release. When does support end. What do the compatibility notes actually say, because “no major version” does not mean “no behavior change”. Optimizer changes ship in any release. Your query plans do not read version numbers. ...

June 11, 2026 · 2 min · Murat Useinov

A worker is not a controller

All the logic inside execute(), exit code always zero, no lifecycle thinking at all. A controller without a request. I still see this console command in every second codebase. Symfony stopped being only an HTTP framework a long time ago, but habits are slower than releases, and with 8.1 giving console and workers first-class attention the excuse is gone. An HTTP request lives for milliseconds. The kernel builds services, handles, throws everything away. A worker lives for hours. Same container, very different lifecycle. Every service that quietly keeps state, an in-memory cache, an accumulating buffer, an entity manager full of tracked objects, is invisible in HTTP and becomes a memory leak in a worker. If your consumer needs a nightly restart by cron, this is where the night went. ...

May 25, 2026 · 2 min · Murat Useinov

MySQL 9.7 LTS is an operational choice

An SQL mode set in 2019 that nobody remembers why. An authentication plugin the driver happened to support. A charset and collation nobody chose. That is what a MySQL upgrade is made of. MySQL 9.7 LTS is out, so I am making the list again. An LTS release of a database is a date more than a feature list. The date until which somebody else worries about patches, and the date after which the worry is yours. For a working PHP project the question is not “what is new”. It is “what did we assume”. ...

April 10, 2026 · 2 min · Murat Useinov

An LLM call is just IO

One method, response back. Laravel 13 makes calling a model feel like calling a mailer, and the demo level is now twenty minutes of work. Which is exactly when architecture starts to matter, because everybody will ship the demo. An LLM call is IO. Slow IO, expensive IO, IO that sometimes fails and sometimes lies. We know how to handle IO like that. We forget, because the response text looks smart and the API looks native to the framework. ...

March 11, 2026 · 2 min · Murat Useinov

Dependency upgrades without a big bang

A composer.lock that had not changed in three years. A colleague showed it to me last week, not as a confession, as a normal fact about the project. Nobody decided this. Every single upgrade looked small and risky, so everybody postponed it. Three years of postponed small risks equals one migration project with a budget and a name. The alternative is dull and it works. Upgrades as normal operations, like backups. ...

February 27, 2026 · 2 min · Murat Useinov

PHP 8.5 one month later

Last week of December, low traffic, good test suite. That is when we moved one project to PHP 8.5. Not brave, convenient. A month later I can sort the release into two piles. Pile one is the demo features. Every release has them. They look great in a tweet-sized example, then you search the codebase and find two places to use them. Fine. Not why you upgrade. Pile two is the boring changes. Less boilerplate in places you touch every day. Types that say what a function really returns. Small things that make PHPStan complain earlier instead of production complaining later. This pile is why you upgrade, and you only see it after some weeks, never in the release notes. ...

January 19, 2026 · 2 min · Murat Useinov

From Kohana to PHP 8.5

A Kohana ORM, an N+1 on a catalog page, and a blog post about it. That was November 2014. It is December 2025, the code around me runs PHP 8.5, and I went back and read that first post. The surface changed completely. A 2014 controller was a fat method: it read the request, validated by hand, called the ORM, formatted the response, sometimes sent an email on the way out. Types lived in docblocks and in hope. Deploy was files over FTP and a prayer. Today the same feature is a typed handler with constructor injection, validation at the boundary, a use case object, a message on a queue for the email, a trace id through the whole thing. The engine checks what a comment used to promise. I do not miss the old way. ...

December 11, 2025 · 2 min · Murat Useinov