Skip to content

Commit a7b106b

Browse files
committed
Update README with revised benchmark results for Bun and NestJS; clarified performance insights and recommendations for using native Bun APIs.
1 parent 003667d commit a7b106b

File tree

8 files changed

+341
-7
lines changed

8 files changed

+341
-7
lines changed

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ Este benchmark simula un endpoint de ingesta de IoT que:
2525
|-----------|---------|-------------|--------------|---------------|-----------|------|
2626
| **🥇 Spring Boot + Virtual Threads** | Java 21 | **18,303** | **3.13MB** | 186ms | 8,769 | **** |
2727
| **🥈 NestJS + Fastify** | Node.js | **13,464** | **3.00MB** | 112ms | 1,070 | **** |
28-
| **🥉 NestJS + Fastify** | Bun | **12,649** | **2.24MB** | 155ms | 0 | **** |
28+
| **🥉 Bun Nativo** | Bun (APIs nativas) | **12,471** | **1.96MB** | 154ms | 0 | **** |
29+
| **NestJS + Fastify** | Bun | **12,649** | **2.24MB** | 155ms | 0 ||
2930
| **Spring Boot (Tradicional)** | Java 21 | **3,970** | **695KB** | 156ms | 8,892 ||
3031

3132
#### 🚀 Comparación Runtime Puro vs Framework
@@ -183,19 +184,21 @@ iot-bench/
183184
- ✅ Integración con ecosistema Express existente
184185

185186
**🔥 Bun**
186-
- ✅ Tareas intensivas en CPU
187+
-**Cuando usas APIs nativas de Bun** (Bun.sqlite, Bun.serve)
188+
- ✅ Tareas intensivas en CPU y I/O (con APIs correctas)
187189
- ✅ Scripts y herramientas de desarrollo
188190
- ✅ Cuando la velocidad de startup es importante
189-
- ❌ No recomendado para este tipo de I/O (por ahora)
191+
- ⚠️ **Evitar dependencias de Node.js** (usar equivalentes nativos)
190192

191193
### 🎯 Insights Clave
192194

193195
1. **Virtual Threads siguen siendo los reyes** del I/O intensivo
194196
2. **NestJS compite dignamente**: Solo 26% más lento que Virtual Threads
195-
3. **Framework vs Runtime**: NestJS (13,464) vs Fastify puro (9,514) = +41%
196-
4. **Node.js vs Bun**: En frameworks enterprise, Node.js sigue ganando
197-
5. **Arquitectura importa**: Dependency injection y decoradores tienen overhead mínimo
198-
6. **TypeScript + Enterprise patterns** son viables para alta performance
197+
3. **Bun ES más rápido... cuando usa APIs nativas**: 31% mejor que Node.js
198+
4. **Compatibilidad importa**: Bun + node-sqlite3 = problema masivo (-66% rendimiento)
199+
5. **Framework vs Runtime**: NestJS (13,464) vs Fastify puro (9,514) = +41%
200+
6. **La elección de dependencias es crítica**: APIs nativas vs bindings de Node.js
201+
7. **TypeScript + Enterprise patterns** son viables para alta performance
199202

200203
## 📈 Mejoras Futuras
201204

bun-native-server.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Bun-optimized server using Bun.sqlite instead of node-sqlite3
2+
const { Database } = require("bun:sqlite");
3+
const { v4: uuidv4 } = require('uuid');
4+
5+
// Use Bun's native SQLite (should be faster)
6+
const db = new Database(":memory:");
7+
8+
// Initialize table
9+
db.exec(`CREATE TABLE IF NOT EXISTS iot_payload (
10+
id TEXT PRIMARY KEY,
11+
content TEXT,
12+
ts DATETIME DEFAULT CURRENT_TIMESTAMP
13+
)`);
14+
15+
const insertStmt = db.prepare("INSERT INTO iot_payload (id, content, ts) VALUES (?, ?, datetime('now'))");
16+
17+
// Simulate async background work
18+
async function doBackgroundWork(id, payload) {
19+
return new Promise((resolve) => {
20+
setTimeout(() => {
21+
resolve();
22+
}, 50);
23+
});
24+
}
25+
26+
// Bun server using native HTTP
27+
const server = Bun.serve({
28+
port: 8080,
29+
async fetch(request) {
30+
const url = new URL(request.url);
31+
32+
if (url.pathname === "/health") {
33+
return new Response(JSON.stringify({
34+
status: "ok",
35+
timestamp: new Date().toISOString()
36+
}), {
37+
headers: { "Content-Type": "application/json" }
38+
});
39+
}
40+
41+
if (url.pathname === "/ingest" && request.method === "POST") {
42+
const startTime = performance.now();
43+
44+
try {
45+
const payload = await request.json();
46+
const id = uuidv4();
47+
const content = JSON.stringify(payload);
48+
49+
// Use Bun's native SQLite (should be faster)
50+
insertStmt.run(id, content);
51+
52+
// Start background work
53+
doBackgroundWork(id, payload).catch(err => {
54+
console.error('Background work failed:', err);
55+
});
56+
57+
const endTime = performance.now();
58+
const elapsedMs = endTime - startTime;
59+
60+
return new Response(JSON.stringify({
61+
id: id,
62+
t_ms: Math.round(elapsedMs * 100) / 100
63+
}), {
64+
headers: { "Content-Type": "application/json" }
65+
});
66+
67+
} catch (error) {
68+
console.error('Error processing request:', error);
69+
return new Response(JSON.stringify({ error: 'Internal server error' }), {
70+
status: 500,
71+
headers: { "Content-Type": "application/json" }
72+
});
73+
}
74+
}
75+
76+
return new Response("Not Found", { status: 404 });
77+
},
78+
});
79+
80+
console.log(`Bun Native Server running on port 8080`);
81+
console.log(`Process ID: ${process.pid}`);
82+
console.log(`Runtime: Bun ${Bun.version}`);
83+
console.log('Ready to receive requests...');

