3434import org .apache .bookkeeper .conf .ServerConfiguration ;
3535import org .apache .bookkeeper .stats .StatsLogger ;
3636import org .apache .bookkeeper .util .collections .ConcurrentLongHashSet ;
37+ import org .apache .bookkeeper .util .collections .ConcurrentLongLongHashMap ;
3738import org .slf4j .Logger ;
3839import org .slf4j .LoggerFactory ;
3940
4546 */
4647public class EntryLocationIndex implements Closeable {
4748
49+ static final String LAST_ENTRY_CACHE_MAX_SIZE = "dbStorage_lastEntryCacheMaxSize" ;
50+ private static final long DEFAULT_LAST_ENTRY_CACHE_MAX_SIZE = 10_000 ;
51+
4852 private final KeyValueStorage locationsDb ;
4953 private final ConcurrentLongHashSet deletedLedgers = ConcurrentLongHashSet .newBuilder ().build ();
54+ private final ConcurrentLongLongHashMap lastEntryCache ;
55+ private final long lastEntryCacheMaxSize ;
5056 private final EntryLocationIndexStats stats ;
5157 private boolean isCompacting ;
5258
5359 public EntryLocationIndex (ServerConfiguration conf , KeyValueStorageFactory storageFactory , String basePath ,
5460 StatsLogger stats ) throws IOException {
5561 locationsDb = storageFactory .newKeyValueStorage (basePath , "locations" , DbConfigType .EntryLocation , conf );
5662
63+ this .lastEntryCacheMaxSize = conf .getLong (LAST_ENTRY_CACHE_MAX_SIZE , DEFAULT_LAST_ENTRY_CACHE_MAX_SIZE );
64+ this .lastEntryCache = ConcurrentLongLongHashMap .newBuilder ()
65+ .expectedItems ((int ) Math .min (lastEntryCacheMaxSize , Integer .MAX_VALUE ))
66+ .build ();
67+
5768 this .stats = new EntryLocationIndexStats (
5869 stats ,
5970 () -> {
@@ -115,6 +126,18 @@ public long getLastEntryInLedger(long ledgerId) throws IOException {
115126 }
116127
117128 private long getLastEntryInLedgerInternal (long ledgerId ) throws IOException {
129+ // Check in-memory cache first to avoid expensive getFloor() calls.
130+ // ConcurrentLongLongHashMap.get() returns -1 if not found.
131+ long cachedLastEntry = lastEntryCache .get (ledgerId );
132+ if (cachedLastEntry >= 0 ) {
133+ if (log .isDebugEnabled ()) {
134+ log .debug ("Found last entry for ledger {} in cache: {}" , ledgerId , cachedLastEntry );
135+ }
136+ stats .getGetLastEntryInLedgerStats ()
137+ .registerSuccessfulEvent (0 , TimeUnit .NANOSECONDS );
138+ return cachedLastEntry ;
139+ }
140+
118141 LongPairWrapper maxEntryId = LongPairWrapper .get (ledgerId , Long .MAX_VALUE );
119142
120143 long startTimeNanos = MathUtils .nowInNano ();
@@ -139,6 +162,11 @@ private long getLastEntryInLedgerInternal(long ledgerId) throws IOException {
139162 if (log .isDebugEnabled ()) {
140163 log .debug ("Found last page in storage db for ledger {} - last entry: {}" , ledgerId , lastEntryId );
141164 }
165+ // Populate cache for future lookups
166+ if (lastEntryCache .size () >= lastEntryCacheMaxSize ) {
167+ lastEntryCache .clear ();
168+ }
169+ lastEntryCache .put (ledgerId , lastEntryId );
142170 return lastEntryId ;
143171 } else {
144172 throw new Bookie .NoEntryException (ledgerId , -1 );
@@ -171,6 +199,18 @@ public void addLocation(Batch batch, long ledgerId, long entryId, long location)
171199 key .recycle ();
172200 value .recycle ();
173201 }
202+
203+ // Update the last entry cache if this entry is newer.
204+ // ConcurrentLongLongHashMap.get() returns -1 if not found.
205+ long cachedLastEntry = lastEntryCache .get (ledgerId );
206+ if (cachedLastEntry < entryId ) {
207+ // Clear the cache if it exceeds the max size to bound memory usage.
208+ // The cache will quickly repopulate with hot ledgers on subsequent reads.
209+ if (cachedLastEntry < 0 && lastEntryCache .size () >= lastEntryCacheMaxSize ) {
210+ lastEntryCache .clear ();
211+ }
212+ lastEntryCache .put (ledgerId , entryId );
213+ }
174214 }
175215
176216 public void updateLocations (Iterable <EntryLocation > newLocations ) throws IOException {
@@ -195,6 +235,7 @@ public void delete(long ledgerId) throws IOException {
195235 // We need to find all the LedgerIndexPage records belonging to one specific
196236 // ledgers
197237 deletedLedgers .add (ledgerId );
238+ lastEntryCache .remove (ledgerId );
198239 }
199240
200241 public String getEntryLocationDBPath () {
0 commit comments