<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FootBallCup</title>
    <description>The latest articles on DEV Community by FootBallCup (@mei_zhang_7ba62b665663941).</description>
    <link>https://dev.to/mei_zhang_7ba62b665663941</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3943240%2F524191b5-ae68-4db8-81db-2c421f0fe8e2.png</url>
      <title>DEV Community: FootBallCup</title>
      <link>https://dev.to/mei_zhang_7ba62b665663941</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mei_zhang_7ba62b665663941"/>
    <language>en</language>
    <item>
      <title>How We Handled Real-Time Traffic Spikes During the World Cup With a Lightweight Mini App</title>
      <dc:creator>FootBallCup</dc:creator>
      <pubDate>Thu, 21 May 2026 06:41:24 +0000</pubDate>
      <link>https://dev.to/mei_zhang_7ba62b665663941/how-we-handled-real-time-traffic-spikes-during-the-world-cup-with-a-lightweight-mini-app-gep</link>
      <guid>https://dev.to/mei_zhang_7ba62b665663941/how-we-handled-real-time-traffic-spikes-during-the-world-cup-with-a-lightweight-mini-app-gep</guid>
      <description>&lt;p&gt;During the last football World Cup, we built a lightweight live-score mini app focused on fast match updates, prediction games, and group discussions.&lt;/p&gt;

&lt;p&gt;The original goal was simple:&lt;/p&gt;

&lt;p&gt;ultra-fast score updates&lt;br&gt;
low mobile bandwidth usage&lt;br&gt;
stable performance during traffic spikes&lt;br&gt;
minimal server costs&lt;br&gt;
What surprised us was how difficult the “goal moment” problem became.&lt;/p&gt;

&lt;p&gt;Whenever a goal happened in a knockout-stage match, traffic exploded almost instantly.&lt;/p&gt;

&lt;p&gt;A single scoring event could trigger:&lt;/p&gt;

&lt;p&gt;massive websocket fan-outs&lt;br&gt;
notification bursts&lt;br&gt;
cache invalidations&lt;br&gt;
ranking recalculations&lt;br&gt;
prediction settlements&lt;br&gt;
social sharing spikes&lt;br&gt;
The UI itself was the easy part.&lt;/p&gt;

&lt;p&gt;The real challenge was keeping latency low while avoiding infrastructure meltdowns.&lt;/p&gt;

&lt;p&gt;Overall Architecture&lt;br&gt;
Here’s the simplified architecture we ended up using:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    +------------------+
                    |  Match Provider  |
                    +--------+---------+
                             |
                             v
                +------------------------+
                |   Match Event Queue    |
                |     Kafka / Redis      |
                +-----------+------------+
                            |
          +-----------------+-----------------+
          |                                   |
          v                                   v
 +----------------+                 +----------------+
 | Score Workers  |                 | Ranking Worker |
 +--------+-------+                 +--------+-------+
          |                                   |
          v                                   v
  +---------------+                 +----------------+
  | Redis Cluster |                 | Prediction DB  |
  +-------+-------+                 +----------------+
          |
          v
 +-------------------+
 | WebSocket Gateway |
 +---------+---------+
           |
 +---------+---------+
 |                   |
 v                   v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;+----------+      +-------------+&lt;br&gt;
| Mini App |      | Mobile Web  |&lt;br&gt;
+----------+      +-------------+&lt;br&gt;
The core idea was:&lt;/p&gt;

&lt;p&gt;Redis handled short-lived hot data&lt;br&gt;
MySQL stored historical match data&lt;br&gt;
WebSocket handled live pushes&lt;br&gt;
Kafka buffered bursts during peak events&lt;br&gt;
The “Goal Spike” Problem&lt;br&gt;
The biggest traffic spikes happened immediately after goals.&lt;/p&gt;

&lt;p&gt;Not during kickoff.&lt;/p&gt;

&lt;p&gt;Not during halftime.&lt;/p&gt;

&lt;p&gt;Only goals.&lt;/p&gt;

&lt;p&gt;Traffic patterns looked roughly like this:&lt;/p&gt;

&lt;p&gt;Minute 0-20      → stable&lt;br&gt;
Goal event       → 12x websocket burst&lt;br&gt;
VAR review       → reconnect storms&lt;br&gt;
Penalty shootout → total chaos&lt;br&gt;
We originally used polling every 5 seconds.&lt;/p&gt;

&lt;p&gt;That completely failed during high-profile matches.&lt;/p&gt;

&lt;p&gt;The problems:&lt;/p&gt;

