All services

Go alongside PHP

One path no longer carries the load. That is no reason to rebuild everything.

The usual proposal is a rewrite: everything in Go, in twelve months, with the same team that keeps operations running on the side. You know how that ends. There is another way. The one path that buckles under load is carved out and put next to your PHP as a Go service, connected over gRPC or HTTP, on the same database or behind a clear boundary. Everything else stays where it is. And before that comes the measurement: if the bottleneck sits in the database, a different language changes nothing about it, and then I tell you so before you commission anything.

Measure first, build afterStrangler fig instead of a rewritegRPC or HTTPFixed price 1,900 €
What you get
  • A measurement under real load showing where the time in the hot path goes
  • The answer to whether a Go service is the instrument here. It may be no
  • One carved-out path as a Go service, running in a branch of its own
  • The interface of the service, with a settled boundary to the data
  • Numbers before and after from the same load test, not from a feeling
  • The route to production in writing: switch-over, way back, effort
Scope & working together

One week, one result, at a fixed price. Putting it live is quoted by scope afterwards, never as an open bill.

Remote from Germany. Straight with me, no agency in between.

The starting point

The language is rarely the problem.

Almost every grown PHP system has a handful of places that behave differently from the rest: the endpoint the app calls on every start. The import that runs for four hours at night. The search that drags everything else down at peak load. The execution model of PHP is entirely adequate for the largest part of an application, but it sits badly with long-lived connections, with work that should run in parallel, and with calls that have to be answered in single-digit milliseconds.

When that becomes visible, a proposal is usually already on the table: rebuild it, this time in Go. That is a decision about twelve months in which nobody delivers anything else and the old system still has to be maintained. The narrower route is called strangler fig and is not a new idea: you carve out exactly that one path, put it alongside as a service of its own, redirect traffic step by step and leave everything else untouched. No cutover date, no big bang, and a way back at every point.

And then comes the half that appears in no proposal: very often this is not worth doing. The most common reason for a slow path is not the language but a query without a fitting index, an N+1 in the ORM, a database running at its limit, or a third-party service being waited on. In all four cases a Go service waits exactly as long as the PHP in front of it did, only with more moving parts. That is why the measurement comes before the build here. If it points at the database, I say so and we stop there.

Does any of this sound familiar?
  • A single endpoint drags the response time of the whole application down.
  • Servers get added at peak load because one path eats the CPU.
  • A nightly import runs for hours and blocks everything that comes after it.
  • Somebody has proposed a full rewrite in Go, and nobody knows how that is meant to end.
  • There is already a Go island a colleague built that nobody else touches.
  • Nobody can say which part of a request actually consumes the time.

Who this is for

For PHP teams with a measured bottleneck, not a rewrite ambition.

For CTOs and backend leads with a productive PHP application. One bounded path hits limits in load, concurrency or latency while the rest of the monolith remains useful.

01

The bottleneck is measured.

Profiles, load data or production metrics identify the path that limits the system.

02

PHP remains part of the solution.

Go takes only the workload where an additional language creates a measurable advantage.

03

The team must operate it.

Deployment, monitoring, interfaces and knowledge transfer matter as much as the Go code.

What I do

What gets built along the way

01

Measurement before the first line of code

k6OpenTelemetrypprof

A load test against the suspected path and a trace showing where the time goes: database, serialization, network and interpreter shown separately. The result is a breakdown in milliseconds and a sentence on whether a service of its own would change anything here. If that sentence comes out negative, it still goes in the report, and the package ends there.

02

The cut: one path, not one system

Strangler figOne pathBoundary

What gets carved out is the smallest piece that can stand on its own, with a clearly named way in and way out. Not a module, not a domain, not "the ordering process", but the one call that carries the load. The tighter the cut, the shorter the way back.

03

The Go service

GolangConcurrencyTests against the old behaviour

A service, not an ecosystem: one binary, few dependencies, concurrency where it earns its place, and a memory footprint you can predict. Plus tests that reproduce the existing path, so the replacement is verifiable rather than merely plausible. It is built in a branch of its own and measured there against real data.

04

The connection to PHP

gRPCProtocol buffersREST

