Skip to content

Commit 5675a1a

Browse files
committed
SQLite: Don't bill for internal queries.
We bill for: - SQL queries issued via the JavaScript SQL API. - DO old-style key/value storage operations. Anything else, we assume is an internal query, and not billed.
1 parent f82812e commit 5675a1a

File tree

7 files changed

+76
-9
lines changed

7 files changed

+76
-9
lines changed

src/workerd/api/sql.c++

+5
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ bool SqlStorage::allowTransactions() const {
111111
"write coalescing.");
112112
}
113113

114+
bool SqlStorage::shouldAddQueryStats() const {
115+
// Bill for queries executed from JavaScript.
116+
return true;
117+
}
118+
114119
SqlStorage::StatementCache::~StatementCache() noexcept(false) {
115120
for (auto& entry: lru) {
116121
lru.remove(entry);

src/workerd/api/sql.h

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class SqlStorage final: public jsg::Object, private SqliteDatabase::Regulator {
6969
bool isAllowedTrigger(kj::StringPtr name) const override;
7070
void onError(kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const override;
7171
bool allowTransactions() const override;
72+
bool shouldAddQueryStats() const override;
7273

7374
SqliteDatabase& getDb(jsg::Lock& js) {
7475
return storage->getSqliteDb(js);

src/workerd/util/sqlite-kv-test.c++

+26-1
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,32 @@ namespace workerd {
1010
namespace {
1111

1212
KJ_TEST("SQLite-KV") {
13+
class TestSqliteObserver: public SqliteObserver {
14+
public:
15+
void addQueryStats(uint64_t read, uint64_t written) override {
16+
rowsRead += read;
17+
rowsWritten += written;
18+
}
19+
20+
uint64_t rowsRead = 0;
21+
uint64_t rowsWritten = 0;
22+
};
23+
1324
auto dir = kj::newInMemoryDirectory(kj::nullClock());
1425
SqliteDatabase::Vfs vfs(*dir);
15-
SqliteDatabase db(vfs, kj::Path({"foo"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY);
26+
TestSqliteObserver sqliteObserver;
27+
SqliteDatabase db(
28+
vfs, kj::Path({"foo"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY, sqliteObserver);
1629
SqliteKv kv(db);
1730

1831
kv.put("foo", "abc"_kj.asBytes());
1932
kv.put("bar", "def"_kj.asBytes());
2033
kv.put("baz", "123"_kj.asBytes());
2134
kv.put("qux", "321"_kj.asBytes());
2235

36+
KJ_EXPECT(sqliteObserver.rowsWritten == 4);
37+
KJ_EXPECT(sqliteObserver.rowsRead == 0);
38+
2339
{
2440
bool called = false;
2541
KJ_EXPECT(kv.get("foo", [&](kj::ArrayPtr<const byte> value) {
@@ -29,6 +45,9 @@ KJ_TEST("SQLite-KV") {
2945
KJ_EXPECT(called);
3046
}
3147

48+
KJ_EXPECT(sqliteObserver.rowsWritten == 4);
49+
KJ_EXPECT(sqliteObserver.rowsRead == 1);
50+
3251
{
3352
bool called = false;
3453
KJ_EXPECT(kv.get("bar", [&](kj::ArrayPtr<const byte> value) {
@@ -38,10 +57,16 @@ KJ_TEST("SQLite-KV") {
3857
KJ_EXPECT(called);
3958
}
4059

60+
KJ_EXPECT(sqliteObserver.rowsWritten == 4);
61+
KJ_EXPECT(sqliteObserver.rowsRead == 2);
62+
4163
KJ_EXPECT(!kv.get("corge", [&](kj::ArrayPtr<const byte> value) {
4264
KJ_FAIL_EXPECT("should not call callback when no match", value.asChars());
4365
}));
4466

67+
KJ_EXPECT(sqliteObserver.rowsWritten == 4);
68+
KJ_EXPECT(sqliteObserver.rowsRead == 2);
69+
4570
auto list = [&](auto&&... params) {
4671
kj::Vector<kj::String> results;
4772
auto callback = [&](kj::StringPtr key, kj::ArrayPtr<const byte> value) {

src/workerd/util/sqlite-kv.h

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ namespace workerd {
1515
// of KJ exceptions which become internal errors.
1616
class SqliteKvRegulator: public SqliteDatabase::Regulator {
1717
void onError(kj::Maybe<int> sqliteErrorCode, kj::StringPtr message) const override;
18+
19+
// We bill for KV operations as rows read/written.
20+
virtual bool shouldAddQueryStats() const override {
21+
return true;
22+
}
1823
};
1924

2025
// Class which implements KV storage on top of SQLite. This is intended to be used for Durable

src/workerd/util/sqlite-test.c++

+27-6
Original file line numberDiff line numberDiff line change
@@ -834,9 +834,17 @@ KJ_TEST("SQLite observer addQueryStats") {
834834
uint64_t rowsWritten = 0;
835835
};
836836

837+
class TestQueryStatsRegulator: public SqliteDatabase::Regulator {
838+
public:
839+
bool shouldAddQueryStats() const override {
840+
return true;
841+
}
842+
};
843+
837844
TempDirOnDisk dir;
838845
SqliteDatabase::Vfs vfs(*dir);
839846
TestSqliteObserver sqliteObserver = TestSqliteObserver();
847+
TestQueryStatsRegulator regulator;
840848
SqliteDatabase db(
841849
vfs, kj::Path({"foo"}), kj::WriteMode::CREATE | kj::WriteMode::MODIFY, sqliteObserver);
842850

@@ -851,17 +859,17 @@ KJ_TEST("SQLite observer addQueryStats") {
851859
int rowsWrittenBefore = sqliteObserver.rowsWritten;
852860
constexpr int dbRowCount = 3;
853861
{
854-
db.run("INSERT INTO things (id) VALUES (10)");
855-
db.run("INSERT INTO things (id) VALUES (11)");
856-
db.run("INSERT INTO things (id) VALUES (12)");
862+
db.run(regulator, "INSERT INTO things (id) VALUES (10)");
863+
db.run(regulator, "INSERT INTO things (id) VALUES (11)");
864+
db.run(regulator, "INSERT INTO things (id) VALUES (12)");
857865
}
858866
KJ_EXPECT(sqliteObserver.rowsRead - rowsReadBefore == dbRowCount);
859867
KJ_EXPECT(sqliteObserver.rowsWritten - rowsWrittenBefore == dbRowCount);
860868

861869
rowsReadBefore = sqliteObserver.rowsRead;
862870
rowsWrittenBefore = sqliteObserver.rowsWritten;
863871
{
864-
auto getCount = db.prepare("SELECT COUNT(*) FROM things");
872+
auto getCount = db.prepare(regulator, "SELECT COUNT(*) FROM things");
865873
KJ_EXPECT(getCount.run().getInt(0) == dbRowCount);
866874
}
867875
KJ_EXPECT(sqliteObserver.rowsRead - rowsReadBefore == dbRowCount);
@@ -871,7 +879,7 @@ KJ_TEST("SQLite observer addQueryStats") {
871879
rowsReadBefore = sqliteObserver.rowsRead;
872880
rowsWrittenBefore = sqliteObserver.rowsWritten;
873881
{
874-
auto stmt = db.prepare("SELECT * FROM things");
882+
auto stmt = db.prepare(regulator, "SELECT * FROM things");
875883
auto query = stmt.run();
876884
KJ_ASSERT(!query.isDone());
877885
while (!query.isDone()) {
@@ -881,11 +889,24 @@ KJ_TEST("SQLite observer addQueryStats") {
881889
KJ_EXPECT(sqliteObserver.rowsRead - rowsReadBefore == dbRowCount);
882890
KJ_EXPECT(sqliteObserver.rowsWritten - rowsWrittenBefore == 0);
883891

892+
// Verify system queries don't affect stats.
893+
rowsReadBefore = sqliteObserver.rowsRead;
894+
rowsWrittenBefore = sqliteObserver.rowsWritten;
895+
db.run("INSERT INTO things (id) VALUES (13)");
896+
{
897+
auto query = db.run("SELECT * FROM things");
898+
while (!query.isDone()) {
899+
query.nextRow();
900+
}
901+
}
902+
KJ_EXPECT(sqliteObserver.rowsRead == rowsReadBefore);
903+
KJ_EXPECT(sqliteObserver.rowsWritten == rowsWrittenBefore);
904+
884905
// Verify addQueryStats works correctly when db is reset
885906
rowsReadBefore = sqliteObserver.rowsRead;
886907
rowsWrittenBefore = sqliteObserver.rowsWritten;
887908
{
888-
auto query = db.run("INSERT INTO things (id) VALUES (100)");
909+
auto query = db.run(regulator, "INSERT INTO things (id) VALUES (100)");
889910
db.reset();
890911
}
891912
KJ_EXPECT(sqliteObserver.rowsRead - rowsReadBefore == 1);

src/workerd/util/sqlite.c++

+4-2
Original file line numberDiff line numberDiff line change
@@ -1291,8 +1291,10 @@ SqliteDatabase::Query::~Query() noexcept(false) {
12911291
}
12921292

12931293
void SqliteDatabase::Query::destroy() {
1294-
//Update the db stats that we have collected for the query
1295-
db.sqliteObserver.addQueryStats(rowsRead, rowsWritten);
1294+
if (regulator.shouldAddQueryStats()) {
1295+
//Update the db stats that we have collected for the query
1296+
db.sqliteObserver.addQueryStats(rowsRead, rowsWritten);
1297+
}
12961298

12971299
// We only need to reset the statement if we don't own it. If we own it, it's about to be
12981300
// destroyed anyway.

src/workerd/util/sqlite.h

+8
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ class SqliteDatabase {
118118
virtual bool allowTransactions() const {
119119
return true;
120120
}
121+
122+
// Whether or not this query's rows read and written should be recorded to the SqliteObserver
123+
// when the query is done. (In other words, determines whether this query is billed.)
124+
//
125+
// We don't bill for TRUSTED queries since they are used internally by the system.
126+
virtual bool shouldAddQueryStats() const {
127+
return false;
128+
}
121129
};
122130

123131
// Use as the `Regulator&` for queries that are fully trusted. As a general rule, this should

0 commit comments

Comments
 (0)