&lt;p&gt;huge duplicate requests&lt;br&gt;
unnecessary database reads&lt;br&gt;
delayed score updates&lt;br&gt;
battery drain on mobile&lt;br&gt;
We replaced polling with websocket streams plus local incremental updates.&lt;/p&gt;

&lt;p&gt;That reduced backend request volume by around 78%.&lt;/p&gt;

&lt;p&gt;Why Mini Apps Were Difficult&lt;br&gt;
A lot of developers outside China aren’t familiar with mini app ecosystems.&lt;/p&gt;

&lt;p&gt;The constraints are surprisingly strict.&lt;/p&gt;

&lt;p&gt;Some examples:&lt;/p&gt;

&lt;p&gt;limited websocket connections&lt;br&gt;
package size limits&lt;br&gt;
aggressive memory recycling&lt;br&gt;
cold-start delays&lt;br&gt;
background execution restrictions&lt;br&gt;
On older Android devices, reconnect storms became a serious issue.&lt;/p&gt;

&lt;p&gt;During penalty shootouts, some devices opened multiple duplicate websocket sessions after network jitter.&lt;/p&gt;

&lt;p&gt;That created ghost connections which kept consuming memory on the gateway layer.&lt;/p&gt;

&lt;p&gt;We eventually added:&lt;/p&gt;

&lt;p&gt;heartbeat validation&lt;br&gt;
duplicate-session eviction&lt;br&gt;
exponential reconnect backoff&lt;br&gt;
local event deduplication&lt;br&gt;
That stabilized things considerably.&lt;/p&gt;

&lt;p&gt;Redis Became the Most Important Layer&lt;br&gt;
Originally we treated Redis as a secondary cache.&lt;/p&gt;

&lt;p&gt;That was a mistake.&lt;/p&gt;

&lt;p&gt;During peak matches, Redis effectively became the real-time backbone of the entire system.&lt;/p&gt;

&lt;p&gt;We used it for:&lt;/p&gt;

&lt;p&gt;live scores&lt;br&gt;
match timelines&lt;br&gt;
ranking snapshots&lt;br&gt;
prediction counters&lt;br&gt;
websocket routing&lt;br&gt;
hot room metadata&lt;br&gt;
One optimization that helped a lot:&lt;/p&gt;

&lt;p&gt;Instead of invalidating large leaderboard structures after every event, we switched to partial incremental ranking updates.&lt;/p&gt;

&lt;p&gt;That reduced ranking recomputation time from several seconds to under 200ms.&lt;/p&gt;

&lt;p&gt;CDN Optimization Helped More Than Expected&lt;br&gt;
One unexpected bottleneck was actually images.&lt;/p&gt;

&lt;p&gt;Especially:&lt;/p&gt;

&lt;p&gt;national flags&lt;br&gt;
player avatars&lt;br&gt;
emoji reactions&lt;br&gt;
sticker packs&lt;br&gt;
During semifinals, image bandwidth briefly exceeded websocket traffic.&lt;/p&gt;

&lt;p&gt;We fixed this by:&lt;/p&gt;

&lt;p&gt;aggressive WebP compression&lt;br&gt;
sprite merging&lt;br&gt;
edge CDN caching&lt;br&gt;
lazy-loading reaction assets&lt;br&gt;
Cold-start speed improved noticeably afterward.&lt;/p&gt;

&lt;p&gt;Prediction Systems Were Surprisingly Expensive&lt;br&gt;
The prediction feature looked harmless at first.&lt;/p&gt;

&lt;p&gt;Users could guess:&lt;/p&gt;

&lt;p&gt;final score&lt;br&gt;
first goal scorer&lt;br&gt;
possession rate&lt;br&gt;
match winner&lt;br&gt;
The problem was settlement traffic.&lt;/p&gt;

&lt;p&gt;When matches ended, millions of prediction calculations triggered almost simultaneously.&lt;/p&gt;

&lt;p&gt;Our first implementation recalculated rankings synchronously.&lt;/p&gt;

&lt;p&gt;That caused database lock contention immediately.&lt;/p&gt;

&lt;p&gt;We eventually moved settlement into async queues and chunked ranking updates into batches.&lt;/p&gt;

&lt;p&gt;That eliminated most timeout issues.&lt;/p&gt;

&lt;p&gt;WebSocket Fan-Out Was the Hardest Part&lt;br&gt;
The most difficult engineering problem was websocket broadcasting.&lt;/p&gt;

&lt;p&gt;A single goal event might need to reach:&lt;/p&gt;