gRPC with protocol buffers where the calls are frequent and a contract between both sides helps. That does assume a long-running PHP process, RoadRunner or FrankenPHP for instance, or calls that tolerate setting up the connection on every request: under classic PHP-FPM the process is short-lived, the gRPC channel does not survive the request, and reusing the HTTP/2 connection is precisely where the gain of gRPC sits. There plain REST with keep-alive is the more honest route, as it is wherever your team gets on better with it. Plus timeouts, retries and a fallback onto the old path, so that a service being down is not a system being down. That part belongs to the route to production, which is written down during this week.

05

Data and boundary

PostgreSQLMySQLRedis

Either the same data with clearly governed access, or a boundary of its own with a defined path for the data. Both are fine, the middle ground is not: two systems writing the same tables as each sees fit is the part that hurts later.

06

Switch-over, operations and the way back

Feature flagsAmazon ECSTerraform

Traffic is redirected behind a feature flag, a fraction first, then more, and the way back is a setting rather than a deployment. The service runs where you already run things, described as infrastructure in code, with the same metrics as the PHP beside it. This step is written down at the end of the week, with an effort estimate, and carried out afterwards as an offer of its own.

Do you have a path that buckles under load?

Send me the path and whatever numbers you have. You get an assessment of whether a service of its own would change anything here, before you commission anything.

How it runs

How this runs

Four steps within a week. After the second you know whether the third is worth it, and that answer is allowed to be no.

STEP 01

A 30 minute conversation

Which path, which load, which numbers you already have. Often a suspicion is on the table by the end of it that has nothing to do with the language. If I have it, I say it right there and not at the end of the week.

STEP 02

Measurement

A load test and tracing against the path, in a test environment with real data volumes or against a load profile from your production. Afterwards it is settled in milliseconds where the time is spent, split by database, serialization, network and interpreter. If the result points at the database or at a single query, it ends here and you keep the findings.

STEP 03

One path, carved out

The service, the interface and the boundary to the data, running in a branch of its own and measured against real data. Before and after on the same operation. Nothing in your production environment is touched along the way, and that is precisely why the fixed price holds.

STEP 04

The route to production

The connection, the switch-over behind a feature flag, the way back and the running of it, in writing and with an effort estimate. That is where the package ends. Whether you walk that route with your team or with me is yours to decide afterwards; from me you get an offer of its own for it, at a fixed price, before it starts.

The other side

What the current state costs you.

The total is not printed below, because I do not know it. The items I do know. A system that buckles under load does not cost once, it keeps costing every month. Do the maths yourself, then the number is yours.

  1. 01
    Servers against a single pathHow many instances run only so that one path survives peak load, and what do they cost per month?
  2. 02
    Drop-offs under loadHow many operations are abandoned during a peak, and what is one abandoned operation worth at your end?
  3. 03
    Night work and on-callHow often does somebody sit with a run that takes too long at night, and what does that time cost per year?
  4. 04
    Features that never arrive because of itWhat did not get built because the system would not have taken the extra load, and what would it have been worth to you?
  5. 05
    The rewrite you are considering insteadWhat figure is in the proposal for the rebuild, and by what factor does it exceed a single carved-out path?

Take the first item on this list and scale it to a year. If that comes to more than the week below costs, the question of whether to measure is already answered. It replaces none of these figures, but it makes the first of them evidenced, and in case of doubt it spares you the last item on this list.

Entry offer

Bottleneck measured, one path carved out. Fixed price 1,900 €

A fixed price, and it holds because the result is a measurement and a branch in your repository. Nothing in your production environment is touched along the way. Putting it live is quoted afterwards, at a fixed price as well, before it starts.

Not a prototype and not a rewrite but one week with a result: you know in milliseconds where the time in the hot path is spent, whether a Go service changes anything about it at all, and if it does, the carved-out path sits runnable in a branch of its own, measured against real data.

