Get In Touch
[email protected]
Back

Rebuilding the Booking Engine for Scale

How we rebuilt Mammutmarsch’s starting group system twice — and made peak registration days boring again

Part of our six-year partnership with Mammutmarsch →

There’s a specific kind of pressure in event ticketing that most e-commerce projects never face.

For a normal online store, traffic arrives gradually. For Mammutmarsch — organizers of hiking events across Germany, Denmark, and Spain — thousands of participants converge on the site at exactly the same minute, because that’s when the starting groups open.

A starting group is a time slot. On a 100/55/42-kilometer hike with thousands of participants, everyone can’t set off at once, so participants are distributed into waves that start at staggered times. Getting an early slot matters to people. So when the groups open at 12:00 on a Saturday, they’re all there at 12:00, refreshing.

That single moment is the hardest technical problem in the entire platform. This is the story of how we solved it — twice.


Chapter 1: The system we inherited (2020)

When we started working with Mammutmarsch in 2020, starting groups technically existed, but not as a system. They were three loose fields — date, time, slots — attached to each event product, managed through ACF, with the participant’s selection written into the order as metadata.

That worked when there was one country and a handful of events. It broke down as soon as you asked it harder questions:

  • A starting group had no unique identity, so if an event manager edited a time, every participant already assigned to that group silently pointed at something different.
  • The participant’s chosen slot was stored as a time value, not a reference — so there was no reliable link between a person and a group.
  • Dates on tickets, on the “My Events” page, and in participant confirmation emails all derived from this fragile data.

And there was one consumer of that data we couldn’t afford to break: Mammutmarsch runs an offline application at the events themselves to print finisher certificates, and it calculates each participant’s time from their starting group. The database is synced to that tool the day before an event. Wrong slot data doesn’t mean a bad UX — it means thousands of certificates printed with the wrong times, at an event that’s already happening.

Our first rebuild gave starting groups real structure: a unique, unchangeable, auto-generated ID for every group, with date, time, available slots, and taken slots as proper fields. Orders began storing the slot ID rather than a timestamp.

The tricky part was the transition. We couldn’t take registration offline for events already selling, so we ran both systems in parallel and mapped them: every legacy group got an ID matching its counterpart in the new system, with a synchronization step to keep them aligned. Old orders were backfilled by script.

It was meant to be temporary. It lasted four years.


Chapter 2: Four years of feature growth (2020–2024)

Once starting groups had a real structure, the business started asking for more from them — and each request was reasonable on its own.

Mass assignment. Not everyone picks a slot. Before registration closes, event managers run a bulk assignment that fills every unassigned order into available groups, earliest first. We rebuilt this to work with the new slot system, then rebuilt it again in 2024 when it was distributing orders without regard to distance.

Scheduled publishing. Event managers only know exact starting times a few weeks out, and they were entering groups manually, live, at the moment of opening. We automated it — groups become visible on the event details and My Events pages on a schedule, without anyone sitting at a keyboard at noon on a Saturday.

Distance-based filtering. A participant registered for 55 km shouldn’t see 42 km groups. We built filtering so each user’s pull-down shows only the groups that belong to their booked distance.

Support tooling. Participants request group changes constantly. The customer support team needed to reassign someone from inside the order details page — without access to the full starting group management screen.

Each of these worked. But every feature was another layer on a foundation that was still, underneath, writing participant slot selections into WordPress’s wp_postmeta — the same table holding every other piece of order data on a site with hundreds of thousands of orders.


Chapter 3: The wall (2023–2024)

By 2023 the symptoms were impossible to ignore, and they all clustered around the same moment: starting group opening day.

The site became inaccessible under peak load. Slot counters produced impossible numbers — groups showing negative free slots, because when hundreds of simultaneous requests read the same counter, calculated a new value, and wrote it back, the writes overwrote each other. Selections made on the My Events page silently failed to save, leaving participants convinced they had a slot when they didn’t.

Mammutmarsch attacked it from the infrastructure side first: server settings were upscaled, then an entirely new, more powerful server was ordered and migrated to.

It helped. It did not solve the problem.

We spent 2024 attacking the software side. We optimized every starting group query, rewrote almost every part of the code from scratch, removed page reloads from the selection flow, and — most importantly — added a validation layer at the moment of writing: if the update would push available slots below zero, the write is rejected rather than committed. That killed the negative-number corruption. The next opening produced clean numbers in the database for the first time in a long while.

But the underlying issue was still there. As the client put it at the end of that season, the capacity problems during starting group openings persisted even after the hardware had already been optimized. You cannot optimize your way out of a data model that’s wrong. Every read of a participant’s starting group was still a query against one enormous, general-purpose table that WordPress was never designed to use this way.


Chapter 4: The rebuild (January–February 2025)