&lt;p&gt;live score rooms&lt;br&gt;
prediction rooms&lt;br&gt;
chat rooms&lt;br&gt;
trending feeds&lt;br&gt;
push notification workers&lt;br&gt;
Naively broadcasting everything everywhere quickly became too expensive.&lt;/p&gt;

&lt;p&gt;We switched to segmented channels:&lt;/p&gt;

&lt;p&gt;match:arg-vs-fra&lt;br&gt;
match:eng-vs-ned&lt;br&gt;
prediction:global&lt;br&gt;
chat:room:3821&lt;br&gt;
This reduced unnecessary fan-outs significantly.&lt;/p&gt;

&lt;p&gt;What Failed&lt;br&gt;
Several things worked terribly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Polling&lt;br&gt;
Completely unsustainable during peak traffic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Full leaderboard recalculation&lt;br&gt;
Too expensive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Large websocket payloads&lt;br&gt;
Mobile packet loss became noticeable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Synchronous push notifications&lt;br&gt;
Created retry storms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real-time chat moderation&lt;br&gt;
Much harder than expected during controversial matches.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What Worked Well&lt;br&gt;
A few things scaled surprisingly well:&lt;/p&gt;

&lt;p&gt;Redis Pub/Sub&lt;br&gt;
websocket segmentation&lt;br&gt;
edge caching&lt;br&gt;
event queues&lt;br&gt;
incremental ranking updates&lt;br&gt;
local optimistic rendering&lt;br&gt;
One interesting UX optimization:&lt;/p&gt;

&lt;p&gt;We updated the score instantly on the client before the full match timeline arrived.&lt;/p&gt;

&lt;p&gt;Users perceived the app as much faster even though backend latency stayed roughly the same.&lt;/p&gt;

&lt;p&gt;Traffic Numbers&lt;br&gt;
Some approximate peak metrics during knockout-stage matches:&lt;/p&gt;

&lt;p&gt;Metric  Peak&lt;br&gt;
Concurrent websocket connections    ~180k&lt;br&gt;
Match event fan-outs/minute ~12M&lt;br&gt;
Redis ops/sec   ~500k&lt;br&gt;
CDN image requests/minute   ~8M&lt;br&gt;
Peak websocket bandwidth    ~4.2 Gbps&lt;br&gt;
The infrastructure costs were still lower than we originally expected because most traffic was transient and cache-heavy.&lt;/p&gt;

&lt;p&gt;Biggest Lesson&lt;br&gt;
The biggest lesson was:&lt;/p&gt;

&lt;p&gt;Real-time sports traffic is extremely bursty.&lt;/p&gt;

&lt;p&gt;Most systems look stable right up until a major goal happens.&lt;/p&gt;

&lt;p&gt;Then everything breaks simultaneously.&lt;/p&gt;

&lt;p&gt;Designing for average traffic is almost meaningless for live sports products.&lt;/p&gt;

&lt;p&gt;You have to design for emotional moments.&lt;/p&gt;

&lt;p&gt;That changes everything:&lt;/p&gt;

&lt;p&gt;traffic patterns&lt;br&gt;
reconnect behavior&lt;br&gt;
sharing behavior&lt;br&gt;
caching strategy&lt;br&gt;
notification systems&lt;br&gt;
moderation workload&lt;br&gt;
If We Rebuilt It Today&lt;br&gt;
We would probably:&lt;/p&gt;

&lt;p&gt;use protobuf for websocket payloads&lt;br&gt;
reduce payload sizes further&lt;br&gt;
move more logic to edge workers&lt;br&gt;
introduce regional websocket routing&lt;br&gt;
precompute ranking snapshots&lt;br&gt;
use CRDT-style synchronization for chat&lt;br&gt;
We’d also invest more in observability earlier.&lt;/p&gt;

&lt;p&gt;During the first week, debugging realtime issues without proper tracing was painful.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;br&gt;
The most interesting part of building a football live-score system wasn’t sports data.&lt;/p&gt;

&lt;p&gt;It was understanding how human behavior changes during high-emotion events.&lt;/p&gt;

&lt;p&gt;Goals create synchronized traffic spikes unlike almost anything else on the internet.&lt;/p&gt;

&lt;p&gt;And penalty shootouts are basically distributed systems chaos testing.&lt;/p&gt;

&lt;p&gt;From: [&lt;a href="https://www.douwen.me/en/archives/1127/" rel="noopener noreferrer"&gt;https://www.douwen.me/en/archives/1127/&lt;/a&gt;]&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
