Suddivisione in più processi
Panoramica
Sezione intitolata “Panoramica”Un deployment di Rebase è normalmente costituito da un unico processo che gestisce tutto: l’API dati, l’autenticazione, lo storage, le tue funzioni personalizzate, il cron e la coda dei job. Questa è la configurazione ideale per quasi tutti i deployment e rimane quella predefinita.
Quando smette di essere la configurazione adatta — ad esempio per via di una funzione personalizzata che blocca l’event loop o di un livello di funzioni che dovrebbe scalare o riavviarsi indipendentemente dall’API — puoi avviare la stessa immagine e lo stesso bundle più volte, facendo in modo che ogni processo gestisca una parte diversa del progetto. Non c’è nulla di nuovo da compilare e nulla che un client debba sapere: gli URL non cambiano.
Una singola variabile d’ambiente determina il ruolo di un processo:
REBASE_ROLE=api # data, auth, admin, storage, meta — everything but functionsREBASE_ROLE=functions # custom functions onlyREBASE_ROLE=worker # no HTTP surface: cron and the job queueREBASE_ROLE=all # the default: everything, one processCosa gestisce ciascun ruolo
Sezione intitolata “Cosa gestisce ciascun ruolo”all |
api |
functions |
worker |
|
|---|---|---|---|---|
/api/auth, /api/data, /api/storage, /api/meta |
✅ | ✅ | — | — |
/api/admin (compresi log ed editor dello schema) |
✅ | ✅ | — | — |
/api/functions/* |
✅ | inoltra (vedi sotto) | ✅ | — |
/api/admin/cron (la superficie admin) |
✅ | ✅ | — | — |
/health, /livez, /metrics |
✅ | ✅ | ✅ | ✅ |
| Gestisce i websocket, consuma eventi di modifica | ✅ | ✅ | — | — |
| Crea lo schema all’avvio | ✅ | ✅ | — | — |
| Esegue lo scheduler del cron | ✅ | ✅ | — | ✅ |
| Esegue i worker della coda dei job | ✅ | ✅ | — | ✅ |
Health e metriche sono presenti su ciascun ruolo senza eccezioni. Un processo che un orchestratore non può sottoporre a probe è un processo di cui non può effettuare il rollout.
Da 0.25 GET /api/auth/config,
ciò che legge la schermata di accesso, segue il resto di /api/auth: lo servono solo api e
all. Su 0.24 lo rendeva disponibile anche un processo functions o worker.
Il realtime è presente nell’elenco perché ha un costo a prescindere dal fatto che venga usato o
meno: un processo che consuma eventi di modifica mantiene attiva una connessione LISTEN al di fuori
del pool per tutta la sua esecuzione, e installa i trigger di acquisizione all’avvio. Solo
un processo che gestisce websocket ha dei destinatari a cui inviare i dati, quindi i due ruoli che
non ne gestiscono non fanno nessuna delle due cose. Le scritture effettuate da tali processi vengono comunque rilevate.
Con la change capture attiva, l’acquisizione avviene tramite trigger di database, quindi una modifica
viene pubblicata dal database anziché dal singolo processo che l’ha eseguita. Con l’acquisizione
disattivata (REALTIME_CDC=off, oppure auto che ripiega perché il ruolo del database non può
creare trigger), un processo worker o functions annuncia invece ogni propria
scrittura sul canale cross-istanza, senza tenere una connessione LISTEN.
In entrambi i casi, una funzione che scrive una riga attiva comunque ogni
subscriber sull’api.
Dai a ogni processo lo stesso REALTIME_CDC. Un processo che possiede lo schema
con REALTIME_CDC=off rimuove i trigger di cattura all’avvio, e un api
ancora impostato su auto resterebbe in ascolto di modifiche che nessuno annuncia.
Docker Compose
Sezione intitolata “Docker Compose”Due servizi a partire da un’unica immagine, un unico bundle e un unico database:
services: api: image: rebasepro/server:latest environment: REBASE_ROLE: api REBASE_FUNCTIONS_UPSTREAM: http://functions:8080 DATABASE_URL: postgres://rebase:${POSTGRES_PASSWORD}@db:5432/rebase JWT_SECRET: ${JWT_SECRET} REBASE_SERVICE_KEY: ${REBASE_SERVICE_KEY} CORS_ORIGINS: ${CORS_ORIGINS} volumes: - ./dist-bundle:/bundle ports: - "8080:8080"
functions: image: rebasepro/server:latest environment: REBASE_ROLE: functions REBASE_MIGRATE_ON_BOOT: none TRUSTED_PROXY_HOPS: 1 DATABASE_URL: postgres://rebase:${POSTGRES_PASSWORD}@db:5432/rebase JWT_SECRET: ${JWT_SECRET} REBASE_SERVICE_KEY: ${REBASE_SERVICE_KEY} CORS_ORIGINS: ${CORS_ORIGINS} volumes: - ./dist-bundle:/bundledocker compose up --scale functions=3Entrambi i processi necessitano dello stesso DATABASE_URL, dello stesso JWT_SECRET e dello stesso
REBASE_SERVICE_KEY — costituiscono un unico deployment, e un token generato da uno deve essere
accettato dall’altro.
Mantenere invariati gli URL
Sezione intitolata “Mantenere invariati gli URL”REBASE_FUNCTIONS_UPSTREAM indica al processo api di inoltrare /api/functions/*
al processo delle funzioni invece di gestirlo direttamente. I client, gli SDK generati e le chiavi
API vedono esattamente la stessa superficie visibile prima della separazione, di conseguenza non è
necessario modificare il codice dell’applicazione né configurare un reverse proxy per provarlo.
Un deployment di produzione potrebbe preferire instradare il percorso direttamente a livello del proprio ingress;
in tal caso, lascia non impostato REBASE_FUNCTIONS_UPSTREAM — il processo api
risponderà con un 404 per tali percorsi e sarà il proxy a monte a decidere dove indirizzarli.
Hop del proxy
Sezione intitolata “Hop del proxy”Quando l’API inoltra la richiesta, aggiunge l’indirizzo del chiamante a X-Forwarded-For. Ciò
fa sì che il processo delle funzioni si trovi dietro un hop di proxy in più rispetto all’API,
e deve esserne informato:
# api behind one ingress → TRUSTED_PROXY_HOPS=1# functions behind that ingress AND the api → TRUSTED_PROXY_HOPS=2TRUSTED_PROXY_HOPS è il numero di reverse proxy effettivamente presenti davanti
a un processo. Ciascuno di essi aggiunge l’indirizzo rilevato a X-Forwarded-For, quindi il
vero client corrisponde all’N-esima voce da destra; tutto ciò che si trova più a sinistra è
fornito dal client e viene ignorato, impedendo così a un chiamante di falsificare l’header per
aggirare le chiavi di rate limiting. Il valore predefinito è 0 — nessun proxy fidato.
Se questo valore viene impostato in modo errato, nulla si rompe in modo evidente: i rate limiter sul processo delle funzioni associano ogni richiesta all’indirizzo del container API, facendo sì che tutti i chiamanti condividano lo stesso bucket e che l’IP registrato per ogni evento di autenticazione sia identico.
Un solo processo gestisce lo schema
Sezione intitolata “Un solo processo gestisce lo schema”In un deployment suddiviso, esattamente un solo processo crea le tabelle e applica le policy
RLS all’avvio, ed è quello api (o all). Tutti gli altri processi devono
impostare:
REBASE_MIGRATE_ON_BOOT=noneQuesto è obbligatorio, non facoltativo: un processo functions o worker lasciato con il
valore predefinito rifiuta di avviarsi, segnalandolo esplicitamente. CREATE … IF NOT EXISTS legge il catalogo
e successivamente vi scrive in due passaggi separati, quindi processi avviati contemporaneamente entrano in
conflitto — e un deployment in cui più istanze competono per effettuare il provisioning dello stesso
schema non è uno scenario previsto.
Da 0.25 In un
server personalizzato, la stessa decisione è l’opzione
provision di createRebaseBackend: true nel processo che possiede lo schema, e
{ collections: false, realtime: false } in tutti gli altri, che mantiene comunque le
tabelle proprie del framework. Anche REBASE_MIGRATE_ON_BOOT=none disattiva lì lo schema
delle collection. Il lavoro in background si decide allo stesso modo: un processo che non
chiama mai start() non esegue né lo scheduler del cron né i worker dei job, e uno che lo
chiama esegue quelli che gli assegna la sua opzione ownership.
Esecuzione di una sola funzione per processo
Sezione intitolata “Esecuzione di una sola funzione per processo”Un processo può gestire un sottoinsieme specifico, consentendo a una funzione particolarmente onerosa di avere un proprio numero di repliche senza doverne spostare il codice:
REBASE_FUNCTIONS_ONLY=send-invoiceREBASE_FUNCTIONS_EXCLUDE=debug-toolsI nomi corrispondono ai nomi dei file senza estensione — lo stesso nome con cui la funzione viene montata. Un nome non presente nel bundle causa il fallimento dell’avvio e l’errore elenca i nomi effettivamente contenuti. Un processo configurato per una specifica funzione esiste esclusivamente per essa, pertanto un refuso che portasse a non erogare silenziosamente nulla rappresenterebbe lo scenario peggiore.
Cron e job in background
Sezione intitolata “Cron e job in background”Entrambi possono già essere eseguiti in sicurezza su più processi: lo scheduler del cron acquisisce
ciascuna coppia (job, slot) nel database, e la coda dei job acquisisce le righe con
FOR UPDATE SKIP LOCKED. Di conseguenza, per impostazione predefinita api continua a eseguire entrambi,
rendendo completa una suddivisione a due servizi senza richiedere un terzo container.
Aggiungi un processo worker quando desideri spostare le attività pianificate fuori dal percorso delle richieste HTTP,
e disattivale sull’API:
api: environment: REBASE_CRON_SCHEDULER: "false" REBASE_JOB_WORKERS: "false"
worker: environment: REBASE_ROLE: worker REBASE_MIGRATE_ON_BOOT: noneUn processo functions non esegue mai nessuno dei due. La sua scalabilità dipende dal
carico delle richieste e viene sostituito liberamente; assegnargli attività pianificate darebbe
al suo numero di repliche un significato improprio.
Tieni presente che l’accodamento — jobQueue.enqueue, l’enqueue di una coda dichiarata, il
publish di un topic — continua a funzionare ovunque, anche su un processo che non esegue
worker — l’accodamento è una scrittura, l’esecuzione è un ciclo di polling, e
solo quest’ultimo viene disattivato da un ruolo.
Cosa non offre la suddivisione
Sezione intitolata “Cosa non offre la suddivisione”Rate limit condivisi, a meno che non vengano richiesti. Lo store predefinito è per singolo processo, quindi N
processi moltiplicano la quota a disposizione di ciascun chiamante per N senza che alcun log lo
segnali. Imposta REBASE_RATE_LIMIT_STORE=sql su ogni processo che gestisce traffico HTTP — il
conteggio avviene in Postgres, quindi il limite rimane lo stesso indipendentemente dal numero di repliche.
(Il chart Helm lo imposta automaticamente e si rifiuta di eseguire il rendering di una topologia multi-processo
che lo lasci impostato su memory).
Canali tra istanze diverse. Broadcast e presence utilizzano per impostazione predefinita un bus in memoria,
che non oltrepassa i confini del singolo processo. Questa è una questione legata al numero di repliche
piuttosto che alla suddivisione dei ruoli — vale ugualmente per un deployment con ruolo singolo
scalato a tre repliche — pertanto imposta REALTIME_CHANNEL_BUS=postgres (o realtime.bus nella
configurazione) ogni volta che più di un processo gestisce websocket.
Scalabilità a zero. Nessuna di queste funzionalità scala un processo a zero o ne avvia uno su richiesta. Si tratta di una funzionalità a livello di piattaforma di orchestrazione, non del runtime.
Tutto il resto che un processo conserva solo per sé è raccolto in un unico elenco: Eseguire più di un’istanza.
Rilascio indipendente di una singola unità
Sezione intitolata “Rilascio indipendente di una singola unità”Tutto quanto descritto sopra suddivide il luogo in cui il carico di lavoro viene eseguito. L’intero sistema viene comunque distribuito come un’unica build: una sola immagine, un solo bundle, aggiornati insieme. Questa è l’impostazione predefinita corretta e la maggior parte dei deployment dovrebbe mantenerla.
Un’unità può anche essere vincolata a una propria build specifica — ad esempio per un fix a una funzione che non richiede il riavvio dell’API:
# values.yamlsplit: truefunctions: enabled: true image: tag: "0.24.0" # this unit only; the rest stay on the release-wide tagSolitamente ha senso bloccare solo il tag: il repository viene ereditato, quindi si tratta di
un unico progetto e di un’unica immagine con una sola unità modificata. bundleUrl svolge lo stesso compito
quando bundle.mode: url.
La regola
Sezione intitolata “La regola”Due unità su build differenti rappresentano due insiemi distinti di collection su un unico database, e una sola unità ne esegue il provisioning. Di conseguenza:
L’unità che gestisce lo schema deve essere aggiornata per prima. Un’unità può rimanere indietro; non deve mai essere più avanti.
Tale unità è il Job di migrazione, oppure l’api quando il Job è disattivato. Un’unità che opera
più avanti rispetto allo schema interroga colonne non ancora esistenti e fa affidamento su policy RLS
che nessuno ha ancora applicato — il primo caso genera un errore SQL su una route, il secondo restituisce un
risultato vuoto con uno stato 200. Un’unità che opera più indietro rappresenta invece il normale stato
di qualsiasi rollout in corso.
Cosa effettua la verifica
Sezione intitolata “Cosa effettua la verifica”Il processo che esegue il provisioning registra nel database la versione dello schema applicata. Tutti gli altri processi calcolano la propria a partire dalle collection caricate ed effettuano un confronto. In caso di discrepanza, viene emesso un avviso che indica entrambe:
⚠️ [schema] The database was last provisioned from a different set of collections than this process was built from (database v1:6f2a…, this process v1:91cd…).Viene emesso un avviso e il servizio continua, poiché durante un rollout tale discrepanza è corretta —
le unità non ancora aggiornate devono essere indietro. Imposta
REBASE_REQUIRE_SCHEMA_MATCH=true (o sharedState.requireSchemaMatch nel
chart) per impedire invece l’avvio, nel caso di un deployment in cui si preferisca non erogare
affatto il servizio piuttosto che erogarlo in modo errato.
Entrambi i lati del confronto sono calcolati, mai letti da un manifest. La versione che una build dichiara su se stessa non è una prova del fatto che il database sia allineato ad essa.
Nulla verifica la direzione — la versione dello schema è un hash, quindi può rilevare una discrepanza ma non quale dei due sia più avanti. Questo rende l’ordine di rollout una regola operativa da seguire, piuttosto che un vincolo imposto dal runtime.
Aggiornamento
Sezione intitolata “Aggiornamento”Invariato: ogni processo esegue la stessa immagine pubblicata, quindi un aggiornamento corrisponde alla
modifica dello stesso tag su ciascuno di essi. Esegui il rollout dell’api per ultimo se desideri che il
provisioning dello schema avvenga per primo rispetto alla nuova versione — anche se in pratica
l’ordine è irrilevante, poiché la procedura di schema è additiva e idempotente.
Risorse correlate
Sezione intitolata “Risorse correlate”- Guida al deployment — il deployment a singolo processo da cui deriva questa suddivisione
- Ambiente e configurazione —
REBASE_ROLE,REBASE_CRON_SCHEDULEReREBASE_MIGRATE_ON_BOOT - Kubernetes — un deployment per ciascun ruolo