Repository navigation
Issue 4900: Read LAC from EntryMemTable in SortedLedgerStorage if it is missing from the Ledger Cache #4902
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |
| import static org.apache.bookkeeper.bookie.BookKeeperServerStats.STORAGE_SCRUB_PAGES_SCANNED; | ||
| import static org.apache.bookkeeper.bookie.BookKeeperServerStats.STORAGE_SCRUB_PAGE_RETRIES; | ||
|
|
||
| import com.google.common.annotations.VisibleForTesting; | ||
| import com.google.common.collect.Lists; | ||
| import com.google.common.util.concurrent.RateLimiter; | ||
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; | ||
|
|
@@ -360,17 +361,21 @@ public boolean entryExists(long ledgerId, long entryId) throws IOException { | |
| public long getLastAddConfirmed(long ledgerId) throws IOException { | ||
| Long lac = ledgerCache.getLastAddConfirmed(ledgerId); | ||
| if (lac == null) { | ||
| ByteBuf bb = getEntry(ledgerId, BookieProtocol.LAST_ADD_CONFIRMED); | ||
| if (null == bb) { | ||
| return BookieProtocol.INVALID_ENTRY_ID; | ||
| } else { | ||
| try { | ||
| bb.skipBytes(2 * Long.BYTES); // skip ledger & entry id | ||
| lac = bb.readLong(); | ||
| lac = ledgerCache.updateLastAddConfirmed(ledgerId, lac); | ||
| } finally { | ||
| ReferenceCountUtil.release(bb); | ||
| } | ||
| lac = getLastAddConfirmedFromLedgerEntry(ledgerId); | ||
| } | ||
| return lac; | ||
| } | ||
|
|
||
| public long getLastAddConfirmedFromLedgerEntry(long ledgerId) throws IOException { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: A short Javadoc would help too:
|
||
| long lac = BookieProtocol.INVALID_ENTRY_ID; | ||
| ByteBuf bb = getEntry(ledgerId, BookieProtocol.LAST_ADD_CONFIRMED); | ||
| if (null != bb) { | ||
| try { | ||
| bb.skipBytes(2 * Long.BYTES); | ||
| lac = bb.readLong(); | ||
| lac = ledgerCache.updateLastAddConfirmed(ledgerId, lac); | ||
| } finally { | ||
| ReferenceCountUtil.release(bb); | ||
| } | ||
| } | ||
| return lac; | ||
|
|
@@ -726,4 +731,9 @@ public void clearStorageStateFlag(StorageState flags) throws IOException { | |
| throw new UnsupportedOperationException( | ||
| "Storage state flags only supported for DbLedgerStorage"); | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| void removeLedgerCacheEntry(long ledgerId) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking: these test-only hooks widen four production classes:
The tests already live in |
||
| ((LedgerCacheImpl) ledgerCache).invalidateLedger(ledgerId); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -240,7 +240,21 @@ public ByteBuf getEntry(long ledgerId, long entryId) throws IOException, BookieE | |
|
|
||
| @Override | ||
| public long getLastAddConfirmed(long ledgerId) throws IOException { | ||
| return interleavedLedgerStorage.getLastAddConfirmed(ledgerId); | ||
| LedgerCache lc = interleavedLedgerStorage.getLedgerCache(); | ||
| Long lac = lc.getLastAddConfirmed(ledgerId); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question about the root cause in the description. I reproduced #4900 with 1 bookie, When the If there really is a path where |
||
| if (null == lac) { | ||
| // If the last add confirmed has fallen out of the ledger cache, | ||
| // it may be in the memTable, and more importantly, it may be | ||
| // in the memTable but _not_ in ledger storage. | ||
| lac = memTable.getLastAddConfirmed(ledgerId); | ||
| if (null != lac) { | ||
| lc.updateLastAddConfirmed(ledgerId, lac); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
| if (null == lac) { | ||
| lac = interleavedLedgerStorage.getLastAddConfirmedFromLedgerEntry(ledgerId); | ||
| } | ||
| return lac; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking, could be a follow-up: After eviction, Consider calling |
||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -461,4 +475,9 @@ public void clearStorageStateFlag(StorageState flags) throws IOException { | |
| throw new UnsupportedOperationException( | ||
| "Storage state flags only supported for DbLedgerStorage"); | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| void removeLedgerCacheEntry(long ledgerId) { | ||
| interleavedLedgerStorage.removeLedgerCacheEntry(ledgerId); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -468,5 +468,34 @@ public void testAddSameEntries() throws IOException { | |
| assertEquals(memTable.kvmap.size(), 0); | ||
| assertEquals(memTable.skipListSemaphore.availablePermits(), initialPermits); | ||
| } | ||
|
|
||
| @Test | ||
| public void testGetLastAddConfirmed() throws IOException { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you also cover:
Minor: the payload's ledger id (offset 0) is 0, while the key is ledger 1. |
||
| final long ledgerId = 1; | ||
| final byte[] bytes = new byte[3 * Long.BYTES]; | ||
| final ByteBuffer bb = ByteBuffer.wrap(bytes); | ||
|
|
||
| bb.putLong(0, 0); | ||
| bb.putLong(Long.BYTES, 0); | ||
| bb.putLong(2 * Long.BYTES, -1); | ||
|
|
||
| memTable.addEntry(ledgerId, 0, bb.duplicate(), this); | ||
|
|
||
| bb.putLong(2 * Long.BYTES, 0); | ||
|
|
||
| memTable.addEntry(ledgerId, 1, bb.duplicate(), this); | ||
|
|
||
| bb.putLong(2 * Long.BYTES, 1); | ||
|
|
||
| memTable.addEntry(ledgerId, 2, bb.duplicate(), this); | ||
|
|
||
| bb.putLong(2 * Long.BYTES, 2); | ||
|
|
||
| memTable.addEntry(ledgerId, 3, bb.duplicate(), this); | ||
|
|
||
| long lac = memTable.getLastAddConfirmed(ledgerId); | ||
|
|
||
| assertEquals(2, lac); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertFalse; | ||
| import static org.junit.Assert.assertTrue; | ||
| import static org.junit.Assert.assertThrows; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checkstyle ( |
||
|
|
||
| import io.netty.buffer.ByteBuf; | ||
| import io.netty.buffer.Unpooled; | ||
|
|
@@ -46,6 +47,7 @@ | |
| import org.apache.bookkeeper.bookie.CheckpointSource.Checkpoint; | ||
| import org.apache.bookkeeper.conf.ServerConfiguration; | ||
| import org.apache.bookkeeper.conf.TestBKConfiguration; | ||
| import org.apache.bookkeeper.proto.BookieProtocol; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused import (checkstyle |
||
| import org.apache.bookkeeper.stats.NullStatsLogger; | ||
| import org.apache.bookkeeper.stats.StatsLogger; | ||
| import org.apache.bookkeeper.test.TestStatsProvider; | ||
|
|
@@ -143,6 +145,11 @@ void checkEntry(long ledgerId, long entryId, long location) throws EntryLookupEx | |
| final long entriesPerWrite = 2; | ||
| final long numOfLedgers = 5; | ||
|
|
||
| private void prepareLedger(final long ledgerId) throws IOException { | ||
| interleavedStorage.setMasterKey(ledgerId, ("ledger-" + ledgerId).getBytes()); | ||
| interleavedStorage.setFenced(ledgerId); | ||
| } | ||
|
|
||
| @Before | ||
| public void setUp() throws Exception { | ||
| File tmpDir = File.createTempFile("bkTest", ".dir"); | ||
|
|
@@ -167,8 +174,7 @@ public void setUp() throws Exception { | |
| for (long entryId = 0; entryId < numWrites; entryId++) { | ||
| for (long ledgerId = 0; ledgerId < numOfLedgers; ledgerId++) { | ||
| if (entryId == 0) { | ||
| interleavedStorage.setMasterKey(ledgerId, ("ledger-" + ledgerId).getBytes()); | ||
| interleavedStorage.setFenced(ledgerId); | ||
| this.prepareLedger(ledgerId); | ||
| } | ||
| ByteBuf entry = Unpooled.buffer(128); | ||
| entry.writeLong(ledgerId); | ||
|
|
@@ -449,4 +455,58 @@ void printInfoLine(String s) { | |
| res = shell.run(new String[] { "localconsistencycheck" }); | ||
| Assert.assertEquals(1, res); | ||
| } | ||
|
|
||
| @Test | ||
| public void testGetLastAddConfirmed() throws Exception { | ||
| final byte[] entry = new byte[Long.BYTES * 3]; | ||
| final ByteBuf bb = Unpooled.wrappedBuffer(entry); | ||
|
|
||
| // Work off of a ledgerId that we haven't already inserted | ||
| final long ledgerId = this.numOfLedgers + 1; | ||
| final long entryId = 2; | ||
| final long lac = 1; | ||
|
|
||
| this.prepareLedger(ledgerId); | ||
| bb.setLong(0, ledgerId); | ||
| bb.setLong(Long.BYTES, entryId); | ||
| bb.setLong(2 * Long.BYTES, lac); | ||
|
|
||
| interleavedStorage.addEntry(bb); | ||
|
|
||
| final long lac1 = interleavedStorage.getLastAddConfirmed(ledgerId); | ||
| final long lac2 = interleavedStorage.getLastAddConfirmed(ledgerId); | ||
|
|
||
| interleavedStorage.removeLedgerCacheEntry(ledgerId); | ||
|
|
||
| final long lac3 = interleavedStorage.getLastAddConfirmed(ledgerId); | ||
|
|
||
| assertEquals(lac, lac1); | ||
| assertEquals(lac, lac2); | ||
| assertEquals(lac, lac3); | ||
| } | ||
|
|
||
| @Test | ||
| public void testGetLastAddConfirmedFromLedgerEntry() throws Exception { | ||
| final byte[] entry = new byte[Long.BYTES * 3]; | ||
| final ByteBuf bb = Unpooled.wrappedBuffer(entry); | ||
|
|
||
| // Work off of a ledgerId that we haven't already inserted | ||
| final long ledgerId = this.numOfLedgers + 2; | ||
| final long entryId = 2; | ||
| final long lac = 1; | ||
|
|
||
| this.prepareLedger(ledgerId); | ||
| bb.setLong(0, ledgerId); | ||
| bb.setLong(Long.BYTES, entryId); | ||
| bb.setLong(2 * Long.BYTES, lac); | ||
|
|
||
| assertThrows("Entry 0 not found", Bookie.NoEntryException.class, () -> { | ||
| interleavedStorage.getLastAddConfirmedFromLedgerEntry(ledgerId); | ||
| }); | ||
|
|
||
| interleavedStorage.addEntry(bb); | ||
|
|
||
| final long lac2 = interleavedStorage.getLastAddConfirmedFromLedgerEntry(ledgerId); | ||
| assertEquals(lac, lac2); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description mentions #4895 "when ledger metadata is in use". In the stock write path,
METAENTRY_ID_*records only go to the journal and never reachLedgerStorage.addEntry, so I don't think this PR depends on #4895 there.If negative entry IDs can reach the memtable in your setup, though,
getLastEntrymay return such an entry. This method would then read bytes 16–24 of a meta payload as the LAC, which gives a bogus and potentially higher (unsafe) value. A cheap guard:if (ekv != null && ekv.getEntryId() >= 0).