Skip to content
Esta página fue generada y traducida con asistencia de IA. Si encuentra alguna imprecisión, no dude en ayudar a mejorarla. Editar en GitHub

Almacenamiento SQLite

PRX-Email usa SQLite como su único backend de almacenamiento, accedido a través del crate rusqlite con la compilación SQLite bundled. La base de datos se ejecuta en modo WAL con claves foráneas habilitadas, proporcionando lecturas concurrentes rápidas y aislamiento confiable de escritura.

Configuración de la Base de Datos

Ajustes por Defecto

AjusteValorDescripción
journal_modeWALWrite-Ahead Logging para lecturas concurrentes
synchronousNORMALDurabilidad/rendimiento equilibrado
foreign_keysONAplicar integridad referencial
busy_timeout5000msTiempo de espera para base de datos bloqueada
wal_autocheckpoint1000 páginasUmbral de punto de control WAL automático

Configuración Personalizada

rust
use prx_email::db::{EmailStore, StoreConfig, SynchronousMode};

let config = StoreConfig {
    enable_wal: true,
    busy_timeout_ms: 5_000,
    wal_autocheckpoint_pages: 1_000,
    synchronous: SynchronousMode::Normal,
};

let store = EmailStore::open_with_config("./email.db", &config)?;

Modos de Sincronización

ModoDurabilidadRendimientoCaso de Uso
FullMáximaEscrituras más lentasCargas de trabajo financieras o de cumplimiento
NormalBuena (predeterminado)EquilibradoUso general en producción
OffMínimaEscrituras más rápidasSolo desarrollo y pruebas

Base de Datos en Memoria

Para pruebas, usa una base de datos en memoria:

rust
let store = EmailStore::open_in_memory()?;
store.migrate()?;

Esquema

El esquema de la base de datos se aplica a través de migraciones incrementales. Ejecutar store.migrate() aplica todas las migraciones pendientes.

Tablas

