Luther Monson, Staff Software Engineer at SUSE

Luther Monson

Go by way of PHP · Containers · VMs · Windows · Kubernetes

Rolling Your Own KV Store Instead of Embedding Redis

Why ePHPm has its own Redis-compatible cache built in instead of running Redis. It also covers a bug that hid in it for four months and how it compares to real Redis on the same machine.

11-Minute Read

Bar chart of redis-benchmark GET and SET throughput against Redis 7.4, ePHPm v0.5.2 and ePHPm v0.2.0

Every PHP deployment I’ve run eventually grew a Redis. Sessions, the WordPress object cache, Laravel’s cache and locks, rate limiting. ePHPm is supposed to be one binary that replaces nginx + php-fpm + Redis, so it needed a cache in the box. I didn’t want to write one. This post covers why I did anyway and how it’s built. It also covers the bug that sat in its eviction code from March until last month. Then I compare it against a stock Redis on the same machine with…

Faking MySQL's Quirks Well Enough to Fool WordPress

Passing WordPress's install tests didn't mean litewire behaved like MySQL. This is the second round of fixes, for the small MySQL behaviors that WordPress and Laravel quietly depend on.

10-Minute Read

A table of MySQL behaviors WordPress relies on, with what litewire answered before and after the August fixes

Last month I wrote about running SQLite behind a MySQL socket with litewire. I ended on “passing an install suite is not the same thing as behaving like MySQL”. That turned out to be accurate. On August 10th a few things happened at once:

Requests That End Wrong: A 200 That Was Really a Crash

Three ePHPm bugs lived in how an embedded PHP request starts and ends. A bailout returned 200, $_POST came up empty and a SIGTERM corrupted the heap.

10-Minute Read

Diagram of a bare zend_bailout being absorbed by php_execute_script's own zend_try instead of reaching ePHPm's guard

The scariest response a server can send isn’t a 500. It’s a 200 that should have been a 500. A crash is loud. The pod restarts, an alert fires, somebody looks. A truncated page with a success status code is quiet. Your CDN caches it and the health check calls the box healthy. A client parses half a document as if it were the whole thing.

Database Connection Pooling with Smart Reset

PHP opens a new MySQL connection on every request, but ePHPm reuses them and cleans up only the ones that need it. Here's how that works, two bugs it had and how much load it takes off MySQL.

10-Minute Read

PHP request connecting to the ePHPm MySQL proxy, which lends it a pooled backend connection and resets it only if the session was dirtied

PHP’s share-nothing model is one of its best properties. It has one expensive side effect: a request can’t keep a database connection for the next request. php-fpm gives you PDO::ATTR_PERSISTENT and mysqli’s p: hosts. But those are per-worker-process. PDO’s keep whatever session state the last request left behind. mysqli at least resets the session with COM_CHANGE_USER on reuse. And almost nobody turns them on. So in practice every PHP request does a TCP connect, a MySQL…

Clustered SQLite in ePHPm, Part 1: Replication by Sidecar

ePHPm first made SQLite highly available by embedding sqld as a child process. This is how that worked and the list of things that went wrong with it.

9-Minute Read

Two ePHPm nodes, each running an embedded sqld child process, connected by gossip, a KV data plane and sqld gRPC

Most PHP sites I have run in the last twenty years did not need a MySQL server. They needed a database, and MySQL was the one every framework spoke. With ePHPm I have been trying to get rid of that server for the common case. The piece that makes it possible is litewire. PHP connects with plain pdo_mysql. litewire speaks the MySQL wire protocol, and the queries land in a SQLite file. WordPress and Laravel don’t know the difference. No mysqld, no child processes, just ephpm and a .db file.

Gossip-Based Clustering for PHP Apps

How ePHPm servers find each other, share data and handle a server joining or leaving. It also measures how fast a write reaches the other servers and covers a bug I found while measuring it.

11-Minute Read

Three ePHPm nodes: small values replicated to every node over gossip, a large value stored on its owner and one replica over a TCP data plane

Most PHP developers never touch clustering primitives. That’s by design. PHP is share-nothing. Each request starts from zero, and anything shared lives somewhere else. Sessions go in Redis. Cache goes in Redis or Memcached. Locks go in Redis. When you run two app servers, the “cluster” is really two stateless boxes pointed at the same Redis. That works until Redis is the thing you’re trying not to run.

Building a Custom SAPI - How PHP Actually Talks to the Outside World

php-fpm, mod_php and the PHP command line all plug into PHP the same way. Here's how ePHPm does it, and the shortcut that broke WordPress.

9-Minute Read

Table mapping PHP operations like echo, header(), $_POST, $_COOKIE and $_SERVER to SAPI callbacks and to the Rust side of ePHPm

If you’ve only ever run PHP behind nginx, you’ve probably never had to think about how echo reaches a socket. When you embed PHP like ePHPm does, it’s the first thing you have to answer. The answer is a SAPI. There is very little written about implementing one. Most of what I know came from reading sapi/cli, sapi/cgi and sapi/embed in php-src. Then I found out which parts I’d misread. This is the write-up I wanted back in March.

SQLite as a Production Database via Protocol Translation

litewire speaks the MySQL wire protocol on one side and SQLite on the other. That lets WordPress and Laravel run against a .db file without a code change.

10-Minute Read

A PHP app connecting over the MySQL wire protocol on port 3306 to litewire, which translates the SQL and runs it against a SQLite file

Most of the PHP sites I’d put on ePHPm don’t need a MySQL server. A brochure WordPress, an internal Laravel tool, a preview environment that lives for an afternoon. What they do need is something that looks like MySQL, because mysqli and pdo_mysql are the only database drivers their code has ever been pointed at. I mentioned this in the ePHPm pragmatism post as a one-paragraph “because”. This is the longer version, now that WordPress 7.0 and Laravel 13 both install and…

The Pragmatism of ePHPm - Every Decision Has a "Because" Behind It

ePHPm hit v0.1.0 this week. It isn't a rewrite-it-in-Rust project. Here's what I built, what I borrowed, what I sent upstream, and what I deliberately haven't built.

7-Minute Read

ePHPm in the center with six boxes: Rust and C wrapper, KV store, chitchat gossip, static-php-cli upstream fixes, litewire, and a dashed box of features not built yet

ePHPm tagged v0.1.0 this week, three months after the first commit. Before the feature list grows any further I want to write down how decisions get made on it. This is mostly for myself. The easiest way to ruin a project like this is to let it turn into a checklist of things other projects have.

PHP Signal Safety - The Init Ordering Bug Nobody Warns You About

Embedding PHP in a multithreaded server means PHP's timeout signals can land on any thread. How ePHPm got that under control.

11-Minute Read

Thread timeline: the main thread installs PHP's signal handlers during php_embed_init, a PHP thread later arms ITIMER_PROF, and SIGPROF is delivered to a tokio worker that has no PHP state

Last week I wrote about why ePHPm embeds PHP from Rust and promised the signal bug its own post. Here it is. It’s the kind of bug where the fix is about ten lines and the explanation is the whole post. Nothing in PHP’s embed documentation (there’s barely any) tells you that initializing PHP has process-wide side effects. Those side effects assume PHP is the only thing in the process.

Recent Posts

Categories

About

Luther is a 20+ year software veteran specializing in backend APIs and architecting products. Employed at SUSE as the Team Lead for the Containers on Windows Team working on Rancher + Kubernetes