gear 1 gear 2 gear 3 gear 4
DE | EN
clock-work-coder

🔒 Wie ich einen festhĂ€ngenden Rails-Migrations-Lock in MySQL debuggt und gelöst habe

KĂŒrzlich bin ich auf ein frustrierendes Problem gestoßen: Rails-Migrationen ließen sich nicht mehr ausfĂŒhren, weil der Advisory Lock, der parallele Migrationen verhindern soll, festhing. Hier zeige ich, wie ich das Problem untersucht und den Lock manuell ĂŒber die Rails-Konsole gelöst habe.


🧠 Das Problem

Rails verwendet bei Migrationen MySQL Advisory Locks, um zu verhindern, dass mehrere Prozesse gleichzeitig Migrationen ausfĂŒhren. Dabei kommen GET_LOCK(lock_id, timeout) und RELEASE_LOCK(lock_id) zum Einsatz.

In meinem Fall schlugen jedoch sowohl das Erhalten als auch das Freigeben des Locks fehl:

ActiveRecord::Base.connection.get_advisory_lock(lock_id)
# => false

ActiveRecord::Base.connection.release_advisory_lock(lock_id)
# => false

Das bedeutete:

  • Ich selbst hielt den Lock nicht.
  • Jemand anderes (oder eine verwaiste Session) hielt ihn noch.
  • Rails-Migrationen waren dadurch blockiert.

🔍 Lock untersuchen

1. Die Lock-ID herausfinden, die Rails verwendet

Rails 6.1 generiert die Lock-ID fĂŒr MySQL wie folgt:

lock_id = ActiveRecord::Migrator::MIGRATOR_SALT * 
  Zlib.crc32(ActiveRecord::Base.connection.current_database)

Damit ist die Lock-ID eindeutig je nach Datenbanknamen. Hinweis: Die genaue Berechnungsweise kann sich je nach Rails-Version Ă€ndern — am besten im Quellcode der eigenen Version nachsehen.

2. Herausfinden, wer den Lock hÀlt

ActiveRecord::Base.connection.select_value("SELECT IS_USED_LOCK(#{lock_id})")
# => 1562  (Beispiel: Thread-ID)

Dieser Befehl gibt die MySQL Thread-ID der Session zurĂŒck, die aktuell den Lock hĂ€lt.


🛠 Den festhĂ€ngenden Lock freigeben

Um den Lock manuell freizugeben, habe ich die Session, die ihn hielt, beendet:

ActiveRecord::Base.connection.execute("KILL 1562")

Danach habe ich geprĂŒft, ob der Lock wirklich weg ist:

ActiveRecord::Base.connection.select_value("SELECT IS_USED_LOCK(#{lock_id})")
# => nil

Jetzt konnte ich den Lock wieder ganz normal anfordern und freigeben:

ActiveRecord::Base.connection.get_advisory_lock(lock_id)
# => true

ActiveRecord::Base.connection.release_advisory_lock(lock_id)
# => true

✅ VollstĂ€ndige IRB-Session

lock_id = ActiveRecord::Migrator::MIGRATOR_SALT * 
  Zlib.crc32(ActiveRecord::Base.connection.current_database)

ActiveRecord::Base.connection.get_advisory_lock(lock_id)
# => false

ActiveRecord::Base.connection.release_advisory_lock(lock_id)
# => false

ActiveRecord::Base.connection.select_value("SELECT IS_USED_LOCK(#{lock_id})")
# => 1562

ActiveRecord::Base.connection.execute("KILL 1562")
# => nil

ActiveRecord::Base.connection.select_value("SELECT IS_USED_LOCK(#{lock_id})")
# => nil

ActiveRecord::Base.connection.get_advisory_lock(lock_id)
# => true

ActiveRecord::Base.connection.release_advisory_lock(lock_id)
# => true

🧠 Fazit

  • Rails nutzt MySQL Advisory Locks zur Steuerung von Migrationen.
  • Diese Locks sind verbindungsspezifisch und unsichtbar, solange man sie nicht explizit abfragt.
  • Mit IS_USED_LOCK() und KILL kann man festhĂ€ngende Locks identifizieren und auflösen.
  • Vorsicht beim Beenden von DB-Sessions — prĂŒfe genau, ob es sicher ist.

Wie man seinen eigenen URL-Shortener baut

Auf Homeday Medium hat mein Kollege Mohamed Barakat ein Einblick gegeben ĂŒber den URL-Shorterner Service den wir Ende 2020 im Rahmen unserer Product Engineering Week (PEW) umgesetzt haben. In der nĂ€chsten PEW (MĂ€rz 2021) möchte ich die Chance nutzen detaillierter auf Implementierungsdetails einzugehen.

RĂŒckblick 24pullrequests 2015

Wie in meinem Blogpost entwickler-weihnachtskalender-2015 beschrieben, hatte ich mir wieder vorgenommen der Open-Source-Gemeinschaft etwas zurĂŒckzugeben. Hier ist nun ein kleiner Überblick ĂŒber die Ergebnisse dieses Unterfangens. Vorweg: Nein, 24 PRs habe ich leider nicht geschafft, es bleibt also weiterhin eine Herausforderung fĂŒr 2016. ^^

Die Projekte

(Ich verzichte auf eine nĂ€here Beschreibung, dafĂŒr haben die Projekte ja eine README.md)

Endergebnis

12 PRs von denen 11 bereits gemerged wurden (siehe auch hier). Das Jahr 2016 starte ich jedenfalls mit dem Vorsatz nicht erst wieder im Dezember an Open-Source zu arbeiten, sondern das vielleicht doch etwas kontinuierlicher zu tun.

Der etwas andere Weihnachtskalender

Auf 24pullrequests.com kann man auch dieses Jahr wieder, anstatt jeden Tag ein TĂŒrchen zu öffnen, der Open-Source-Gemeinschaft kleine Geschenke machen. Indem man tĂ€glich bis zu Weihnachten etwas zu einem oder mehreren Projekten in Form eines Pull-Requests (PR) beitrĂ€gt und so etwas zurĂŒck gibt.

Warum?

Viele Entwickler arbeiten schließlich Tag fĂŒr Tag mit Frameworks und Tools, die der Gemeinschaft frei, offen und sogar verbesserbar(!) zur VerfĂŒgung gestellt werden. Außerdem lernt man wahnsinnig viel, wenn man sich in die Gefilde neuer, fremder Projekte wagt.

Strategie

Nachdem ich mit meiner Strategie vom letzten Jahr, immer zu einem unterschiedlichen Projekt etwas beitragen zu wollen, gescheitert bin (es wurden schlußendlich nur ~ 6 PRs). Werde ich dieses Jahr einen anderen Ansatz ausprobieren; Den Fokus auf eine kleine gegrenzte Auswahl von Projekten setzen und dort dafĂŒr aber mehrere BeitrĂ€ge einreichen. Jedes Projekt kostet nĂ€mlich anfĂ€nglich einen recht beachtlichen Aufwand der Einarbeitung und diese ĂŒberhaupt respektive deren Tests zum Laufen zu bringen. (2-3 Stunden bevor man am eigentlichen Beitrag arbeiten kann und das tĂ€glich war einfach zu viel fĂŒr mich nach der Arbeit ^^)

Happy Coding!

Meinen Fortschritt seht ihr hier. Mal schauen ob ich meinem Ziel dieses Jahr nÀher komme.