DuckDB vs SQLite: when each one is the right embedded database
SQLite suits application state and many small transactions, while DuckDB suits analytical queries over large data with bulk appends. Teams usually reach this comparison when a flat file or an ad hoc script has stopped being enough: an application's local store has grown, a pipeline is scanning millions of rows for every report, or someone must pick one embedded engine for a new service. SQLite's own guidance is that it is not directly comparable to client/server databases and competes with fopen() more than with PostgreSQL. DuckDB, by contrast, is built for complex, long-running queries over large portions of a dataset, using a columnar-vectorized engine, and it expects changes to arrive as large-scale bulk appends rather than single-row writes.
Four decision drivers settle most cases. The first is write pattern: frequent small inserts and updates from the application favor SQLite, while periodic bulk loads favor DuckDB. The second is the number of concurrent writers. SQLite allows unlimited readers but only one writer at any instant per database file. DuckDB in read-write mode is also limited to one process per file, and multiple writer threads inside that process conflict only when they change the same row at the same time. The third is data volume. SQLite's documented limit is 281 terabytes, and it suggests a client/server database once content may reach the terabyte range. The fourth is whether both jobs share one file, which should be tested before the design is fixed.
Before committing, confirm three points in your own environment: the peak write rate and whether writers come from separate processes, the largest table and expected growth over the next budget cycle, and how analysts will read the data while the application writes. A common pattern is to keep operational state in SQLite and copy closed data into DuckDB files or Parquet for analysis, which avoids sharing one file between both engines. Check the DuckDB concurrency documentation against your access pattern before deciding that one file can serve both.
Which engine fits which workload
This table maps common workloads to the engine that usually fits better and the reason behind it. Read each row against your own write pattern and query shape, because the deciding factor is almost always how data is written and how it is read.
| Workload | Better fit | Reason |
|---|---|---|
| Mobile or desktop app state (settings, sessions, user records) | SQLite | SQLite is built for application state and many small transactions, and its when-to-use guidance positions it as a local data store embedded in the application itself. |
| Local reporting over Parquet files | DuckDB | DuckDB's columnar-vectorized engine targets complex queries over large portions of the data, and it reads Parquet by naming the file in the FROM clause. |
| Event logs written row by row | SQLite | Row-by-row inserts from one process fit SQLite's transactional model. DuckDB expects large-scale changes such as bulk appends, so frequent single-row writes are a poor match. |
| Ad hoc analysis of CSV exports | DuckDB | DuckDB queries CSV files directly from the FROM clause, so analysts can run aggregations without first loading the data into a server or a schema. |
| Multi-process writers to one database file | Neither as a plain file | SQLite allows only one writer at any instant per file, and DuckDB read-write mode is a single process. Multi-process writes need a server or a different architecture, so confirm the design before choosing either engine. |
| Analytics that must scale past one machine | Neither as an embedded file | SQLite suggests a client/server database once content may reach the terabyte range. Confirm whether a server-based analytical system is needed before committing. |
| Many concurrent readers over one file | Either, with conditions | SQLite supports an unlimited number of simultaneous readers. DuckDB allows several processes to read in READ_ONLY mode, but none of them can write during that time. |
How each engine is built and what that means for operations
Both engines run inside the host process, so neither needs a database server. The differences show up in how data is stored, how writes are coordinated and what routine operations look like once the application is in production.
Embedded in the host process
Both SQLite and DuckDB run inside the application or analysis process rather than as a separate service. There is no daemon to install, patch or monitor, and no port to secure. The operational work moves to the host application's deployment, release and file handling. Check whether your application already manages file permissions, temporary storage and process restarts for this model, because the database is only as available as the process that embeds it.
Row store against columnar engine
SQLite stores each row together, which suits reading or changing one record at a time, such as application state. DuckDB uses a columnar-vectorized engine that reads only the columns a query needs across large portions of the data, which suits complex analytical queries. Expect the wrong layout to cost you: point lookups in a columnar engine are not its strength, and large scans in a row store are slower than they need to be. Benchmark your own query shapes before deciding.
Where SQLite sits in the landscape
SQLite's own guidance is that it is not directly comparable to client/server databases such as PostgreSQL. It positions itself against fopen(), meaning a library for reading and writing structured files inside an application. Read that positioning as a scope statement: if your workload needs many separate application servers writing to one database, SQLite is the wrong tool regardless of speed. Confirm that your deployment keeps the database on local or reliably shared storage.
File-based backup and restore
Both engines store data in a single file that you can copy, but a copy taken while a write is in progress can be inconsistent. Define backups with the engine's own consistency mechanism rather than a plain file copy, and test a restore on a separate host. For DuckDB, include any attached files and extension configuration in the backup scope. Ask your team who owns the schedule, retention and restore drills when the database lives inside an application rather than on a managed server.
Upgrades and extension management
With no server, an upgrade means shipping a new engine version inside your application build and verifying that existing files still open. DuckDB files and extensions change with versions, so pin the engine and extension versions in your build, then test on a copy of production data. SQLite's on-disk format is stable, but application code still needs regression tests. Track release notes for both engines as part of your normal dependency review.
Monitoring and platform reach
Neither engine exposes a server metrics endpoint, so monitoring comes from the host application: query timing, file size, lock wait and error logs. DuckDB runs on Linux, macOS and Windows on x86 and ARM CPUs, and DuckDB-Wasm runs it in web browsers. Confirm that your target platforms, including any browser or edge deployment, have the engine builds and extensions you need before committing, and add disk and temporary-space alerts to the application's existing monitoring.
Concurrency, file formats, extensions and limits compared
This table compares how SQLite and DuckDB handle concurrent access, file formats, extensions and size limits, using only documented facts. Read each row as a constraint that could rule one engine in or out for your workload, and check the DuckDB rows against the version you plan to deploy.
| Capability | SQLite | DuckDB |
|---|---|---|
| Simultaneous readers | Unlimited simultaneous readers per database file | READ_ONLY mode lets several processes read the file, but none can write while it is open that way |
| Writers per file | One writer at any instant per database file | Read-write mode lets one process read and write the file; multiple writer threads work inside that process |
| Write conflicts | Writers queue behind the single write lock for the file | Within one process, a conflict error occurs only when two threads change the same row at the same time |
| Multi-process writes | Supported through the single-writer rule on one file | Requires the Quack remote protocol, which is beta as of v1.5.2, or DuckLake with PostgreSQL as the catalog database |
| Maximum database size | Limited to 281 terabytes; SQLite suggests a client/server database when content may reach the terabyte range | No size limit is stated in the supplied documentation; confirm against your data volume and hardware |
| Querying CSV and Parquet | Requires importing data into tables before querying | Names CSV and Parquet files directly in the FROM clause |
| Core extensions for other data sources | Not applicable as a core feature of the engine | postgres, mysql and sqlite extensions read and write those databases; most core extensions sit at the secondary (best-effort) support tier |
| Table formats and cloud storage | Not a documented capability of SQLite | iceberg, delta and ducklake table format extensions, httpfs for HTTP(S) and S3, and vss for vector similarity search |
What the MIT license covers for DuckDB and what it means for SQLite
DuckDB is released under the MIT License, and the project's intellectual property is held by the DuckDB Foundation. For a procurement file, that means the core engine's license is short, permissive and does not depend on a paid edition, since the project does not publish edition tiers that we can confirm from supplied material. Your counsel should still read the license text itself and record which version of DuckDB you evaluated.
- Core license: The engine is MIT licensed, which generally permits commercial use, modification and redistribution subject to keeping the copyright and license notice.
- Ownership: The DuckDB Foundation holds the project's intellectual property, which is relevant if you need to know who controls the project's direction or assigns rights.
- Third-party extension: The motherduck extension is maintained by a third party and connects DuckDB to the MotherDuck service. Its terms and support are not the same as the core project's.
- SQLite terms: SQLite's public-domain dedication and any commercial support options are separate questions. Ask counsel to confirm SQLite's terms directly, and ask the vendors what support they sell.
- Records: Keep the license and notice text for each DuckDB version in your third-party software inventory.
Confirm all of these points with your own counsel and with each vendor before signing anything.
For platform teams, the license question becomes a packaging question. DuckDB runs embedded inside your application process, so there is no server to license, but the extensions you load may have different maintainers and support levels. Those details affect what you ship, patch and test.
- Core extensions: The postgres, mysql, sqlite, iceberg, delta, ducklake, httpfs, vss and spatial extensions are listed as core extensions, but most sit at a secondary, best-effort support tier. Check the tier before you depend on one in production.
- Third-party extension: Treat motherduck as a separate dependency with its own maintenance cycle, and pin and test it independently of the core engine.
- Build and distribution: Record which DuckDB version and which extensions each application build bundles, so you can answer license and vulnerability questions quickly.
- Upgrades: Plan upgrade tests against the release you pin, since extension compatibility can lag core releases.
- Sourcing: Confirm where each extension binary comes from and whether your environment allows it to download at runtime.
Before you ship, confirm the support tier and maintainer for each extension your code loads, using the DuckDB core extensions documentation.
Product teams care about what they can ship and sell. An application that embeds DuckDB ships the engine as part of its own binary or package, so the MIT notice and any extension terms travel with the product. An application that embeds SQLite faces the same packaging question under SQLite's own terms, which counsel should confirm.
- Shipping: Include the DuckDB MIT notice in your product documentation or notices file, as the license requires for distributed copies.
- Customer contracts: Check whether any customer agreement restricts third-party components or requires a list of them.
- Extension choice: Features that depend on the motherduck extension or other secondary-tier extensions may carry support expectations you cannot pass on to customers without testing.
- Support path: The engine's license does not include a support contract. If you need a support commitment, ask the vendors or a services partner for a written scope.
- Exit planning: Because the license is permissive, switching away from DuckDB is a technical task, not a licensing one, but the data format and SQL dialect still determine the effort.
Ask your legal team to confirm how these points apply to your distribution model before the first release that includes an embedded database.
Where identity, extensions and external data connect
Neither engine ships a user directory, so access control is set by the host application or the file system, and external data arrives through extensions. DuckDB's extensions reach PostgreSQL, MySQL, SQLite, S3 and HTTP(S), but most sit at the secondary best-effort support tier. Confirm identity and encryption requirements against your own environment before you rely on any of them.
No built-in user directory
Embedded engines have no login server, roles or user accounts. Access control comes from the host application that opens the file and from operating system permissions on the database file. For SQLite and DuckDB alike, anyone who can read the file can read its data. If your audit needs named users, row-level rights or a central identity provider, that logic must live in the application layer or in a server database such as PostgreSQL. Confirm with your security team which of these controls they expect to see.
Attaching PostgreSQL and MySQL
DuckDB's postgres, mysql and sqlite extensions read and write those databases from inside a DuckDB session. This lets analytical queries join against operational tables without first copying them. The credentials you use to attach are stored in your configuration, so decide how they are injected and who can read them. Test the extension version you deploy against your database versions, since the extensions are maintained separately from the core engine.
Reading S3 and HTTP(S) sources
The httpfs extension lets DuckDB read files over HTTP(S) and from S3 object storage. Your network policy then decides what the engine can reach, and bucket policies and access keys decide what it can read. Treat remote reads as outbound traffic that needs the same review as any other data pipeline. Confirm which storage endpoints, proxies and certificate authorities your environment requires before you point production jobs at remote data.
Secondary support tier risk
Most core extensions sit at the secondary, best-effort support tier. That means their maintenance and fix timing differ from the core engine's, so a regression can land between releases. Pin the DuckDB and extension versions in your build, record them in your change log, and run your own query tests after each upgrade. Ask your vendor or the DuckDB project which extensions your workload depends on and what support tier each one holds.
Third-party MotherDuck connector
The core extensions list includes a motherduck extension that is maintained by a third party and connects DuckDB to the MotherDuck service. Using it sends data and queries outside your infrastructure, which may conflict with a self-hosted or air-gapped requirement. Before you install any third-party extension, confirm where its traffic goes, who maintains it and whether your data residency rules allow it.
Encryption and compliance checks
Neither engine's core design supplies the encryption, key management or audit logging that some regulators expect, so these controls usually come from the storage layer, the operating system or the host application. Write down how the database file is encrypted at rest, who holds the keys and how access is logged. Confirm each requirement against your own environment and your compliance framework, rather than assuming the embedded engine covers it.
What drives the cost of running each engine and of moving between them
The cost of running SQLite or DuckDB is mostly engineering time, and the largest share is one-time: deciding how data is modeled for each job before anything ships. A SQLite schema for application state is shaped around transactional writes and foreign keys, while a DuckDB schema for analysis is usually wider, denormalized and loaded in bulk. Ask your engineers to write down which tables serve the application and which serve reports, because that decision drives every later migration script. Migration scripts and schema design are one-time work, but they are paid for again whenever the data model changes.
Backup and file lifecycle is the recurring cost that is easiest to underestimate, because both engines store a database as a single file that is simple to copy and easy to lose track of. Each file needs a backup method that is safe while a writer is active, a retention rule, and a restore test. For DuckDB, Parquet exports are often a practical archive format, which means a second set of files to manage. Confirm the backup approach for each file with your own operations team before you pick an engine, not after.
Upgrades, extension testing and query tuning recur for both engines, and DuckDB adds one extra item. Each DuckDB release, and each core extension you depend on, needs regression testing against your queries, since extensions carry different support tiers and the postgres, iceberg or httpfs extension may change behavior between versions. Query tuning is also ongoing: analytical queries over growing Parquet or CSV data need periodic review of file layout and filters. The largest later cost comes from a move if write volume grows. Moving from a single-writer file to a multi-writer system typically means new infrastructure, a new concurrency design and rewritten write paths, so price that risk now and ask any implementation partner to quote it as a separate line item.
How to move analytics out of SQLite into DuckDB without breaking the application
This sequence lets a team move analytical reporting out of SQLite and into DuckDB while the application keeps its transactional write path. Each step ends with a check, so the team knows when it can move to the next one.
Inventory which queries are analytical
The application team lists every SQL statement the application runs, then tags each one as transactional (short reads and writes tied to a user action) or analytical (scans, aggregations and joins over large portions of the data). Analytical queries are the candidates for DuckDB, because DuckDB targets long-running queries over large data, while SQLite stays with the many small transactions it handles well. The output is a short table of query, owner, frequency and tag. The step is done when the team agrees on which queries move and which stay in SQLite.
Attach or export the relevant tables
The data engineer or application developer makes the SQLite tables readable from DuckDB. DuckDB's sqlite extension can attach an existing SQLite file directly, which is the fastest route for a first test. For a longer-term copy, export the tables to Parquet and query the files by name from DuckDB. Record the exact file paths, the DuckDB version and the attach method in the migration notes. The step is done when a DuckDB session can list the tables and row counts match the SQLite source for each one.
Validate results against SQLite on a sample
The developer runs each candidate analytical query in both SQLite and DuckDB against the same sample of data, then compares row counts, aggregates and sort order. Differences often come from NULL handling, integer division, date and time functions, string collation and type affinity, so review those first. Confirm the results match on the sample and on the full dataset for at least one representative date range. The output is a signed-off comparison log. The step is done when every candidate query returns identical results or each difference has a documented, accepted explanation.
Move read-only reporting first
The team points the reporting screens, scheduled exports and dashboards at DuckDB while the application keeps writing to SQLite. For concurrency, open the DuckDB file in READ_ONLY mode, because several processes can read a DuckDB file that way but none can write to it. This keeps the write path unchanged and limits the blast radius if a report misbehaves. The output is a reporting service running on DuckDB in production for a defined period. The step is done when reports match the baseline and the on-call team has a rollback to the SQLite queries.
Rebuild connection handling and scheduled loads
The developer replaces SQLite connection code in the reporting path with DuckDB connections, and sets up a scheduled load that refreshes the analytical copy from the source. Connection pooling and locking assumptions from SQLite do not carry over, so the team decides who owns the write connection, when the read-only copy refreshes and how a failed load is detected. Document the schedule, the refresh method and the alert that fires on a failed load. The step is done when a full refresh runs unattended and the application log shows each load's outcome.
Decide whether the write path changes
Only after reporting is stable, the architecture owner decides whether writes should move. SQLite allows one writer at a time per database file, and DuckDB allows multiple writer threads inside one process but needs a different approach for writes from several processes, such as the Quack remote protocol (beta as of DuckDB v1.5.2) or DuckLake with PostgreSQL as the catalog. If the application's write load is small and transactional, it usually stays on SQLite. The output is a written decision record. The step is done when the team has either kept SQLite for writes or scheduled a separate, tested write migration with its own review.
What implementing each engine involves and where projects run long
For an application team, the first decision is which process owns each database file. SQLite already allows one writer per file, so most application teams keep their transactional state there and add DuckDB for analytical reads. A common pattern is one process that opens the DuckDB file in read-write mode, with other services opening it in READ_ONLY mode, which lets several processes read but none write. Check how your application spawns workers and background jobs, because a second read-write open from another process is usually the first failure teams hit. Confirm that your language binding closes connections cleanly, since a leaked handle can block the writer you planned on.
A data team building a DuckDB pipeline spends its effort on file layout, ingestion jobs and extension choices. Because DuckDB reads CSV and Parquet by naming the files in the FROM clause, early work is often about schema drift and file naming, not the engine itself. List every extension the pipeline depends on, such as postgres, mysql, sqlite, iceberg, delta, ducklake, httpfs or vss. Most core extensions sit at the secondary, best-effort support tier, so pin versions, test each upgrade against your own data, and read the core extensions overview for the tier of each one you rely on before you commit to it.
Concurrency design is where projects most often run long. Writing from several processes requires either the Quack remote protocol, which was in beta as of DuckDB v1.5.2, or DuckLake with PostgreSQL as the catalog database. The second option adds a PostgreSQL service that someone must operate, back up and secure. Confirm the current status of Quack with the project before you plan around it. Row-level conflicts are the surprise. Within one process, several writer threads can update different tables or different subsets of one table, but a write fails with a conflict error when two threads change the same row at the same time. Teams that assumed SQLite's single-writer lock would handle contention should load-test hot keys and build retry logic into the write path.
Questions teams ask before choosing or hiring help
Can we use DuckDB and SQLite together in one application?
Yes, and this is a common split: keep SQLite for application state and small transactional writes, then run analytical queries in DuckDB. The two engines do not share a file, so the boundary has to be designed. Most teams copy the data they need from SQLite into DuckDB on a schedule, or read the SQLite file through DuckDB's sqlite extension. Decide early which system is the source of truth for each table, how often the analytical copy refreshes, and who owns the job that moves data between them. Confirm with your own engineers how the extension behaves at your data volumes before you depend on it.
Does DuckDB replace SQLite for our application?
No, not for transactional application state. SQLite's documentation says it competes with fopen() rather than with client/server databases, and it is built for many small reads and writes inside one application. DuckDB targets analytical (OLAP) workloads, with complex, long-running queries over large portions of the data and bulk appends rather than frequent single-row changes. If your application writes one row at a time from user actions, SQLite fits that pattern better. If your main job is slicing large datasets for reports, DuckDB is the better match for that workload.
How does concurrency work when several processes write to DuckDB?
Only one process can write to a DuckDB database file in read-write mode. In READ_ONLY mode, several processes can read the file, but none can write. Writing from several processes needs either the Quack remote protocol, which was in beta as of DuckDB v1.5.2, or DuckLake with PostgreSQL as the catalog database. Inside one process, DuckDB supports multiple writer threads, and a write fails with a conflict error only when two threads change the same row at the same time. SQLite also allows only one writer per database file at any instant, so ask your engineers to map each write path before choosing a design.
Can we query Parquet and CSV files without loading them first?
Yes. DuckDB queries CSV and Parquet files by naming them directly in the FROM clause, so a file can be read in place without a separate load step. For files in S3 or served over HTTP(S), the httpfs core extension provides access. Performance and memory use depend on file layout, the size of the data scanned and the queries you run, so test with a representative sample from your own environment. The extensions list also shows that most core extensions carry a secondary, best-effort support tier, which is worth raising in any written scope.
What work do our own engineers need to do?
Because DuckDB is embedded, there is no database server to install, patch or back up in the traditional sense. The work shifts to the host application: packaging the library with your code, managing the database file and its backups, choosing how writers coordinate, and monitoring the data pipeline that feeds it. Engineers also need to track DuckDB version upgrades, since releases such as v1.5.6 arrive on the project's own schedule. Plan for SQL and data modeling skills, schema changes and testing of queries against your real data volumes.
When is a consultant worth hiring for this work?
A consultant is most useful when the design has real trade-offs that your team has not yet tested: how application writes and analytical reads should be split, how data moves between the two engines, or how a multi-writer design should work. The cost comes from the time of the people who do the design and build, so it is shaped by the number of data sources, the number of write paths and how much testing is needed. Ask for a fixed scope with written deliverables, so you can see what is included before work starts. DeployOpen's engagements are typically fixed-scope, and quotes follow from the written scope rather than a published price list.
What should we ask for in a written scope?
Ask for the following in writing:
- A list of each data store, its owner and whether it is the source of truth
- The write paths, expected concurrency and the chosen design for multi-writer access
- The files, formats and extensions in use, with the support tier of each extension
- The migration or sync plan from SQLite to DuckDB, including rollback steps
- Backup, restore and upgrade procedures for the database file, with test results
- Acceptance criteria based on query behavior at your real data volume
Also confirm what is excluded, and who runs the work after handover.
How DeployOpen helps choose and implement an embedded data stack
DeployOpen plans, implements and documents self-hosted open-source platforms inside the client's own infrastructure, so an embedded data stack is built where the application and its data already live. We start from which workload needs columnar analytics and which needs row-level application state, then agree the boundary between the two in writing.
How we scope
- Map which application writes need SQLite's single-writer row store and which reporting queries need DuckDB's columnar engine
- Record file locations, read-only versus read-write access patterns and which processes open each database file
- Agree deliverables, timelines and exclusions in writing before work starts, since most engagements are fixed-scope
- Confirm target platforms among Linux, macOS, Windows, x86 and ARM before choosing extensions or client libraries
How we build
- Implement the chosen split, including how the application writes to SQLite and how analytical jobs read or copy data into DuckDB
- Verify extension choices such as httpfs, postgres or sqlite against the support tier documented for each, then test the ones you rely on
- Test concurrency behavior under realistic write patterns, including conflict errors when two threads change the same row
- Document file formats, backup steps and upgrade paths for each database file
How we hand over
- Deliver written runbooks covering file locations, access modes, backup and restore, and how to move data between the two stores
- Provide configuration and code documentation so your engineers can extend queries and schemas without DeployOpen involvement
- Supply test evidence and a list of open questions to confirm with your own environment or the upstream projects
- Walk your team through the handover and record decisions for future reviews
How we support
- Offer ongoing support, upgrades and continuous testing as a retainer after handover
- Track DuckDB and SQLite release changes that affect your deployment and plan upgrades in a scoped change
- Re-test extensions and concurrency assumptions when your workload, data volume or access pattern changes
- Quotes follow from the written scope, so ask for a breakdown of one-time build work and recurring retainer work

