mirror of
https://github.com/hanzoai/papers.git
synced 2026-10-22 17:20:17 +00:00
123 lines
4.8 KiB
TeX
123 lines
4.8 KiB
TeX
\section{Specification}
|
|
|
|
|
|
\subsection{Components}
|
|
|
|
The replication system consists of three components:
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\begin{tabular}{@{}lll@{}}
|
|
\toprule
|
|
\textbf{Component} & \textbf{Image} & \textbf{Purpose} \\
|
|
\midrule
|
|
\texttt{hanzoai/replicate} & \texttt{ghcr.io/hanzoai/replicate} & WAL replication + age encryption \\
|
|
\texttt{hanzoai/age} & \texttt{ghcr.io/hanzoai/age} & X25519 key generation \\
|
|
\texttt{hanzoai/s3} & \texttt{ghcr.io/hanzoai/s3} & S3-compatible object storage \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\caption{System components.}
|
|
\label{tab:components}
|
|
\end{table}
|
|
|
|
\texttt{hanzoai/replicate} is a fork of Litestream incorporating Beeper's
|
|
age encryption patches, rebased onto mainline Litestream, and maintained as
|
|
part of the Hanzo ecosystem. The binary is called \texttt{replicate}. It
|
|
accepts the same configuration format as Litestream with one addition: an
|
|
\texttt{age} block specifying identities and recipients.
|
|
|
|
\texttt{hanzoai/age} is a container image packaging the \texttt{age-keygen}
|
|
binary for key generation in init containers and CI pipelines.
|
|
|
|
\texttt{hanzoai/s3} (HIP-0032) provides the S3-compatible storage backend.
|
|
|
|
\subsection{Replication Protocol}
|
|
|
|
The replication lifecycle for a single SQLite database:
|
|
|
|
\begin{enumerate}[nosep]
|
|
\item \textbf{Startup.} \texttt{replicate} reads the configuration file,
|
|
connects to the S3 endpoint, and checks for an existing replica.
|
|
|
|
\item \textbf{Restore (if needed).} If the local database does not exist
|
|
and a replica exists in S3, \texttt{replicate} downloads the most recent
|
|
snapshot, decrypts it with the age identity, replays all subsequent WAL
|
|
segments (each decrypted individually), and writes the restored database
|
|
to the configured path.
|
|
|
|
\item \textbf{Monitor.} \texttt{replicate} monitors the SQLite WAL file
|
|
using \texttt{inotify} (Linux) or \texttt{kqueue} (macOS). When new
|
|
frames are written, it reads them from the WAL.
|
|
|
|
\item \textbf{Encrypt.} Each WAL segment is encrypted with the
|
|
configured age recipients before upload. Encryption happens in memory.
|
|
Plaintext WAL data never touches S3.
|
|
|
|
\item \textbf{Upload.} The encrypted WAL segment is uploaded to S3 at the
|
|
configured path. The upload is atomic (multipart with commit).
|
|
|
|
\item \textbf{Snapshot.} Periodically (default: every 24 hours),
|
|
\texttt{replicate} takes a full snapshot of the database, encrypts it,
|
|
and uploads it to S3. Old snapshots are retained according to the
|
|
configured retention policy.
|
|
|
|
\item \textbf{Shutdown.} On SIGTERM, \texttt{replicate} flushes any
|
|
pending WAL frames, uploads them, and exits cleanly. Kubernetes sends
|
|
SIGTERM before SIGKILL, so graceful shutdown is the normal path.
|
|
\end{enumerate}
|
|
|
|
\subsection{Encryption Layers}
|
|
|
|
Data is protected by three independent encryption layers:
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\begin{tabular}{@{}llll@{}}
|
|
\toprule
|
|
\textbf{Layer} & \textbf{Mechanism} & \textbf{Scope} & \textbf{Protects Against} \\
|
|
\midrule
|
|
1. Disk & sqlcipher (AES-256-CBC) & Database at rest & Local filesystem access \\
|
|
2. S3 & age (X25519 + ChaCha20) & Replicas in storage & S3 operator, bucket leak \\
|
|
3. Wire & TLS 1.3 & In transit & Network interception \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\caption{Encryption layers. Each layer is independent---compromise of one
|
|
does not compromise the others.}
|
|
\label{tab:encryption}
|
|
\end{table}
|
|
|
|
Layer 1 (sqlcipher) is managed by Hanzo Base. Layer 2 (age) is managed by
|
|
\texttt{replicate}. Layer 3 (TLS) is managed by the S3 endpoint. This HIP
|
|
specifies Layer 2.
|
|
|
|
\subsection{Recovery Objectives}
|
|
|
|
\begin{table}[H]
|
|
\centering
|
|
\begin{tabular}{@{}lll@{}}
|
|
\toprule
|
|
\textbf{Metric} & \textbf{Target} & \textbf{Mechanism} \\
|
|
\midrule
|
|
RPO (Recovery Point Objective) & $\leq$ 1 second & Continuous WAL streaming \\
|
|
RTO (Recovery Time Objective) & $\leq$ 30 seconds & Snapshot restore + WAL replay \\
|
|
Retention & 72 hours WAL, 30 days snapshots & S3 lifecycle policy \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\caption{Recovery objectives.}
|
|
\label{tab:recovery}
|
|
\end{table}
|
|
|
|
The 1-second RPO is achieved by the default \texttt{sync-interval} of 1
|
|
second. WAL frames generated within the last sync interval may be lost on
|
|
unclean shutdown (SIGKILL without preceding SIGTERM). In practice,
|
|
Kubernetes provides a 30-second grace period between SIGTERM and SIGKILL, so
|
|
data loss on clean shutdown is zero.
|
|
|
|
The 30-second RTO accounts for S3 download latency for the most recent
|
|
snapshot (typically 1--5 seconds for databases under 1~GiB) plus WAL replay
|
|
time (linear in the number of frames since the last snapshot).
|
|
|
|
% ============================================================================
|
|
% 4. CONFIGURATION
|
|
% ============================================================================
|