EDEvangelos Dimitriadis
  • Über mich
  • Blog
  • Leistungen
  • Projekte
  • OpenSource
  • Uses
  • Kontakt
← Zurück zum Blog

Go in Produktion: wann ein Service nicht in PHP gehört

ED
Evangelos Dimitriadis
22. Juni 2026
Go in Produktion: wann ein Service nicht in PHP gehört

Foto von Jens Lelie auf Unsplash

Eine Laravel-App, die seit Jahren zuverlässig läuft, bekommt eine neue Anforderung: ein Live-Dashboard, das tausende Browser gleichzeitig per WebSocket offen halten soll. Das erste Setup mit PHP-FPM fällt um, lange bevor die tausend erreicht sind, weil jeder Prozess eine Verbindung hält und der Speicher davonläuft. Das ist nicht Laravels Schuld. Es ist das falsche Werkzeug für genau diesen einen Job.

Ich liefere den Großteil meiner Arbeit in PHP und Laravel aus, und das soll so bleiben. Genau deshalb ist dieser Artikel kein Plädoyer für einen Rewrite und keine "Go ist besser"-Predigt. Es geht um die schmale Menge an Workloads, bei denen PHP gegen sein eigenes Laufzeitmodell kämpft, und warum Go dort oft die bessere Wahl ist. Die Liste ist kurz, und das ist Absicht.

Versionsstand ist Go 1.26, Stand Juni 2026.

Wofür PHP und Laravel die richtige Wahl sind

Fangen wir bei der Wahrheit an, die in solchen Artikeln gern untergeht: Für das meiste, was ein Mittelständler baut, ist Laravel die richtige Wahl, nicht ein Kompromiss. Request-Response-Web-Apps, CRUD, Business-Logik, Admin-Bereiche, Kundenportale. Das ist Laravels Heimat, und es ist verdammt gut darin.

Der Hebel ist das Ökosystem. Eloquent, Queues, Auth, Validation, das ganze Framework-Batterie-Paket ist da und durchdacht. Ein Feature, das in Laravel an einem Nachmittag steht, ist in Go oft Tage Handarbeit, weil man die Hälfte der Batterien selbst bauen muss. Diese Entwicklungsgeschwindigkeit ist kein Luxus, sie ist der Grund, warum kleine Teams überhaupt liefern können.

Den Großteil davon in Go neu zu bauen wäre teurer und langsamer, ohne Gewinn. Wer das vorhat, sollte zuerst lesen, was ein Rewrite wirklich kostet. Die interessante Frage ist nicht, ob man Laravel ersetzt, sondern wo daneben ein zweiter Stack seinen Platz hat.

Wo PHP gegen das Laufzeitmodell kämpft

PHP-FPM ist Prozess pro Request. Eine Anfrage kommt rein, ein Prozess bearbeitet sie, gibt die Antwort und ist wieder frei. Für Web-Apps ist dieses Modell ideal, weil es einfach und robust ist. Bei einer Handvoll Workloads wird genau dieses Modell zum Problem.

Viele gleichzeitige Verbindungen

WebSockets, Server-Sent Events, alles Real-Time. Hier bleibt die Verbindung offen, manchmal stundenlang, und tut die meiste Zeit nichts. Im Prozess-pro-Request-Modell bindet jede offene Verbindung einen Prozess und dessen Speicher. Bei tausend gleichzeitigen Verbindungen sind das tausend Prozesse, und der Server geht in die Knie. Octane mit Swoole ist PHPs eigene Antwort darauf und verschiebt die Grenze deutlich, aber es löst das grundsätzliche Modell-Problem nicht. Go hält dieselben tausend Verbindungen mit tausend Goroutinen, die zusammen weniger Speicher brauchen als eine Handvoll PHP-Prozesse.

Hochnebenläufiges IO-Fan-out

Ein Service, der tausende ausgehende Calls parallel abfeuert, etwa ein Aggregator, der viele APIs gleichzeitig abfragt und die Antworten zusammenführt. In PHP ist echte Parallelität dafür ein Kampf, mit Tricks über Process-Forking oder Async-Erweiterungen. In Go ist es das, wofür die Sprache gebaut wurde: ein paar Zeilen mit Goroutinen, und tausend Calls laufen gleichzeitig.

CPU-gebundene Verarbeitung

Bild- und Videoverarbeitung, großes Parsing, kryptografische Arbeit. PHP ist pro Kern langsam, und solange ein Worker rechnet, ist er blockiert. Ein Bildverarbeitungs-Job, der zehn Sekunden CPU frisst, hält den ganzen Worker zehn Sekunden lang. Go ist pro Kern deutlich schneller und verteilt die Arbeit ohne Verrenkungen über mehrere Kerne.

Binaries zum Verteilen

CLI-Tools, Agenten und Daemons, die auf fremden Maschinen laufen sollen, ohne dass dort eine PHP-Runtime installiert ist. Ein Go-Programm kompiliert zu einem einzigen statischen Binary, das man kopiert und startet. Kein Runtime-Setup, keine Abhängigkeiten, keine Versionskonflikte. Für ein Werkzeug, das ein Kunde auf seinem Server laufen lassen soll, ist das ein enormer Unterschied.