What you get

  • A measurement of the hot path under real load: where the time goes, in milliseconds rather than in guesses
  • The breakdown shown separately: database, serialization, network, interpreter. Only after that is the question of language worth discussing
  • The answer to whether a Go service is the instrument here. It may be no, and then that no is the result you paid for
  • If it is yes: one path carved out and running as a Go service, in a branch of its own, measured against real data, up to eight hours of implementation. Before and after on the same operation. If the path carries more than that, the cut is drawn tighter
  • The route to production in writing: connection, switch-over, way back, with an effort estimate

What you do not get

  • No go-live. The connection, the switch-over of traffic and the running of it are an offer of their own, quoted after the result, at a fixed price, before it starts
  • No rewrite and no change of language. What runs in PHP stays in PHP
  • No second path and no implementation beyond those eight hours. The measurement covers the whole picture, one path gets carved out
  • No optimization of your queries. If the measurement points there, it goes in the findings and is a project of its own
  • No database migration and no change to your data model
  • No changes to your interface and no work on the frontend
  • No ongoing support without an agreement of its own
  • Read access to the repository is enough, at least to the path in question. The branch is built alongside and handed over to you; if it is to sit in the repository itself, write access to a branch of its own is needed. Plus a reachable contact from the team
  • A test environment with real data volumes that may be measured against. A load profile from your production helps on top of that but does not replace the environment: having to build one first would take up the week
  • Permission to instrument in that environment, with OpenTelemetry, pprof, Xdebug or the equivalent. Without instrumentation there is no breakdown by database, serialization, network and interpreter, only a total
  • One path means one call or one run with a named way in and way out. Not a module, not a domain and not "the ordering process"
  • From around 200,000 lines or several applications the scope is agreed beforehand
  • If the measurement shows a Go service is not the instrument, the package ends there. The findings are then the result and are invoiced in full, because they spare you a project
  • If the measurement says yes but even the tightest possible cut does not fit into the week, the package ends with the measurement, a proposed cut and an offer for carving it out. The price stays the same
  • What is produced sits in your repository and belongs to you
  • Fixed price, regardless of how long I need
  • The week is counted from the day the access is in place
  • The amount is credited if you commission the go-live afterwards
  • Price net, plus VAT
  • You are committed to nothing further. Some teams walk the route to production themselves afterwards

The outcome

What is different afterwards

An answer instead of a guess

After the measurement it is settled in milliseconds where the time in the hot path is spent, split by database, serialization, network and interpreter. That answers the question of language rather than deferring it. If the answer is yes, the carved-out path sits alongside and the difference is a number in your before-and-after test, not a percentage promise on this page.

The existing system stays

Your PHP is not replaced, not rewritten and not frozen. Your team keeps working where it knows its way around and does not have to learn a second language on the side to ship a bug fix.

A way back that is not theoretical

The way back is part of the plan before the first traffic is redirected: as long as the feature flag stands and the old path runs, taking it back is a setting. That is the real difference to a rewrite. There the way back is gone after the third month, and nobody notices the moment it happens.

The second service is legwork

The cut, the measurement track and the connection to PHP are thought through once. The next path is then a decision about the cut and not a new project.

Technologies I use

Technologies I use

Languages
  • Golang
  • PHP 8
Connections
  • gRPC
  • Protocol buffers
  • REST
Data
  • PostgreSQL
  • MySQL
  • Redis
Messaging
  • Kafka
  • RabbitMQ
Runtime
  • Amazon ECS
  • AWS Lambda
  • Terraform
Measurement
  • k6
  • pprof
  • OpenTelemetry
Approach
  • Strangler fig
  • Feature flags

From practice

Also from real projects

Tim Rutte, Cloud & Software Architect

Who you are talking to

Directly with me as a freelancer. No agency in between.

I am Tim Rutte. More than 20 years in software development, many of them in grown PHP systems where I placed Go services alongside without stopping operations. You talk to the person who touches your code, from the first call to the handover.

  • 20+years in software development
  • 50+successful projects
  • 2003working remotely since then
More about me

Common questions

Common questions about Go alongside PHP

Do we have to move off PHP for this?

No, and that is the point. Your PHP stays the system where everyday work happens. Alongside it sits a service that does exactly one thing, namely the one the execution model of PHP suits badly. If that service disappears again tomorrow, the old path keeps running for as long as the feature flag stands.

