Créer un moteur d'enchères en temps réel avec Supabase Realtime
Votre premier moteur d'enchères interrogeait la base de données chaque seconde. Les enchères arrivaient dans le désordre. Certaines disparaissaient complètement. Vous avez ajouté un serveur WebSocket séparé pour la deuxième version — mieux, mais maintenant vous gérez deux infrastructures. La troisième tentative a utilisé Supabase Realtime, et les conditions de course se sont arrêtées. Pas de boucle d'interrogation. Pas de serveur de socket autonome. Les déclencheurs PostgreSQL diffusent les mises à jour d'enchères au moment où elles s'engagent, et chaque client connecté voit le même état en moins de 50 millisecondes. J'ai livré trois systèmes d'enchères en deux ans. La version Supabase est la seule que je n'ai pas dû reconstruire après le lancement. Voici l'architecture qui l'a rendue possible — et la fonction qui a failli la casser.
Supabase Realtime repose sur le Journal d'Avance Avant Écriture (WAL) de PostgreSQL et utilise un serveur basé sur Elixir pour envoyer les modifications de base de données via WebSockets aux clients connectés. Pour un système d'enchères, cela signifie que chaque enchère qui frappe votre base de données se propage instantanément à chaque enchérisseur regardant cette enchère. Pas d'interrogation. Pas d'infrastructure pub/sub séparée. Votre base de données est votre système d'événements.
Construisons-en un à partir de zéro.
Table des matières
- Aperçu de l'architecture
- Schéma de base de données et configuration
- Déclencheurs PostgreSQL pour la logique d'enchères
- Abonnement côté client avec JavaScript
- Gestion des conditions de course et validation des enchères
- Suivi de la présence des enchérisseurs actifs
- Optimisation des performances et considérations de production
- Supabase Realtime vs alternatives
- Déploiement et mise à l'échelle de votre système d'enchères
- FAQ
Aperçu de l'architecture
Avant d'écrire du code, comprenons ce que nous construisons et comment les pièces s'assemblent.
Supabase Realtime vous donne trois primitives qui correspondent parfaitement aux exigences d'enchères:
- Postgres Changes: Abonnez-vous aux événements INSERT, UPDATE et DELETE sur vos tables d'enchères et d'enchères. Quand quelqu'un place une enchère, chaque abonné reçoit les nouvelles données de ligne en quelques millisecondes.
- Broadcast: Envoyez des messages éphémères aux participants du canal. Parfait pour les notifications "vous avez été surenchéri" qui n'ont pas besoin d'être persistantes.
- Presence: Suivez qui regarde actuellement une enchère. Cela vous permet d'afficher "14 enchérisseurs regardent" dans votre interface utilisateur et de détecter les sessions fantômes.
Le flux de données ressemble à ceci:
- L'enchérisseur soumet une enchère via votre interface
- Un appel RPC ou une insertion directe frappe votre table
bids - Un déclencheur PostgreSQL valide le montant de l'enchère et met à jour
auctions.current_high_bid - Supabase Realtime détecte le changement WAL et le pousse à tous les abonnés du canal de cette enchère
- Un deuxième déclencheur déclenche un événement Broadcast pour notifier le précédent enchérisseur le plus élevé qu'il a été surenchéri
- Chaque client connecté met à jour son interface utilisateur en temps réel
La latence du placement de l'enchère à la mise à jour de l'interface sur tous les clients est généralement inférieure à 100ms. J'ai mesuré le p99 à environ 80-90ms en production sur le niveau Pro de Supabase.
Pourquoi ne pas simplement utiliser l'interrogation?
Je sais que certains d'entre vous pensent "ne puis-je pas simplement interroger toutes les 500ms?" Vous pouvez. Mais avec 200 enchérisseurs simultanés sur une seule enchère, c'est 400 requêtes par seconde frappant votre base de données pour une seule enchère. Multipliez cela par 50 enchères actives et vous êtes à 20 000 requêtes par seconde — dont la plupart ne retournent rien de nouveau. Les WebSockets inversent ce modèle: zéro requête quand rien ne change, mises à jour instantanées quand quelque chose change.
Schéma de base de données et configuration
Voici le schéma que j'utilise. Il est volontairement simple — vous pouvez l'étendre, mais la structure principale gère la plupart des types d'enchères.
-- Table des enchères
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()
);
-- Table des enchères
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 pour les recherches d'enchères rapides par enchère
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);
-- Critique: activer l'identité de réplica pour Realtime
ALTER TABLE auctions REPLICA IDENTITY FULL;
ALTER TABLE bids REPLICA IDENTITY FULL;
Le paramètre REPLICA IDENTITY FULL est essentiel. Sans lui, Supabase Realtime n'obtient que la clé primaire sur les événements UPDATE et DELETE — pas les données de ligne complètes. Pour un système d'enchères, vous avez besoin de la charge complète pour que les clients puissent mettre à jour les montants d'enchères sans effectuer une requête séparée.
Activation de la réplication Realtime
Dans le tableau de bord Supabase, allez à Database → Replication et basculez la réplication pour les deux tables auctions et bids. Vous pouvez également le faire avec SQL:
BEGIN;
-- Supprimer la publication existante si elle existe
DROP PUBLICATION IF EXISTS supabase_realtime;
-- Créer une publication avec les deux tables
CREATE PUBLICATION supabase_realtime FOR TABLE auctions, bids;
COMMIT;
Sécurité au niveau des lignes
Ne sautez pas cela. RLS est votre couche de validation côté serveur.
ALTER TABLE auctions ENABLE ROW LEVEL SECURITY;
ALTER TABLE bids ENABLE ROW LEVEL SECURITY;
-- Tout le monde peut voir les enchères actives
CREATE POLICY "Public auction viewing" ON auctions
FOR SELECT USING (status IN ('active', 'ended', 'sold'));
-- Les utilisateurs authentifiés peuvent voir toutes les enchères sur les enchères actives
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'
)
);
-- Seuls les utilisateurs authentifiés peuvent placer des enchères
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()
)
);
Déclencheurs PostgreSQL pour la logique d'enchères
C'est là que la vraie magie se produit. La base de données applique toute la logique d'enchères côté serveur — le client ne peut pas tricher.
Validation des enchères et déclencheur de mise à jour de l'enchère
CREATE OR REPLACE FUNCTION process_new_bid()
RETURNS TRIGGER AS $$
DECLARE
v_auction auctions%ROWTYPE;
BEGIN
-- Verrouiller la ligne d'enchère pour prévenir les conditions de course
SELECT * INTO v_auction
FROM auctions
WHERE id = NEW.auction_id
FOR UPDATE;
-- Valider que l'enchère est active
IF v_auction.status != 'active' THEN
RAISE EXCEPTION 'Auction is not active';
END IF;
-- Valider que l'enchère n'a pas pris fin
IF v_auction.ends_at < NOW() THEN
RAISE EXCEPTION 'Auction has ended';
END IF;
-- Valider que le montant de l'enchère dépasse l'enchère la plus élevée actuelle + incrément minimum
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;
-- Empêcher l'auto-surenchère
IF v_auction.highest_bidder_id = NEW.user_id THEN
RAISE EXCEPTION 'You are already the highest bidder';
END IF;
-- Mettre à jour l'enchère avec la nouvelle enchère la plus élevée
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();
Ce verrou FOR UPDATE sur la ligne d'enchère est critique. Sans lui, deux enchères arrivant simultanément pourraient toutes deux lire le même current_high_bid, passer toutes deux la validation, et toutes deux être insérées. Le verrou sérialise l'accès.
Diffuser les notifications surenchérées
Ce déclencheur se déclenche après une enchère réussie et envoie une notification éphémère au canal d'enchère:
CREATE OR REPLACE FUNCTION notify_outbid()
RETURNS TRIGGER AS $$
DECLARE
v_previous_bidder UUID;
BEGIN
-- Trouver qui vient de se faire surenchérir
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;
-- Diffuser la notification surenchérée s'il y avait un enchérisseur précédent
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();
Abonnement côté client avec JavaScript
Câblons maintenant l'interface. Je vais montrer cela avec des modèles JavaScript/React vanille — la même approche fonctionne si vous construisez avec Next.js ou tout autre framework.
Initialiser le 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 // Limiter le trafic des enchères
}
}
}
);
Ce paramètre eventsPerSecond importe. Sur une enchère chaude avec des dizaines d'enchères par seconde, vous ne voulez pas vous re-rendre 50 fois par seconde. Vingt mises à jour par seconde plus que suffisent pour une interface utilisateur fluide.
S'abonner à un canal d'enchère
function subscribeToAuction(auctionId, callbacks) {
const channel = supabase.channel(`auction:${auctionId}`);
channel
// Écouter les nouvelles enchères via Postgres Changes
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'bids',
filter: `auction_id=eq.${auctionId}`
}, (payload) => {
callbacks.onNewBid(payload.new);
})
// Écouter les modifications d'état de l'enchère
.on('postgres_changes', {
event: 'UPDATE',
schema: 'public',
table: 'auctions',
filter: `id=eq.${auctionId}`
}, (payload) => {
callbacks.onAuctionUpdate(payload.new);
})
// Écouter les notifications de diffusion surenchérées
.on('broadcast', { event: 'outbid' }, ({ payload }) => {
callbacks.onOutbid(payload);
})
// Suivre les enchérisseurs actifs 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') {
// Suivre la présence de cet utilisateur
await channel.track({
user_id: supabase.auth.getUser()?.data?.user?.id,
status: 'watching',
joined_at: new Date().toISOString()
});
}
});
return channel;
}
Crochet React pour les abonnements aux enchères
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(() => {
// Récupérer l'état initial
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();
// S'abonner aux mises à jour en temps réel
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 };
}
Gestion des conditions de course et validation des enchères
Les conditions de course sont la plus grande source de bogues dans les systèmes d'enchères. Voici comment je les gère.
Côté serveur: PostgreSQL fait le travail lourd
Le SELECT ... FOR UPDATE dans notre fonction de déclenchement est la première ligne de défense. Mais il y a un autre modèle que j'ai commencé à utiliser — les verrous consultatifs pour les enchères à forte contention:
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
-- Générer une clé de verrou déterministe à partir de UUID d'enchère
v_lock_key := ('x' || left(p_auction_id::text, 15))::bit(64)::bigint;
-- Acquérir un verrou consultatif (bloque les enchères simultanées sur la même enchère)
PERFORM pg_advisory_xact_lock(v_lock_key);
-- Maintenant sûr d'insérer (le déclenchement gère la 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;
Appellez ceci depuis le client en utilisant l'RPC Supabase:
const { data, error } = await supabase.rpc('place_bid_safe', {
p_auction_id: auctionId,
p_user_id: user.id,
p_amount: bidAmount
});
Côté client: Interface utilisateur optimiste avec restauration
Afficher l'enchère immédiatement dans l'interface, mais être prêt à la restaurer si le serveur la rejette:
async function handleBidSubmit(amount) {
const optimisticBid = {
id: crypto.randomUUID(),
amount,
user_id: user.id,
placed_at: new Date().toISOString(),
_optimistic: true
};
// Afficher immédiatement
setBids(prev => [optimisticBid, ...prev]);
try {
await placeBid(amount);
// L'enchère réelle arrivera via Realtime et remplacera celle optimiste
} catch (err) {
// Supprimer l'enchère optimiste en cas d'échec
setBids(prev => prev.filter(b => b.id !== optimisticBid.id));
showError(err.message);
}
}
Suivi de la présence des enchérisseurs actifs
Afficher combien de personnes regardent une enchère crée de l'urgence. Le suivi de la présence est très simple avec Supabase:
// Mettre à jour le statut de l'utilisateur quand il commence à enchérir
async function updatePresenceStatus(channel, status) {
await channel.track({
user_id: user.id,
status, // 'watching', 'bidding', 'won'
last_active: new Date().toISOString()
});
}
Du côté de l'affichage, vous pouvez décomposer l'état de présence pour afficher combien sont activement en train d'enchérir par rapport à simplement regarder:
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
};
}
Optimisation des performances et considérations de production
Limitation de débit et déblocage
Une guerre d'enchères peut générer des dizaines d'événements par seconde. Voici ce que je configure:
- Côté serveur:
eventsPerSecond: 20sur la configuration du client Supabase - Côté client: Débloquer le bouton d'enchère à 300ms pour prévenir les clics doubles
- Mises à jour de l'interface: Utilisez
requestAnimationFramepour les animations de liste d'enchères
Synchronisation des fins d'enchères
Ne faites pas confiance à l'horloge du client. Utilisez un travail cron PostgreSQL via pg_cron:
-- Exécuter toutes les 10 secondes pour fermer les enchères expirées
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();
$$
);
Extension anti-snipe
La plupart des plates-formes d'enchères prolongent la date limite si une enchère arrive dans les dernières secondes:
-- Ajouter au déclenchement process_new_bid
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 alternatives
J'ai utilisé la plupart de ceux-ci en production. Voici une comparaison honnête:
| Fonctionnalité | Supabase Realtime | Pusher | Ably | Firebase RTDB | Socket.io (auto-hébergé) |
|---|---|---|---|---|---|
| Sync BD native | ✅ PostgreSQL WAL | ❌ Service séparé | ❌ Service séparé | ✅ Arbre JSON | ❌ Manuel |
| Latence (p99) | ~80-100ms | ~60ms | ~50ms | ~100ms | ~40ms (dépend de l'infra) |
| Max événements/sec | 200k+ | 10k (Pro) | 50k | 100k | Illimité (vous le mettez à l'échelle) |
| Intégration d'auth | Intégré (RLS + JWT) | Personnalisé | Basé sur les jetons | Firebase Auth | Personnalisé |
| Presence | ✅ Intégré | ✅ Intégré | ✅ Intégré | ✅ Intégré | ✅ Intégré |
| Niveau gratuit | 500K MAU, 200 concurrent | 100 connexions | 6M msgs/mois | 1GB stocké | $0 (coûts d'hébergement) |
| Prix Pro | $25/mois | $49/mois | $29/mois | Payant à l'utilisation | ~$100-500/mois (AWS) |
| Idéal pour | Apps temps réel BD-centrées | Pub/sub simple | Haute fiabilité | Apps mobiles | Contrôle total |
Pour un système d'enchères spécifiquement, Supabase gagne parce que vos enchères sont déjà dans PostgreSQL. Vous n'avez pas besoin de synchroniser entre une base de données et un système pub/sub séparé. L'enchère frappe la BD, la BD déclenche le push WebSocket. Une seule source de vérité.
Si vous construisez sur une architecture de CMS sans tête, Supabase s'intègre naturellement aux côtés de la livraison de contenu sans ajouter un autre service à gérer.
Déploiement et mise à l'échelle de votre système d'enchères
Pour la plupart des projets, le niveau Pro géré Supabase à $25/mois gère confortablement jusqu'à 10 000 enchères quotidiennes. Voici ce à quoi faire attention:
- Limites de connexion: Le niveau Pro vous donne 500 connexions Realtime simultanées. Si vous en avez besoin de plus, vous devrez mettre à niveau ou implémenter un regroupement de connexions sur le client.
- Taille du WAL: Les enchères à haut volume génèrent un trafic WAL important. Surveillez votre emplacement de réplication pour éviter le gonflement du disque.
- Nombre de canaux: Chaque enchère reçoit son propre canal. Avec des milliers d'enchères actives, testez que votre client se désabonne correctement des enchères terminées.
Pour un interface construite avec Astro ou Next.js, le client JS Supabase fonctionne de manière identique — assurez-vous simplement de l'initialiser côté client pour les abonnements Realtime.
Si vous construisez quelque chose qui doit gérer une vraie mise à l'échelle — des centaines de milliers d'enchérisseurs simultanés — contactez-nous. Nous avons architecturé ces systèmes à grande échelle et pouvons vous aider à éviter les pièges. Vous pouvez également consulter notre page de tarification pour les engagements basés sur des projets.
FAQ
Combien d'enchérisseurs simultanés Supabase Realtime peut-il gérer?
Supabase Realtime peut gérer plus de 200 000 événements par seconde sur des serveurs distribués sur leur plate-forme gérée. Le niveau Pro à $25/mois supporte jusqu'à 500 connexions simultanées par projet. Pour les enchères plus importantes, le niveau Enterprise offre des limites personnalisées, ou vous pouvez auto-héberger le serveur Realtime (c'est open source) sur votre propre infrastructure.
Supabase Realtime est-il assez rapide pour une enchère en direct?
Oui. Dans mes tests, la latence de bout en bout du placement de l'enchère à la notification du client fait en moyenne environ 50-80ms, avec p99 sous 100ms. Pour le contexte, un temps de réaction humain est d'environ 200-300ms, donc les enchères apparaissent effectivement instantanées. Le goulot d'étranglement est rarement Supabase — c'est généralement la connexion réseau du client.
Comment empêcher les conditions de course quand deux personnes enchérissent simultanément?
Utilisez le verrouillage au niveau des lignes de PostgreSQL SELECT ... FOR UPDATE à l'intérieur d'une fonction de déclenchement, ou utilisez des verrous consultatifs via pg_advisory_xact_lock(). Cela sérialise le traitement des enchères par enchère pour qu'une seule enchère soit validée à la fois. L'enchère "perdante" obtient toujours validée — elle voit simplement l'enchère la plus élevée mise à jour du gagnant et soit elle réussit (si elle est toujours plus élevée) soit échoue avec une erreur appropriée.
Puis-je utiliser Supabase Realtime avec Next.js ou Astro?
Absolument. Le client @supabase/supabase-js fonctionne dans n'importe quel environnement JavaScript. Pour Next.js, initialisez le client Supabase dans un composant client (puisque Realtime nécessite des WebSockets du navigateur) et utilisez-le à l'intérieur de crochets useEffect. Pour Astro, utilisez-le dans des îles interactives côté client. Le code d'abonnement est identique quel que soit votre choix de framework.
Que se passe-t-il si la connexion d'un utilisateur se déconnecte pendant une enchère?
Supabase Realtime tente automatiquement de se reconnecter. Quand le client se reconnecte et se réabonne, il reçoit l'état actuel. Pour les enchères critiques, je recommande également de récupérer l'état d'enchère le plus récent via une requête standard à la reconnexion pour s'assurer que rien n'a été manqué pendant la fenêtre de déconnexion. Le système Presence supprimera automatiquement l'utilisateur déconnecté après un délai d'attente.
Comment gérer les heures de fin d'enchère avec précision?
Ne faites jamais confiance aux minuteurs côté client pour les heures de fin d'enchère — ils peuvent être manipulés. Utilisez l'extension PostgreSQL pg_cron pour vérifier et fermer les enchères expirées toutes les 10 secondes côté serveur. Envoyez l'horodatage du serveur aux clients pour qu'ils puissent afficher un compte à rebours, mais la détermination réelle de la fin se produit toujours dans la base de données.
Supabase Realtime est-il gratuit pour les petits projets?
Le niveau gratuit Supabase inclut Realtime avec jusqu'à 200 connexions simultanées et 500 000 utilisateurs actifs mensuels. C'est suffisant pour un site d'enchères de loisir ou une MVP. Si vous exécutez une plate-forme d'enchères de production avec un trafic important, le niveau Pro à $25/mois avec $0,09/GB d'égressé est où vous voudrez commencer. C'est considérablement moins cher que d'exécuter votre propre infrastructure WebSocket.
Comment tester un système d'enchères en temps réel localement?
Utilisez l'interface de ligne de commande Supabase (supabase start) pour exécuter une instance Supabase locale avec Realtime activé. Ouvrez plusieurs onglets de navigateur pour simuler plusieurs enchérisseurs. Pour les tests de charge, j'utilise un script Node.js simple qui crée 100+ clients Supabase et les fait enchérir les uns contre les autres selon un minuteur. Cela détecte les conditions de course et vous aide à affiner votre paramètre eventsPerSecond avant d'aller en production.