Der gemeinsame Nenner all dieser Fälle: Es sind keine Request-Response-Web-Apps. Genau da, wo Laravel stark ist, ist Go überflüssig. Und genau da, wo PHP gegen sein Modell kämpft, spielt Go seine Stärke aus.

Warum Go für diese Fälle

Goroutinen sind der Kern. Sie sind so billig, dass hunderttausende gleichzeitig machbar sind, wo Prozesse oder Betriebssystem-Threads längst aufgeben. Der Go-Scheduler verteilt sie über die verfügbaren Kerne, ohne dass man sich darum kümmern muss. Nebenläufigkeit, die in anderen Sprachen ein Projekt ist, ist in Go ein paar Zeilen:

package main
 
import (
	"context"
	"golang.org/x/sync/errgroup"
)
 
func fetchAll(ctx context.Context, urls []string) ([]Result, error) {
	results := make([]Result, len(urls))
	g, ctx := errgroup.WithContext(ctx)
	g.SetLimit(100) // maximal 100 gleichzeitig
 
	for i, url := range urls {
		i, url := i, url
		g.Go(func() error {
			r, err := fetch(ctx, url)
			if err != nil {
				return err
			}
			results[i] = r
			return nil
		})
	}
 
	if err := g.Wait(); err != nil {
		return nil, err
	}
	return results, nil
}

Tausend parallele Calls, sauber begrenzt, mit Fehlerbehandlung. Das ist der Code, für den man in PHP eine ganze Bibliothek und viel Vorsicht braucht.

Dazu kommt das Deployment-Modell. Go kompiliert zu einem einzelnen statischen Binary, das man in ein winziges Container-Image legt, oft wenige Megabyte statt hunderte. Kein Runtime-Setup, schneller Start, niedriger Speicherbedarf. Das passt zu vielen kleinen Fargate-Tasks und zu Scale-to-Zero-Mustern. Die Standardbibliothek bringt HTTP, Nebenläufigkeit und Encoding mit, ohne dass man ein Framework braucht.

Und Go ist eine reife Plattform, keine Wette. Version 1.26 vom Februar 2026 hat den Green-Tea-Garbage-Collector als Default, der je nach Programm 10 bis 40 Prozent weniger GC-Overhead bringt, dazu rund 30 Prozent weniger cgo-Overhead. Die Sprache wird seit über zehn Jahren behutsam weiterentwickelt, ohne ihre Einfachheit zu verlieren.

Der ehrliche Gegenpunkt

Go ist kein besseres PHP, es ist ein anderes Werkzeug. Für CRUD und Business-Logik ist es umständlicher: mehr Boilerplate, kein Eloquent, kein Validation- und Auth-Paket aus dem Karton. Was in Laravel ein Composer-Paket ist, ist in Go oft Eigenbau. Das Ökosystem für Business-Apps ist kleiner, weil Go aus der Infrastruktur-Welt kommt, nicht aus der Web-App-Welt.

Und es gibt die Team-Frage. Eine zweite Sprache, die niemand im Team beherrscht, ist ein Risiko, kein Vorteil. Wer Go einführt, braucht mindestens zwei Leute, die es können, sonst wird der eine Go-Service zum Bus-Faktor-eins-Problem.

Die Konsequenz ist einfach: Den Admin-Bereich, das Kundenportal, die Rechnungslogik baut man nicht in Go, nur weil ein Service nebenan in Go läuft. Die Sprache folgt dem Workload, nicht der Mode.

Polyglott ohne Wildwuchs

Ein zweiter Stack ist eine laufende Steuer. Zwei CI-Pipelines, zwei Hiring-Profile, zwei Betriebs-Runbooks, ständiger Kontextwechsel im Kopf der Entwickler. Diese Steuer zahlt man nicht einmal, sondern jeden Monat. Sie lohnt sich nur, wenn der Workload sie rechtfertigt.

Die Regel, die Wildwuchs verhindert: pro Service entscheiden, nicht pro Vorliebe. Ein Go-Service braucht einen konkreten Grund und eine klare Naht. Konkret heißt das, der Go-Service hat eine schmale, definierte Aufgabe, und Laravel bleibt das System of Record. Geteilt werden nur Verträge, nicht Code. Eine HTTP-API mit einem OpenAPI-Schema, eine gemeinsame Queue, oder dieselbe Datenbank nur über klar gezogene Grenzen. Was man vermeidet, ist die enge Kopplung des Go-Service an Laravels interne Modelle, denn dann hat man sich einen verteilten Monolithen gebaut, der die Nachteile beider Welten vereint.

Das eigentliche Anti-Ziel ist, Microservices nach Sprache aufzuteilen. Nicht jeder Service braucht eine eigene Sprache. Die meisten gehören zusammen in eine Laravel-Codebasis, und nur der eine Workload, der wirklich heraussticht, bekommt seinen eigenen Go-Service.

Konkrete Entscheidungs-Szenarien

Diese Tabelle ordnet reale Workloads der richtigen Sprache zu:

