惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Forbes - Security
Forbes - Security
C
CERT Recently Published Vulnerability Notes
NISL@THU
NISL@THU
I
Intezer
S
Schneier on Security
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
月光博客
月光博客
G
Google Developers Blog
S
Securelist
WordPress大学
WordPress大学
T
Tailwind CSS Blog
IT之家
IT之家
C
Cybersecurity and Infrastructure Security Agency CISA
A
Arctic Wolf
C
CXSECURITY Database RSS Feed - CXSecurity.com
J
Java Code Geeks
宝玉的分享
宝玉的分享
阮一峰的网络日志
阮一峰的网络日志
V
Vulnerabilities – Threatpost
P
Privacy & Cybersecurity Law Blog
博客园 - 聂微东
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
P
Privacy International News Feed
The Hacker News
The Hacker News
L
LINUX DO - 热门话题
爱范儿
爱范儿
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
有赞技术团队
有赞技术团队
博客园 - 司徒正美
C
Cyber Attacks, Cyber Crime and Cyber Security
大猫的无限游戏
大猫的无限游戏
Recent Commits to openclaw:main
Recent Commits to openclaw:main
G
GRAHAM CLULEY
小众软件
小众软件
T
Tenable Blog
Jina AI
Jina AI
Simon Willison's Weblog
Simon Willison's Weblog
Security Latest
Security Latest
Application and Cybersecurity Blog
Application and Cybersecurity Blog
AWS News Blog
AWS News Blog
T
Troy Hunt's Blog
博客园 - Franky
量子位
博客园_首页
P
Proofpoint News Feed

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Reddit's Karma Score Is a Lie. Here's the Math Behind It.
Archit Agarw · 2026-05-11 · via DEV Community

Two comments. Same subreddit. Same topic. Posted minutes apart.

One has +847 karma. It sits at the top, gets gilded, spawns a thread of 200 replies. The person who wrote it gets DMs. Their next post gets upvoted reflexively because people remember the name.

The other has +1. It exists somewhere below the fold, unseen by 99% of readers. Same words, different fate.

That number — Reddit karma — is one of the most consequential invisible forces on the internet. It shapes what millions of people read, what opinions gain traction, whose voices carry weight in a community. And most Reddit users have a vague sense of how it works: upvotes good, downvotes bad, net score determines visibility.

What almost nobody knows is what's actually happening inside Reddit's infrastructure every time someone clicks that arrow.

The score you see? It's a lie. Not a malicious one. A mathematically necessary, deliberately constructed approximation — and the algorithm behind it is one of the most elegant ideas in distributed systems.

This is that story.

What Reddit Karma Actually Is

Let's start simple, because the simple version matters.

Every post and comment on Reddit has a score. That score is calculated as:

Score = Upvotes − Downvotes

A post with 1,200 upvotes and 200 downvotes shows a score of +1,000. A comment with 50 upvotes and 80 downvotes shows -30. Reddit uses this score to rank content — higher scores float up, lower scores sink. In highly active subreddits, the first few minutes of score accumulation can determine whether a post reaches the front page or disappears forever.

The score also feeds into account-level karma, which affects posting privileges, community credibility, and how other users perceive you before they've read a single word you've written.

Simple math. Massive consequences.

Now let's build the system that computes it.