In January 2025, we proposed the thing that had been on the table since 2020: move starting group data out of postmeta entirely, into a dedicated set of tables built for it.

The client agreed — and then named the deadline. The next season’s first major starting group opening was February 8th. The one after that, February 22nd. This wasn’t a rebuild we could ship “when it’s ready.” It had to be live, tested, and handling thousands of concurrent users on a specific Saturday, six weeks out.

Three things had to happen at once:

Structure setup and data migration. We designed the new tables and moved every existing starting group and participant assignment into them — including live events with registrations already open.

Unifying two systems into one. The parallel old/new architecture from 2020 was still running. Rather than migrate it forward, we merged both into a single implementation and deleted the divergence. This is the part that pays off invisibly: no more “which system is this event using?”, no more code paths that only trigger for legacy events, and a dramatically smaller number of database queries per page load.

Rebuilding the management interface. The old admin used ACF fields on each event product, which by definition wrote into the general table. It had to be replaced. We built a dedicated Starting Groups screen — with the option to enable and disable individual groups, so event managers could stage extra groups in advance without exposing them to participants yet.

We deliberately kept one thing unchanged: the client’s clear instruction was that the people running the system needed to operate it without surprises on opening weekend. So we resisted moving the management workflow to an unfamiliar place until after the season’s critical openings had passed.

Testing ran through the first week of February, on live infrastructure, against a real deadline. Then February 8th arrived — the season’s biggest opening.

It held. There were brief peaks where the site was under strain, but they lasted moments and affected few users. The client’s verdict that evening was that the new functionality was much faster and working properly — the goal had been reached.

And it would seem that four years of accumulated architectural debt, cleared just in five weeks. But without all the work that we did refactoring the functionality during previous years, it wouldn’t be possible to achieve it in such a short time.


Chapter 5: What came after (2025–2026)

Shipping the migration was the start, not the finish.

The REST API and admin functions. Immediately after go-live, we discovered the order details page — the one the support team uses — was still reading from the old system, showing wrong groups. We rebuilt that view against the new API and moved it into the plugin.

“Blocked” slots. This came out of a real operational problem. Event managers sometimes need to shrink a group after people are already in it — a group planned for 100 gets reduced to 90 for logistical reasons. Previously they did this with negative numbers, which is exactly what had been corrupting counts. So we built the concept properly: a blocked column. When a participant leaves a group with blocked slots, the freed seat isn’t handed back to the public — it absorbs into the block until the group reaches its new real size. Support can still move people into “full” groups deliberately; the public just can’t book into them.

Statistics. A summary table of free, taken, and blocked slots per distance and in total, right on the management screen — so event managers stop doing arithmetic across twenty rows to answer “are we full?”

Rollout to Denmark and Spain. With the Danish season opening on April 12th, the new structure had to reach the other two countries fast — not least because all three countries’ data merges into that same offline certificate tool the day before events. We migrated DK and ES onto the new tables, and while we were there, centralized the event display logic into a single plugin shared by all three sites. Before, each site had its own files. Now there’s one implementation, and a fix in one place is a fix everywhere.

Continuous refinement. Sortable group lists. Correct ordering in the user-facing dropdown, after we learned that participants click the first option without reading it. Hiding exact remaining-slot counts from participants while keeping them visible to support. Scroll handling for events that grew past twenty groups per distance.


What this actually took

Looking back at six years of tickets, a few things stand out that aren’t in any spec.

The data model was the bottleneck all along — and we didn’t have permission to fix it for four years. Migrating live booking data is genuinely risky, and there was always an event three weeks out. Not to mention that there were always tasks that had to be done right away, which made it impossible to set aside time for architecture-related tasks. The fix was only possible after we had already added all the necessary functionality and refactored everything else, and when the pain of not fixing it exceeded the risk of the migration.

“Optimize the server” is a very expensive way to postpone a schema decision. Mammutmarsch bought a bigger server and still had capacity problems, because the load wasn’t really about hardware. Every hour spent tuning around postmeta was an hour not spent leaving it.

The offline certificate printer shaped the entire architecture. An offline tool at the finish line, syncing the database the day before, meant every decision had to preserve a stable, unambiguous participant-to-slot mapping across three countries. Constraints like this rarely appear in the brief — they surface in conversation, months in, and they’re the ones that matter most.

The features that mattered most came from watching people work. Blocked slots, enable/disable toggles, sortable lists, “available” instead of exact numbers — none of these were on a roadmap. Each came from an event manager or a support agent hitting friction on a real event day.

Today, starting group openings are a scheduled operational event rather than a crisis. Which is the actual goal of infrastructure work: when it’s done right, nothing happens.


Working with a WordPress or WooCommerce platform that’s outgrown its foundations? That’s most of what we do. Let’s talk.

Alexey Velichko
Alexey Velichko
https://gonextg.com

We use cookies to give you the best experience. Privacy Policy