WorkloadWahlWarum
Admin-Bereich, CRUD, Business-LogikLaravelÖkosystem, Tempo, System of Record
WebSocket- oder SSE-Gateway, viele offene VerbindungenGobillige Nebenläufigkeit, niedriger Speicher
Aggregator mit tausenden parallelen API-CallsGoGoroutinen, IO-Fan-out
Bild- oder VideoverarbeitungGoCPU-gebunden, blockiert sonst den Worker
Geplanter Report, der nachts läuftLaravelQueue-Job, kein Grund für Go
CLI-Tool zur Verteilung an KundenGoEinzel-Binary, keine Runtime
API-Gateway oder Proxy im Hot PathGoLatenz und Durchsatz
Standard-Webhook-EmpfängerLaravelpasst ins bestehende System

Sobald viele Verbindungen, hohe Nebenläufigkeit, CPU-Last oder ein zu verteilendes Binary im Spiel sind, kippt die Waage zu Go. Bei allem anderen bleibt Laravel die naheliegende Wahl.

Wie beide auf AWS koexistieren

Der Go-Service läuft als eigener ECS-Fargate-Service, neben den Laravel-Services aus dem Fargate-Setup. Gleiche VPC, eigene Task-Definition, eigene Skalierung. Die Kommunikation läuft entweder über HTTP, intern hinter dem Load Balancer oder per Service Discovery, oder über eine gemeinsame Queue wie SQS oder Redis, je nachdem, wie eng die Kopplung sein soll. Ein WebSocket-Gateway spricht direkt mit den Browsern und meldet Ereignisse per Queue an Laravel. Ein Bildverarbeitungs-Service zieht Jobs aus derselben Queue, in die Laravel sie legt.

Das Container-Image ist der Teil, der Freude macht. Ein Multi-Stage-Build kompiliert das Binary und legt es in ein leeres Image:

# Build-Stage
FROM golang:1.26 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/gateway
 
# Finale Stage: leeres Image, nur das Binary
FROM scratch
COPY --from=build /app /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/app"]

Das Ergebnis ist oft wenige Megabyte groß statt der hunderte eines PHP-Images. Der Start ist nahezu sofort, was bei Scale-Out und Scale-to-Zero zählt. Der Deploy fügt sich in dieselbe Pipeline ein: Image nach ECR, rollendes Service-Update. Der Go-Service ist auf der Plattform-Ebene einfach ein weiterer Container, der die Container-Basis teilt.

Anti-Patterns

Eine funktionierende Laravel-App in Go neu schreiben. Weil Go schneller ist. Teuer, riskant, und der Gewinn ist fast immer kleiner als die Kosten. Wenn die App ein Performance-Problem hat, liegt es selten an PHP und fast immer an einer Query oder einer fehlenden Cache-Schicht.

Microservices nach Sprache schneiden. Künstliche Grenzen, mehr Betrieb, kein Mehrwert. Services schneidet man nach fachlichen Grenzen, nicht nach Programmiersprache.

Go für CRUD. Viel Boilerplate, langsame Entwicklung, kein Ökosystem-Hebel. Das ist Laravels Heimspiel.

Zwei Sprachen für ein Team, das kaum eine besetzen kann. Wer Mühe hat, genug PHP-Entwickler zu finden, verdoppelt das Problem nicht durch einen Go-Service. Das ist Risiko, das als Breite verkauft wird.

Verfrühte Polyglottie. Einen zweiten Stack einführen, bevor es einen Workload gibt, der ihn wirklich braucht. Der zweite Stack folgt dem Bedarf, nicht der Neugier.

Den Go-Service eng an Laravels Modelle koppeln. Statt an einen klaren Vertrag. So baut man sich einen verteilten Monolithen, der die Komplexität von Microservices mit der Kopplung eines Monolithen vereint.

Fazit

Die ehrliche Antwort auf "Go oder PHP" ist "beides, an der richtigen Stelle". Laravel bleibt der Default für das meiste, was ein Mittelständler baut, und das aus guten Gründen: Ökosystem, Tempo, ein bewährtes System of Record. Go übernimmt die schmale Menge an Workloads, bei denen PHP gegen sein Laufzeitmodell kämpft, also viele Verbindungen, hohe Nebenläufigkeit, CPU-Last und Binaries zum Verteilen.

Der Auslöser ist nie die Sprache an sich, sondern ein konkreter Workload. Wer einen zweiten Stack einführt, weil ein bestimmter Service in Laravel nicht mehr trägt, trifft eine gute Entscheidung. Wer es tut, weil Go auf Konferenzen gut klingt, kauft sich eine laufende Steuer ohne Gegenwert. Ein zweiter Stack rechnet sich nur mit einer klaren Naht und einem klaren Grund, und man entscheidet pro Service, nicht pro Vorliebe.


Stoßen Sie mit Ihrer PHP- oder Laravel-App an eine Grenze und überlegen, ob ein zweiter Stack die Antwort ist? Kontaktieren Sie mich für ein Architektur- und Stack-Fit-Review, das prüft, wo ein zweiter Stack wirklich Sinn ergibt und wo er nur Komplexität wäre.

Über michBlogProjekteKontaktImpressumDatenschutzerklärung

Made in Gerlingen, 2026