Go vs Node.js vs Rails vs Spring — Architecture
Concurrency Architecture Comparison
How Go, Node.js, Ruby on Rails, and Spring Java handle concurrent traffic — and why it matters for performance.
Node.js — Single-threaded Event Loop
┌─────────────────────────────────────────┐
│ Node.js Process │
├─────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ │
│ │ Single Thread (V8 Engine) │ │
│ │ │ │
│ │ Event Loop (libuv): │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ 1. Timers │ │ │
│ │ │ 2. I/O callbacks │ │ │
│ │ │ 3. Poll (new I/O) │ │ │
│ │ │ 4. Check (setImmediate)│ │ │
│ │ │ 5. Close callbacks │ │ │
│ │ └───────────────────────┘ │ │
│ │ ↕ │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ Thread Pool (libuv) │ │ │
│ │ │ (4 threads for I/O) │ │ │
│ │ └───────────────────────┘ │ │
│ └─────────────────────────────────┘ │
│ │
│ Non-blocking: handles 1000s of │
│ connections on 1 thread │
└─────────────────────────────────────────┘- Concurrency: Single-threaded event loop + async I/O
- Blocking?: CPU-heavy work blocks everything
- Scaling: Cluster mode (1 process per CPU core)
- Best for: I/O-heavy, real-time (WebSocket, chat, APIs)
Go (Golang) — Goroutines + M:N Scheduler
┌─────────────────────────────────────────────────┐
│ Go Runtime │
├─────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Go Scheduler (GMP model) │ │
│ │ │ │
│ │ G = Goroutine (lightweight, ~2KB stack)│ │
│ │ M = OS Thread │ │
│ │ P = Processor (logical CPU) │ │
│ │ │ │
│ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │
│ │ │ G │ │ G │ │ G │ │ G │ │ G │ ... │ │
│ │ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ │ │
│ │ └──┬───┘ │ └──┬───┘ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ P │ │ P │ │ P │ │ │
│ │ └──┬──┘ └──┬──┘ └──┬──┘ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ M │ │ M │ │ M │ │ │
│ │ │(OS) │ │(OS) │ │(OS) │ │ │
│ │ └─────┘ └─────┘ └─────┘ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 100K+ goroutines on a few OS threads │
│ Preemptive scheduling (no starvation) │
└─────────────────────────────────────────────────┘- Concurrency: M:N scheduling — millions of goroutines on few OS threads
- Blocking?: Goroutines block individually, scheduler moves others
- Scaling: Naturally uses all CPU cores
- Best for: High-concurrency services, microservices, infrastructure
Ruby on Rails — Multi-process + GVL
┌─────────────────────────────────────────────────────┐
│ Ruby on Rails (Puma server) │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Master Process │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ Worker 1 │ │ Worker 2 │ │ Worker 3 │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌Thread┐ │ │ ┌Thread┐ │ │ ┌Thread┐ │ │ │
│ │ │ │ Req │ │ │ │ Req │ │ │ │ Req │ │ │ │
│ │ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │ │
│ │ │ ┌Thread┐ │ │ ┌Thread┐ │ │ ┌Thread┐ │ │ │
│ │ │ │ Req │ │ │ │ Req │ │ │ │ Req │ │ │ │
│ │ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │ │
│ │ │ 5 threads│ │ 5 threads│ │ 5 threads│ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ GVL: only 1 thread runs Ruby at a time per process │
│ Total capacity: workers × threads (e.g. 3×5 = 15) │
└─────────────────────────────────────────────────────┘- Concurrency: Multi-process (Puma workers) + threads within each
- Blocking?: GVL limits true parallelism per process; I/O releases it
- Scaling: Add more processes (costs ~100-300MB each)
- Best for: Rapid development, CRUD apps, startups
Spring Java — Thread Pool + Virtual Threads
┌─────────────────────────────────────────────────────┐
│ Spring Boot (Tomcat/Netty) │
├─────────────────────────────────────────────────────┤
│ │
│ Classic (Spring MVC + Tomcat): │
│ ┌─────────────────────────────────────────────┐ │
│ │ Thread Pool (200 threads default) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │Thread│ │Thread│ │Thread│ │Thread│ ... │ │
│ │ │ Req1 │ │ Req2 │ │ Req3 │ │ Req4 │ │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ │ Each thread: ~1MB stack │ │
│ │ 200 threads = 200 concurrent requests │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ Modern (Java 21+ Virtual Threads / Loom): │
│ ┌─────────────────────────────────────────────┐ │
│ │ Millions of Virtual Threads possible │ │
│ │ ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐ │ │
│ │ │VT││VT││VT││VT││VT││VT││VT││VT│ ... │ │
│ │ └┬─┘└┬─┘└┬─┘└┬─┘└┬─┘└┬─┘└┬─┘└┬─┘ │ │
│ │ └───┴───┴┬──┘ └───┴┬──┘ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ OS Thread│ │ OS Thread│ (few) │ │
│ │ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘- Concurrency: Thread pool (classic) or Virtual Threads (Java 21+)
- Blocking?: Classic threads block; Virtual Threads park cheaply
- Scaling: Vertical (RAM/threads) + horizontal
- Best for: Enterprise, complex business logic, large teams
Traffic Performance Comparison
MetricGoNode.jsSpring JavaRailsRequests/sec (JSON API)~300K–500K~50K–100K~100K–200K~5K–15KLatency (p99)Very lowLowLow-mediumMedium-highMemory per connection~2KB~few KB~1MB (classic) / ~few KB (VT)~10-50MB (process)Max concurrent connectionsMillions100K+10K (classic) / Millions (VT)Hundreds–thousandsCold start~5ms~100ms~2-5 sec~5-10 secCPU-heavy tasksExcellentPoor (blocks loop)ExcellentPoor (GVL)I/O-heavy tasksExcellentExcellentGood-ExcellentModerateThroughput Visualization
Requests/sec (same hardware):
Go: ████████████████████████████████████ ~500K
Spring(VT): █████████████████████ ~200K
Node.js: ████████████ ~100K
Rails: ██ ~10K
Memory per connection:
Go: █ 2KB (goroutine)
Node.js: █ few KB (event)
Spring(VT): █ few KB (virtual thread)
Spring: ██████ 1MB (OS thread)
Rails: ██████████████████ 10-50MB (process)Key Terms Glossary
KeywordFull NameUsed InMeaningGVLGlobal VM LockRubyOnly 1 thread runs Ruby at a time per processGILGlobal Interpreter LockPythonSame concept as GVL for PythonEvent Loop—Node.jsSingle thread cycles through I/O eventslibuv—Node.jsC library implementing event loop + thread poolGoroutine—GoLightweight green thread (~2KB)GMPGoroutine, Machine, ProcessorGoGo's scheduler modelVirtual ThreadProject LoomJava 21+Lightweight thread on JVM (like goroutine)M:N scheduling—Go, Java VTM green threads on N OS threadsV8—Node.jsGoogle's JS engine (compiles to machine code)JITJust-In-TimeJava, Node.jsCompiles to native code at runtimeAOTAhead-Of-TimeGoCompiles before running → fast cold startPuma—RailsMulti-process + multi-thread web serverTomcat—SpringThread-pool based web serverPreemptive—Go, OS threadsScheduler can interrupt running tasksCooperative—Node.jsTask must yield control voluntarilyWhy GVL Matters
Ruby (GVL) — 4 CPU cores, 1 process, 5 threads:
Effective CPU usage: 1 core (GVL blocks others)
Fix: 4 processes → 4 cores used
Cost: 4 × 200MB = 800MB RAM
Go (no lock) — 4 CPU cores, 1 process, 1000 goroutines:
Effective CPU usage: 4 cores (scheduler distributes)
Cost: 1000 × 2KB = 2MB RAM
→ 400× more memory-efficient for same parallelismWhen to Use What
Use CaseBest ChoiceWhyHigh-traffic APIGoRaw performance, low memory, simple deployReal-time (WebSocket, chat)Node.js or GoEvent-driven, many idle connectionsEnterprise / complex logicSpring JavaEcosystem, type safety, large teamsStartup MVP / rapid devRailsDeveloper speed, conventionsCPU-intensive processingGo or JavaTrue parallelism across all cores10K+ concurrent connectionsGoGoroutines scale with minimal overheadThe Core Insight
The real difference is how they handle waiting (DB queries, API calls, network I/O):
- Go: spawns goroutine (2KB), parks it when waiting, resumes instantly
- Node.js: registers callback, moves on, comes back when done
- Java (classic): blocks entire OS thread (1MB) while waiting
- Java (Virtual Threads): like Go — parks cheaply
- Rails: blocks thread + limited by GVL, needs more processes = more RAM
For high-traffic services, Go and Node.js architectures are fundamentally better suited because waiting doesn't waste expensive resources.
#golang (1)
#nodejs (1)
#ruby on rails (1)
#spring (1)
#java (3)
#gvl (2)
#event loop (2)
#goroutine (2)
#concurrency (2)
#performance (1)