The Simple Version (That Works Fine Until It Doesn't)

Imagine Reddit in its early days. A few thousand users. One web server. One database.

The schema for tracking votes is straightforward:

CREATE TABLE post_scores (
    post_id   BIGINT PRIMARY KEY,
    score     INT DEFAULT 0
);

Enter fullscreen mode Exit fullscreen mode

When someone upvotes:

UPDATE post_scores SET score = score + 1 WHERE post_id = ?;

Enter fullscreen mode Exit fullscreen mode

When someone downvotes:

UPDATE post_scores SET score = score - 1 WHERE post_id = ?;

Enter fullscreen mode Exit fullscreen mode

User loads the page, you return score. Done.

This works beautifully for a small community. Your database handles concurrent updates with row-level locking. Reads are fast. Writes are fast. The score is always accurate.

And then the site grows.

The Moment Everything Gets Complicated

Reddit today serves over 50 million daily active users. Popular posts on r/worldnews or r/AskReddit can accumulate thousands of votes in minutes. During major news events, that number spikes even higher.

One database server cannot handle that write throughput. So you do what every scaling team eventually does — you distribute.

You spin up multiple database replicas. You deploy servers in the US, Europe, and Asia so votes from each region get handled locally. Latency drops. Throughput goes up. Everything seems fine.

Until two servers try to update the same post's score at the same time.

Here's the problem in its simplest form. The US server reads the score: 142. The EU server reads the score: 142. A user in the US clicks upvote — the US server wants to write 143. Simultaneously, a user in Europe clicks downvote — the EU server wants to write 141.

Both writes land. One of them overwrites the other. A real vote disappears.

At small scale this is a rounding error. At Reddit scale, with thousands of concurrent votes per second on a single viral post, you're losing a meaningful number of real votes from real users. Users who clicked an arrow and expected their opinion to count.

Let's be honest — this is not a hypothetical. This is the exact class of problem that has caused production incidents at companies you use every day.

The Approaches That Don't Work

The instinct is to reach for coordination. Make the servers talk to each other before writing.

Distributed locks: before updating the score, acquire a lock on that post's row across all replicas. Only one server writes at a time. Others wait.

This works. It's also slow. Lock acquisition across data centres separated by hundreds of milliseconds of network latency adds that latency to every single vote. During peak traffic, lock contention turns your vote system into a queue. Users notice.

Two-phase commit (2PC): a coordinator server proposes the write, all participants acknowledge, then the coordinator commits. Guarantees consistency across all nodes.

Also works. Also slow. 2PC requires multiple round trips between servers. It assumes all participants are available — if any node is down or unreachable during the commit phase, the transaction blocks. In a system operating across multiple continents, "all participants available" is never guaranteed.

Both approaches trade availability for consistency. That's a valid trade-off in some systems. For a vote counter on a social platform — where a few hundred milliseconds of delay on every click would be immediately noticeable and wildly unacceptable — it's not.

There had to be a better way.

A Different Question

The distributed systems community eventually asked a different question.

Instead of "how do we make all servers agree before writing?" — what if we asked: "what constraints can we place on the data so that disagreements become impossible?"

This line of thinking led to CRDTs — Conflict-free Replicated Data Types. Data structures specifically designed so that concurrent updates on multiple replicas can always be merged correctly, without coordination, without locks, without consensus rounds.

The key insight: if you design your data structure such that the merge operation is always unambiguous — no matter what order updates arrive, no matter how long replicas were disconnected — you get eventual consistency for free.

I covered the simplest CRDT — the G-Counter — in Episode 1 of this series. The short version: each replica keeps its own local count, only ever increments it, and merge means "take the maximum per replica." YouTube's view counter works this way. There's no conflict possible because a count that can only grow is always unambiguous — the higher number is always more up to date.

But G-Counters have an obvious limitation.

They only go up.

Why Upvotes Alone Aren't Enough

Reddit's score isn't a count of upvotes. It's upvotes minus downvotes. You need both directions.

A G-Counter can track upvotes fine. But downvotes require decrement. And decrement is where things get complicated — because "subtract one" from a shared counter across distributed replicas brings back exactly the ambiguity we were trying to avoid.

Two servers both see the score as 50. Both receive a downvote simultaneously. Both want to write 49. Same lost vote problem as before, just through a different door.

So: we need increment and decrement, without coordination, with correct merging across replicas.

Here's where things get interesting.

The Insight: You Never Actually Decrement

This is the moment the whole thing clicks.

What if you never decremented anything? What if every downvote was secretly an increment — just to a different counter?

Go back to the lemonade stand mental model. You and your friends are tracking the cash register. Money comes in from customers. Money goes out for supplies. You need to track both.

You already know the G-Counter trick: give everyone a notebook where they can only add tally marks. Works for money coming in.

For money going out, the solution is almost embarrassingly simple: give everyone two notebooks.

  • A green notebook for every dollar that came in
  • A red notebook for every dollar that went out

Neither notebook ever loses a mark. Both only ever grow. At the end of the day, you sync the green notebooks (take max per person), sync the red notebooks (take max per person), and do one subtraction:

Green total − Red total = what's in the register

That's a PN-Counter. Positive-Negative Counter.

Two G-Counters. One subtraction at read time. No coordination. No locks.

The "decrement" you thought you needed was never a real operation. It was always just an increment to a second counter that you subtract when you read the value. The counter doesn't go down — your accounting does.

The Code

gotype PNCounter struct {
    inc GCounter
    dec GCounter
}

func NewPNCounter() PNCounter {
    return PNCounter{
        inc: Zero(),
        dec: Zero(),
    }
}

Enter fullscreen mode Exit fullscreen mode

Two G-Counters. That's the entire data structure.
Reading the score:

func (p PNCounter) Value() int64 {
    return p.inc.Value() - p.dec.Value()
}

Enter fullscreen mode Exit fullscreen mode

The subtraction only happens here — at read time, when someone loads the page. Never during updates. Never during merges.

Upvoting:

func (p PNCounter) Inc(r ReplicaId) PNCounter {
    return PNCounter{
        inc: p.inc.Inc(r),
        dec: p.dec,
    }
}

Enter fullscreen mode Exit fullscreen mode

Downvoting:

func (p PNCounter) Dec(r ReplicaId) PNCounter {
    return PNCounter{
        inc: p.inc,
        dec: p.dec.Inc(r),
    }
}

Enter fullscreen mode Exit fullscreen mode

Look at Dec. It doesn't subtract anything. It calls .Inc() on the decrement counter.

A downvote is an upvote to the wrong notebook.

If you showed this to someone without context they'd think it was a bug. It's the entire algorithm.

Merging two replicas:

func MergePN(a, b PNCounter) PNCounter {
    return PNCounter{
        inc: Merge(a.inc, b.inc),
        dec: Merge(a.dec, b.dec),
    }
}

Enter fullscreen mode Exit fullscreen mode

Run the G-Counter merge twice — once on the increment counters, once on the decrement counters. Same "take the maximum per replica" logic. Nothing new to prove. Everything PN-Counter needs, it inherits from G-Counter.

What This Looks Like on Reddit's Servers

Let's trace a viral comment across three servers: US, EU, Asia.
T=0: Comment just posted. All counters at zero.

US:   inc={US:0}, dec={US:0}  →  score: 0
EU:   inc={EU:0}, dec={EU:0}  →  score: 0
Asia: inc={As:0}, dec={As:0}  →  score: 0

Enter fullscreen mode Exit fullscreen mode

T=1: Activity floods in. US gets 8 upvotes. EU gets 3 upvotes and 5 downvotes. Asia gets 2 downvotes. All happening simultaneously, all independent.

US:   inc={US:8}, dec={US:0}   →  local score: 8
EU:   inc={EU:3}, dec={EU:5}   →  local score: -2
Asia: inc={As:0}, dec={As:2}   →  local score: -2

Enter fullscreen mode Exit fullscreen mode

T=2: US and EU sync.

inc = Merge({US:8}, {EU:3}) = {US:8, EU:3}
dec = Merge({US:0}, {EU:5}) = {US:0, EU:5}
Score = (8+3) - (0+5) = 6

Enter fullscreen mode Exit fullscreen mode

US and EU now agree: score is +6.

T=3: Asia reconnects after a brief network partition and syncs.

inc = {US:8, EU:3, As:0}  →  total: 11
dec = {US:0, EU:5, As:2}  →  total: 7
Score = 11 - 7 = 4

Enter fullscreen mode Exit fullscreen mode

Final score: +4. Every upvote counted. Every downvote counted. No vote lost during the partition. No coordination during any of the voting itself.

This is the lie in the title: the +4 you see wasn't computed from one authoritative source. It was assembled from fragments of state scattered across three continents and merged after the fact.

It's an approximation. A consistent, mathematically guaranteed approximation — but an approximation nonetheless.

The Trade-Off Worth Naming

PN-Counter inherits G-Counter's main cost: every sync ships full state. Two maps, N entries each, across every gossip round. For tens of replicas this is fine. For thousands of edge nodes, the bandwidth adds up.

The more important trade-off is eventual consistency. When you're actively voting on a hot post, the score you see is the best approximation your nearest server has right now. Somewhere in Europe, a server might briefly disagree. They'll converge — they always do — but there's a window where different users see slightly different scores.

For Reddit, this is entirely acceptable. Nobody's life depends on whether the karma score reads 1,042 or 1,047 for 200 milliseconds. The system is highly available, survives network partitions cleanly, and handles write throughput that would collapse a coordinated system.

The trade-off is: exact consistency in exchange for scale and availability. Reddit made that trade. Most social platforms do.

The Problem PN-Counter Doesn't Solve

Here's where it gets interesting again — and where the next problem is quietly waiting.

PN-Counter has no floor. No ceiling. Nothing prevents the score from going to -50,000 if enough downvotes arrive. And nothing stops it from going to +10,000,000 either.

For Reddit karma, that's fine. But consider:

  • A distributed rate limiter that needs to cap requests at 1,000 per minute. The counter must not go above 1,000, enforced across all replicas.

  • An inventory system where stock can't go below zero. You can't sell items you don't have.

  • A per-user karma floor — some subreddits prevent karma from dropping below a certain value to protect new users from pile-ons.

These are bounded operations. And PN-Counter, with its two unbounded G-Counters, cannot enforce bounds without coordination.

You'd think: just check the current value before decrementing, and reject the operation if you'd go below the floor.

Try that across distributed replicas and you immediately reintroduce the problem we just spent this entire article solving. Two replicas both see the counter at 1 (the floor). Both receive a decrement. Both check locally — "1 is above 0, I can proceed." Both decrement. Counter is now -1 across the board. The floor was violated.

This is the problem the Bounded Counter CRDT was designed for. It's the next piece in this series — and it's the most complex CRDT we'll cover, because enforcing limits without coordination requires a genuinely different approach.

The short version: you can't do it with just max and subtraction. You need to distribute the permission to decrement itself.

But that's next time.

The Thread Running Through All of This

Two episodes in, the pattern is clear.

Every CRDT solves a hard distributed problem by finding a constraint that eliminates ambiguity. G-Counter said: make it grow-only, and max is always correct. PN-Counter said: never decrement — maintain two grow-only counters and subtract at read time.

The constraint is never a workaround. It's the design.

When you next open Reddit and see a karma score, you're looking at the output of two grow-only counters being summed and subtracted on a server near you — a server that may not have talked to its peers in the last few hundred milliseconds and doesn't need to.

The number is an approximation. But it's a principled one, built on math that guarantees eventual correctness without sacrificing the availability and performance that make Reddit usable at scale.

That's not a lie. That's engineering.

What's Next

This is Episode 2 of a series on CRDTs — the data structures powering Google Docs, Redis, Riak, and distributed infrastructure at scale.

  • Episode 1: G-Counter — why YouTube's view count is always approximate
  • Episode 2: PN-Counter — Reddit karma and the two-notebook trick (you're here)
  • Episode 3: G-Set — grow-only sets, and where they appear in the wild
  • Episode 4: LWW Registry — when "latest timestamp wins" is good enough
  • Episode 5: OR-Set — the elegant fix for the "deleted but still there" problem
  • Episode 6: Map CRDT — composing CRDTs into richer structures
  • Episode 7: The full picture — when to reach for CRDTs and when to walk away

Subscribe to the newsletter to get each episode as it drops.

If you're building distributed systems and want to think through architecture decisions with someone who has spent years deep in Go and distributed infrastructure — I do 1:1 sessions on Topmate.