Il lake è lo storage del database, non una sua replica. Ecco perché Postgres rimane veloce, l'analisi è sempre aggiornata e non serve alcuna pipeline CDC
Il post di Reynold Xin "From monolith to Lakebase to LTAP" ha spiegato cos'è l'LTAP e come l'architettura Lakebase lo renda possibile: una copia governata dei dati operativi che serve sia Postgres che l'analisi, senza pipeline CDC e senza un secondo database.
Molti dei commenti successivi hanno letto quell'affermazione attraverso sistemi già noti alle persone. Sono emerse spesso quattro interpretazioni:
Ognuno di questi miti si basa su un'errata assunzione su come Lakebase memorizza i dati. Vediamoli uno alla volta.
La realtà: L'LTAP mantiene una copia duratura di ciascuna tabella nel lake. I livelli di fronte ad essa trattengono alcuni degli stessi byte per rendere quella tabella duratura e veloce, ma nessuno di essi è un secondo database.
Dall'esterno, l'LTAP può sembrare una normale pipeline. Postgres scrive righe, un processo monitora il log, le modifiche arrivano in una tabella Iceberg e l'analisi legge Iceberg. Quella pipeline è CDC. È anche la struttura della replica logica, del mirroring e della maggior parte dei prodotti zero-ETL. Quindi è facile classificare l'LTAP come lo stesso design.
Per capire perché non lo sia, è utile essere precisi su cosa costituisca una seconda copia. Ci sono due modi diversi in cui un sistema può finire per memorizzare gli stessi dati in più di un posto:
Quindi il test è semplice: questo livello può essere interrogato o utilizzato in failover come database autonomo? Se sì, è una replica. Se no, è un tier.
Con questo test, Postgres è sempre stato organizzato a livelli (tiered). Il WAL, il buffer cache e gli indici memorizzano tutti byte che si sovrappongono all'heap. Nessuno chiama un indice una replica della tabella, perché non ha un proprio catalogo o una propria timeline.
L'LTAP mantiene la stessa gerarchia. La sposta sull'object storage e consente a un motore lakehouse di leggere il tier inferiore e duraturo.
Ecco come appare questa gerarchia in Lakebase, seguendo una scrittura e poi una lettura:
L'object storage funziona allo stesso modo. Quando si effettua un PUT di un oggetto in S3, questo viene memorizzato più volte, spesso in diverse zone di disponibilità utilizzando l'erasure coding. L'API fa comunque riferimento a un solo oggetto e nessun processo di sincronizzazione viene eseguito tra le copie. Lakebase applica la stessa idea davanti al lake: log, cache, pageserver, object storage. I file extra sono il modo in cui una tabella viene resa duratura e veloce.
Il CDC crea un secondo database. Decodifica un flusso di modifiche logiche in uno schema esterno, solitamente basato su chiave primaria, ed esegue l'upsert in una tabella che l'origine non legge mai. Quella seconda tabella può essere interrogata autonomamente e può disallinearsi rispetto all'origine: i tipi possono divergere, il DDL può mancare, uno slot di replica può perdere righe e le tabelle che nessuno ha selezionato non compaiono mai. Il risultato sono due cataloghi.
L'LTAP non ha una seconda tabella da mantenere allineata. Non esiste un elenco di tabelle da selezionare. Una tabella che esiste in Postgres esiste nel lake e le pagine servite dall'origine sono cache di quello store.
La realtà: due codifiche senza perdita delle stesse righe possono servire diversi percorsi di accesso - pagine di heap per ricerche puntuali, Parquet per le scansioni. Ogni query legge il layout che risulta veloce per essa.
Anche se i file extra formano una gerarchia, il layout rimane una questione separata. Le ricerche puntuali sono efficienti sulle righe, le scansioni sono efficienti sulle colonne. Se c'è una sola codifica su disco, uno di questi pattern di accesso paga in termini di I/O. Definire la tabella come una singola copia logica non cambia questo aspetto.
Questo ragionamento è corretto, ma si basa su un'assunzione: che una copia implichi una sola codifica. Non è così, finché due formati possono rappresentare le stesse righe senza perdita.
Quindi la domanda è se i due formati dell'LTAP siano senza perdita:
Poiché nulla viene perso, la copia colonnare può essere lo store duraturo nel lake e le pagine di heap ne diventano una cache. Ciascun percorso di accesso legge quindi il formato più adatto. Le ricerche colpiscono pagine da 8 KB sul nodo di calcolo, sul disco locale e sul pageserver. Le scansioni colpiscono Parquet tramite un motore OLAP. Gli indici rimangono nel tier caldo; non vengono transcodificati in Parquet.
Postgres funziona già in questo modo. Mantiene due strutture fisiche sulle stesse righe per diversi percorsi di accesso: un heap e un indice di copertura. Nessuno tratta l'indice come una replica CDC di customer. SELECT * FROM t WHERE id = 1 e SELECT count(*) FROM t sono due percorsi di accesso su una singola tabella.
Un'osservazione più specifica è valida. I file colonnari di Postgres sono ordinati per heap: le righe si trovano nell'ordine del numero di pagina, perché la pagina è l'unità riscritta ad ogni compattazione. Questo layout è efficiente per ricostruire una pagina e per riscrivere solo le pagine sporche. È una chiave di clustering scadente per WHERE order_date >= …. Se una query di un data warehouse richiede tale clustering, può essere ricavata nello stesso modo in cui le tabelle silver vengono già ricavate da qualsiasi origine operativa. Ciò che non è più necessario è una pipeline CDC il cui scopo era creare la copia nel lake.
La realtà: ogni query analitica legge uno snapshot consistente. I commit non ancora arrivati nel lake vengono letti dai pageserver, in modo che nulla di ciò che è stato eseguito prima della query venga tralasciato.
Iceberg e Delta forniscono l'isolamento degli snapshot sullo storage a oggetti, che presenta una latenza elevata. È quindi naturale concludere che l'analisi sia sempre in ritardo, che molte tabelle piccole non possano rimanere aggiornate e che un join tra tabelle non possa essere coerente. Questa descrizione si adatta all'inserimento di CDC in un lake.
Tutto dipende da un presupposto: che una query possa vedere solo ciò che è già stato salvato nello storage a oggetti. In LTAP, una query può vedere anche le pagine che non si trovano ancora nello storage a oggetti. Questo cambia il quadro, ed è più semplice capirlo seguendo una query.
Quando viene avviata una query analitica,
Postgres non gestisce il traffico di lettura bulk: restituisce un numero e, al massimo, quelle pagine recenti. Il lake non deve contenere l'ultimo commit affinché questo sia visibile. Deve contenere quasi tutto, oltre a un modo per recuperare le pagine confermate dopo l'ultima materializzazione. L'LSN rappresenta il limite: indica al motore fino a che punto deve spingersi l'unione.
Lo stesso LSN mantiene anche la coerenza dei join. CDC si applica tabella per tabella, quindi una relazione può trovarsi in una posizione di slot successiva rispetto a un'altra, e un join mescola due punti nel tempo. LTAP legge ogni tabella a un singolo LSN, lo stesso tipo di limite che Postgres utilizza per REPEATABLE READ, anziché basarsi sull'ultimo flush di ciascuna pipeline. Tale LSN è un watermark condiviso. Sono in fase di sviluppo API per la lettura delle modifiche recenti avvenute dopo un watermark, sia in modo nativo per Lakebase sia come estensione per il Postgres a monte.
Le tabelle piccole rappresentano un costo in termini di metadati, non un limite di freschezza. L'overhead di Iceberg è costituito da file e snapshot. Per una relazione con un numero esiguo di righe, tale gestione costa più di quanto faccia risparmiare e il layout non influisce sulle prestazioni di scansione. Queste tabelle rimangono nella cache delle pagine, partecipano alla lettura coerente con l'LSN e saltano il commit di Iceberg.
Ciò lascia due affermazioni che sembrano simili ma non lo sono:
Lo snapshot colonnare materializzato può essere in ritardo rispetto a quello principale dell'intervallo di compattazione più recente, ma la query in sé non lo è. Il fatto che i file Parquet possano essere in ritardo di diversi minuti non significa che un lettore non sia in grado di visualizzare i dati confermati.
La realtà: la replica logica è un modo costoso per mantenere il lake sincronizzato con il database. Riesegue ogni modifica, riga per riga, e richiede una pipeline in grado di tenere il passo. La decodifica del WAL in parallelo non risolve il problema, poiché i record di log non possono essere elaborati in modo indipendente.
Un modo standard per eseguire analisi su un Postgres aggiornato consiste nell'abilitare la replica logica. Debezium, PeerDB, pgoutput e strumenti simili partono dal presupposto che il database emetta già un flusso di modifiche e si chiedono quale formato di storage sia necessario in aggiunta.
La questione è se un flusso di modifiche sia un buon modo per costruire una tabella nel lake. Due proprietà del flusso indicano che non lo è: la quantità di dati da leggere e la quantità di stato da mantenere.
La replica logica legge ogni modifica
Il WAL è un log fisico delle modifiche alle pagine, scritto affinché Postgres possa gestire crash e recupero. La decodifica logica lo trasforma in eventi di riga nell'ordine dei commit. Questa conversione non può comprimere molti aggiornamenti alla stessa pagina nell'immagine corrente della pagina; ogni modifica deve passare attraverso il processo. Su una pagina calda, Postgres può registrare lo stesso blocco da 8 KB cento volte tra un checkpoint e l'altro. La decodifica logica emette cento eventi, che un job a valle inserisce o aggiorna (upsert) in Parquet tramite chiave primaria. Al contrario, un percorso basato sui livelli di pagina legge la pagina una sola volta. Nei carichi di lavoro intensivi in scrittura che abbiamo misurato, CDC legge almeno il volume del WAL, mentre la compattazione delle pagine sporche potrebbe leggerne solo una frazione.
La replica logica gestisce lo stato
Uno slot di replica è un cursore nel WAL che il consumer deve continuare ad avanzare. Se il consumer rimane indietro, il WAL rimane sul disco finché lo slot non viene eliminato o il disco si riempie. La decodifica logica non emette DDL, quindi ALTER TABLE, le modifiche ai tipi e le rinomine rendono gli eventi incoerenti con il catalogo corrente o invalidano lo slot. Le tabelle senza chiave primaria richiedono REPLICA IDENTITY FULL. Poiché la pipeline è costosa, viene tipicamente applicata a un sottoinsieme di tabelle. Questo è l'opposto di ogni tabella Postgres che appare nel lake semplicemente perché esiste.
Un'affermazione correlata: è possibile saltare la decodifica logica e decodificare il WAL in parallelo
I pageserver consumano già il WAL, quindi un percorso parallelo da log a Parquet può sembrare un piccolo passaggio aggiuntivo. È possibile in linea di principio. Non è stato realizzato come estrattore di produzione e non è il percorso utilizzato da LTAP. I record del WAL sono ordinati perché quelli successivi presuppongono quelli precedenti. La visibilità risiede in pg_xact, nell'array di processi e nel catalogo a quel LSN, non nella tupla. Lavoratori indipendenti non possono decodificare ciascuno un intervallo di byte disgiunto ed emettere righe correnti corrette.
Ciò che LTAP parallelizza sono le pagine, non i record di log. Il sistema chiede quali pagine sono cambiate dall'ultimo snapshot (riassunti WAL su Postgres 17+ o alberi LSM - log-structured merge trees del pageserver), e ogni pagina sporca rappresenta un'attività indipendente. Quel set viene partizionato, le pagine vengono transcodificate e viene eseguito il commit di un singolo snapshot di Iceberg. Il log rappresenta il modo in cui le scritture sono state rese persistenti e uno dei metodi per scoprire quali pagine sono cambiate. Lo snapshot rappresenta le pagine correnti, non la riesecuzione di ogni record che le ha rese sporche.
Nulla di tutto ciò rende la replica lo strumento sbagliato per i suoi scopi previsti: migrazioni, aggiornamenti di versioni principali e passaggi a un secondo Postgres. Tuttavia, l'utilizzo della replica logica come storage crea una seconda fonte di verità.
Tutti e quattro i miti derivano dallo stesso malinteso: trattare ogni materializzazione dei dati come un secondo database.
LTAP mantiene una singola tabella logica e il lake ne costituisce la copia persistente. Pagine di heap, indici e cache sono il modo in cui Postgres gestisce quella tabella con latenza OLTP, un po' come un indice di copertura gestisce le query su un heap. Un motore OLAP analizza direttamente il Parquet e unisce le modifiche transazionalmente coerenti dall'ultima materializzazione.
Nessuno di questi livelli aggiunge un secondo catalogo o una seconda fonte di verità che possa divergere.
Per un'analisi approfondita di come l'architettura di storage di Lakebase porti a LTAP, consulta questo post.
(Questo post sul blog è stato tradotto utilizzando strumenti basati sull'intelligenza artificiale) Post originale
Iscriviti al nostro blog e ricevi gli ultimi articoli direttamente nella tua casella di posta.