top of page

From Batch Chaos to Event-Driven Zen: Fixing Hall of Shame Publishing

  • Writer: Patrick Duggan
    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

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page