Skip to content

Commit f769e96

Browse files
committed
Clean up sql-kv-put-multiple-transaction autogate
We’ve been using this in production for over a month and have no intention of reverting the autogate.
1 parent c732fdb commit f769e96

File tree

4 files changed

+33
-143
lines changed

4 files changed

+33
-143
lines changed

src/workerd/io/actor-sqlite-test.c++

Lines changed: 3 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,9 @@ KJ_TEST("check put multiple wraps operations in a transaction") {
235235
}
236236
}
237237

238-
KJ_TEST(
239-
"check put multiple wraps operations in a transaction with sql-kv-put-multiple-transaction autogate") {
238+
KJ_TEST("check put multiple wraps operations in a transaction") {
240239
ActorSqliteTest test;
241240

242-
// This test should reflect the same behavior we saw without the autogate enabled.
243-
util::Autogate::initAutogateNamesForTest({"sql-kv-put-multiple-transaction"_kj});
244-
245241
kj::Vector<ActorCache::KeyValuePair> putKVs;
246242
putKVs.add(ActorCache::KeyValuePair{kj::str("foo"), kj::heapArray(kj::str("bar").asBytes())});
247243

@@ -285,105 +281,10 @@ KJ_TEST(
285281
}
286282
}
287283

288-
KJ_TEST("check put multiple wraps operations in a transaction and does not rollback on error") {
289-
ActorSqliteTest test;
290-
291-
if (util::Autogate::isEnabled(util::AutogateKey::SQL_KV_PUT_MULTIPLE_TRANSACTION)) {
292-
// We should skip this test as it will expect a different behavior when
293-
// SQL_KV_PUT_MULTIPLE_TRANSACTION is set
294-
KJ_DBG("Skipping test because SQL_KV_PUT_MULTIPLE_TRANSACTION is enabled.");
295-
return;
296-
}
297-
298-
// Let's deinit the autogate. This will enforce the old behavior where putMultiple would commit
299-
// some puts, until a single put failed.
300-
util::Autogate::deinitAutogate();
301-
302-
kj::Vector<ActorCache::KeyValuePair> putKVs;
303-
304-
// Add some regular key-value pairs that we know are supported
305-
putKVs.add(ActorCache::KeyValuePair{kj::str("foo"), kj::heapArray(kj::str("bar").asBytes())});
306-
putKVs.add(ActorCache::KeyValuePair{kj::str("foo2"), kj::heapArray(kj::str("bar2").asBytes())});
307-
putKVs.add(ActorCache::KeyValuePair{kj::str("foo3"), kj::heapArray(kj::str("bar3").asBytes())});
308-
309-
// Now create a key that's too large. Should fail with string or blob too big: SQLITE_TOOBIG
310-
auto tooLongKey = kj::heapString(2200000);
311-
tooLongKey.asArray().fill('a');
312-
// Add it to our KV array
313-
putKVs.add(
314-
ActorCache::KeyValuePair{kj::str(tooLongKey), kj::heapArray(kj::str("bar").asBytes())});
315-
316-
// NoTxn test
317-
{
318-
// Check that we're in a NoTxn
319-
KJ_ASSERT(!test.actor.isCommitScheduled());
320-
try {
321-
test.putMultiple(putKVs.releaseAsArray());
322-
// We should fail with correct error before reaching here.
323-
KJ_UNREACHABLE;
324-
} catch (kj::Exception& e) {
325-
KJ_ASSERT(
326-
e.getDescription() == "expected false; jsg.Error: string or blob too big: SQLITE_TOOBIG");
327-
}
328-
KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get("foo"))) == kj::str("bar").asBytes());
329-
KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get("foo2"))) == kj::str("bar2").asBytes());
330-
KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get("foo3"))) == kj::str("bar3").asBytes());
331-
// During write, all NoTxn operations are wrapped in an ImplicitTxn. Since some puts succeeded
332-
// we need to flush commit.
333-
auto commitFulfiller = kj::mv(test.pollAndExpectCalls({"commit"})[0]);
334-
commitFulfiller->fulfill();
335-
}
336-
337-
{
338-
// Let's clear the db.
339-
test.actor.deleteAll({});
340-
auto commitFulfiller = kj::mv(test.pollAndExpectCalls({"commit"})[0]);
341-
commitFulfiller->fulfill();
342-
KJ_ASSERT(expectSync(test.get(kj::str("foo"))) == nullptr);
343-
KJ_ASSERT(expectSync(test.get(kj::str("foo2"))) == nullptr);
344-
KJ_ASSERT(expectSync(test.get(kj::str("foo3"))) == nullptr);
345-
}
346-
347-
// ExplicitTxn test
348-
{
349-
KJ_ASSERT(!test.actor.isCommitScheduled());
350-
// Similar to the previous putMultiple, but wrapped in a transactionSync (ExplicitTxn)
351-
test.putMultipleExplicitTxn(putKVs.releaseAsArray());
352-
auto commitFulfiller = kj::mv(test.pollAndExpectCalls({"commit"})[0]);
353-
commitFulfiller->fulfill();
354-
// This was wrapped in an explicit transaction. We rolled back as expected.
355-
KJ_ASSERT(expectSync(test.get(kj::str("foo"))) == nullptr);
356-
KJ_ASSERT(expectSync(test.get(kj::str("foo2"))) == nullptr);
357-
KJ_ASSERT(expectSync(test.get(kj::str("foo3"))) == nullptr);
358-
}
359-
360-
// ImplicitTxn test
361-
{
362-
// A single put will create an ImplicitTxn that we can use to wrap our putMultiple into.
363-
KJ_ASSERT(!test.actor.isCommitScheduled());
364-
test.put("baz", "bat");
365-
366-
// By now, we should check there's a commit scheduled in a ImplicitTxn.
367-
KJ_ASSERT(test.actor.isCommitScheduled());
368-
test.putMultiple(putKVs.releaseAsArray());
369-
370-
auto commitFulfiller = kj::mv(test.pollAndExpectCalls({"commit"})[0]);
371-
// The single put succeeded, but the putMultiple did not.
372-
KJ_ASSERT(KJ_ASSERT_NONNULL(expectSync(test.get("baz"))) == kj::str("bat").asBytes());
373-
KJ_ASSERT(expectSync(test.get(kj::str("foo"))) == nullptr);
374-
KJ_ASSERT(expectSync(test.get(kj::str("foo2"))) == nullptr);
375-
KJ_ASSERT(expectSync(test.get(kj::str("foo3"))) == nullptr);
376-
commitFulfiller->fulfill();
377-
}
378-
}
379-
380-
KJ_TEST(
381-
"check put multiple wraps operations in a transaction and rollback on error with sql-kv-put-multiple-transaction autogate") {
284+
KJ_TEST("check put multiple wraps operations in a transaction and rollback on error") {
382285
ActorSqliteTest test;
383286

384-
// With the autogate enabled, we expect that putMultiple is all of nothing, rolling back if a
385-
// single put fails.
386-
util::Autogate::initAutogateNamesForTest({"sql-kv-put-multiple-transaction"_kj});
287+
// We expect that putMultiple is all or nothing, rolling back if a single put fails.
387288

388289
kj::Vector<ActorCache::KeyValuePair> putKVs;
389290

src/workerd/io/actor-sqlite.c++

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -532,46 +532,39 @@ kj::Maybe<kj::Promise<void>> ActorSqlite::put(Key key, Value value, WriteOptions
532532
kj::Maybe<kj::Promise<void>> ActorSqlite::put(kj::Array<KeyValuePair> pairs, WriteOptions options) {
533533
requireNotBroken();
534534
// TODO(cleanup): Most of this code comes from DurableObjectStorage::transactionSync and could be re-used.
535-
if (util::Autogate::isEnabled(util::AutogateKey::SQL_KV_PUT_MULTIPLE_TRANSACTION)) {
536-
if (currentTxn.is<NoTxn>()) {
537-
// If we are not in a transaction, let's use an ExplicitTxn, which should rollback automatically
538-
// if some put fails.
539-
540-
// TODO(someday) this should really start an ImplicitTxn, which has the advantage of
541-
// supporting allowUnconfirmed.
542-
disableAllowUnconfirmed(options, "multi put will create an ExplicitTxn");
543-
544-
auto txn = startTransaction();
545-
txn->put(kj::mv(pairs), options);
546-
txn->commit();
547-
} else {
548-
if (currentTxn.is<ExplicitTxn*>()) {
549-
disableAllowUnconfirmed(options, "multi put is using an already-existing ExplicitTxn");
550-
}
535+
if (currentTxn.is<NoTxn>()) {
536+
// If we are not in a transaction, let's use an ExplicitTxn, which should rollback automatically
537+
// if some put fails.
551538

552-
// If we are in a transaction, let's just set a SAVEPOINT that we can rollback to if needed.
553-
db->run(
554-
{.regulator = SqliteDatabase::TRUSTED}, kj::str("SAVEPOINT _cf_put_multiple_savepoint"));
555-
for (const auto& pair: pairs) {
556-
try {
557-
kv.put(pair.key, pair.value, {.allowUnconfirmed = options.allowUnconfirmed});
558-
} catch (kj::Exception& e) {
559-
// We need to rollback to the putMultiple SAVEPOINT. Do it, and then release the SAVEPOINT.
560-
db->run({.regulator = SqliteDatabase::TRUSTED},
561-
kj::str("ROLLBACK TO _cf_put_multiple_savepoint"));
562-
db->run({.regulator = SqliteDatabase::TRUSTED},
563-
kj::str("RELEASE _cf_put_multiple_savepoint"));
564-
kj::throwFatalException(kj::mv(e));
565-
}
566-
}
567-
// We're done here. RELEASE the savepoint.
568-
db->run(
569-
{.regulator = SqliteDatabase::TRUSTED}, kj::str("RELEASE _cf_put_multiple_savepoint"));
570-
}
539+
// TODO(someday) this should really start an ImplicitTxn, which has the advantage of
540+
// supporting allowUnconfirmed.
541+
disableAllowUnconfirmed(options, "multi put will create an ExplicitTxn");
542+
543+
auto txn = startTransaction();
544+
txn->put(kj::mv(pairs), options);
545+
txn->commit();
571546
} else {
572-
for (auto& pair: pairs) {
573-
kv.put(pair.key, pair.value, {.allowUnconfirmed = options.allowUnconfirmed});
547+
if (currentTxn.is<ExplicitTxn*>()) {
548+
disableAllowUnconfirmed(options, "multi put is using an already-existing ExplicitTxn");
549+
}
550+
551+
// If we are in a transaction, let's just set a SAVEPOINT that we can rollback to if needed.
552+
db->run(
553+
{.regulator = SqliteDatabase::TRUSTED}, kj::str("SAVEPOINT _cf_put_multiple_savepoint"));
554+
for (const auto& pair: pairs) {
555+
try {
556+
kv.put(pair.key, pair.value, {.allowUnconfirmed = options.allowUnconfirmed});
557+
} catch (kj::Exception& e) {
558+
// We need to rollback to the putMultiple SAVEPOINT. Do it, and then release the SAVEPOINT.
559+
db->run({.regulator = SqliteDatabase::TRUSTED},
560+
kj::str("ROLLBACK TO _cf_put_multiple_savepoint"));
561+
db->run(
562+
{.regulator = SqliteDatabase::TRUSTED}, kj::str("RELEASE _cf_put_multiple_savepoint"));
563+
kj::throwFatalException(kj::mv(e));
564+
}
574565
}
566+
// We're done here. RELEASE the savepoint.
567+
db->run({.regulator = SqliteDatabase::TRUSTED}, kj::str("RELEASE _cf_put_multiple_savepoint"));
575568
}
576569
return kj::none;
577570
}

src/workerd/util/autogate.c++

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ kj::StringPtr KJ_STRINGIFY(AutogateKey key) {
2525
return "streaming-tail-worker"_kj;
2626
case AutogateKey::TAIL_STREAM_REFACTOR:
2727
return "tail-stream-refactor"_kj;
28-
case AutogateKey::SQL_KV_PUT_MULTIPLE_TRANSACTION:
29-
return "sql-kv-put-multiple-transaction"_kj;
3028
case AutogateKey::NumOfKeys:
3129
KJ_FAIL_ASSERT("NumOfKeys should not be used in getName");
3230
}

src/workerd/util/autogate.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ enum class AutogateKey {
2020
STREAMING_TAIL_WORKER,
2121
// Enable refactor used to consolidate the different tail worker stream implementations.
2222
TAIL_STREAM_REFACTOR,
23-
// Enable wrapping DO SQL KV put multiple in a transaction, fully rolling back if some put fails
24-
SQL_KV_PUT_MULTIPLE_TRANSACTION,
2523
NumOfKeys // Reserved for iteration.
2624
};
2725

0 commit comments

Comments
 (0)