Skip to content

Community platforms

Reference build

Fifty thousand concurrent voters without touching the database

A live polling platform that counts votes in Redis and flushes to PostgreSQL in batches — cutting database write load by 97% while acknowledging votes in under 20 milliseconds.

1M+votes processed

At a glance

Duration
35 weeks
Team size
3 people
Engagement
New build
Project type
Web application
Industry
Media & Entertainment

The situation

The challenge

Naive vote counting collapses at scale: 50,000 concurrent voters means 50,000 simultaneous write transactions and COUNT queries against PostgreSQL. Multiple accounts and bots threatened the credibility of the results on top of that.

What we did

Count where counting is cheap, persist on a schedule, and accept that the database being five seconds behind is invisible to everyone.

The calls that mattered

  • Redis counts, PostgreSQL records

    INCR for O(1) tallying and SADD for deduplication, with Lua scripts making check-and-increment atomic. A five-second batch flush to PostgreSQL removed 97% of the write load.

  • Diff-based broadcasts on a fixed cadence

    500ms broadcasts carrying only what changed. Pushing full tallies per vote is what turns a popular poll into a self-inflicted denial of service.

What changed

total votes processed
1M+total votes processed
concurrent voters
50Kconcurrent voters
p99 vote acknowledgement
<20msp99 vote acknowledgement
reduction in database write load
97%reduction in database write load
  • 50K — without degradation

A million votes processed, 50,000 concurrent voters held without degradation, sub-20ms acknowledgement, and no vote corruption incidents.

Services used

  • Redis-first vote counting with atomic Lua operations
  • Batched persistence layer and reconciliation
  • Layered anti-fraud: fingerprinting, rate limits, challenges

What we would do differently

Every project has one of these. Publishing it is the point — a case study with no regrets in it is marketing, not evidence.

Scaling here was about accepting eventual consistency in the right place. The flush delay makes database records briefly stale, but users read results from Redis and perceive them as live — decoupling the two let each be optimised on its own terms.