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.