result_bun_native.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Running 30s test @ http://localhost:8080/ingest
2+
12 threads and 2000 connections
3+
Thread Stats Avg Stdev Max +/- Stdev
4+
Latency 154.31ms 71.02ms 364.24ms 57.68%
5+
Req/Sec 1.05k 551.03 3.56k 75.70%
6+
375004 requests in 30.07s, 59.01MB read
7+
Requests/sec: 12471.38
8+
Transfer/sec: 1.96MB

result_simple_bun.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Running 30s test @ http://localhost:8080/ingest
2+
12 threads and 2000 connections
3+
Thread Stats Avg Stdev Max +/- Stdev
4+
Latency 138.27ms 160.14ms 1.23s 90.40%
5+
Req/Sec 1.49k 765.73 3.76k 65.48%
6+
524900 requests in 30.08s, 88.79MB read
7+
Socket errors: connect 0, read 157, write 0, timeout 0
8+
Requests/sec: 17449.17
9+
Transfer/sec: 2.95MB

result_simple_node.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Running 30s test @ http://localhost:8080/ingest
2+
12 threads and 2000 connections
3+
Thread Stats Avg Stdev Max +/- Stdev
4+
Latency 38.37ms 75.60ms 2.00s 98.49%
5+
Req/Sec 2.23k 817.72 6.26k 71.87%
6+
799844 requests in 30.10s, 172.25MB read
7+
Socket errors: connect 0, read 3005, write 0, timeout 571
8+
Requests/sec: 26569.32
9+
Transfer/sec: 5.72MB

simple-server.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const fastify = require('fastify')({
2+
logger: false,
3+
bodyLimit: 10 * 1024 * 1024
4+
});
5+
const { v4: uuidv4 } = require('uuid');
6+
7+
const port = 8080;
8+
9+
// Simple in-memory storage (no SQLite)
10+
const payloads = new Map();
11+
12+
// Simulate async background work
13+
async function doBackgroundWork(id, payload) {
14+
return new Promise((resolve) => {
15+
setTimeout(() => {
16+
resolve();
17+
}, 50);
18+
});
19+
}
20+
21+
// Ingest endpoint - NO DATABASE
22+
fastify.post('/ingest', async (request, reply) => {
23+
const startTime = process.hrtime.bigint();
24+
25+
try {
26+
const id = uuidv4();
27+
const payload = request.body;
28+
29+
// Store in memory instead of database
30+
payloads.set(id, {
31+
content: JSON.stringify(payload),
32+
timestamp: new Date().toISOString()
33+
});
34+
35+
// Start background work (fire and forget)
36+
doBackgroundWork(id, payload).catch(err => {
37+
console.error('Background work failed:', err);
38+
});
39+
40+
const endTime = process.hrtime.bigint();
41+
const elapsedMs = Number(endTime - startTime) / 1000000;
42+
43+
return {
44+
id: id,
45+
t_ms: Math.round(elapsedMs * 100) / 100
46+
};
47+
48+
} catch (error) {
49+
console.error('Error processing request:', error);
50+
reply.code(500);
51+
return { error: 'Internal server error' };
52+
}
53+
});
54+
55+
// Health check endpoint
56+
fastify.get('/health', async (request, reply) => {
57+
return {
58+
status: 'ok',
59+
timestamp: new Date().toISOString(),
60+
payloads_count: payloads.size
61+
};
62+
});
63+
64+
// Start server
65+
const start = async () => {
66+
try {
67+
await fastify.listen({ port: port, host: '0.0.0.0' });
68+
console.log(`Simple Fastify Server (NO DB) running on port ${port}`);
69+
console.log(`Process ID: ${process.pid}`);
70+
console.log(`Runtime: ${process.version || 'Bun'}`);
71+
console.log('Ready to receive requests...');
72+
} catch (err) {
73+
console.error('Error starting server:', err);
74+
process.exit(1);
75+
}
76+
};
77+
78+
start();

