Construyendo un núcleo OLTP: Contratos API, tamaño de página y ajuste adaptativo
Los desarrolladores de una nueva base de datos OLTP implementaron una arquitectura estricta con capas aisladas usando interfaces de traits en Rust. Cada capa tiene contratos API claros, el tamaño de página es configurable por tablespace, y la configuración la gestiona un Resource Broker sin necesidad de ajuste manual. Los diagnósticos se integran vía USDT y eBPF.
Configuración de tamaño de página y tablespaces
Los tamaños de página de datos van de 4-8 KB para OLTP puro a 16-32 KB para HTAP en NVMe. Cada tablespace es un archivo independiente con un tamaño de página fijo tras la creación. Por defecto, es 16 KB. El PageId es compuesto: [tablespace_id:16][page_index:48]. El BufferPool enruta las solicitudes a subcaches por tamaño.
El superbloque al inicio del archivo almacena metadatos del tablespace de forma independiente al tamaño de página. Al montar, verifica la configuración; las discrepancias impiden el arranque sin degradación.
Capas arquitectónicas y contratos API
El núcleo se divide en cuatro capas:
- Capa de Adaptador (async tokio): termina TLS, parsea el protocolo de red.
#[async_trait]
pub trait NetworkAdapter {
async fn handle_connection(&self, stream: Box<dyn ConnectionStream>) -> Result<(), NetworkError>;
}
- Capa de Compatibilidad: parsea SQL a AST, emula pg_catalog, traduce a LogicalPlan, rechaza características no soportadas (error 0A000).
pub trait CompatLayer {
fn translate_query(&self, ast: SqlAst) -> Result<LogicalPlan, CompatError>;
}
- Motor Principal (sync): optimizador, ejecutor, gestor de transacciones. Se ejecuta en un pool spawn_blocking.
pub trait ExecutionEngine {
fn execute_plan(&self, plan: LogicalPlan, session: &Session) -> Result<ResultSet, ExecutionError>;
}
- Gestor de Almacenamiento: fachada sobre PageProvider y TransactionLogSink. El núcleo solicita páginas y registros UNDO.
pub trait StorageManager {
fn pin_page(&self, page_id: PageId, mode: LockMode) -> Result<PageGuard, StorageError>;
fn append_undo(&self, txn_id: TxnId, record: UndoRecord) -> Result<UndoPtr, StorageError>;
}
Los límites entre capas evitan fugas de detalles: las métricas de bloqueos excluyen esperas asíncronas.
Ajuste adaptativo y Resource Broker
Unos 60 parámetros se dividen en presupuestos, límites de seguridad y anulaciones. Los operadores establecen límites de alto nivel:
[resources]
memory_budget = "16GB"
cpu_budget = "auto"
io_iops = 5000
Los asesores (MemoryAdvisor, IoAdvisor, CpuAdvisor) redistribuyen recursos cada segundo:
- CpuAdvisor: DOP y pool de hilos vs. limitación.
- MemoryAdvisor: BufferPool vs. work_mem bajo carga.
- IoAdvisor: prioridad de transacciones, conciencia de ráfagas en la nube.
Medidas de seguridad
- Histeresis: ventana de 5s, pasos del 5% para evitar oscilaciones.
- Límites mínimos: 128 MB para BufferPool/UNDO.
- Transición suave: nuevos límites para nuevas asignaciones.
- Anulaciones expertas sobrescriben el ajuste automático.
Purgado de log UNDO en segundo plano en lugar de VACUUM. Cambios en tiempo de ejecución sin reinicio.
Diagnósticos: sondas USDT y eBPF
Los mecanismos de diagnóstico están integrados en el binario. Sondas USDT para análisis en runtime, eBPF para trazado sin sobrecarga.
Lecciones clave:
- Tamaño de página por tablespace con verificaciones al inicio.
- Contratos estrictos de traits entre capas evitan fugas.
- Resource Broker: presupuestos → ajuste automático con límites de seguridad.
- Diagnósticos vía eBPF/USDT en el núcleo.
- Olvídate del ajuste manual por mecanismos adaptativos.
— Editorial Team
Aún no hay comentarios.