From Batch Chaos to Event-Driven Zen: Fixing Hall of Shame Publishing
- Patrick Duggan
- Nov 4, 2025
- 4 min read
# From Batch Chaos to Event-Driven Zen: Fixing Hall of Shame Publishing
**Issue:** #191 (CLOSED)
**Philosophy:** "Only new assholes who EARN the shame"
**Architecture:** Event-driven > batch processing
The Problem (Again)
Earlier today we DDoS'd ourselves for a penny by running a broken batch publisher every hour.
**The broken flow:**
1. Auto-blocker blocks malicious IP
2. Hall of Shame entry created in Azure Table Storage
3. **Hourly scheduler** scans for "unpublished" entries
4. **Broken regex matching** thinks ALL 256 are unpublished
5. Publishes same 204 posts every hour
6. Repeat forever
**Cost:** $0.01 + blog spam + elevated blood pressure
**Quick fix:** Disabled the scheduler
**Proper fix:** Event-driven publishing (just deployed)
The Solution: Publish at Block-Time
Architecture Change
**OLD (batch processing):**
**NEW (event-driven):**
Code Implementation
**Location:** `microservices/analytics-dashboard/lib/auto-blocker.js:310-329`
Why Event-Driven is Better
1. Publish Exactly Once
**Batch processing:**
- Runs every hour, even if no new blocks
- Requires matching logic to find "unpublished" entries
- Broken matching = infinite republishing
**Event-driven:**
- Publishes when blocking happens
- No matching logic needed (we have the IP right there)
- Publish once, done
2. No Regex Matching Required
**Batch processing:**
**Event-driven:**
3. Graceful Failure
**Batch processing:**
- Scheduler fails → no blogs published until next run
- Blocking and blogging coupled in scheduler
- Hard to debug (which part failed?)
**Event-driven:**
- Blocking succeeds even if blog publishing fails
- Clear error logs per IP
- Independent operations
4. Real-Time Publishing
**Batch processing:**
- Block at 10:15 AM
- Wait until 11:00 AM for scheduler
- Blog published 45 minutes later
**Event-driven:**
- Block at 10:15 AM
- Blog published at 10:15 AM
- Instant Hall of Shame gratification
Benefits Realized
Technical
✅ **Simpler architecture** (no scheduler needed)
✅ **No regex matching** (direct IP→blog)
✅ **No batch processing overhead** (publish on-demand)
✅ **Graceful degradation** (blocking works even if blogging fails)
✅ **Real-time publishing** (assholes shamed immediately)
Operational
✅ **No more blog spam** (publish once per block)
✅ **No infinite loops** (no broken matching logic)
✅ **Easy debugging** (clear per-IP logs)
✅ **Cost-efficient** (only run when needed)
Philosophical
✅ **"Only new assholes who EARN the shame"** (not retroactive batch)
✅ **Event-driven > batch processing** (proven again)
✅ **Fail fast, fail loud** (don't hide blog errors)
Testing Plan
Next Auto-Block Execution
**Monitor logs for:**
**Or on failure:**
Manual Test
Verification
1. ✅ Next IP blocked → Hall of Shame blog appears at www.dugganusa.com
2. ✅ No duplicate posts created
3. ✅ Blog published within seconds of blocking (not hours later)
4. ✅ Blocking succeeds even if blog publishing fails
Changes Deployed
**1. Auto-blocker integration** (`lib/auto-blocker.js:310-329`)
- Added event-driven publishing after `addToHallOfShame()`
- Publishes immediately for each blocked IP
- Graceful error handling
**2. Scheduler disabled** (`lib/scheduler-manager.js:85`)
**3. Blog posts published:**
- [Self-DDoS for a Penny](https://www.dugganusa.com/post/we-ddos-d-ourselves-for-a-penny-and-called-it-load-testing) (the incident)
- This post (the fix)
**4. GitHub Issue closed:**
- Issue #191: Event-Driven vs Batch Publishing ✅
Lessons Learned
1. Event-Driven > Batch Processing
**When to use batch:**
- Processing historical data
- Known bounded datasets
- Non-time-sensitive operations
**When to use event-driven:**
- Real-time operations (blocking assholes)
- One-to-one mappings (block → blog)
- Operations that should happen exactly once
**Our case:** Blocking → blogging is 1:1, real-time → **Event-driven wins**
2. Don't Rely on Complex Matching Logic
**Batch processing required:**
- Regex to match filenames
- Fetch from Wix to compare
- Logic to determine "unpublished"
- **All points of failure**
**Event-driven requires:**
- IP address (we already have it)
- **That's it**
3. Fail Independently
**Coupled operations:**
**Independent operations:**
4. Test Your Assumptions
**We assumed:**
- Wix slugs matched our filename pattern
- Regex would find published posts
- Batch publishing would "just work"
**Reality:**
- Wix uses different slug format
- Regex matched NOTHING
- Batch publishing created infinite loop
**Lesson:** Test integration points, don't assume
Cost Analysis
Original Bug
- **Financial cost:** $0.01 (one penny)
- **API calls:** 800-1,600 to Wix
- **Time to detect:** ~3 hours
- **Infrastructure impact:** Zero (handled it fine)
Fix Implementation
- **Development time:** 45 minutes
- **Code changed:** 27 lines added
- **Tests written:** Manual test plan
- **Deployment:** Commit + push
- **Cost:** $0.00 (free architecture improvement)
ROI
- **Eliminated:** Hourly scheduler overhead
- **Eliminated:** Regex matching complexity
- **Eliminated:** Batch processing logic
- **Gained:** Real-time publishing
- **Gained:** Simpler architecture
- **Gained:** Better error handling
**Net benefit:** Infinite (simpler is better)
Philosophy: "Only New Assholes Who EARN the Shame"
**The Problem:**
Batch publishing treated all 256 Hall of Shame files equally. Some were from weeks ago, already published, but the broken matching logic republished them anyway.
**The Fix:**
Event-driven publishing only publishes when someone NEW gets blocked. You don't EARN the Hall of Shame by existing in a markdown file. You EARN it by getting blocked TODAY.
**The Principle:**
Actions (blocking) should trigger reactions (blogging) immediately. Not hours later via batch processing. Not retroactively via scheduler. **Right when you fuck around, you find out.**
That's event-driven architecture in a nutshell.
What's Next
**Immediate (done):**
- ✅ Event-driven publishing deployed
- ✅ Scheduler disabled
- ✅ Issue #191 closed
- ✅ Blog posts published
**Near-term (monitoring):**
- Monitor next auto-block execution
- Verify Hall of Shame blogs appear
- Confirm no duplicate posts
- Validate error handling
**Long-term (enhancements):**
- Rate limiting (if we block 100+ IPs at once)
- Retry logic (if Wix API fails)
- Queue-based publishing (for resilience)
- Metrics (publish success rate, latency)
**But for now:** KISS. Event-driven inline publishing works. Ship it.
Final Thought: From Chaos to Zen
**This morning:** Panic ("We're being DDoS'd!")
**Mid-morning:** Diagnosis (We DDoS'd ourselves)
**Afternoon:** Fix (Event-driven publishing)
**Evening:** Blog about it (Learning in Motion)
**Cost:** One penny + 45 minutes + this blog post
**Value:** Better architecture + teaching content + proof that we learn from fuckups
That's bootstrapping. That's Learning in Motion. That's building in public.
**From batch chaos to event-driven zen in under 6 hours.**
Not bad for a penny.
**🎯 Status:** Issue #191 CLOSED
**📦 Deployed:** Event-driven Hall of Shame publishing
**🚀 Next block:** Will auto-publish blog immediately
**💰 Cost:** $0.01 (original bug) + $0.00 (fix)
**🧘 Philosophy:** Event-driven > batch processing
**🏆 Quote:** "Only new assholes who EARN the shame"
*Generated with: Satisfaction, after fixing the fuckup*
*Published via: The same system we just fixed (meta)*
*Lesson: Event-driven beats batch every time*
*Status: Zen achieved*
🎯💡🚀🏆✅




Comments