Why We Moved from SQLite to MySQL
SQLite got us to a working product fast. Here is the exact point where it stopped being the right choice, and what the migration actually cost.
Every product we run sits on roughly the same foundation. That was not the plan at the start — it is what happened after enough painful redeploys taught us which decisions were worth standardising.
None of that is exotic. That is the point. Every one of these has excellent documentation, a large community, and predictable failure modes. When something breaks at 2am, boring technology is a feature.
ChatCubie originally stored uploaded images on the server's local disk with multer. It worked perfectly — until the first redeploy, when the host rebuilt the directory and every uploaded file vanished.
The fix was moving to Cloudflare R2. The lesson was broader: on managed hosting, treat the filesystem as temporary. Anything that needs to outlive a deploy belongs in object storage or a database.
The first version used SQLite. It is genuinely excellent for getting moving. But once you have concurrent writers and a hosting setup where the process can be restarted underneath you, its assumptions stop matching reality.
Migrating to MySQL meant converting roughly two hundred database calls to async/await. Doing it earlier would have taken an afternoon. Doing it later took a week.
The economic calendar in Cubie FX had a bug where events showed at the wrong hour for some users. The cause was mixing server-local time with UTC in different code paths.
The rule that fixed it and has not caused a problem since: store UTC, convert at the moment of display, never in between.
The things that are cheap to do from day one and expensive to retrofit:
The last one is easy to miss. A markdown renderer that allows arbitrary URL schemes will happily render a javascript: link, which is a cross-site scripting hole hiding in a feature nobody thinks of as risky.
Not sharding or microservices. It looked like: fix the N+1 queries, add indexes to the columns you filter on, cache the responses that do not change, compress images before upload rather than after, and stop re-fetching data the client already has.
Most performance work is undoing something unnecessary, not adding something clever.