Back to flin
flin

FlinDB vs SQLite: por qué construimos el nuestro

Una comparación detallada de FlinDB y SQLite -- dónde se solapan, dónde divergen, y por qué construimos un motor de base de datos personalizado para el lenguaje de programación FLIN en lugar de embeber SQLite.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 9 min flin
EN/ FR/ ES
flinrust

"¿Por qué no simplemente usar SQLite?"

Escuchamos esta pregunta desde el primer día que anunciamos FlinDB. Y es una pregunta justa. SQLite es el motor de base de datos más desplegado del mundo. Es embebido, basado en archivos, sin configuración (más o menos), probado en batalla en miles de millones de dispositivos, y de código abierto. Si necesitas una base de datos embebida, SQLite es la opción obvia.

Elegimos construir la nuestra de todos modos. Este artículo explica por qué -- no para desprestigiar a SQLite (que es una obra maestra de ingeniería) sino para articular los problemas específicos que SQLite no puede resolver para FLIN, y las decisiones de diseño que tomamos al construir FlinDB.

Dónde se solapan

FlinDB y SQLite comparten características fundamentales:

FuncionalidadSQLiteFlinDB
Embebido
Basado en archivos
Compatible ACID
Soporte WAL
Transacciones
Claves foráneas
Sin servidor

Ambos se ejecutan en proceso. Ambos almacenan datos en archivos locales. Ambos proporcionan garantías ACID. Ambos soportan logging de escritura anticipada. Para el caso de uso de "necesito una base de datos embebida simple para una aplicación de un solo usuario", ambos funcionarían.

La divergencia comienza cuando miras para qué fue diseñada cada base de datos.

El problema del acceso remoto

La queja número uno de los usuarios de SQLite que lo superan: sin acceso remoto.

SQLite es una biblioteca. Se ejecuta dentro del proceso de tu aplicación. No hay protocolo de red. No hay puerto TCP. Si necesitas una app móvil y un panel web que accedan a los mismos datos, no puedes apuntar ambos a un archivo SQLite.

La solución estándar es construir una API REST delante de SQLite. Esto no es culpa de SQLite. SQLite fue diseñado para ser una base de datos a nivel de aplicación, no un servidor. Pero la consecuencia es que cada aplicación SQLite que necesita acceso de red debe construir su propia capa de API -- duplicando esfuerzo en millones de proyectos.

En FLIN, las rutas SON la API:

flinentity Task {
    title: string @required @min(1)
    completed: bool = false
    created_at: timestamp = now()
}

api GET /tasks {
    @doc "List all tasks"
    @returns [Task]
    return json(Task.all())
}

api POST /tasks {
    @doc "Create a new task"
    @body { title: string }
    @returns Task
    task = Task.create(body)
    return json(task, 201)
}

Ejecutas flin serve y tienes un servidor HTTP con una API REST, documentación auto-generada, validación de tipos y respuestas JSON. La base de datos y la API son lo mismo.

El problema de escrituras concurrentes

SQLite usa bloqueo a nivel de base de datos. Un escritor a la vez. Punto. El modo WAL ayuda con lecturas pero no con escrituras. Bajo carga pesada de escritura, SQLite se convierte en un cuello de botella.

FlinDB implementa bloqueo a nivel de fila con detección de deadlocks:

rustpub struct LockManager {
    row_locks: DashMap<EntityRowKey, LockInfo>,
    wait_graph: RwLock<HashMap<TransactionId, HashSet<TransactionId>>>,
}

Las actualizaciones concurrentes a diferentes filas proceden en paralelo. Solo las actualizaciones concurrentes a la misma fila se serializan. Para una aplicación web manejando múltiples solicitudes simultáneas, esta es la diferencia entre una API responsiva y un cuello de botella.

El problema del viaje en el tiempo

SQLite no rastrea historial. Una sentencia UPDATE sobrescribe el valor anterior. Una sentencia DELETE elimina la fila. Desaparecida. Construir consultas temporales sobre SQLite requiere cientos de líneas de triggers SQL para cada tabla.

En FlinDB, el viaje en el tiempo está integrado en el modelo de almacenamiento:

flin// Get the user's state at any point in time
user_then = User.find(1) @ "2025-06-15"
user_now = User.find(1)

// Compare states
if user_then.email != user_now.email {
    log("Email changed since June 15th")
}

// Get entity history
history = User.find(1).history

