¿De verdad necesitas una base de datos?
(dbpro.app)- Todas las bases de datos son, al final, conjuntos de archivos estructurados sobre un sistema de archivos, por lo que una aplicación en etapa inicial puede lograr rendimiento suficiente gestionando archivos directamente
- Al implementar el mismo servidor en Go, Bun y Rust y comparar tres enfoques —escaneo de archivos, mapa en memoria y búsqueda binaria en disco—, se observó que incluso con acceso simple a archivos se puede lograr un alto rendimiento
- El enfoque con mapa en memoria mostró el mejor rendimiento (hasta 169k req/s), mientras que SQLite fue estable con 25k req/s, aunque con cierto overhead
- La mayoría de los servicios pueden manejar hasta alrededor de 90 millones de DAU con un solo archivo SQLite, por lo que en la etapa inicial del producto no hace falta una base de datos separada
- La adopción de una base de datos se vuelve necesaria cuando el dataset supera la RAM o se requieren joins, búsquedas con múltiples condiciones, escrituras concurrentes o transacciones
¿De verdad necesitas una base de datos?
- Una base de datos es, al final, un conjunto de archivos: SQLite es un solo archivo y PostgreSQL está compuesto por un directorio y procesos
- Todas las bases de datos leen y escriben en el sistema de archivos, y funcionan de la misma forma que cuando el código llama a
open() - Por eso, la pregunta clave no es “¿vas a escribir archivos?”, sino “¿vas a usar los archivos de una base de datos o vas a administrarlos tú mismo?”
- Muchas aplicaciones en etapa inicial pueden lograr rendimiento suficiente incluso administrándolos directamente
- Todas las bases de datos leen y escriben en el sistema de archivos, y funcionan de la misma forma que cuando el código llama a
Configuración del experimento
- Se implementó el mismo servidor HTTP en Go, Bun (TypeScript) y Rust, comparando dos estrategias de almacenamiento
- Se usaron tres archivos JSONL:
users.jsonl,products.jsonl,orders.jsonl - Creación con
POST /usersy consulta conGET /users/:id - Solo la ruta de consulta (GET) se tomó como objetivo del benchmark
- Se usaron tres archivos JSONL:
-
Enfoque 1: leer el archivo en cada solicitud
- En cada request se abre el archivo, se escanean todas las líneas, se parsea el JSON y se verifica si el ID coincide
- En promedio hay que leer la mitad del archivo, así que la complejidad es O(n)
- A medida que crecen los datos, la velocidad de procesamiento por request cae con fuerza
-
Enfoque 2: cargar todo en memoria
- Al iniciar, se lee el archivo completo y se guarda en un hash map basado en ID
- Las escrituras se reflejan tanto en el mapa como en el archivo, y las lecturas son una sola consulta al mapa, con complejidad O(1)
- El archivo actúa como almacenamiento persistente y el mapa como índice
- Go usa
sync.RWMutexy RustRwLockpara permitir lecturas en paralelo
-
Enfoque 3: búsqueda binaria en disco
- Una solución intermedia para consultas rápidas sin subir todos los datos a la RAM
- Se genera un archivo de datos ordenado por ID y un archivo de índice de ancho fijo (58 bytes por registro)
- Con
ReadAtse busca en el índice en O(log n) y luego se lee un solo registro desde el offset correspondiente - Al agregar nuevos registros se rompe el orden, por lo que hace falta regenerar o fusionar periódicamente el índice
- Ese patrón de fusión se parece al funcionamiento de un LSM-tree
Entorno del benchmark
- Tamaño del dataset: 10k, 100k y 1M de registros
- Herramienta de carga: wrk, con requests GET aleatorios durante 10 segundos, 4 hilos y 50 conexiones concurrentes
- Pruebas en la misma máquina (Apple M1 Mac mini, macOS 15) con Go 1.26, Bun 1.3 y Rust 1.94
- En Go también se comparó adicionalmente con búsqueda binaria (disco) y SQLite (
modernc.org/sqlite)
Resultados principales
- Caída del rendimiento en escaneo lineal: con 1M de registros, Go bajó a 23 req/s y Bun a 19 req/s
- Búsqueda binaria (disco): entre 10k y 1M de registros pasó de 45k a 38k req/s, una caída de solo 15%
- Gracias al page cache del sistema operativo, la parte superior del índice permanece siempre en memoria
- SQLite: mantuvo rendimiento consistente con 25k req/s y latencia promedio de 2 ms
- La búsqueda binaria fue aproximadamente 1.7 veces más rápida que SQLite; en consultas simples por PK hay overhead en SQLite
- El mapa en memoria fue el más rápido: entre 97k y 169k req/s, con latencia menor a 0.5 ms
- Bun fue más rápido que Go: Bun 106k req/s frente a Go 97k req/s
- Bun se basa en JavaScriptCore + Zig (uWebSockets) y evita libuv
- Rust dominó en escaneo lineal: fue entre 3 y 6 veces más rápido que Go, probablemente por eficiencia de parsing JSON e I/O
-
Mejor opción según el caso de uso
- Máximo throughput absoluto: mapa en memoria en Rust (169k req/s)
- Mejor sin cargar todo en RAM: búsqueda binaria en Go (~40k req/s)
- Si necesitas SQL: SQLite (25k req/s)
- Implementación más simple: escaneo lineal en Go (~20 líneas de código)
Qué significan 25,000 req/s
- En tráfico web normal se asume una proporción pico:promedio = 2:1
- Promedio de 12,500 req/s → pico de 25,000 req/s
- Suponiendo que un usuario activo hace 10 consultas por hora y que en el pico hay una tasa de concurrencia del 10%
- Fórmula de requests en pico: DAU × 0.000278
- Resultado del cálculo de DAU de saturación para cada enfoque
- Escaneo lineal en Go: 2.8M
- Búsqueda binaria en Go: 144M
- SQLite: 90M
- Mapa en memoria en Go: 349M
- Mapa en memoria en Bun: 381M
- Mapa en memoria en Rust: 608M
- La mayoría de los productos nunca llegan a esas cifras
- Ejemplo: 10,000 clientes SaaS → 3 req/s, una app con 100,000 DAU → 30 req/s
- En conclusión, la mayoría de los productos en etapa inicial no necesitan una base de datos
- Y si la necesitas, un solo archivo SQLite puede manejar hasta 90 millones de DAU
Cuándo sí necesitas una base de datos
-
Cuando el dataset ya no cabe en RAM
- Con decenas de millones de registros, solo el índice ya puede requerir varios GB
- Hace falta paginación de datos, y una base de datos se encarga de eso automáticamente
-
Cuando necesitas consultar por campos distintos al ID
- Las búsquedas con múltiples condiciones requieren escaneo de archivos o mapas adicionales
- Mantener varios mapas es, en la práctica, implementar tu propio motor de consultas
-
Cuando necesitas joins
- Hay que leer y combinar varios archivos, y SQL resulta más eficiente
-
Cuando hay escrituras concurrentes desde múltiples procesos
- Los mapas en memoria de cada instancia quedan separados y se pierde consistencia
- Hace falta una única fuente externa de verdad → ese es el rol de la base de datos
-
Cuando necesitas escrituras atómicas entre entidades
- Es necesario garantizar el éxito o fracaso conjunto de la creación de un pedido y el descuento de inventario
- Habría que implementar un log de transacciones por separado, y la DB lo resuelve con ACID
- En cambio, herramientas internas, side projects y productos iniciales sin esas restricciones
- pueden funcionar perfectamente dentro de la RAM de un solo servidor
- y luego los archivos JSONL pueden migrarse fácilmente a una base de datos
Apéndice y código disponible
- Incluye código del servidor en Go, Bun y Rust
- También se entregan los datos de seed y el script para ejecutar benchmarks (
run_bench.sh) - El archivo ZIP incluye
go-server/,bun-server/,rust-server/yseed.ts - El script genera datos en tres escalas, corre pruebas de carga con wrk y luego termina
Información relacionada con DB Pro
-
DB Pro** es un cliente de bases de datos para Mac, Windows y Linux**
- Integra funciones de consulta, exploración y administración
- Incluye una plataforma web colaborativa y soporte de IA incorporado
- En la versión más reciente, agrega soporte para conectar bases de datos SQLite de Val Town
- En la v1.3.0 se añadieron creación de bases de datos, editor de múltiples consultas y conexión con PlanetScale Vitess
26 comentarios
¿Qué demonios es esta estupidez? ¿Creen que la base de datos se usa por rendimiento?
Sí, yo también pensé que quizá habría alguna idea nueva, así que revisé el texto original, pero ¿qué es esto...
En vez de arrancar con cosas básicas como que la memoria es cara y por eso se usa disco, o la estabilidad operativa en producción, o la atomicidad, se ponen de golpe a comparar velocidades, y la verdad da hasta risa.
“Vendemos DB, pero eso no significa que siempre necesites una DB”. No sé si quieren hacer marketing al dejar un artículo así y decir esto con tanta soltura... Aunque intente verlo de forma positiva, a veces me pongo algo cínico.
Supongo que al menos nos quedó un benchmark.
Es el típico código de boquilla.
Me parece un texto muy bueno. En especial, este tipo de material con esos 'números' es valioso. Estamos en una época en la que no es fácil encontrar desarrolladores que al menos tengan una idea aproximada del overhead que tienen el código que hacemos y el stack tecnológico que traemos para usar, así que lo leí con gusto.
Yo también estoy de acuerdo. Creo que es un material que aporta una intuición importante sobre la mechanical sympathy y sobre cómo modular el ritmo del desarrollo. Como
Latency Numbers Every Programmer Should Know.Y tampoco me pareció que este texto dijera que una dirección específica sea incondicionalmente mejor. Más bien, como las cifras que mostraron todos los enfoques mencionados en el texto se veían como un “rendimiento que sobra para la gran mayoría de los negocios”, lo leí como una invitación a elegir el enfoque que mejor se adapte a la situación del problema.
Y las joyitas de las respuestas son un extra.
Si hay una razón para hacerlo así, entonces valdría la pena considerarlo, ¿no? Por ejemplo, si las limitaciones de rendimiento fueran realmente muy severas.
Pero en la mayoría de los casos, ¿realmente hay una razón para elegir esto a la fuerza? No es como si la base de datos no ofreciera ventajas...
Solo se siente como un cambio de perspectiva, pero todos están muy sensibles.
Así es. Se puede ver como una propuesta de que, al inicio del negocio, cuando todavía no hay muchos usuarios, en lugar de comprar una base de datos o complicarse demasiado, podría ser posible llegar hasta que el negocio se estabilice solo con I/O básico de archivos.
Yo también estoy de acuerdo. A veces, en los servicios, se le da a la BD más importancia de la necesaria y, en ocasiones, incluso se invierte demasiado en el diseño, como si romper la normalización fuera a causar un gran problema.
No se trata de no usar una BD, sino de refrescar un poco la perspectiva sobre por qué la usamos y cuál es realmente la base del servicio.
Al final, el balance siempre es lo importante.
Desde el momento en que eliges SQLite para un servidor de producción, tienes que estar pensando constantemente en cuándo migrar a otra cosa.
Antes valía la pena pensarlo porque el costo del DB en sí (compra de servidores, IDC, licencias, etc.) era alto,
pero hoy en día supuestamente se puede montar con un simple clic, así que ¿de verdad hace falta pensarlo tanto?
Incluso ahora, las bases de datos son caras.
Claro, si es un "proyecto en etapa inicial o una aplicación pequeña", puede que no necesites una base de datos. No solo la base de datos, también puedes resolver más o menos cualquier otro elemento como sea. El problema es cuando escala. Es solo un artículo para ver números por diversión.
https://hackers.pub/@gnh1201/2025/…
A veces no hace falta instalar una base de datos aparte. Aunque está limitado a Windows...
Con solo ver el título, me solté a carcajadas.
A veces pienso si realmente es necesario que las entidades principales garanticen la persistencia mediante un RDBMS. También hay bastantes tecnologías alternativas para proporcionar una SSOT.
Si se rompe SQLite, no hay nada que hacer...
¿Hay casos en los que
sqlitese corrompa? Me da curiosidad. Excluyendo traslados o eliminaciones anormales de archivos.Comentarios de Hacker News
Me gusta mucho este artículo. Muestra muy bien lo rápidas que son las computadoras
Aun así, no estoy de acuerdo con la conclusión de la parte final. El autor dijo que no aplica a apps con muchas restricciones, como aquellas donde “varios procesos necesitan escribir al mismo tiempo”, pero en la práctica incluso en productos en etapa inicial suele pasar que workers separados, como cron o una message queue, necesiten escribir simultáneamente
Se puede hacer que solo escriba el servidor principal, pero eso aumenta la complejidad de la arquitectura
Así que, desde una perspectiva puramente de escala, estoy de acuerdo con el autor, pero en un sentido más amplio creo que es mejor usar una base de datos. En particular, SQLite es una opción razonable
Si necesitas escalar, puedes cachear en memoria los datos a los que accedes con frecuencia. La combinación que uso es SQLite + caché en memoria
A veces S3 funciona, pero sigue teniendo muchas limitaciones para usarlo como reemplazo completo
Es mucho más simple y barato porque no hace falta administrar ni respaldar un servidor de DB aparte
Me encanta SQLite, pero me di cuenta de que no es la respuesta para todos los problemas
Al crear una app de diccionario del lado del cliente, probé el port wasm de SQLite, pero el archivo de la DB era más grande de lo esperado, no comprimía bien y además cargaba lento
Al final cambié a un enfoque donde genero índices directamente desde el archivo TSV original y lo comprimo con zstd, para luego descomprimirlo cada vez en wasm. Esto fue mucho más rápido que SQLite
El tamaño del módulo también bajó de 800KB a 52KB, y no había problema aunque levantara varias instancias al mismo tiempo
Para la búsqueda de cadenas usé stringzilla, y es absurdamente rápido
SQLite es excelente, pero no es la respuesta correcta en todos los casos
El benchmark de SQLite está poco optimizado
Con solo agregar
el rendimiento en mi máquina saltó de 27,700 r/s a 89,687 r/s
Probé prepared statements y cambiar timestamp a int, pero no hubo una gran diferencia
El artículo estaba bien, pero la parte de que “todas las DB acceden al filesystem con open()” no es precisa
Apps como SQLite usan mmap para mapear directamente el archivo al espacio de memoria. Con este método se pueden saltar syscalls y acceder mucho más rápido
Más adelante el artículo explica el proceso de leer todo el archivo en memoria, pero habría sido mejor usar mmap
Aun así, no necesariamente diría que mmap siempre es mejor. Hay gente que prefiere manejarlo directamente en la lógica de la aplicación en vez de depender de la API del OS
Como referencia, está el estudio de CMU sobre mmap
Decir que “funciona como open()” es una simplificación, pero técnicamente no está mal
Hace mucho hice una pequeña web app de ventas en Perl, pero como no podía instalar nada en el servidor del ISP, usé un hash basado en archivos
El cliente lo siguió usando tal cual por más de 20 años hasta que falleció, y cuando su familia tomó el control lo cambiaron por Wordpress
La última vez que lo revisé tenía cientos de miles de pedidos y el rendimiento seguía siendo aceptable
Gracias a la evolución del hardware, esta estructura medio hack aguantó mucho más de lo esperado. Si fuera hoy, probablemente SQLite también habría sido suficiente
Si implementas el storage por tu cuenta, puedes entender cómo funciona una DB
Tienes que manejar índices y estructuras de datos de forma eficiente, y al final llegas a la conclusión de que “si no era un juguete, debiste haber usado una DB desde el principio”
Relational Databases Aren’t Dinosaurs, They’re Sharks En comparación con la pequeña ganancia que obtienes en una app chica, es muchísimo mayor la pérdida de tiempo por reinventar la rueda
En la época del Cretácico los tiburones ya tenían casi la misma forma que hoy, y sobrevivieron después sin grandes cambios
En cambio, dinosaurios, pterosaurios y mosasaurios desaparecieron, mientras que tiburones, cocodrilos y serpientes grandes siguen existiendo casi igual hasta hoy gracias a un diseño optimizado
Creo que las DB relacionales son algo así
Disfruto leer este tipo de artículos
Aun así, en el 99% de los casos sigo usando una DB con SQL y transacciones
Pero en un proyecto personal reciente probé manejar los datos con un filesystem simple basado en archivos YAML, y a mi escala no hubo ningún problema de rendimiento
Que fuera legible por humanos y permitiera diff era más importante que el rendimiento
De todas formas, en la mayoría de los casos elegiría una DB con lenguaje de consultas y consistencia garantizada
Al final siempre terminas necesitando las funciones de una DB y las garantías ACID
Cada vez que me toca usar un store legacy de archivos planos, sufro tratando de pegarle consistencia, transacciones y un lenguaje de consultas a la fuerza. Al final es reinventar la rueda
En el momento en que necesitas atomicidad, una DB se vuelve indispensable
Implementar escrituras atómicas sobre un filesystem es muy frágil
Por eso muchas DB sufren problemas de corrupción de datos cuando hay crashes. Antes pasaba con RocksDB en Windows
Implementarlo por mi cuenta me parece una locura. Estaría bien aprender a escribir de forma segura usando la API del OS, pero hoy en día eso ya es una habilidad demasiado de nicho
Además, es muy probable que quien venga después no pueda mantenerlo. Al final terminarías migrando a una DB
Como mínimo, hay que escribir en un archivo temporal dentro del mismo filesystem, hacer
fsyncy luego reemplazar conrenameSi escribes toda la DB a un archivo temporal, haces flush y luego lo reemplazas con move, en Unix eso es atómico
Pero esto no escala en absoluto. Incluso una actualización pequeña obliga a reescribir el archivo completo, y además necesitas manejar locks. Solo resuelve una parte de ACID
Como referencia, DuckDB, que es una DB OLAP, también funciona excelentemente en workloads out-of-core
Enlace a la documentación oficial
Se puede vivir sin refrigerador, pero sería incómodo.
No hay razón para no usarlo si puedes hacerlo.
¿Eres un usuario de Ilbe o qué?
¿Si digo que no, entonces ya soy de Ilbe? ¿Y yo que soy de Gyeongsang-do qué?
Quiero reportarlo, pero no sé cómo hacerlo. Uf.
Parece que este comentario refleja la forma de pensar tan encerrada de los desarrolladores coreanos y el nivel de GeekNews.
Explica cuál es ese nivel, por qué evaluaste el nivel de esa manera, y dilo usando al menos dos entre lógica/hechos/ciencia/estadística, sí.
Jaja, con solo ver las palabras ya se nota que es de DC Inside, Ilbe o Fmkorea, así que no le hagas caso.