Pending writes queue#4309
Conversation
jjezra
left a comment
There was a problem hiding this comment.
Since this queue is unused, I'll be happy to have it in the code as a foundation to the write_only_with_queue development while adding checks and improvements.
| * <p>The queue is intended to hold pending operations that arrive while a background indexer | ||
| * (or any other background worker) is running. Front-end transactions enqueue items |
There was a problem hiding this comment.
How about -
The queue is intended to hold pending foreground operations that arrive while a background worker is running and possibly holding resources.
| /** | ||
| * Default maximum queue size; protects against unbounded growth on persistent failure. | ||
| */ | ||
| public static final int DEFAULT_MAX_QUEUE_SIZE = 10_000; |
There was a problem hiding this comment.
Can this default be increased to 100K?
Since the consequences are severe (either re-indexing the whole record store or rejecting user IO) the buffer should be big enough to absorb occasional indexing/merging failures that may keep an active queue for a while.
There was a problem hiding this comment.
This is only used by one test, perhaps it should be moved there, rather than here.
There was a problem hiding this comment.
Done. Changed to 100K. Assuming that all callers will actually tune on a per-case basis.
| * read and the commit | ||
| */ | ||
| @Nonnull | ||
| public CompletableFuture<Boolean> ensureQueueEmpty(@Nonnull FDBRecordContext context) { |
There was a problem hiding this comment.
isQueueEmpty might be a better name.
|
|
||
| try (FDBRecordContext txA = openContext()) { | ||
| // TX_A asserts the queue is empty | ||
| assertTrue(queue.ensureQueueEmpty(txA).join()); |
There was a problem hiding this comment.
Can we add a test where ensureQueueEmpty tx succeeds and the add item fails?
There was a problem hiding this comment.
That will not fail as such - the caller should provide another flag to guarantee it (since the enqueue does not read the range)
There was a problem hiding this comment.
Added a comment for the class
| * versionstamped-key + atomic-counter idioms, not from anything queue-specific.</p> | ||
| */ | ||
| @Test | ||
| void testDrainScanAndClearDoesNotConflictWithConcurrentEnqueue() { |
There was a problem hiding this comment.
I suppose that it's a "nice to have" :)
| * a way readers must distinguish. Readers reject entries whose stored version exceeds this | ||
| * constant. | ||
| */ | ||
| public static final int CURRENT_VERSION = 0; |
There was a problem hiding this comment.
Note: IIUC this means that the caller can't use the version field to adjust what they write, that must be within the payload.
There was a problem hiding this comment.
Yes. Payload versioning does not come for free with this field, though a specific payload provider can affect the overall version for certain use cases if needed.
| /** | ||
| * Default maximum queue size; protects against unbounded growth on persistent failure. | ||
| */ | ||
| public static final int DEFAULT_MAX_QUEUE_SIZE = 10_000; |
There was a problem hiding this comment.
This is only used by one test, perhaps it should be moved there, rather than here.
| * <li><b>Capacity.</b> A configurable maximum queue size; {@link #enqueue} fails with | ||
| * {@link PendingWritesQueueTooLargeException} once the size counter reaches the limit. | ||
| * A value of {@code 0} disables the cap.</li> |
There was a problem hiding this comment.
Perhaps worth noting that this is approximate (since it is written at snapshot, you can end up with a bigger queue by the amount written by concurrent transactions)
| * @param entry the entry to clear | ||
| */ | ||
| public void clearEntry(@Nonnull FDBRecordContext context, @Nonnull PendingWritesQueueEntry<T> entry) { | ||
| SplitHelper.deleteSplit(context, queueSubspace, entry.getKeyTuple(), true, false, false, null); |
There was a problem hiding this comment.
Does deleteSplit result in a read-conflict on the entry? Will two transactions conflict if they both try to clear the same entry? (other modifications aren't allowed, anyways)
There was a problem hiding this comment.
Normally, two clear operations wouldn't conflict. Discussed offline and agreed that the correct system behavior is that they should. Added read conflict for the clearEntry and modified the test.
| * @throws com.apple.foundationdb.record.provider.foundationdb.FDBExceptions.FDBStoreTransactionConflictException | ||
| * via the transaction's commit if another transaction enqueued into the queue between this | ||
| * read and the commit |
There was a problem hiding this comment.
this is misleading. This method does not throw this exception, nor does the future. IMO the explanation in the paragraph should be sufficient
| RecordCursor<PendingWritesQueueEntry<TestQueuePayload>> cursor = | ||
| queue.getQueueCursor(context, scanProps, continuation); | ||
| RecordCursorResult<PendingWritesQueueEntry<TestQueuePayload>> last = null; | ||
| while (true) { |
There was a problem hiding this comment.
You could use cursor.forEachResult to simplify this code a bit.
| last = next; | ||
| break; | ||
| } | ||
| collected.add(next.get()); |
There was a problem hiding this comment.
It would probably be good to assert that this is 7, or at least that we actually apply a limit, and don't just read it all at once, but maybe the limiting is already tested below.
There was a problem hiding this comment.
Added assertion on the number of continuations hit
| try (FDBRecordContext ctxA = openContext(); | ||
| FDBRecordContext ctxB = openContext()) { | ||
| for (int i = 0; i < 4; i++) { | ||
| queue.enqueue(ctxA, payload("A-" + i), 0).join(); |
There was a problem hiding this comment.
Should you force a getReadVersion call?
I think it's possible that if these are all blind-writes, then GRV doesn't happen until commit, in which case they are not actually overlapping.
There was a problem hiding this comment.
enqueue reads the queue size (via SNAPSHOT) so I think we should be ok
| } | ||
|
|
||
| // While the drain transaction is still open, another transaction enqueues a fresh | ||
| // entry. It must commit cleanly because enqueue never conflicts with anything. |
There was a problem hiding this comment.
| // entry. It must commit cleanly because enqueue never conflicts with anything. | |
| // entry. It must commit cleanly because enqueue only conflicts with ensureQueueEmpty. |
| assertScanThrows(readerQueue, RecordCoreStorageException.class); | ||
| } | ||
|
|
||
| private <T extends com.google.protobuf.Message> void assertScanThrows( |
There was a problem hiding this comment.
Can Message be imported rather than fully-qualified.
There was a problem hiding this comment.
Actually replaced by ?
Create a write-pending-queue for use with the indexer when in write-only mode.
The created queue provides the following guarantees:
ensureEmptyoperationTesting involves 3 test classes:
Resolves #4311