Rules: size and cost

Last updated 29 October 2026

View .md

Check these rules when you decide how your program commits: each keeps the bill small and every transaction within the limit.

The examples use the connection open() returns, a node:sqlite DatabaseSync, as Connect opens it.

Batch a step’s writes into one transaction

Every commit costs at least one write, however small, and waits its turn to reach Tonbo. Twenty rows committed one by one cost twenty writes; in one transaction they cost one (see the worked example).

Do
db.exec("BEGIN");
try {
  for (const event of step.events) insert.run(step.id, JSON.stringify(event));
  db.exec("COMMIT"); // one commit, one write
} catch (error) {
  if (db.isTransaction) db.exec("ROLLBACK");
  throw error;
}
Don’t
for (const event of step.events) insert.run(step.id, JSON.stringify(event)); // a commit per row

Write what changed, not the whole state

A commit costs one write per 10 KiB of the pages it changes, so rewriting a large value every step costs many writes where a new row costs one (see the worked example).

Do
db.prepare("INSERT INTO messages(conversation, body) VALUES (?, ?)")
  .run(id, JSON.stringify(message));
Don’t
conversation.messages.push(message);
db.prepare("UPDATE conversations SET body = ? WHERE id = ?")
  .run(JSON.stringify(conversation), id); // the whole conversation, every turn

Keep each transaction under 128 MiB

A transaction over 128 MiB of changed pages fails with too_large and commits nothing (see Limits). Load large data in batches.

Do
for (let at = 0; at < rows.length; at += 1000) {
  db.exec("BEGIN");
  for (const row of rows.slice(at, at + 1000)) insert.run(row.id, row.body);
  db.exec("COMMIT");
}
Don’t
db.exec("BEGIN");
for (const row of rows) insert.run(row.id, row.body); // millions of rows
db.exec("COMMIT"); // fails with too_large and commits nothing