Case study - payments

How GolfNext stopped losing checkouts at peak payment load

At low traffic, payments worked. At peak, exactly when the most money was moving, they did not. Mavka traced it to one quiet backend decision: payment waiting lived inside worker threads.

Client: GolfNextGolf venue tech & paymentsDK - NO - SESpring - AWS SQS

Key results

~10x

peak payment load

BeforeCheckouts started failing at ~100 payments/min.

After~1,000 payments/min sustained on the same flow.

0

new servers added

BeforeMore hardware looked like the obvious answer.

AfterThe same backend capacity worked after the wait moved out of threads.

Minutes

deploys and rollback

BeforeLong polling drains slowed instance replacement.

AfterReleases stopped waiting on idle payment checks.


The problem

Around a hundred payments a minute, checkout started failing outright.

Customers scanned a QR code, paid with MobilePay, Vipps, or Swish, and waited. Under normal traffic it worked. Around 100 payments a minute, new payments started failing.

It looked like a flaky payment provider or an undersized backend. It was neither.

01

Scan QR code

Customer starts checkout at the venue

02

Create payment order

GolfNext opens a payment session

03

Confirm in mobile app

MobilePay, Vipps, or Swish handles approval

04

Wait for confirmation - the break point

The backend used to hold a worker thread here


Why it was hard to see

The waiting was happening in the wrong place.

The backend used blocking polling: after creating a payment, it held a worker thread and repeatedly checked the provider until the customer confirmed or the flow timed out.

When enough payments were pending at once, the thread pool filled with payments doing almost nothing. New payments had no thread to run on, so they failed even though the backend was barely working.


What Mavka changed

Two steps: contain the damage, then remove the cause.

Step 01 - contain

Isolate payments onto their own thread pool

A payment spike could no longer starve the rest of the backend. The pool still filled with idle waits, but the damage stopped spreading.

Step 02 - cure

Replace polling with an SQS delayed-message loop

Instead of holding a thread while waiting, the service queues a short check-payment-status task with a 3-5s delay and releases the thread.

One thread · one payment window (~2 min)

Before, one thread served one payment. Now it serves about ten.

Picture a worker thread as a call-center operator. Blocking polling kept an operator on the line for the whole payment — mostly listening to hold music, unable to take another. Moving the wait into an SQS queue frees the operator between checks, so the same one clears roughly ten payments in that window.

Before · blocking polling

One operator, stuck on one call

Payment #1holds the operator the whole ~2 min — mostly on hold music

A new payment gets a busy line. = the held thread can’t accept it

1 operator = 1 payment / ~2 min

The payment isn’t even done yet, but the operator is already tied up — so the next customer can’t get through.

After · SQS callback

One operator, a queue that calls back

☎️ Callback queue · rings back in 3–5sSQS delayed message · any free operator answers

Same window, about ten payments cleared. = thread released between checks

1 operator ≈ 10 payments / ~2 min

Nobody holds the line for silence — the queue rings back when it’s time to act.

Before

A held thread can’t accept a new payment

1 thread = 1 payment
After

One thread handles several payments

1 thread ≈ 10 payments

"Mavka did not just throw servers at it. They found the real cause and rebuilt the flow. The same setup now handles roughly ten times the load."

- Peter Selmer, CEO, GolfNext


If this sounds familiar

You do not need to know the root cause yet.

These problems usually arrive as messy product symptoms first: failed checkouts, long waits, confused customers, and peak periods the system cannot quite absorb.

  1. 01Payments sometimes disappear, fail, or need manual follow-up.
  2. 02Customers wait too long after confirming a payment in the app.
  3. 03Checkout works on a normal day, but breaks when venues are busiest.
  4. 04Support hears "I paid, but nothing happened" more often than it should.
  5. 05The team keeps adding capacity, but the peak-load problem comes back.

Fix a revenue-critical system

We fix the engineering problems that block revenue: payment reliability, backend scalability, cloud cost, release safety, and senior architecture work that turns unclear failures into operable systems.

Book an audit