← All case studies MySQL → PostgreSQL

CodePen left MySQL 5.6 for PostgreSQL instead of upgrading in place

CodePen Radio 351, 20 January 2022. Alex Vazquez and Chris Coyier describe a move to PostgreSQL 13.3 with about a minute of read-only mode. AWS DMS kept MySQL and PostgreSQL in sync. Elasticsearch was a reason, not a finished removal.

CodePen Radio 351 went up on 20 January 2022. Chris Coyier hosts. Alex Vazquez, the CTO, led the work. The show notes say the main relational database was already on PostgreSQL when they recorded, after a couple of intense weeks plus earlier planning.

CodePen had been on MySQL 5.6, which Alex dates to 2013. A jump to MySQL 8 looked like the same size of project as a move to PostgreSQL 13.3. They picked PostgreSQL. The team was nine people, with no dedicated database admin. Alex calls the principle tech minimalism. Fewer data stores, so the same people can go deeper on the editor.

The MySQL → PostgreSQL playbook is the type map, the SQL port, and the rehearsal. This episode is one company’s cutover, told as a conversation. I am using the transcript where it is more specific than the show notes.

Why PostgreSQL, beyond a version bump

MySQL 5.6 locked the Pens table when they added a column. They worked around that with a side table for columns they needed to touch. After the move, adding or dropping a column was an ordinary migration, and they dropped that side table. Alex thinks a current MySQL or MariaDB would have unlocked the table too. The extra they wanted from PostgreSQL was one store that could also cover jobs they were doing elsewhere.

They sent every save to Elasticsearch, then searched there. PostgreSQL text search was good enough for how they searched, and it would remove a second server, backup, and permission set. Analytics was another candidate. Alex had been interested in ClickHouse, then argued for keeping analytics in PostgreSQL because the questions were simple and a second store makes a deletion request harder to prove. Queues were on SQS, which PostgreSQL can also do. File storage was S3 for Pro uploads. PostgreSQL large objects were something they were exploring, not something the episode says they turned on.

The show notes list search, analytics, queues, and static assets as consolidation. In the conversation, those are the reasons to prefer PostgreSQL over MySQL 8. The finished work they describe is the relational database. They do not say Elasticsearch, SQS, or S3 were already gone.

They had used PostgreSQL before. Most writes sat behind Rails or GORM, so a lot of CRUD SQL changed with the adapter. Hand-written SQL still had to be ported.

How they rehearsed

Local Docker was the first filter. They moved a small copy from MySQL 5.6 to PostgreSQL 13.3 with pgloader and asked whether the app still behaved. Alex is explicit that pgloader was for that local copy. They did not use it for production.

Staging was a separate AWS account, built with Pulumi, with the same instance types as production. He loaded a snapshot of real data onto a PostgreSQL server of the size they meant to run. He moved that real data set 10 to 15 times before production, because size changes the timing. His downtime budget was 30 minutes. He wanted less.

The production cutover

AWS Database Migration Service read MySQL, loaded PostgreSQL, and kept copying new writes while MySQL stayed the live database. A full load took about 12 hours. After that, the copy stayed caught up. The Pens table was the slow one, about 30 to 45 seconds behind.

They did not write dual-write code in the app. Alex did not want an untested second write path. The sync was one way, MySQL to PostgreSQL.

A flag at the single save chokepoint rejected writes when read-only was on. Reads still served. Signups and saves stopped. He flipped the flag on a server, as a config value, so he would not wait on a deploy. The show notes call the result near-zero downtime. In the conversation Chris pins the read-only window at about a minute.

Parallel web servers were already pointed at PostgreSQL and hidden from traffic. Once read-only was on, he switched Cloudflare at the edge. He says Cloudflare’s internal switch has a 30 to 60 second delay. He watched the new servers take traffic, with a path back to MySQL still open.

The point of no return is removing read-only on the PostgreSQL servers. New Pens would exist only there. He considered a reverse sync and skipped it. The plan was to roll forward, with snapshots. They had moved smaller read-mostly services onto PostgreSQL before the main flip, so those could tolerate the lag.

He wanted this done before he and Dee went on parental leave. Dee shared the move, the checks, and Go code that compared the result. Rachel reviewed. Chris and Marie handled the user warnings.

What broke

One setting in Database Migration Service asked, in language Alex misread, whether text columns should have a size limit. He thought the note was about binary columns. Some values were wrong until they fixed them from the old data. They skipped outside consultants because of the calendar, and he treats that as the cause of the small mistakes.

Custom SQL kept showing up later. Marie’s dashboard queries needed PostgreSQL syntax. A query for the top 100 Pens of the year, written years earlier, needed a port. Alex’s figure is that they had about 99% of the query changes in before the flip, and the rest were small enough that they would not lose anyone’s content.

He does not publish a row count, a Pens-table size, or a hosting-bill delta. Average response time is not in this episode. The gain he repeats is schema change on the big table, and docs that match the database they run.

What I would copy

Price a MySQL 8 upgrade and a PostgreSQL move as the same project if you are still on 5.6 and the table locks. CodePen’s tie-breaker was the other stores they hoped to fold in later. Write down which of those folds are in this project, and which are a wish. The episode mixes them.

Rehearse on a real snapshot until the clock is boring. Ten to fifteen loads is what made a one-minute read-only window believable. pgloader was their laptop tool. Production was Database Migration Service plus a flag at the one place the app saves.

Know the one-way door. After read-only comes off, a MySQL rollback loses the new rows unless you built the reverse copy. They chose not to.

The MySQL → PostgreSQL playbook is the type and SQL checklist behind the 99% he claims. Use the episode for the read-only minute and for the text-column setting he got wrong.