Bouw een Real-Time Veilingmotor met Supabase Realtime
Je eerste veilingmotor polde elke seconde de database. Biedingen kwamen in willekeurige volgorde aan. Sommige verdwenen helemaal. Je voegde een aparte WebSocket-server toe voor de tweede versie — beter, maar nu beheer je twee infrastructuren. De derde poging gebruikte Supabase Realtime, en de race conditions stopten. Geen polling loop. Geen standalone socket server. PostgreSQL triggers broadcast bid updates het moment dat ze committen, en elke verbonden client ziet dezelfde staat binnen 50 milliseconden. Ik heb in twee jaar drie veilingsystemen uitgerold. De Supabase versie is de enige die ik niet na lancering hoefde te herbouwen. Hier is de architectuur die het werkende maakte — en de ene functie die het bijna verbrak.
Supabase Realtime zit bovenop PostgreSQL's Write-Ahead Log (WAL) en gebruikt een Elixir-gebaseerde server om databasewijzigingen via WebSockets naar verbonden clients te pushen. Voor een veilingsysteem betekent dit dat elke bieding die je database raakt onmiddellijk naar elke bieder die die veiling bekijkt verspreidt. Geen polling. Geen aparte pub/sub infrastructuur. Je database is je event systeem.
Laten we er eentje vanaf nul bouwen.
Inhoudsopgave
- Architectuuroverzicht
- Databaseschema en setup
- PostgreSQL triggers voor veilinglogica
- Client-side abonnement met JavaScript
- Race conditions en bid validatie afhandelen
- Presence tracking voor actieve bieders
- Prestatieafstemming en productiebeschouwingen
- Supabase Realtime versus alternatieven
- Je veilingsysteem implementeren en schalen
- Veelgestelde vragen
Architectuuroverzicht
Voordat we code schrijven, gaan we begrijpen wat we bouwen en hoe de stukken samenpassen.
Supabase Realtime geeft je drie primitieven die perfect aansluiten op vereisen:
- Postgres Changes: Abonneer je op INSERT, UPDATE en DELETE events op je bid en auction tabellen. Wanneer iemand een bieding plaatst, krijgen alle abonnees de nieuwe rijgegevens binnen milliseconden.
- Broadcast: Stuur kortstondige berichten naar kanaaldeelnemers. Perfect voor "je bent overboden" meldingen die niet persistent hoeven te zijn.
- Presence: Volg wie momenteel een veiling bekijkt. Hiermee kun je "14 bieders kijken" in je UI weergeven en ghost sessies detecteren.
De gegevensstroom ziet er als volgt uit:
- Bieder dient een bieding in via je frontend
- Een RPC call of directe insert raakt je
bidstabel - Een PostgreSQL trigger valideert het biedingsbedrag en werkt
auctions.current_high_bidbij - Supabase Realtime pikt de WAL verandering op en pusht deze naar alle abonnees op dat veilingkanaal
- Een tweede trigger stuurt een Broadcast event om de vorige topbieder op de hoogte te stellen dat ze zijn overboden
- Elke verbonden client werkt hun UI in real-time bij
De latency van bid plaatsing tot UI update over alle clients is typisch onder 100ms. Ik heb p99 gemeten op ongeveer 80-90ms in productie op Supabase's Pro tier.
Waarom niet gewoon polling gebruiken?
Ik weet dat sommigen van jullie denken "kan ik niet gewoon elke 500ms pollen?" Dat kun je. Maar bij 200 gelijktijdige bieders op een enkele veiling, dat zijn 400 verzoeken per seconde die je database raken voor één veiling. Vermenigvuldig dat met 50 actieve veilingen en je bent op 20.000 queries per seconde — waarvan de meeste niets nieuws opleveren. WebSockets draaien dit model om: nul queries wanneer niets verandert, instant updates wanneer iets wel verandert.
Databaseschema en setup
Hier is het schema dat ik gebruik. Het is opzettelijk eenvoudig — je kunt het uitbreiden, maar de kernstructuur behandelt de meeste veilingtypes.
-- Auctions tabel
CREATE TABLE auctions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
item_name TEXT NOT NULL,
description TEXT,
starting_price DECIMAL(12,2) NOT NULL DEFAULT 0,
current_high_bid DECIMAL(12,2) DEFAULT 0,
highest_bidder_id UUID REFERENCES auth.users(id),
min_increment DECIMAL(12,2) DEFAULT 1.00,
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('scheduled', 'active', 'ended', 'sold', 'cancelled')),
starts_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ends_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + INTERVAL '30 minutes',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Bids tabel
CREATE TABLE bids (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
auction_id UUID NOT NULL REFERENCES auctions(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
amount DECIMAL(12,2) NOT NULL,
placed_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT positive_amount CHECK (amount > 0)
);
-- Index voor snelle bid lookups per veiling
CREATE INDEX idx_bids_auction_amount ON bids(auction_id, amount DESC);
CREATE INDEX idx_bids_auction_time ON bids(auction_id, placed_at DESC);
-- Kritisch: enable replica identity voor Realtime
ALTER TABLE auctions REPLICA IDENTITY FULL;
ALTER TABLE bids REPLICA IDENTITY FULL;
De REPLICA IDENTITY FULL instelling is essentieel. Zonder deze krijgt Supabase Realtime alleen de primary key op UPDATE en DELETE events — niet de volledige rijgegevens. Voor een veilingsysteem heb je de volledige payload nodig zodat clients bieddingsbedragen kunnen bijwerken zonder een apart query uit te voeren.
Replicatie inschakelen
Ga in het Supabase Dashboard naar Database → Replication en schakel replicatie in voor beide auctions en bids tabellen. Je kunt dit ook doen met SQL:
BEGIN;
-- Verwijder bestaande publicatie als deze bestaat
DROP PUBLICATION IF EXISTS supabase_realtime;
-- Maak publicatie met beide tabellen
CREATE PUBLICATION supabase_realtime FOR TABLE auctions, bids;
COMMIT;
Row-Level Security
Sla dit niet over. RLS is je server-side validatielaag.
ALTER TABLE auctions ENABLE ROW LEVEL SECURITY;
ALTER TABLE bids ENABLE ROW LEVEL SECURITY;
-- Iedereen kan actieve veilingen bekijken
CREATE POLICY "Public auction viewing" ON auctions
FOR SELECT USING (status IN ('active', 'ended', 'sold'));
-- Geverifieerde gebruikers kunnen alle biedingen op actieve veilingen bekijken
CREATE POLICY "View bids on active auctions" ON bids
FOR SELECT USING (
EXISTS (
SELECT 1 FROM auctions
WHERE auctions.id = bids.auction_id
AND auctions.status = 'active'
)
);
-- Alleen geverifieerde gebruikers kunnen biedingen plaatsen
CREATE POLICY "Place bids" ON bids
FOR INSERT WITH CHECK (
auth.uid() = user_id
AND EXISTS (
SELECT 1 FROM auctions
WHERE auctions.id = auction_id
AND auctions.status = 'active'
AND auctions.ends_at > NOW()
)
);
PostgreSQL triggers voor veilinglogica
Hier gebeurt de echte magie. De database dwingt alle veilinglogica server-side af — de client kan niet vals spelen.
Bid validatie en veiling update trigger
CREATE OR REPLACE FUNCTION process_new_bid()
RETURNS TRIGGER AS $$
DECLARE
v_auction auctions%ROWTYPE;
BEGIN
-- Lock de veiling rij om race conditions te voorkomen
SELECT * INTO v_auction
FROM auctions
WHERE id = NEW.auction_id
FOR UPDATE;
-- Valideer dat veiling actief is
IF v_auction.status != 'active' THEN
RAISE EXCEPTION 'Veiling is niet actief';
END IF;
-- Valideer dat veiling niet is afgelopen
IF v_auction.ends_at < NOW() THEN
RAISE EXCEPTION 'Veiling is afgelopen';
END IF;
-- Valideer dat bieddingsbedrag huidge hoog + minimale verhoging overschrijdt
IF NEW.amount < v_auction.current_high_bid + v_auction.min_increment THEN
RAISE EXCEPTION 'Bieding moet minstens % hoger zijn dan huідige topbieding van %',
v_auction.min_increment, v_auction.current_high_bid;
END IF;
-- Voorkom zelf-overbodenen
IF v_auction.highest_bidder_id = NEW.user_id THEN
RAISE EXCEPTION 'Je bent al de hoogste bieder';
END IF;
-- Update veiling met nieuw topbod
UPDATE auctions
SET
current_high_bid = NEW.amount,
highest_bidder_id = NEW.user_id,
updated_at = NOW()
WHERE id = NEW.auction_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER validate_and_process_bid
BEFORE INSERT ON bids
FOR EACH ROW
EXECUTE FUNCTION process_new_bid();
Die FOR UPDATE lock op de veiling rij is kritiek. Zonder deze zouden twee biedingen die gelijktijdig arriveren beide dezelfde current_high_bid kunnen lezen, beide validatie doorstaan, en beide ingevoegd worden. De lock serialiseert toegang.
Broadcast overbodenen meldingen
Deze trigger vuur na een succesvolle bieding en stuurt een kortstondige melding naar het veilingkanaal:
CREATE OR REPLACE FUNCTION notify_outbid()
RETURNS TRIGGER AS $$
DECLARE
v_previous_bidder UUID;
BEGIN
-- Vind wie net is overboden
SELECT user_id INTO v_previous_bidder
FROM bids
WHERE auction_id = NEW.auction_id
AND id != NEW.id
ORDER BY amount DESC
LIMIT 1;
-- Broadcast overbodenen melding als er een vorige bieder was
IF v_previous_bidder IS NOT NULL THEN
PERFORM realtime.send(
jsonb_build_object(
'auction_id', NEW.auction_id,
'new_high', NEW.amount,
'outbid_user', v_previous_bidder,
'new_leader', NEW.user_id
),
'outbid',
'auction:' || NEW.auction_id::text,
true
);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER after_bid_notify
AFTER INSERT ON bids
FOR EACH ROW
EXECUTE FUNCTION notify_outbid();
Client-side abonnement met JavaScript
Laten we nu de frontend aansluiten. Ik zal dit tonen met vanilla JavaScript/React patronen — dezelfde aanpak werkt als je bouwt met Next.js of elk ander framework.
Initialiseer de client
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
realtime: {
params: {
eventsPerSecond: 20 // Throttle voor veilingverkeer
}
}
}
);
Die eventsPerSecond parameter doet ertoe. Op een hete veiling met tientallen biedingen per seconde, wil je niet 50 keer per seconde herstellen. Twintig updates per seconde is meer dan genoeg voor een vloeiende UI.
Abonneer op een veilingkanaal
function subscribeToAuction(auctionId, callbacks) {
const channel = supabase.channel(`auction:${auctionId}`);
channel
// Luister naar nieuwe biedingen via Postgres Changes
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'bids',
filter: `auction_id=eq.${auctionId}`
}, (payload) => {
callbacks.onNewBid(payload.new);
})
// Luister naar veilingstatuswijzigingen
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'auctions',
filter: `id=eq.${auctionId}`
}, (payload) => {
callbacks.onAuctionUpdate(payload.new);
})
// Luister naar overbodenen broadcast meldingen
.on('broadcast', { event: 'outbid' }, ({ payload }) => {
callbacks.onOutbid(payload);
})
// Volg actieve bieders via Presence
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState();
const bidderCount = Object.keys(state).length;
callbacks.onPresenceUpdate(bidderCount, state);
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
// Volg de presence van deze gebruiker
await channel.track({
user_id: supabase.auth.getUser()?.data?.user?.id,
status: 'watching',
joined_at: new Date().toISOString()
});
}
});
return channel;
}
React Hook voor veilingabonnementen
import { useState, useEffect, useCallback } from 'react';
function useAuction(auctionId) {
const [auction, setAuction] = useState(null);
const [bids, setBids] = useState([]);
const [bidderCount, setBidderCount] = useState(0);
const [isOutbid, setIsOutbid] = useState(false);
useEffect(() => {
// Laad initiële staat
async function loadAuction() {
const { data: auctionData } = await supabase
.from('auctions')
.select('*')
.eq('id', auctionId)
.single();
setAuction(auctionData);
const { data: bidData } = await supabase
.from('bids')
.select('*')
.eq('auction_id', auctionId)
.order('amount', { ascending: false })
.limit(20);
setBids(bidData || []);
}
loadAuction();
// Abonneer op real-time updates
const channel = subscribeToAuction(auctionId, {
onNewBid: (bid) => {
setBids(prev => [bid, ...prev].slice(0, 20));
setIsOutbid(false);
},
onAuctionUpdate: (updated) => setAuction(updated),
onOutbid: (payload) => {
const currentUser = supabase.auth.getUser()?.data?.user;
if (payload.outbid_user === currentUser?.id) {
setIsOutbid(true);
}
},
onPresenceUpdate: (count) => setBidderCount(count)
});
return () => {
supabase.removeChannel(channel);
};
}, [auctionId]);
const placeBid = useCallback(async (amount) => {
const user = (await supabase.auth.getUser()).data.user;
const { data, error } = await supabase
.from('bids')
.insert({
auction_id: auctionId,
amount: parseFloat(amount),
user_id: user.id
})
.select()
.single();
if (error) throw new Error(error.message);
return data;
}, [auctionId]);
return { auction, bids, bidderCount, isOutbid, placeBid };
}
Race conditions en bid validatie afhandelen
Race conditions zijn de grootste bron van bugs in veilingsystemen. Hier is hoe ik dit aanpak.
Server-side: PostgreSQL doet het zware werk
De SELECT ... FOR UPDATE in onze trigger functie is de eerste verdedigingslinie. Maar er is een ander patroon dat ik ben gaan gebruiken — advisory locks voor high-contention veilingen:
CREATE OR REPLACE FUNCTION place_bid_safe(
p_auction_id UUID,
p_user_id UUID,
p_amount DECIMAL
)
RETURNS TABLE(bid_id UUID, new_high DECIMAL) AS $$
DECLARE
v_lock_key BIGINT;
v_bid_id UUID;
BEGIN
-- Genereer een deterministische lock sleutel van auction UUID
v_lock_key := ('x' || left(p_auction_id::text, 15))::bit(64)::bigint;
-- Verkrijg advisory lock (blokkeert gelijktijdige biedingen op dezelfde veiling)
PERFORM pg_advisory_xact_lock(v_lock_key);
-- Nu veilig om in te voegen (trigger handelt validatie af)
INSERT INTO bids (auction_id, user_id, amount)
VALUES (p_auction_id, p_user_id, p_amount)
RETURNING id INTO v_bid_id;
RETURN QUERY
SELECT v_bid_id, p_amount;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Roep dit aan van de client met behulp van Supabase's RPC:
const { data, error } = await supabase.rpc('place_bid_safe', {
p_auction_id: auctionId,
p_user_id: user.id,
p_amount: bidAmount
});
Client-side: Optimistische UI met rollback
Toon de bieding onmiddellijk in de UI, maar wees bereid om deze terug te draaien als de server deze afwijst:
async function handleBidSubmit(amount) {
const optimisticBid = {
id: crypto.randomUUID(),
amount,
user_id: user.id,
placed_at: new Date().toISOString(),
_optimistic: true
};
// Toon onmiddellijk
setBids(prev => [optimisticBid, ...prev]);
try {
await placeBid(amount);
// Werkelijke bieding komt via Realtime en vervangt optimistische
} catch (err) {
// Verwijder optimistische bieding bij fout
setBids(prev => prev.filter(b => b.id !== optimisticBid.id));
showError(err.message);
}
}
Presence tracking voor actieve bieders
Het tonen hoeveel mensen een veiling bekijken creëert urgentie. Presence tracking is doodseenvoudig met Supabase:
// Update gebruikersstatus wanneer ze beginnen met bieden
async function updatePresenceStatus(channel, status) {
await channel.track({
user_id: user.id,
status, // 'watching', 'bidding', 'won'
last_active: new Date().toISOString()
});
}
Aan de weergavekant kun je de presence staat afbreken om te tonen hoevelen actief bieden versus alleen kijken:
function parseBidderStats(presenceState) {
const users = Object.values(presenceState).flat();
return {
total: users.length,
bidding: users.filter(u => u.status === 'bidding').length,
watching: users.filter(u => u.status === 'watching').length
};
}
Prestatieafstemming en productiebeschouwingen
Throttling en Debouncing
Een biedingsoorlog kan tientallen events per seconde genereren. Hier is wat ik configureer:
- Server-side:
eventsPerSecond: 20op de Supabase client config - Client-side: Debounce de bid knop op 300ms om dubbele klikken te voorkomen
- UI updates: Gebruik
requestAnimationFramevoor bid list animaties
Veilingeinde timing
Vertrouw niet op de client klok. Gebruik een PostgreSQL cron job via pg_cron:
-- Voer elke 10 seconden uit om verlopen veilingen te sluiten
SELECT cron.schedule(
'close-expired-auctions',
'*/10 * * * * *',
$$
UPDATE auctions
SET status = CASE
WHEN highest_bidder_id IS NOT NULL THEN 'sold'
ELSE 'ended'
END
WHERE status = 'active'
AND ends_at <= NOW();
$$
);
Anti-snipe verlenging
De meeste veilingplatformen verlengen de deadline als een bieding binnenkomt in de laatste paar seconden:
-- Voeg toe aan de process_new_bid trigger
IF v_auction.ends_at - NOW() < INTERVAL '30 seconds' THEN
UPDATE auctions
SET ends_at = ends_at + INTERVAL '30 seconds'
WHERE id = NEW.auction_id;
END IF;
Supabase Realtime versus alternatieven
Ik heb de meeste hiervan in productie gebruikt. Hier is een eerlijke vergelijking:
| Functie | Supabase Realtime | Pusher | Ably | Firebase RTDB | Socket.io (zelf gehost) |
|---|---|---|---|---|---|
| Native DB sync | ✅ PostgreSQL WAL | ❌ Aparte service | ❌ Aparte service | ✅ JSON tree | ❌ Handmatig |
| Latency (p99) | ~80-100ms | ~60ms | ~50ms | ~100ms | ~40ms (hangt van infra af) |
| Max events/sec | 200k+ | 10k (Pro) | 50k | 100k | Ongelimiteerd (je schaalt het) |
| Auth integratie | Ingebouwd (RLS + JWT) | Aangepast | Token-gebaseerd | Firebase Auth | Aangepast |
| Presence | ✅ Ingebouwd | ✅ Ingebouwd | ✅ Ingebouwd | ✅ Ingebouwd | ✅ Ingebouwd |
| Gratis tier | 500K MAU, 200 concurrent | 100 connections | 6M msgs/mo | 1GB opgeslagen | $0 (hostingkosten) |
| Pro pricing | $25/mo | $49/mo | $29/mo | Pay-as-you-go | ~$100-500/mo (AWS) |
| Best voor | DB-gerichte real-time apps | Eenvoudige pub/sub | Hoge betrouwbaarheid | Mobiele apps | Volledige controle |
Voor een veilingsysteem specifiek, wint Supabase omdat je biedingen al in PostgreSQL staan. Je hoeft niet tussen een database en een aparte pub/sub systeem te synchroniseren. De bieding raakt de DB, de DB triggert de WebSocket push. Één bron van waarheid.
Als je bouwt op een headless CMS architectuur, past Supabase natuurlijk naast contentlevering zonder nog een service toe te voegen om te beheren.
Je veilingsysteem implementeren en schalen
Voor de meeste projecten handelt Supabase's managed Pro tier op $25/maand gemakkelijk tot 10.000 dagelijkse veilingen. Hier is wat je in de gaten moet houden:
- Verbindingslimieten: Pro tier geeft je 500 gelijktijdige Realtime verbindingen. Als je meer nodig hebt, moet je upgraden of connection pooling aan de client-kant implementeren.
- WAL grootte: High-volume bidding genereert aanzienlijk WAL verkeer. Monitor je replicatie slot om schijfgroei te voorkomen.
- Aantal kanalen: Elke veiling krijgt zijn eigen kanaal. Met duizenden actieve veilingen, test dat je client zich correct van beëindigde veilingen afmeldt.
Voor een frontend gebouwd met Astro of Next.js, werkt de Supabase JS client identiek — zorg ervoor dat je het client-side initialiseert voor Realtime abonnementen.
Als je iets bouwt dat serieuze schaal moet afhandelen — honderdduizenden gelijktijdige bieders — neem contact met ons op. We hebben deze systemen op schaal gearchitecteerd en kunnen je helpen valkuilen te vermijden. Je kunt ook onze prijzingspagina controleren voor op project gebaseerde contracten.
Veelgestelde vragen
Hoeveel gelijktijdige bieders kan Supabase Realtime afhandelen? Supabase Realtime kan meer dan 200.000 events per seconde afhandelen over verdeelde servers op hun managed platform. De Pro tier op $25/maand ondersteunt tot 500 gelijktijdige verbindingen per project. Voor grotere veilingen biedt de Enterprise tier aangepaste limieten, of je kunt de Realtime server zelf hosten (het is open source) op je eigen infrastructuur.
Is Supabase Realtime snel genoeg voor een live veiling? Ja. In mijn testen bedraagt de end-to-end latency van bid insert naar client melding gemiddeld ongeveer 50-80ms, met p99 onder 100ms. Voor context, een menselijke reactietijd is ongeveer 200-300ms, dus biedingen lijken effectief onmiddellijk. De bottleneck is zelden Supabase — het is meestal de clientnetwerk verbinding.
Hoe voorkom ik race conditions wanneer twee mensen gelijktijdig bieden?
Gebruik PostgreSQL's SELECT ... FOR UPDATE rij-level locking binnenin een trigger functie, of gebruik advisory locks via pg_advisory_xact_lock(). Dit serialiseert bid verwerking per veiling zodat slechts één bid tegelijk wordt gevalideerd. Het "verlies" bid wordt nog steeds gevalideerd — het ziet alleen de bijgewerkte topbieding van de winnaar en slaagt ofwel (als het nog steeds hoger is) of mislukt met een passende fout.
Kan ik Supabase Realtime gebruiken met Next.js of Astro?
Absoluut. De @supabase/supabase-js client werkt in elke JavaScript omgeving. Voor Next.js, initialiseer de Supabase client in een client component (omdat Realtime browser WebSockets nodig heeft) en gebruik het binnenin useEffect hooks. Voor Astro, gebruik het in client-side interactieve islands. De abonnementscode is identiek ongeacht je framework keuze.
Wat gebeurt er als de verbinding van een gebruiker midden-veiling wordt verbroken? Supabase Realtime probeert automatisch opnieuw verbinding te maken. Wanneer de client opnieuw verbinding maakt en zich opnieuw abonneert, ontvangt het de huidge staat. Voor kritieke veilingen, ik raad aan ook de nieuwste veilingsstaat via een standaard query op reconnectie te halen om ervoor te zorgen dat niets wordt gemist tijdens het verbreken van de verbinding. Het Presence systeem verwijdert de verbroken gebruiker automatisch na een timeout.
Hoe handel ik veilingeindetijden nauwkeurig af?
Vertrouw nooit op client-side timers voor veilingeindetijden — deze kunnen worden gemanipuleerd. Gebruik PostgreSQL's pg_cron extensie om elke 10 seconden server-side te controleren op en verlopen veilingen te sluiten. Stuur de servertijdstempel naar clients zodat zij een aftelling kunnen weergeven, maar de daadwerkelijke eindbepaling gebeurt altijd in de database.
Is Supabase Realtime gratis voor kleine projecten? Supabase's gratis tier bevat Realtime met tot 200 gelijktijdige verbindingen en 500.000 maandelijks actieve gebruikers. Dat is genoeg voor een hobby veilingsite of MVP. Als je een productie veilingplatform met betekenisvol verkeer draait, de Pro tier op $25/maand met $0.09/GB egress is waar je mee wilt beginnen. Het is aanzienlijk goedkoper dan je eigen WebSocket infrastructuur draaien.
Hoe test ik een real-time biedingssysteem lokaal?
Gebruik de Supabase CLI (supabase start) om een lokale Supabase instantie met Realtime ingeschakeld te draaien. Open meerdere browsertabbladen om meerdere bieders te simuleren. Voor load testen, ik gebruik een eenvoudig Node.js script dat 100+ Supabase clients maakt en deze tegen elkaar laat bieden op een timer. Dit pakt race conditions en helpt je je eventsPerSecond parameter af te stemmen vóór je naar productie gaat.