Turso has three pricing tiers:
- Starter (free, with limits)
- Scaler (monthly cost, with limits)
- Enterprise (unlimited)
For a breakdown of what each plan costs and includes, see the pricing page.
Usage-based billing model
For the Starter and Scaler plans, Turso usage is limited monthly based on the following usage observations during each calendar month:
- Number of table rows read
- Number of table rows written
- Amount of total storage
The accounting for this usage is based on SQLite internal implementation details. This documentation explains the usage patterns that affect the above metrics. In order to understand the usage of a particular database operation, you must first understand how SQLite implements that operation.
Show current monthly usage
You can use the Turso CLI to inspect usage statistics for a database for the current calendar month.
Accounting for rows read
A "row read" is more accurately described as a "row scanned" by SQLite during the process of executing a statement. There are two things to know as you observe the rows read metric provided by the Turso CLI:
- A SQL query may cause more rows to be scanned than are returned by that query.
- A SQL update statement incurs at least one row read for each row updated.
Queries with aggregate functions
Use of aggregate functions such as
incur one row read for each table row considered in the aggregation.
You may want to optimize both the cost and performance of aggregation queries by using another table to store aggregate values, and keeping it up to date (using a transaction) as the original table is updated.
Queries without a supporting index (full table scan)
Any query that is not able to make use of an index on a table incurs one row read for each row in the table.
Read some general advice on avoiding expensive table scans.
Table joins, subqueries, and compound queries
A query that consults multiple tables incurs one row read for each row that was considered from each table participating in the query.
A SQL update command that changes row data incurs one read (in addition to one write) for each row updated. If the update uses a filter to choose rows to modify, and SQLite is unable to use an index to find those rows, it performs a full table scan, incurring one read for each row in the entire table, in addition to one row write for each row updated.
ALTER TABLE row reads
Use of an
ALTER TABLE statement that effectively rewrites the contents of each
row in a table requires a full table scan, which incurs one row read for each
existing row in the table.
ALTER TABLE DROP COLUMN statement might not cause a full-table scan.
ALTER TABLE statement might also incur row
Adding an index to a table with existing rows
Adding an index to a table requires a full table scan, and incurs one read for each row that currently exists in the table.
SQLite system tables
SQLite tables that are maintained internally, such as
dbstat and tables
sqlite_ do not incur row reads when used in a query.
Commands that incur no row reads
If a command doesn't incur any row reads or row writes as part of its execution,
the command instead incurs one row read. For example,
select 1 incurs one row
Accounting for rows written
A "row written" includes both a newly inserted row and an update of a single existing row.
ALTER TABLE row writes
ALTER TABLE statements may incur one row write for each existing row if row
data is modified during the operation. Read about how SQLite works for
different types of alterations.
ALTER TABLE statement might also incur row reads.
A row inserted or updated during a transaction still incurs a row write even if the transaction isn't committed.
Accounting for total storage
With SQLite databases, the VACUUM command is normally used to optimize storage. This command is currently disabled in Turso. In the future, we hope to provide a way for developers to compact the total storage of a database.
For billing plans with monthly usage quotas for row reads, row writes, and total
storage, a query that causes overage fails with the error code
Advice for reducing usage
To minimize the incurred reads and writes on your database:
- Read about the SQLite query planner to understand how queries work.
- Use SQLite's EXPLAIN QUERY PLAN statement to understand if a query performs a full table scan, and if it's using the best available index to minimize reads.
- Ensure that every query that filters rows is able to use an index to find the relevant rows. Without an index, SQLite performs a table scan to find the rows, which costs one read for each existing row in the table.
- Add all required indexes at the time a table is created. Adding indexes to tables with existing rows incurs one read for each existing row.
Report issues with billing
If there is a question or problem with your bill or its accounting, contact email@example.com.