El operador @ no es una funcionalidad añadida. Es un operador a nivel de lenguaje que mapea al event store EAVT. Cada guardado crea una nueva versión. Cada versión se preserva. El viaje en el tiempo es una búsqueda en índice, no una reconstrucción.

El problema de la búsqueda semántica

SQLite tiene FTS5 para búsqueda de texto completo. Es bueno para coincidencia de palabras clave. No entiende significado.

FlinDB tiene búsqueda semántica integrada con embeddings vectoriales:

flinentity Product {
    name: text
    description: semantic text
}

// Finds products by meaning, not just keywords
results = search "comfortable seating for work" in Product by description
// Returns: "Ergonomic Office Chair with lumbar support"
// Even though no words match exactly

El tipo semantic text genera automáticamente embeddings vectoriales, los almacena en un índice vectorial interno y habilita búsqueda por similitud coseno. El modo de búsqueda híbrida combina ranking de palabras clave BM25 con similitud vectorial usando Reciprocal Rank Fusion.

El problema de seguridad de tipos

SQLite tiene tipado notoriamente laxo. Una columna declarada como INTEGER puede contener una cadena. El sistema de tipos de SQLite es más una sugerencia que un mecanismo de aplicación.

FlinDB aplica tipos a nivel de esquema:

flinentity User {
    name: text
    age: int
    email: text @pattern(email, "^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$", "Invalid email")
}

// This fails with a type error:
save User { name: "Thales", age: "not a number", email: "bad" }
// Error 1: age must be int, got text
// Error 2: email does not match pattern: Invalid email

FlinDB valida en el momento del guardado, no en el momento de la consulta. Los datos inválidos nunca entran en la base de datos.

El problema del lenguaje de consulta

Esta es la diferencia más fundamental. SQLite habla SQL. FLIN habla FLIN.

sql-- SQLite
SELECT tasks.*, users.name as author_name
FROM tasks
JOIN users ON tasks.user_id = users.id
WHERE users.id = ?
ORDER BY tasks.created_at DESC
LIMIT 10;
flin// FLIN
Task.where(user: auth.user)
    .include(user)
    .order(created_at: desc)
    .limit(10)

SQL es una cadena embebida en otro lenguaje. Tiene su propia sintaxis, sus propios mensajes de error, su propio sistema de tipos. La inyección SQL es posible porque la consulta es una cadena que se interpola. Los ORMs existen únicamente para traducir entre el lenguaje de aplicación y SQL.

Las consultas de FlinDB son expresiones FLIN. Son verificadas de tipos por el compilador. No pueden ser inyectadas. No necesitan un ORM porque no hay desajuste de impedancia entre el lenguaje de consulta y el lenguaje de aplicación.

Comparación de rendimiento

Para cargas de trabajo de un solo hilo, SQLite y FlinDB rinden similarmente:

OperaciónSQLiteFlinDB
Lectura (una fila)~10 microsegundos~15 microsegundos
Lectura (1.000 filas)~1 ms~1,5 ms
Escritura (una fila)~50 microsegundos~60 microsegundos
Búsqueda de texto completo~1 ms~1 ms

FlinDB es ligeramente más lento para operaciones básicas porque mantiene historial de versiones e índices. La sobrecarga es insignificante para cargas de trabajo de aplicaciones.

Para cargas de trabajo concurrentes, FlinDB escala dramáticamente mejor:

OperaciónSQLiteFlinDB
100 escrituras concurrentes~5.000 microsegundos (serializado)~150 microsegundos (paralelo)
Búsqueda semánticaN/A~50 ms
Consulta de viaje en el tiempoN/A~20 microsegundos

La mejora de 33x en escrituras concurrentes viene del bloqueo a nivel de fila. SQLite serializa todas las escrituras a través de un solo bloqueo de base de datos. FlinDB solo serializa escrituras a la misma fila.

Cuándo usar cada uno

Usa SQLite cuando: - Necesitas una base de datos embebida simple sin acceso de red - Aplicaciones de escritorio de un solo usuario - Apps móviles (almacenamiento local) - Pruebas y prototipado - Cargas de trabajo con mucha lectura y escrituras mínimas - Estás construyendo una biblioteca, no una aplicación

