← All Posts

September 24, 2026

Anonymous Submissions and the Egress Scare

By

A viewer's idea for keeping bracket submitters anonymous, a Supabase bill that nearly maxed out the free tier in one bad day, and the best traffic day this site has ever had.

Anonymous Submissions & the Egress Scare card from Aux Battles, showing Anonymous bracket submissions, her idea
Anonymous Submissions and the Egress Scare

Two very different threads this week: a small, well-aimed feature request from a streamer testing bracket mode, and a Supabase bill that came uncomfortably close to the free tier's ceiling in the same 24 hours. Both get their own section, because they deserve different kinds of honesty. One is a nice story about somebody helping ship something. The other is closer to how it actually felt reading a request log at 1am.

Anonymous Submissions, Nikki's Idea

The bracket match screen has always shown who submitted each song, which is fine for two friends and a little awkward on a big stream, where a song from the streamer's favorite mod reads differently to chat than the same song from a stranger. Nikki Glicky was the one who actually said it out loud: chat should be voting for the song, not for whoever posted it.

That's a real setting now. Turn on Anonymous Submissions and both song slots, the vote bars, the bye card and the bracket tree itself keep every entrant's name off screen until that round is over, when the reveal shows who actually made it through. It only shows up on bracket mode, since a classic battle already plays every song without a name attached to it in the first place, there's nothing there to hide.

Thanks for the idea, Nikki. If you haven't already, go watch her stream: twitch.tv/nikkiglicky.

The Best Day This Site Has Had

Worth saying plainly, because the next two sections are not a good look otherwise: this was a strong week for actual traffic. September 21st hit 870 visitors in a single day by GA4's count, a new personal best, comfortably ahead of anything recorded before it.

Which is exactly the kind of growth that turns a sloppy query into a real bill. That's the less flattering half of this post.

The Bill That Almost Maxed Out

Supabase's free tier caps egress, the data actually shipped out of the database, at 5 GB a month. A look at the usage dashboard that same day showed a day that had burned through roughly 500 MB on its own, with the monthly total on track to clear the entire 5 GB allowance before the billing cycle was even done. That's not a gentle warning, that's a site about to either get throttled or force an unplanned upgrade.

The uncomfortable part, same as it usually is with this project, is that nothing was actually broken. Every request was succeeding. The database wasn't struggling. It was just being asked to hand back far more data than the traffic on the site should have needed, over and over, from the same handful of endpoints.

Where It Was Actually Going

The worst offender was the star rating screen. Every client watching a battle subscribes to vote changes for the room, and the handler used to respond to any vote event by re-reading every vote for the current song from scratch, the full row, every column. That sounds harmless until you count what "any vote event" means: if five people are rating a song, the fifth vote doesn't write once, it triggers five separate full re-reads, one for every client watching, each pulling back the whole list of votes so far. N voters costs N-squared reads, and a Twitch host relaying chat's votes fans that same read out to every viewer's tab at once.

The comment left behind in the fix puts a number on it: this one pattern was responsible for something like a third of all the Supabase traffic the site was generating. Pulling the project's own request log for the hours right before the fix landed backs that up closely, close to a third of every request hitting the API in that window was this same query, repeated.

A smaller, quieter cost sat right next to it. Submitting a single star rating called Supabase's auth endpoint to confirm who was voting before writing the vote itself, a full round trip to /auth/v1/user for an id the browser was already holding in its own session. Multiply that by every rating, on every song, in every room, and it's a lot of calls that never needed to leave the browser at all.

The Fix

Three changes went out, two of them the same night the numbers were found:

  • Stop re-reading, start merging. The battle screen now reads the vote list once, on subscribe, and after that folds each realtime vote event straight into the tally it already holds in memory. No more full re-fetch per vote, no matter how many people are in the room.
  • Stop asking who's voting. Submitting a vote now reads the voter's id off the session already sitting in the browser instead of calling out to confirm it, cutting one full network round trip off every single rating.
  • A database cleanup alongside it. A batch of Postgres row-security policies got checked against Supabase's own performance advisor: several were re-evaluating auth.uid() once per row instead of once per query, two foreign keys had no covering index and were forcing full table scans on a delete or join, and a handful of internal trigger functions were reachable from outside the app by anyone who knew their name. All three are fixed in the same migration.

The numbers moved fast. The cleanest way to measure this without confusing it with how much anyone happened to be playing at the time is reads against writes on the votes table: the hours before the fix were running at roughly eleven reads for every vote actually cast. The hours after, roughly two. That's the fix working, the storm is gone, and what's left is close to the one read a client actually needs when it opens the screen.

What Is Still Open

The auth round trip removed from voting is still sitting in a few other places, room setup and a couple of preference reads, that fire far less often and were never what was burning through the free tier, so they're lower priority rather than ignored.

And honestly, the number that matters most, a full day's egress total with the fix in place, is still being watched rather than known. The worst of it landed hours before this post went out. What's already measurable is that the one query responsible for roughly a third of the site's traffic stopped acting like it, and that felt worth writing down while it was fresh.