Erstellen Sie eine Echtzeit-Bidding-Engine mit Supabase Realtime
Ich habe drei Auktionssysteme über zwei Jahre hinweg gebaut. Das erste war ein Desaster — alle Sekunde die Datenbank abfragen, Race Conditions überall, Gebote verschwinden ins Nichts. Das zweite war besser, erforderte aber die Verwaltung eines separaten WebSocket-Servers neben der Haupt-API. Das dritte? Das werde ich dir jetzt zeigen. Es nutzt Supabase Realtime, und es ist das erste Mal, dass sich der Aufbau einer Bidding-Engine wirklich *richtig* anfühlt.
Supabase Realtime sitzt auf PostgreSQLs Write-Ahead Log (WAL) und nutzt einen Elixir-basierten Server, um Datenbankänderungen über WebSockets an verbundene Clients zu pushen. Für ein Auktionssystem bedeutet das: Jedes Gebot, das deine Datenbank erreicht, wird sofort an jeden Bieter weitergeleitet, der diese Auktion beobachtet. Kein Polling. Keine separate Pub/Sub-Infrastruktur. Deine Datenbank ist dein Event-System.
Lass uns von Grund auf ein System aufbauen.
Inhaltsverzeichnis
- Architektur-Übersicht
- Datenbankschema und Einrichtung
- PostgreSQL-Trigger für Gebot-Logik
- Client-seitige Subscription mit JavaScript
- Umgang mit Race Conditions und Gebot-Validierung
- Presence-Tracking für aktive Bieter
- Performance-Optimierung und Überlegungen für die Produktion
- Supabase Realtime vs. Alternativen
- Bereitstellung und Skalierung deines Auktionssystems
- FAQ
Architektur-Übersicht
Bevor wir Code schreiben, wollen wir verstehen, was wir bauen und wie die einzelnen Teile zusammenpassen.
Supabase Realtime gibt dir drei Primitive, die perfekt auf Auktionsanforderungen abgebildet werden:
- Postgres Changes: Abonniere INSERT-, UPDATE- und DELETE-Events auf deinen Gebots- und Auktionstabellen. Wenn jemand ein Gebot abgibt, erhalten alle Abonnenten die neuen Zeilendaten innerhalb von Millisekunden.
- Broadcast: Sende kurzlebige Nachrichten an Kanalteilnehmer. Perfekt für "du wurdest überboten"-Benachrichtigungen, die nicht persistent sein müssen.
- Presence: Verfolge, wer gerade eine Auktion beobachtet. Damit kannst du "14 Bieter beobachten" in deiner UI anzeigen und Ghost-Sessions erkennen.
Der Datenflusses sieht so aus:
- Bieter reicht ein Gebot über dein Frontend ein
- Ein RPC-Aufruf oder direkter Insert trifft deine
bids-Tabelle - Ein PostgreSQL-Trigger validiert den Gebotsbetrag und aktualisiert
auctions.current_high_bid - Supabase Realtime erfasst die WAL-Änderung und pusht sie an alle Abonnenten auf dem Auktionskanal
- Ein zweiter Trigger feuert ein Broadcast-Event ab, um den vorherigen Höchstbieter zu benachrichtigen, dass er überboten wurde
- Jeder verbundene Client aktualisiert sein UI in Echtzeit
Die Latenz vom Gebotseintrag bis zur UI-Aktualisierung über alle Clients beträgt typischerweise unter 100 ms. Ich habe p99 in der Produktion auf Supabase Pro mit etwa 80-90 ms gemessen.
Warum nicht einfach Polling verwenden?
Ich weiß, dass einige von euch denken "kann ich nicht alle 500 ms abfragen?" Du kannst. Aber bei 200 gleichzeitigen Bietern auf einer einzelnen Auktion sind das 400 Anfragen pro Sekunde, die deine Datenbank für eine Auktion treffen. Multipliziere das mit 50 aktiven Auktionen und du bist bei 20.000 Queries pro Sekunde — von denen die meisten nichts Neues zurückgeben. WebSockets drehen dieses Modell um: Null Queries, wenn sich nichts ändert, sofortige Aktualisierungen, wenn etwas sich tut.
Datenbankschema und Einrichtung
Hier ist das Schema, das ich verwende. Es ist absichtlich einfach — du kannst es erweitern, aber die Kernstruktur bewältigt die meisten Auktionstypen.
-- Auctions table
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 table
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 for fast bid lookups per auction
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);
-- Critical: enable replica identity for Realtime
ALTER TABLE auctions REPLICA IDENTITY FULL;
ALTER TABLE bids REPLICA IDENTITY FULL;
Die REPLICA IDENTITY FULL-Einstellung ist essentiell. Ohne sie erhält Supabase Realtime bei UPDATE- und DELETE-Events nur den Primärschlüssel — nicht die vollständigen Zeilendaten. Für ein Auktionssystem benötigst du die vollständige Payload, damit Clients Gebotsbträge aktualisieren können, ohne eine separate Query zu machen.
Realtime-Replikation aktivieren
Im Supabase-Dashboard gehst du zu Database → Replication und schaltest die Replikation für beide Tabellen auctions und bids ein. Alternativ kannst du das mit SQL machen:
BEGIN;
-- Remove existing publication if it exists
DROP PUBLICATION IF EXISTS supabase_realtime;
-- Create publication with both tables
CREATE PUBLICATION supabase_realtime FOR TABLE auctions, bids;
COMMIT;
Row-Level Security
Überspringe das nicht. RLS ist deine serverseitige Validierungsschicht.
ALTER TABLE auctions ENABLE ROW LEVEL SECURITY;
ALTER TABLE bids ENABLE ROW LEVEL SECURITY;
-- Anyone can view active auctions
CREATE POLICY "Public auction viewing" ON auctions
FOR SELECT USING (status IN ('active', 'ended', 'sold'));
-- Authenticated users can view all bids on active auctions
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'
)
);
-- Only authenticated users can place bids
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-Trigger für Gebot-Logik
Hier passiert die echte Magie. Die Datenbank setzt alle Gebot-Logik serverseitig durch — der Client kann nicht betrügen.
Gebot-Validierung und Auktions-Update-Trigger
CREATE OR REPLACE FUNCTION process_new_bid()
RETURNS TRIGGER AS $$
DECLARE
v_auction auctions%ROWTYPE;
BEGIN
-- Lock the auction row to prevent race conditions
SELECT * INTO v_auction
FROM auctions
WHERE id = NEW.auction_id
FOR UPDATE;
-- Validate auction is active
IF v_auction.status != 'active' THEN
RAISE EXCEPTION 'Auction is not active';
END IF;
-- Validate auction hasn't ended
IF v_auction.ends_at < NOW() THEN
RAISE EXCEPTION 'Auction has ended';
END IF;
-- Validate bid amount exceeds current high + minimum increment
IF NEW.amount < v_auction.current_high_bid + v_auction.min_increment THEN
RAISE EXCEPTION 'Bid must be at least % higher than current high bid of %',
v_auction.min_increment, v_auction.current_high_bid;
END IF;
-- Prevent self-outbidding
IF v_auction.highest_bidder_id = NEW.user_id THEN
RAISE EXCEPTION 'You are already the highest bidder';
END IF;
-- Update auction with new high bid
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();
Das FOR UPDATE-Lock auf der Auktionszeile ist kritisch. Ohne es könnten zwei Gebote, die gleichzeitig ankommen, beide den gleichen current_high_bid lesen, beide die Validierung bestehen und beide eingefügt werden. Das Lock serialisiert den Zugriff.
Broadcast-Benachrichtigungen zum Überbieten
Dieser Trigger feuert nach einem erfolgreichen Gebot und sendet eine kurzlebige Benachrichtigung an den Auktionskanal:
CREATE OR REPLACE FUNCTION notify_outbid()
RETURNS TRIGGER AS $$
DECLARE
v_previous_bidder UUID;
BEGIN
-- Find who just got outbid
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 outbid notification if there was a previous bidder
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-seitige Subscription mit JavaScript
Jetzt verdrahten wir das Frontend. Ich zeige das mit Vanilla-JavaScript/React-Patterns — der gleiche Ansatz funktioniert, wenn du mit Next.js oder einem anderen Framework baust.
Initialisiere den 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 for auction traffic
}
}
}
);
Dieser eventsPerSecond-Parameter ist wichtig. Bei einer heißen Auktion mit Dutzenden Geboten pro Sekunde möchtest du nicht 50-mal pro Sekunde neu rendern. Zwanzig Updates pro Sekunde reichen mehr als aus für eine flüssige UI.
Abonniere einen Auktionskanal
function subscribeToAuction(auctionId, callbacks) {
const channel = supabase.channel(`auction:${auctionId}`);
channel
// Listen for new bids via Postgres Changes
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'bids',
filter: `auction_id=eq.${auctionId}`
}, (payload) => {
callbacks.onNewBid(payload.new);
})
// Listen for auction status changes
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'auctions',
filter: `id=eq.${auctionId}`
}, (payload) => {
callbacks.onAuctionUpdate(payload.new);
})
// Listen for outbid broadcast notifications
.on('broadcast', { event: 'outbid' }, ({ payload }) => {
callbacks.onOutbid(payload);
})
// Track active bidders 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') {
// Track this user's presence
await channel.track({
user_id: supabase.auth.getUser()?.data?.user?.id,
status: 'watching',
joined_at: new Date().toISOString()
});
}
});
return channel;
}
React Hook für Auktions-Subscriptions
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(() => {
// Fetch initial state
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();
// Subscribe to 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 };
}
Umgang mit Race Conditions und Gebot-Validierung
Race Conditions sind die größte einzelne Quelle von Bugs in Auktionssystemen. Hier ist, wie ich sie handhabe.
Serverseitig: PostgreSQL macht die schwere Arbeit
Das SELECT ... FOR UPDATE in unserer Trigger-Funktion ist die erste Verteidigungslinie. Aber es gibt noch ein anderes Muster, das ich zu verwenden begonnen habe — Advisory Locks für hochfrequente Auktionen:
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
-- Generate a deterministic lock key from auction UUID
v_lock_key := ('x' || left(p_auction_id::text, 15))::bit(64)::bigint;
-- Acquire advisory lock (blocks concurrent bids on same auction)
PERFORM pg_advisory_xact_lock(v_lock_key);
-- Now safe to insert (trigger handles validation)
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;
Rufe das vom Client aus unter Verwendung von Supabase RPC auf:
const { data, error } = await supabase.rpc('place_bid_safe', {
p_auction_id: auctionId,
p_user_id: user.id,
p_amount: bidAmount
});
Client-seitig: Optimistisches UI mit Rollback
Zeige das Gebot sofort in der UI an, aber sei bereit, es zurückzurollen, wenn der Server es ablehnt:
async function handleBidSubmit(amount) {
const optimisticBid = {
id: crypto.randomUUID(),
amount,
user_id: user.id,
placed_at: new Date().toISOString(),
_optimistic: true
};
// Show immediately
setBids(prev => [optimisticBid, ...prev]);
try {
await placeBid(amount);
// Real bid will arrive via Realtime and replace optimistic one
} catch (err) {
// Remove optimistic bid on failure
setBids(prev => prev.filter(b => b.id !== optimisticBid.id));
showError(err.message);
}
}
Presence-Tracking für aktive Bieter
Das Anzeigen, wie viele Personen eine Auktion beobachten, schafft Dringlichkeit. Presence-Tracking ist mit Supabase überraschend einfach:
// Update user status when they start bidding
async function updatePresenceStatus(channel, status) {
await channel.track({
user_id: user.id,
status, // 'watching', 'bidding', 'won'
last_active: new Date().toISOString()
});
}
Auf der Display-Seite kannst du den Presence-State aufteilen, um zu zeigen, wie viele aktiv bieten vs. nur zuschauen:
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
};
}
Performance-Optimierung und Überlegungen für die Produktion
Throttling und Debouncing
Ein Bieterkampf kann Dutzende Events pro Sekunde erzeugen. Hier ist, was ich konfiguriere:
- Serverseitig:
eventsPerSecond: 20auf der Supabase-Client-Config - Client-seitig: Drossele den Gebot-Button bei 300 ms, um Doppelklicks zu verhindern
- UI-Updates: Verwende
requestAnimationFramefür Gebotslisten-Animationen
Auktions-End-Timing
Vertraue nicht der Client-Uhr. Verwende einen PostgreSQL-Cron-Job über pg_cron:
-- Run every 10 seconds to close expired auctions
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-Erweiterung
Die meisten Auktionsplattformen verlängern die Frist, wenn ein Gebot in den letzten Sekunden kommt:
-- Add to the 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 vs. Alternativen
Ich habe die meisten davon in der Produktion verwendet. Hier ist ein ehrlicher Vergleich:
| Feature | Supabase Realtime | Pusher | Ably | Firebase RTDB | Socket.io (selbst gehostet) |
|---|---|---|---|---|---|
| Native DB-Sync | ✅ PostgreSQL WAL | ❌ Separater Service | ❌ Separater Service | ✅ JSON-Baum | ❌ Manuell |
| Latenz (p99) | ~80-100ms | ~60ms | ~50ms | ~100ms | ~40ms (hängt von Infra ab) |
| Max Events/Sek | 200k+ | 10k (Pro) | 50k | 100k | Unbegrenzt (du skalierst es) |
| Auth-Integration | Built-in (RLS + JWT) | Custom | Token-basiert | Firebase Auth | Custom |
| Presence | ✅ Built-in | ✅ Built-in | ✅ Built-in | ✅ Built-in | ✅ Built-in |
| Free Tier | 500K MAU, 200 gleichzeitig | 100 Verbindungen | 6M Msg/Mo | 1GB gespeichert | $0 (Hosting-Kosten) |
| Pro Preis | $25/Mo | $49/Mo | $29/Mo | Pay-as-you-go | ~$100-500/Mo (AWS) |
| Am besten für | DB-zentrische Echtzeit-Apps | Einfaches Pub/Sub | Hohe Zuverlässigkeit | Mobile Apps | Vollständige Kontrolle |
Für ein Auktionssystem speziell gewinnt Supabase, weil deine Gebote bereits in PostgreSQL sind. Du musst nicht zwischen einer Datenbank und einem separaten Pub/Sub-System synchronisieren. Das Gebot trifft die DB, die DB triggert den WebSocket-Push. Eine einzige Wahrheitsquelle.
Wenn du auf einer Headless-CMS-Architektur aufbaust, passt Supabase natürlich zur Content-Bereitstellung, ohne einen zusätzlichen Service zu verwalten.
Bereitstellung und Skalierung deines Auktionssystems
Für die meisten Projekte bewältigt Supabase's verwalteter Pro-Tier bei $25/Monat problemlos bis zu 10.000 tägliche Auktionen. Hier ist, worauf du achten solltest:
- Verbindungslimits: Pro-Tier gibt dir 500 gleichzeitige Realtime-Verbindungen. Wenn du mehr brauchst, musst du upgraden oder Client-seitiges Connection Pooling implementieren.
- WAL-Größe: Hochvolumen-Bieten generiert erheblichen WAL-Traffic. Überwache deinen Replikations-Slot, um Festplattenverschleiß zu vermeiden.
- Kanal-Anzahl: Jede Auktion bekommt ihren eigenen Kanal. Mit Tausenden aktiven Auktionen teste, dass dein Client sich ordnungsgemäß von beendeten Auktionen abmeldet.
Für ein Frontend gebaut mit Astro oder Next.js funktioniert der Supabase JS-Client identisch — stelle nur sicher, dass du ihn Client-seitig für Realtime-Subscriptions initialisierst.
Wenn du etwas baust, das wirkliche Skalierung handhaben muss — Hundertausende gleichzeitige Bieter — kontaktiere uns. Wir haben diese Systeme im großen Maßstab architektiert und können dir helfen, die Fallstricke zu vermeiden. Du kannst auch unsere Preisseite ansehen für projektbasierte Engagements.
FAQ
Wie viele gleichzeitige Bieter kann Supabase Realtime verarbeiten? Supabase Realtime kann über 200.000 Events pro Sekunde über verteilte Server auf ihrer verwalteten Plattform verarbeiten. Der Pro-Tier bei $25/Monat unterstützt bis zu 500 gleichzeitige Verbindungen pro Projekt. Für größere Auktionen bietet der Enterprise-Tier benutzerdefinierte Limits, oder du kannst den Realtime-Server selbst hosten (er ist Open Source) auf deiner eigenen Infrastruktur.
Ist Supabase Realtime schnell genug für eine Live-Auktion? Ja. In meinen Tests beträgt die End-to-End-Latenz von der Gebot-Einfügung bis zur Client-Benachrichtigung durchschnittlich etwa 50-80 ms mit p99 unter 100 ms. Zum Vergleich: Eine menschliche Reaktionszeit beträgt etwa 200-300 ms, also erscheinen Gebote effektiv augenblicklich. Der Engpass ist selten Supabase — es ist normalerweise die Client-Netzwerkverbindung.
Wie verhindere ich Race Conditions, wenn zwei Personen gleichzeitig bieten?
Verwende PostgreSQL's SELECT ... FOR UPDATE Zeilen-Level-Locking innerhalb einer Trigger-Funktion oder verwende Advisory Locks über pg_advisory_xact_lock(). Dies serialisiert die Gebot-Verarbeitung pro Auktion, sodass nur ein Gebot gleichzeitig validiert wird. Das "verlierende" Gebot wird immer noch validiert — es sieht nur das aktualisierte Höchstgebot des Gewinners und schlägt entweder fehl (wenn es immer noch höher ist) oder schlägt mit einer angemessenen Fehlermeldung fehl.
Kann ich Supabase Realtime mit Next.js oder Astro verwenden?
Absolut. Der @supabase/supabase-js Client funktioniert in jeder JavaScript-Umgebung. Für Next.js initialisiere den Supabase-Client in einer Client-Komponente (da Realtime Browser-WebSockets benötigt) und verwende ihn in useEffect-Hooks. Für Astro verwende ihn in Client-seitigen interaktiven Inseln. Der Subscription-Code ist unabhängig von deiner Framework-Wahl identisch.
Was passiert, wenn eine Benutzerverbindung während einer Auktion abbricht? Supabase Realtime versucht automatisch eine Wiederverbindung. Wenn sich der Client neu verbindet und sich erneut anmeldet, erhält er den aktuellen Status. Für kritische Auktionen empfehle ich auch, den neuesten Auktionsstatus über eine Standard-Query bei Wiederverbindung abzurufen, um sicherzustellen, dass während des Trennfensters nichts verpasst wird. Das Presence-System entfernt den getrennte Benutzer automatisch nach einem Timeout.
Wie handhabe ich Auktions-End-Zeiten präzise?
Vertraue nicht auf Client-seitige Timer für Auktions-End-Zeiten — sie können manipuliert werden. Verwende PostgreSQL's pg_cron-Erweiterung, um alle 10 Sekunden abgelaufene Auktionen serverseitig zu überprüfen und zu schließen. Sende den Server-Zeitstempel an Clients, damit sie einen Countdown anzeigen können, aber die tatsächliche Endbestimmung erfolgt immer in der Datenbank.
Ist Supabase Realtime kostenlos für kleine Projekte? Supabase's Free-Tier enthält Realtime mit bis zu 200 gleichzeitigen Verbindungen und 500.000 monatlichen aktiven Benutzern. Das reicht für eine Hobby-Auktionsseite oder ein MVP. Wenn du eine Produktions-Auktionsplattform mit bedeutsamem Traffic betreibst, ist der Pro-Tier bei $25/Monat mit $0,09/GB Ausgang, wo du starten möchtest. Es ist erheblich billiger als der Betrieb deiner eigenen WebSocket-Infrastruktur.
Wie teste ich ein Echtzeit-Bietungssystem lokal?
Verwende die Supabase CLI (supabase start), um eine lokale Supabase-Instanz mit Realtime aktiviert zu betreiben. Öffne mehrere Browser-Tabs, um mehrere Bieter zu simulieren. Für Load-Tests verwende ich ein einfaches Node.js-Skript, das 100+ Supabase-Clients erstellt und sie gegen einen Timer auf einer Auktion bieten lässt. Dies erfasst Race Conditions und hilft dir, deinen eventsPerSecond-Parameter zu tunen, bevor du in Produktion gehst.