Usa FlinDB cuando: - Estás construyendo una aplicación FLIN (obviamente) - Necesitas consultas de viaje en el tiempo - Necesitas búsqueda semántica/IA - Necesitas escrituras concurrentes - Quieres generación automática de API - Quieres seguridad de tipos y validación en la capa de datos - Necesitas suscripciones en tiempo real

Usa PostgreSQL cuando: - Necesitas funcionalidades SQL avanzadas (CTEs, funciones de ventana) - Tienes millones de conexiones concurrentes - Necesitas replicación de grado empresarial - Tienes un equipo DBA dedicado - Estás construyendo un data warehouse

La evaluación honesta

SQLite es mejor que FlinDB siendo una base de datos SQL embebida de propósito general. Tiene 25 años de pruebas en batalla, miles de millones de despliegues y una suite de especificación de millones de pruebas. FlinDB no puede igualar esta madurez.

FlinDB es mejor que SQLite siendo el motor de almacenamiento para aplicaciones FLIN. Habla el lenguaje nativamente. Proporciona funcionalidades que SQLite no tiene (versionado temporal, búsqueda semántica, suscripciones en tiempo real, bloqueo a nivel de fila). Elimina categorías completas de configuración que SQLite requiere (capa API, ORM, sistema de migraciones, seguimiento de historial).

No construimos FlinDB porque SQLite sea malo. Lo construimos porque FLIN necesita una base de datos que fue diseñada para FLIN -- una que comparta su filosofía de configuración cero, diseño centrado en entidades y conciencia temporal. SQLite fue diseñado para una era y un paradigma diferentes. FlinDB fue diseñado para FLIN.


Esta es la Parte 14 de la serie "Cómo construimos FlinDB", documentando cómo construimos un motor de base de datos embebido completo para el lenguaje de programación FLIN.

Navegación de la serie: - [067] Tree Traversal and Integration Testing - [068] FlinDB Hardening for Production - [069] FlinDB vs SQLite: Why We Built Our Own (estás aquí) - [070] Persistence in the Browser

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

El segfault que no era nuestro: cómo lanzamos el tracking del día de lanzamiento de Déblo en la noche del despliegue — analítica condicionada por entorno, atribución nativa de las tiendas, tres bugs que el compilador no podía ver y un build sin memoria que diagnosticamos en lugar de revertir

El 1 de julio de 2026 — el día del lanzamiento — el riesgo nunca fue el texto. Era que las campañas de pago salieran a ciegas. Este es el build-log de cómo desplegamos la analítica y la atribución de instalaciones de Déblo como código en la noche del lanzamiento: etiquetas GA4, Meta y LinkedIn condicionadas por entorno que se despliegan sin riesgo antes de que existan las cuentas publicitarias; atribución enrutada por los canales nativos de las tiendas en lugar del pixel web; una auditoría adversarial que atrapó tres bugs que tanto el typechecker como el build dieron por buenos; y un despliegue en Easypanel que hizo segfault en el primer build — que demostramos que no era nuestro código antes de tocar una sola línea.

18 min Jul 1, 2026
deblolaunch-dayclaude-opus-4.8claude-code +26
Thales & Claude thales

Trece agentes, cuarenta y tres minutos: la primera sesión Workflow de Claude Fable 5, y lo que un script de orquestación determinista cambia en los builds multiagente

Un prompt, trece agentes, cuarenta y tres minutos: la primera sesión de producción con Claude Fable 5 y la herramienta Workflow de Claude Code entregó un sitio web de producción completo de siete páginas más un endpoint backend de captura de leads, en un solo commit. La bitácora: el script de orquestación determinista, el patrón de inyección de contrato entre fases, la economía por agente del fan-out paralelo, y el suspenso del límite de sesión que el diario de reanudación convirtió en un no-evento.

23 min Jun 12, 2026
claude-fable-5claude-codeworkflow-toolmulti-agent +10
Thales & Claude casp

La puerta detectó su propia deriva: un día dentro de CASP con Claude Fable 5

Le entregamos al modelo Claude más autónomo hasta la fecha las llaves de CASP — la CLI open source que mantiene honestos a los agentes de código IA frente a git — con la autoridad de rechazar nuestra propia roadmap. Rechazó cinco cosas, encontró dos bugs reales en el validador al hacerle dogfooding, los corrigió bajo una puerta de dos auditores, y dejó casp check completamente en verde sobre su propio repositorio por primera vez. CASP 0.3.0 es el resultado.

15 min Jun 10, 2026
caspzerosuiteworkflowai-cto +9