Zum Inhalt springen

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.

Umfang und Konditionen

Zusammenarbeit
Fester Umfang, schriftlich vereinbart, bevor die Arbeit beginnt. Kein Tagessatz gegen ein offenes Backlog.
Preis und Dauer
Beides wird je Projekt festgelegt, sobald der Umfang steht. Gemeinsam angeboten, bevor etwas gebaut wird.
Was wir von Ihnen brauchen
Eine Person, die entscheiden darf, und Zugang zu Ihrem Repository und Ticketsystem.
Nicht enthalten
Alles außerhalb des vereinbarten Umfangs. Es wird ein eigener Umfang statt eines Änderungsauftrags.
Kosten Dritter
Hosting, Lizenzen, API-Gebühren und SaaS-Abonnements schließen und zahlen Sie selbst.
Rechnungsstellung
Codefacture Yazılım A.Ş., Türkiye. EUR, USD oder GBP per Überweisung, ohne türkische Umsatzsteuer auf exportierte Leistungen.

Häufige Fragen

Was ist der größte praktische Unterschied zu MySQL?
Eine gescheiterte Migration lässt nichts zurück. PostgreSQL wendet Schemaänderungen in einer Transaktion an, also rollt eine Migration, die an ihrer vierten Anweisung scheitert, die ersten drei zurück, und die Datenbank ist genau wie vorher. Bei MySQL sind die ersten drei angewendet, die Migration gilt als nicht gelaufen, und jemand muss von Hand herausfinden, welche Hälfte passiert ist - meist an einem Freitag, meist in Produktion. Diese eine Eigenschaft ändert, wie zuversichtlich ein Team deployt.
Kann Eloquent ein Datenbank-Constraint umgehen?
Ein Datenbank-Constraint nicht, und genau darum liegt es dort. Model-Validierung, Observer und Events werden von einem Mass-Update umgangen, von einem Query-Builder-Aufruf, der das Model überspringt, und von jedem in Eile geschriebenen Console-Command. Ein Unique-Index, ein Fremdschlüssel oder ein Check-Constraint hält gegen all das, weil er unterhalb der Anwendung erzwungen wird. In einem System mit Geld oder Bestand ist das der Unterschied zwischen einem Fehler, der nicht passieren kann, und einem, der nur noch nicht passiert ist.
Ist JSONB ein Grund, kein Schema zu entwerfen?
Nein, und die Falle ist real genug, sie zu benennen. JSONB ist wirklich nützlich für Daten, deren Form Sie nicht kontrollieren - die Nutzlast eines Webhooks, Einstellungen pro Mandant, ein Formular, das der Kunde konfiguriert - und mit einem GIN-Index ist es ordentlich abfragbar statt eine nur beschreibbare Spalte. Was es nicht werden darf, ist der Ort, an den neue Felder wandern, weil eine Migration nach Aufwand aussah. Der Test ist einfach: wenn Sie irgendwann danach filtern, sortieren, joinen oder auswerten, ist es eine Spalte.
Brauchen wir eine separate Auswertungsdatenbank?
Meist nicht, und das ist eine Sache weniger, die zu synchronisieren und zu betreiben ist. Window Functions, Common Table Expressions und Lateral Joins lassen eine Abfrage in der Datenbank tun, was Anwendungen sonst dadurch tun, dass sie tausende Zeilen nach PHP holen und darüber laufen. Gegen eine Read-Replica gerichtet bleibt das vom transaktionalen Betrieb weg. Ein eigenes Warehouse verdient seinen Platz, wenn Auswertungen wirklich mit der Produktionslast konkurrieren oder mehrere Systeme umspannen - und das ist eine Messung, keine Annahme.
Sollen wir das statt Ihrer Seite zur Individualsoftware lesen oder zusätzlich?
Zusätzlich. Jene Seite behandelt, wie ein Bau zugeschnitten, geführt und übergeben wird; diese behandelt ausschließlich, was die Datenbankwahl ändert - denn wer ein Team für Laravel und PostgreSQL sucht, will diese Frage beantwortet haben und nicht die allgemeine. Am kaufmännischen Rahmen ändert sich nichts.
Anrufen+1 848 272 7583WhatsApp+90 850 308 5436E-Mailinfo@codefacture.comKontaktseite