How a Release Failed
Find the English version below
Hi ,
Nach 9 Stunden stand die Entscheidung fest: Wir müssen zurückrollen.
Der erste Release seit fast 1,5 Jahren war gescheitert.
Und dabei lief es so gut. Die minutiöse Vorbereitung der letzten Wochen hatte sich ausgezahlt. Alles lief nach Plan. Die notwendigen Änderungen an den VMs verliefen wie erwartet. Die große Migration war nach einer Stunde Laufzeit erfolgreich durchgelaufen. Die neuen Contentfiles konnten erfolgreich mit dem neuen System deployed werden. Die neue CD Pipeline lief zum ersten Mal auf Production – und war erfolgreich.
Es dauerte zwar, aber um 13:00 Uhr war die neue Version online.
Es lief zu gut.
Noch war die Maintenance-Page geschaltet.
Und wir begannen, das System zu testen. Automatisierte Tests wurden ausgeführt, und der neue Content wurde getestet. Smoketests wurden durchgeführt.
Ich schaute mit den Entwicklern auf die Logs. Und da war etwas Komisches:
WARNING: Possible connection leak detected.
Die Warnung war kein Einzelfall.
Sie war hundertfach in den Logs.
Haben wir ein Problem?
An sich sieht das System gut aus. Niemand berichtet von einem Fehler. Die Tests laufen durch. Niemand berichtet von Timeouts oder unerwartetem Verhalten. Alles ist ruhig.
Also ein Blick ins Monitoring:
Wenn Connection Leaks im Log zu sehen sind, dann sollten wir viele aktive Verbindungen im Pool haben. Hikari hat eine MBean registriert. Auf die können wir zugreifen. Aber... alles ist gut. Wir haben nie mehr als eine Verbindung offen.
Was ist hier also los?
Woher kommt der Leak?
Ein weiterer Blick ins Log enthüllt die Übeltäter: die 3rd-party API. Die Authentifizierung der API benötigt einen RPC-Call auf einen anderen Service. Und dieser blockiert. Und wir halten die Datenbankverbindung offen.
Das können wir verifizieren!
Was passiert, wenn wir hier Last erzeugen? Ein Entwickler hat noch einen Lasttest, der 100 Clients simulieren kann.
Und tatsächlich.
Kaum läuft er an: Das System wird unbenutzbar. Unser Connectionpool ist sofort erschöpft.
Verdammt. Das hätten wir vorhersehen können.
Was können wir machen?
Einen Umbau kriegen wir heute nicht mehr hin. Das Risiko ist zu groß.
Wie viele Requests pro Sekunde haben wir live auf der API? Ungefähr drei pro Sekunde. Was wäre, wenn wir den Connectionpool so groß machen, dass er das abdeckt? Der TCP-Timeout liegt bei 60 Sekunden. Also: 60 x 3 = 180. Sagen wir, wir erhöhen den Pool auf 500. Dann haben wir etwas Puffer.
Und wir würden uns Zeit erkaufen. Für den richtigen Fix.
Also los.
Konfiguration ändern. Deployen. Alles sieht gut aus. Das System ist da.
Nächster Lasttest.
Mist.
Die Anwendung ist wieder unbenutzbar. Wir haben nur 100 offene Verbindungen. Aber die Datenbank spielt nicht mit. 100% CPU-Auslastung.
Es ist 16 Uhr.
Fällt noch jemandem etwas ein? Wir könnten im Apache ein Rate-Limiting auf die API einstellen. Er dürfte aber nur einen parallelen Call zulassen.
Das ist unrealistisch. Es macht die API praktisch unbenutzbar.
Wir könnten die API abschalten. Das geht aber nicht. Der finanzielle Schaden wäre zu hoch.
Was, wenn wir die alte Version neben der neuen deployen?
Die API ist kompatibel. Es gab keine Änderungen. Wir könnten die API auf die alte Anwendung routen.
Vorstellbar, dass das funktioniert. Aber haben wir alles bedacht? Das haben wir nie getestet. Und unsere Infrastruktur ist dafür nicht ausgelegt. Das Risiko ist zu hoch.
Das wars.
So können wir nicht online gehen.
Es ist 16:30 Uhr.
Wir sind gescheitert.
Wir rollen zurück.
Nach 9 Stunden – haben wir die alte Version wieder am Laufen.
Dann wird der Whisky aufgemacht.
Sind wir gescheitert?
Ja.
Aber es war auch ein Erfolg.
Es war klar, dass es nach so langer Zeit ein Kraftakt wird. Ich hatte mit wesentlich mehr Problemen gerechnet, bis die Anwendung überhaupt wieder online ist.
Und das war kein Problem. Wir haben bewiesen, dass wir wissen, wie die Anwendung zu halten ist. Wir wissen, wie wir sie deployen. Und all unsere Änderungen aus den letzten Monaten waren erfolgreich.
Dass wir auf den letzten Metern einen solchen Fehler finden, ist ärgerlich. Doch es ist nicht die Zeit, im Selbstmitleid zu versinken. Wir werden den Fehler ausmerzen. Der Fix wird nicht schwer. Und dann probieren wir es wieder. Wir werden es schaffen. Wir werden den Fuß durch die Tür kriegen.
Und dann können wir nach vorne blicken. Dann können wir dafür sorgen, nie wieder in eine solche Lage zu kommen.
Ich freue mich drauf!
Rule the Backend,
~ Marcus
Hi ,
After 9 hours, the decision was clear: We had to roll back.
The first release in almost 1.5 years had failed.
And it was going so well. The meticulous preparation of the past weeks had paid off. Everything was going according to plan. The necessary changes to the VMs went as expected. The major migration was successfully completed after one hour of runtime. The new content files were successfully deployed with the new system. The new CD pipeline ran for the first time on production – and it was successful.
It took a while, but by 1:00 PM, the new version was online.
It was going too well.
The maintenance page was still switched on.
And we began to test the system. Automated tests were executed, and the new content was tested. Smoke tests were conducted.
I looked at the logs with the developers. And there was something strange:
WARNING: Possible connection leak detected.
The warning was not an isolated case.
It appeared hundreds of times in the logs.
Do we have a problem?
The system seemed fine on its own. No one reported an error. The tests were running through. No one reported timeouts or unexpected behavior. Everything was calm.
So, a look at the monitoring:
If connection leaks are visible in the log, then we should have many active connections in the pool. Hikari had registered an MBean. We could access that. But... everything was fine. We never had more than one connection open.
What's going on here?
Where is the leak coming from?
A further look at the log revealed the culprits: the 3rd-party API. The authentication of the API required an RPC call to another service. And it was blocking. And we were keeping the database connection open.
We can verify this!
What happens if we generate load here? A developer still had a load test that could simulate 100 clients.
And indeed.
As soon as it started: The system became unusable. Our connection pool was exhausted immediately.
Damn. We should have foreseen this.
What can we do?
We can't do a rebuild today. The risk is too great.
How many requests per second do we have live on the API? About three per second. What if we make the connection pool big enough to cover that? The TCP timeout is 60 seconds. So: 60 x 3 = 180. Let's say, we increase the pool to 500. That gives us some buffer.
And we would buy time. For the right fix.
So, let's go.
Change the configuration. Deploy. Everything looks good. The system is there.
Next load test.
Damn.
The application is unusable again. We only have 100 open connections. But the database is not cooperating. 100% CPU usage.
It's 4 PM.
Does anyone have another idea? We could set a rate limit on the API in Apache. But it should only allow one parallel call.
That's unrealistic. It would make the API practically unusable.
We could shut down the API. But that's not an option. The financial damage would be too high.
What if we deploy the old version alongside the new one?
The API is compatible. There were no changes. We could route the API to the old application.
It's conceivable that this works. But have we considered everything? We have never tested this. And our infrastructure is not designed for it. The risk is too high.
That's it.
We can't go live like this.
It's 4:30 PM.
We have failed.
We roll back.
After 9 hours – we had the old version running again.
Then the whisky is opened.
Have we failed?
Yes.
But it was also a success.
It was clear that it would be a challenge after such a long time. I expected many more problems until the application was back online.
And that was not a problem. We have proven that we know how to maintain the application. We know how to deploy it. And all our changes from the last few months were successful.
Finding such a mistake in the final stages is annoying. But now is not the time to wallow in self-pity. We will eradicate the error. The fix will not be hard. And then we'll try again. We will make it. We will get our foot through the door.
And then we can look forward. Then we can make sure to never end up in such a situation again.
I'm looking forward to it!
Rule the Backend,
~ Marcus