mermaid
erDiagram
    accounts {
        int id PK
        text email
        text display_name
        int created_at
        int updated_at
    }

    folders {
        int id PK
        int account_id FK
        text name
        text path
        int created_at
        int updated_at
    }

    messages {
        int id PK
        int account_id FK
        int folder_id FK
        text message_id UK
        text subject
        text sender
        text recipients
        text snippet
        text body_text
        text body_html
        text attachments_json
        text references_header
        int received_at
        int created_at
        int updated_at
    }

    outbox {
        int id PK
        int account_id FK
        text to_recipients
        text subject
        text body_text
        text in_reply_to_message_id
        text provider_message_id
        text status
        int retries
        text last_error
        int next_attempt_at
        int created_at
        int updated_at
    }

    sync_state {
        int id PK
        int account_id FK
        int folder_id FK
        text cursor
        int last_synced_at
        text status
        int updated_at
    }

    feature_flags {
        text key PK
        text description
        int default_enabled
        text risk_level
        int updated_at
    }

    account_feature_flags {
        int account_id FK
        text feature_key FK
        int enabled
        int updated_at
    }

    accounts ||--o{ folders : has
    accounts ||--o{ messages : has
    accounts ||--o{ outbox : has
    accounts ||--o{ sync_state : has
    accounts ||--o{ account_feature_flags : has
    folders ||--o{ messages : contains
    feature_flags ||--o{ account_feature_flags : overrides

Índices

TablaÍndicePropósito
messages(account_id)Filtrar mensajes por cuenta
messages(folder_id)Filtrar mensajes por carpeta
messages(subject)Búsqueda LIKE en asuntos
messages(account_id, message_id)Restricción única para UPSERT
outbox(account_id)Filtrar buzón de salida por cuenta
outbox(status, next_attempt_at)Reclamar registros elegibles del buzón de salida
sync_state(account_id, folder_id)Restricción única para UPSERT
account_feature_flags(account_id)Búsquedas de indicadores de características

Migraciones

Las migraciones están embebidas en el binario y se aplican en orden:

MigraciónDescripción
0001_init.sqlTablas de cuentas, carpetas, mensajes, sync_state
0002_outbox.sqlTabla de buzón de salida para pipeline de envío
0003_rollout.sqlIndicadores de características y indicadores de características por cuenta
0005_m41.sqlRefinamientos de esquema M4.1
0006_m42_perf.sqlÍndices de rendimiento M4.2

Las columnas adicionales (body_html, attachments_json, references_header) se añaden via ALTER TABLE si no están presentes.

Ajuste de Rendimiento

Cargas de Trabajo de Lectura Intensiva

Para aplicaciones que leen mucho más de lo que escriben (clientes de email típicos):

rust
let config = StoreConfig {
    enable_wal: true,              // Concurrent reads
    busy_timeout_ms: 10_000,       // Higher timeout for contention
    wal_autocheckpoint_pages: 2_000, // Less frequent checkpoints
    synchronous: SynchronousMode::Normal,
};

Cargas de Trabajo de Escritura Intensiva

Para operaciones de sincronización de alto volumen:

rust
let config = StoreConfig {
    enable_wal: true,
    busy_timeout_ms: 5_000,
    wal_autocheckpoint_pages: 500, // More frequent checkpoints
    synchronous: SynchronousMode::Normal,
};

Análisis del Plan de Consulta

Comprueba consultas lentas con EXPLAIN QUERY PLAN:

sql
EXPLAIN QUERY PLAN
SELECT * FROM messages
WHERE account_id = 1 AND subject LIKE '%invoice%'
ORDER BY received_at DESC LIMIT 50;

Planificación de Capacidad

Factores de Crecimiento

TablaPatrón de CrecimientoEstrategia de Retención
messagesTabla dominante; crece con cada sincronizaciónPurgar mensajes antiguos periódicamente
outboxAcumula historial enviado + fallidoEliminar registros enviados antiguos
Archivo WALPicos durante ráfagas de escrituraPunto de control automático

Umbrales de Monitoreo

  • Rastrea el tamaño del archivo de BD y el tamaño de WAL de forma independiente
  • Alerta cuando el WAL sigue siendo grande después de múltiples puntos de control
  • Alerta cuando el backlog fallido del buzón de salida supera el SLO operacional

Mantenimiento de Datos

Ayudantes de Limpieza

rust
// Delete sent outbox records older than 30 days
let cutoff = now - 30 * 86400;
let deleted = repo.delete_sent_outbox_before(cutoff)?;
println!("Deleted {} old sent records", deleted);

// Delete messages older than 90 days
let cutoff = now - 90 * 86400;
let deleted = repo.delete_old_messages_before(cutoff)?;
println!("Deleted {} old messages", deleted);

SQL de Mantenimiento

Comprobar la distribución de estados del buzón de salida:

sql
SELECT status, COUNT(*) FROM outbox GROUP BY status;

Distribución por antigüedad de mensajes:

sql
SELECT
  CASE
    WHEN received_at >= strftime('%s','now') - 86400 THEN 'lt_1d'
    WHEN received_at >= strftime('%s','now') - 604800 THEN 'lt_7d'
    ELSE 'ge_7d'
  END AS age_bucket,
  COUNT(*)
FROM messages
GROUP BY age_bucket;

Punto de control WAL y compactación:

sql
PRAGMA wal_checkpoint(TRUNCATE);
VACUUM;

VACUUM

VACUUM reconstruye el archivo de base de datos completo y requiere espacio libre en disco igual al tamaño de la base de datos. Ejecútalo en una ventana de mantenimiento después de grandes eliminaciones.

Seguridad SQL

Todas las consultas de base de datos usan sentencias parametrizadas para prevenir inyección SQL:

rust
// Safe: parameterized query
conn.execute(
    "SELECT * FROM messages WHERE account_id = ?1 AND message_id = ?2",
    params![account_id, message_id],
)?;

Los identificadores dinámicos (nombres de tablas, nombres de columnas) se validan contra ^[a-zA-Z_][a-zA-Z0-9_]{0,62}$ antes de usarlos en cadenas SQL.

Siguientes Pasos

Released under the Apache-2.0 License.