You don’t have to get rid of your Doctrine migrations…
OK, let’s think for a moment. When you deploy a Docker container on Fargate, you’ll inevitably have several instances of your container. Even if you have low traffic, you’ll still have Symfony Messenger running.
And so, what happens if you put a doctrine:migrations:migrate in the entrypoint?
You break everything. Both instances will try to modify the database schema at the same time, and it’s going to be a disaster: the fastest one will have already applied the changes, and the other will try to modify what’s already been modified.
Goodbye, database schema…
Except… we’re smarter than that.
What if we wrapped the Symfony migration command inside another command that checks we’re absolutely sure we’re the only instance handling the migration?
How? With Symfony’s Locker and a Redis server…
But that’s not enough.
You need to ensure that Symfony’s Lock Component is actually using Redis as the backend (and not the local filesystem — otherwise it’s pointless).
And you also need to ensure that communication with the Redis server is OK…
With that alone, we’re already eliminating most race conditions.
A race condition happens when two actions that should never occur at the same time end up happening at the exact same moment due to unlucky timing.
So, how can we be completely sure to eliminate race conditions (for obsessive people like me)?
By waiting a certain amount of time before trying to acquire the lock and run the migration…
But how do we do that? True randomness doesn’t exist in computing. Ideally, AWS would inject an environment variable into our container with the instance number… except they don’t.
BUT they do inject an environment variable with something unique…
They inject the ECS_CONTAINER_METADATA_URI_V4 environment variable.
So what if we used it as a seed to guide our rand() between one and five seconds?
Since the seed data is totally different, there’s no way both rand() calls end up producing the same number of milliseconds.