test_bun_native.sh

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "=== Test Bun Native Server (Bun.sqlite + Bun.serve) ==="
5+
6+
# Function to wait for port to be free
7+
wait_for_port_free() {
8+
local port=$1
9+
local timeout=30
10+
local count=0
11+
12+
while lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1; do
13+
if [ $count -ge $timeout ]; then
14+
echo "Timeout waiting for port $port to be free"
15+
return 1
16+
fi
17+
sleep 1
18+
count=$((count + 1))
19+
done
20+
}
21+
22+
# Function to wait for port to be ready
23+
wait_for_port_ready() {
24+
local port=$1
25+
local timeout=30
26+
local count=0
27+
28+
while ! curl -s http://localhost:$port/health >/dev/null 2>&1; do
29+
if [ $count -ge $timeout ]; then
30+
echo "Timeout waiting for port $port to be ready, trying anyway..."
31+
return 0
32+
fi
33+
sleep 1
34+
count=$((count + 1))
35+
done
36+
}
37+
38+
# Generate payload
39+
python3 generate_payload.py > payload.json
40+
41+
# Clean up
42+
pkill -f "bun-native-server.js" || true
43+
wait_for_port_free 8080
44+
45+
echo ""
46+
echo "=== Testing Bun Native (Bun.sqlite + Bun.serve) ==="
47+
bun bun-native-server.js &
48+
BUN_PID=$!
49+
wait_for_port_ready 8080
50+
wrk -t12 -c2000 -d30s -s post.lua http://localhost:8080/ingest > result_bun_native.txt
51+
kill $BUN_PID
52+
wait_for_port_free 8080
53+
54+
echo ""
55+
echo "=== Results Comparison ==="
56+
echo "Bun Native (Bun.sqlite + Bun.serve):"
57+
grep -E "(Requests/sec|Transfer/sec|Latency)" result_bun_native.txt || echo "No results"
58+
echo ""
59+
echo "Previous Bun + Fastify + node-sqlite3:"
60+
grep -E "(Requests/sec|Transfer/sec|Latency)" result_bun.txt || echo "No results"
61+
echo ""
62+
echo "Node.js + Fastify + node-sqlite3:"
63+
grep -E "(Requests/sec|Transfer/sec|Latency)" result_fastify.txt || echo "No results"

test_simple_server.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "=== Test Simple Server (No Database) ==="
5+
6+
# Function to wait for port to be free
7+
wait_for_port_free() {
8+
local port=$1
9+
local timeout=30
10+
local count=0
11+
12+
echo "Waiting for port $port to be free..."
13+
while lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1; do
14+
if [ $count -ge $timeout ]; then
15+
echo "Timeout waiting for port $port to be free"
16+
return 1
17+
fi
18+
sleep 1
19+
count=$((count + 1))
20+
done
21+
echo "Port $port is now free"
22+
}
23+
24+
# Function to wait for port to be ready
25+
wait_for_port_ready() {
26+
local port=$1
27+
local timeout=30
28+
local count=0
29+
30+
echo "Waiting for port $port to be ready..."
31+
while ! curl -s http://localhost:$port/health >/dev/null 2>&1; do
32+
if [ $count -ge $timeout ]; then
33+
echo "Timeout waiting for port $port to be ready, trying anyway..."
34+
return 0
35+
fi
36+
sleep 1
37+
count=$((count + 1))
38+
done
39+
echo "Port $port is ready"
40+
}
41+
42+
# Function to stop application
43+
stop_app() {
44+
local pid=$1
45+
if [ ! -z "$pid" ] && kill -0 $pid 2>/dev/null; then
46+
echo "Stopping server (PID: $pid)..."
47+
kill $pid
48+
wait_for_port_free 8080
49+
fi
50+
}
51+
52+
# Generate payload
53+
python3 generate_payload.py > payload.json
54+
55+
# Clean up
56+
pkill -f "simple-server.js" || true
57+
wait_for_port_free 8080
58+
59+
echo ""
60+
echo "=== Testing Node.js (No Database) ==="
61+
node simple-server.js &
62+
NODE_PID=$!
63+
wait_for_port_ready 8080
64+
wrk -t12 -c2000 -d30s -s post.lua http://localhost:8080/ingest > result_simple_node.txt
65+
stop_app $NODE_PID
66+
67+
echo ""
68+
echo "=== Testing Bun (No Database) ==="
69+
bun simple-server.js &
70+
BUN_PID=$!
71+
wait_for_port_ready 8080
72+
wrk -t12 -c2000 -d30s -s post.lua http://localhost:8080/ingest > result_simple_bun.txt
73+
stop_app $BUN_PID
74+
75+
echo ""
76+
echo "=== Results Comparison (No Database) ==="
77+
echo "Node.js (No DB):"
78+
grep -E "(Requests/sec|Transfer/sec|Latency)" result_simple_node.txt || echo "No results"
79+
echo ""
80+
echo "Bun (No DB):"
81+
grep -E "(Requests/sec|Transfer/sec|Latency)" result_simple_bun.txt || echo "No results"

0 commit comments

Comments
 (0)