PHP/Symfony

A closer look at continuous migration

An application migration strategy

Translated from French with AI assistance. Read the original

When I joined Y-Proximité, after picking up the many warning signs described in this series of posts, I felt the need to start migrating our multi-tenant (1 DB = X customers), white-label “CMS” application built for site networks. Today it hosts about 2,000 sites/landing pages.

You can read in some places that a rewrite is evil. I decided to tell the story of our migration (politically less abrasive than a rewrite), which took 2 years. Here is my logbook of the migration to “Yprox v2”, covering several topics.

Continuous migration: why and how

The goal of continuous migration is to make a new version of your application available while keeping the existing one running, so you get a continuous transition from version X to version Y.

It lowers the risks of the migration: instead of one big “switchover” button from version X to version Y, you turn the migration on for specific features, which leaves more room for improvements based on user feedback. It also sends reassuring signals to your management: the migration’s progress is visible in “real time” (as opposed to waiting 2 years, in my case, before seeing the product “run” in production).

On this subject, I recommend François Zaninotto’s excellent talk (in French) on continuously migrating a Symfony2 application.

In our case, we set up a partial continuous migration. Only our internal users benefited from it, as it only covered our application’s back office. The front office is the famous “big red button” of the final production release. The goal was to continuously migrate everything we could (“could” meaning a good benefit/time ratio).

Organizing branches & production

The first step was creating a develop-2 branch to hold all the changes. We then set up 2 different URLs to reach the 2 back offices in real time. The database is shared between these 2 versions.

Migration strategies

The developer in charge of the migration was free to go about it however they liked. I do, however, recommend removing every trace of the old version’s code from the new version’s branch. It keeps devs from copying/pasting/moving code without reading/understanding it (and therefore missing an opportunity to refactor it).

The mantra was then “take inspiration from it, refactor it, and failing that, keep it as is”.

In our team, this removal of the legacy code happened almost 1 year after the migration started, to give us time to rebuild the architectural foundations and get v2 to a minimum working state.

We came across two kinds of situations during the continuous migration:

1/ The coexistence strategy

Module X is available on both v1 and v2, because the changes are compatible with each other.

2/ The replacement strategy

Module X is migrated to v2 and contains changes that are incompatible with the existing version (known as BC breaks). In our case, that usually means the database structure. The new entity responsible for that schema change is then “backported” to v1 (remember, the database is shared between the 2 versions). The SQL migration is run in production, and module X is no longer available on v1: it redirects to v2.

How do you apply your old version’s patches to the new one?

At first it’s fairly easy with a few rebases/merges, then, depending on how far the branches have drifted apart, it gets harder and harder.

Scenario: you patch a bug in the old version that inevitably affects the new one too (since the branch was created from v1).

Result: very quickly, that rebase (to keep a clean history) turns into mental torture:

(For the masochistic devs who still want to go down this road, you can also try: Git rerere)

And then that chasm of structural changes (new namespaces, reorganized code) made it impossible to rebase/merge from the v1 branch at all. A few months later, the branch broke up with master for good. </3

Looking back, the real “pain in the ass” is that whenever you fix a bug on v1, you need to know whether it affects v2, then patch methods that sometimes have nothing in common across versions except their business purpose.

Continuous deployment

Every pull request could be tested in a dedicated environment thanks to the Pull Request Builder.

The develop-2 branch had a continuous deployment process (Travis runs an Ansible application deployment playbook through a Tower server). At our peak, we reached 32 deployments a day (watch out, Amazon…).

Read on: III. Migration: Taking stock

  • Infra 14
  • PHP/Symfony 4
  • Dev tools 3
  • AI 1

Article 9 of 22All articles