Individualsoftware-Entwicklung
Individualsoftware mit Laravel und PostgreSQL
Was PostgreSQL einem Laravel-Bau gibt, das MySQL nicht gibt - eine Migration, die beim Scheitern zurückrollt, Garantien, die Eloquent nicht umgehen kann, und Auswertungen ohne zweite Datenbank.
Laravel läuft auf beiden, PostgreSQL zu wählen ist also eine Entscheidung und kein Standard - und es lohnt, genau zu sagen, was sie bringt, denn die verfügbaren Vergleiche handeln von Benchmarks und fast keiner davon, was an einem Donnerstagnachmittag während eines Deployments tatsächlich passiert.
Eine gescheiterte Laravel-Migration rollt zurück
Das ist der Unterschied, der das Verhalten eines Teams ändert, und selten der, den jemand erwähnt.
PostgreSQL wendet Schemaänderungen transaktional an. Eine Migration mit sechs Anweisungen, die an der vierten scheitert, lässt die Datenbank genau so, wie sie war. Nichts halb, nichts zu entwirren, und die Migration kann korrigiert und erneut ausgeführt werden.
MySQL nicht. Die ersten drei Anweisungen sind angewendet, die Migration gilt als nicht gelaufen, und jemand muss nun von Hand feststellen, welche Hälfte davon existiert. In Produktion, unter Zeitdruck, mit halb fertigem Deployment.
Die Folge ist kulturell statt technisch. Wo eine gescheiterte Migration rücknehmbar ist, werden Migrationen so geschrieben, wie sie sein sollten - klein, konkret, geprüft. Wo eine gescheiterte Migration ein Vorfall ist, werden sie gebündelt, aufgeschoben und am Ende von Hand angewendet. So driften Schemata aus der Versionskontrolle, und das ist der Zustand, den wir in geerbten Projekten am häufigsten finden. Dazu passend: die Migration, die die Seite abschoss.
Constraints, die Eloquent nicht umgehen kann
Eine im Model erzwungene Regel hält für Code, der durch das Model geht.
Sie hält nicht für ein Mass-Update. Sie hält nicht für einen Query-Builder-Aufruf, der direkt in die Tabelle schreibt, und den hat jede Codebasis irgendwo. Sie hält nicht für das Console-Command, das jemand um neun Uhr abends geschrieben hat, um ein Datenproblem zu beheben, und nicht für den Import, der einmal im Quartal läuft und nie geprüft wurde.
Unterhalb der Anwendung erzwungen, hält es für jedes davon. Ein Unique-Index entscheidet zwei gleichzeitige Anfragen auf denselben Datensatz, was Vorher-Prüfen nie kann - Prüfung und Insert sind getrennte Anweisungen, und die Lücke dazwischen ist, wo das Duplikat entsteht. Ein Fremdschlüssel macht eine verwaiste Zeile unmöglich statt unwahrscheinlich. Ein Check-Constraint hält eine Menge in der Zeile positiv, nicht bloß in dem Formular, das zuletzt validiert hat.
PostgreSQL legt zwei dazu, die praktisch zählen. Ein Exclusion-Constraint weist eine Buchung ab, deren Zeitraum sich mit einer bestehenden für dieselbe Ressource überschneidet, und schließt damit eine Race Condition, die in PHP wirklich schwer zu schließen ist. Und ein partieller Index deckt nur die Zeilen, die es wert sind, sodass eine Abfrage über offene Aufträge nicht die Kosten von fünf Jahren abgeschlossener trägt.
JSONB, und wo die Spalte anfangen sollte
Manche Daten haben kein Schema zu entwerfen. Eine Nutzlast von einem Anbieter, der sie ohne Ankündigung ändert. Einstellungen pro Mandant. Ein Formular, das der Kunde selbst baut.
PostgreSQL behandelt das ordentlich statt widerwillig - JSONB wird kompakt gespeichert, mit echten Operatoren abgefragt, mit GIN indexiert und sauber auf ein Eloquent-Attribut gecastet. Eine vernünftige Entwurfsentscheidung, kein Rückzug.
Der Verfall ist allmählich und immer dieselbe Geschichte. Einen Schlüssel in JSON hinzuzufügen braucht keine Migration und zieht kein Review an, also wandern die Felder dorthin. Im zweiten Jahr kann niemand eine Frage zu den Daten beantworten, ohne PHP zu lesen, und ein Feld wieder in eine Spalte zu ziehen heißt, eine Migration zu schreiben, die drei Jahre uneinheitlich geformter Zeilen parst.
Der Test, der das verhindert: wenn Sie irgendwann danach filtern, sortieren, joinen oder auswerten, ist es eine Spalte. Alles andere darf in JSONB leben und sollte es.
Auswertungen ohne zweite Datenbank
Sehr viele Laravel-Anwendungen holen tausende Zeilen nach PHP und laufen darüber, um eine Zahl zu erzeugen, weil die Abfrage, die es in SQL täte, unbekannt aussah.
Window Functions machen laufende Summen, Ranglisten und Periodenvergleiche zu einer einzigen Abfrage. Common Table Expressions machen eine mehrstufige Berechnung lesbar statt verschachtelt. Lateral Joins behandeln die Top-drei-pro-Kunde-Form, die sonst zur N+1-Schleife wird. Jedes davon ersetzt eine Seite Anwendungscode durch eine Abfrage, die die Datenbank in einem Durchgang ausführt.
Auf eine Read-Replica gerichtet bleiben diese Auswertungen ganz vom transaktionalen Betrieb weg. Ein eigenes Warehouse ist eine echte Antwort, wenn Auswertungen wirklich mit der Produktion konkurrieren oder mehrere Systeme umspannen - aber das sollte einer Messung folgen und nicht vorausgehen, und die meisten Anwendungen erreichen es später als erwartet. Datenbank- und Eloquent-Engineering ist, wo diese Arbeit sitzt.
Was sich in Eloquent tatsächlich ändert
Ehrlich zur Reibung, denn es gibt welche.
Zeichenkettenvergleiche sind case-sensitiv, also kann ein where auf eine Mail
oder eine Referenz, das auf MySQL funktionierte, aufhören zu treffen. LIKE ist
case-sensitiv und ILIKE ist die Form, die es nicht ist - und das ist nicht
portabel, also braucht eine für eine Datenbank geschriebene Abfrage auf der
anderen Aufmerksamkeit.
Vergleiche sind streng typisiert. Eine Zeichenkette dort zu übergeben, wo die Spalte ein Integer ist, löst einen Fehler aus statt still zu konvertieren - das ist das bessere Verhalten und es wird in Code auffallen, der sich auf die Konvertierung verlassen hat.
Auto-Increment-Schlüssel nutzen Sequenzen, was beim Seeding mit expliziten Kennungen und beim Kopieren von Zeilen zwischen Umgebungen zählt. Und alles als rohes SQL Geschriebene ist jetzt datenbankspezifisch, was ein Argument dafür ist, rohes SQL an wenigen benannten Stellen zu halten statt verteilt über Models.
Keines davon ist ein Hindernis. Alle sind billiger in Woche eins zu kennen als in einer Testsuite zu entdecken, die lokal durchläuft und auf Staging scheitert.
Die Form der Arbeit mit Laravel und PostgreSQL, und wo das Schema darin liegt
In der ersten Phase, absichtlich, neben dem Ablauf, um den sie gebaut ist - nicht in einem Entwurfsdokument davor und nicht in einem Review danach.
Vier Entscheidungen lohnen die Nennung, weil sie bei Aufschub zu Migrationen über jede verbundene Tabelle werden. Die Menge der Constraints und was jeder unmöglich machen soll. Die Grenze zwischen Spalte und JSONB. Wie sich eine Schemaänderung gegen eine Tabelle verhält, die irgendwann vierzig Millionen Zeilen hält - das entscheidet, ob ein Index nebenläufig erzeugt wird oder mit einem Lock, den niemand eingeplant hat. Und ob Auswertungen von einer Replica lesen, denn das nachträglich einzuziehen heißt, jede Abfrage daraufhin zu prüfen, welche Verbindung sie benutzte.
Der Rest des Projekts ist strukturiert wie auf der Seite zur Individualsoftware - Phasen, jede schriftlich vereinbart, jede endend mit etwas Ausgelieferten.
Was Sie zu einem Laravel- und PostgreSQL-Projekt bekommen
Über die Anwendung und ihr Repository hinaus die Dinge, die dieser Wahl eigentümlich sind.
Die Migrationen, die die Historie der Datenbank sind und der Grund, warum eine Schemaänderung wie jeder andere Code geprüft werden kann. Eine Notiz zum Datenmodell, die die Form erklärt statt sie zu wiederholen. Ein Indexverzeichnis, das sagt, für welche Abfrage jeder Index existiert, damit ein künftiger Entwickler, der einen entfernt, weiß, was langsamer wird. Die Constraints kommentiert mit dem, was sie verhindern. Und die Auswertungsabfragen aufgeschrieben und erklärt - eine Window Function, die niemand versteht, wird beim ersten Änderungsbedarf durch eine PHP-Schleife ersetzt.
Wann MySQL die bessere Antwort ist
Wenn Ihr Hosting, Ihr Betriebsteam oder die Richtlinie Ihres Kunden um MySQL gebaut ist, ist das eine echte Rahmenbedingung und kein technischer Mangel. Ein Team, das eine Datenbank wiederherstellen, überwachen und tunen kann, ist mehr wert als ein geringfügig besserer Funktionsumfang in einer, die niemand betrieben hat.
Wenn eine bestehende Anwendung schon auf MySQL läuft und gesund ist, lohnt eine Migration für die oben genannten Eigenschaften allein die Störung selten. Tun Sie es, wenn es einen anderen Grund gibt, die Datenschicht anzufassen.
Und wenn die Anwendung klein ist, überwiegend liest und das Schema feststeht, wird der Unterschied zwischen beiden nicht das sein, was über den Projekterfolg entscheidet.