Our system is slow. Would a Go service not help anyway?

Usually not, and that is the most common case. If the time is spent in a query without a fitting index, in an N+1 in the ORM, in a database at its limit or in waiting for a third-party service, then a Go service waits exactly as long as the PHP in front of it did, only with more moving parts. Without a measurement the question of language is a coin toss. That is why it comes last here and the measurement comes first.

How do you recognize a case where it is worth it?

When the time is spent in your own code rather than in waiting: many concurrent, long-lived connections; work that could run in parallel but does not in a request-per-process model; serialization or computation in volume; a call that has to be answered in single-digit milliseconds. You see that in the trace, not in the discussion.

Why Go and not simply faster PHP?

Because the two belong together, and the PHP route is usually the cheaper one. A properly configured OPcache, preloading and above all tidied-up queries get more out of most paths than a second service does. The JIT deliberately does not belong in that list: it helps where something is genuinely computed, and that is rarely the bottleneck, because typical web applications hang on input and output rather than on the CPU. On top of that, the execution model itself can be changed without changing the language: RoadRunner, FrankenPHP and Swoole keep the PHP process alive and address exactly the three weaknesses named above, namely long-lived connections, concurrency and calls that have to be answered in single-digit milliseconds. And anyone still on PHP 7 or older has the upgrade as the first lever anyway, which has a page of its own here. Go only earns its place where none of that is enough. If your case falls into one of the groups before that, I say so and you can spare yourself this page.

gRPC or HTTP, which do you use?

Your team has a say in that, and so does your runtime. gRPC with protocol buffers is the better choice where the calls are frequent, a binding contract between both sides helps, and a long-running process sits on the PHP side, RoadRunner or FrankenPHP for instance. Under classic PHP-FPM the process is short-lived, the channel does not survive the request, and that removes the actual gain of gRPC, namely the reused HTTP/2 connection. There, and wherever your people are more familiar with REST, plain REST with keep-alive is the more honest choice. A tool nobody understands in operation is not progress.

Do two systems then reach the same database?

Either that, with clearly governed access, or a boundary of its own with a defined path for the data. Both work. What does not work is the middle ground: two systems writing the same tables as each sees fit. Which of the two it becomes belongs in the cut and is settled before anything is built.

We have a grown PHP system. Is this the right page or is legacy modernization?

That is decided by the cut of your problem and not by the age of the code. This page is about exactly one path that buckles under load while the rest does its job. If the estate itself is the problem, because nobody wants to touch it any more, because there are no tests and every change hurts in three places, then legacy modernization is the better entry point, and then I say so. It has a page of its own here under the services.

We run a SaaS that buckles under load. This page or the one on SaaS platforms?

Both entry offers cost 1,900 euros fixed, take a week, deliver a branch and are evidenced by the same case study. The only question is what breaks at your end. If it is the tenancy model, the billing or the usage metering, meaning the cut of the platform itself, then the page on SaaS platforms is the right one. If it is not the tenancy model that breaks but a single path, while the rest of the platform carries, you are in the right place here.

Nobody here knows Go. Who runs it afterwards?

That question comes up before the go-live rather than after it, and that is the advantage of the small cut: after the week the service sits in a branch, the route into operation sits beside it in writing, and only then do you decide. It is also why it stays a service and not an ecosystem: one binary, few dependencies, the same metrics and the same delivery as the rest. If your team says it does not want to run it, you have discarded a branch rather than the twelfth month of a rewrite.

What does a Go service alongside PHP cost?

The measurement with the carved-out path costs 1,900 euros net and the result is there after a week. That is a fixed price rather than a starting price, and the reason sits in the cut: what is produced in that week is a measurement and a branch in your repository. Nothing in your production environment is touched, so the effort does not hang on other people's approvals, environments and dates either. Not included is the go-live, meaning the connection, the switch-over of traffic and the running of it. That is quoted after the result, at a fixed price as well, before it starts, because only then is its scope known. If the measurement shows a Go service is not the instrument, it ends there. What you have paid for is one week and an answer that spares you a twelve-month project, and that is the cheapest outcome of them all.

Other services

What else I help with.