3535import org .apache .bookkeeper .conf .ServerConfiguration ;
3636import org .apache .bookkeeper .stats .StatsLogger ;
3737import org .apache .bookkeeper .util .collections .ConcurrentLongHashSet ;
38+ import org .apache .bookkeeper .util .collections .ConcurrentLongLongHashMap ;
3839
3940/**
4041 * Maintains an index of the entry locations in the EntryLogger.
4546@ CustomLog
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 () -> {
@@ -114,6 +125,18 @@ public long getLastEntryInLedger(long ledgerId) throws IOException {
114125 }
115126
116127 private long getLastEntryInLedgerInternal (long ledgerId ) throws IOException {
128+ // Check in-memory cache first to avoid expensive getFloor() calls.
129+ // ConcurrentLongLongHashMap.get() returns -1 if not found.
130+ long cachedLastEntry = lastEntryCache .get (ledgerId );
131+ if (cachedLastEntry >= 0 ) {
132+ if (log .isDebugEnabled ()) {
133+ log .debug ("Found last entry for ledger {} in cache: {}" , ledgerId , cachedLastEntry );
134+ }
135+ stats .getGetLastEntryInLedgerStats ()
136+ .registerSuccessfulEvent (0 , TimeUnit .NANOSECONDS );
137+ return cachedLastEntry ;
138+ }
139+
117140 LongPairWrapper maxEntryId = LongPairWrapper .get (ledgerId , Long .MAX_VALUE );
118141
119142 long startTimeNanos = MathUtils .nowInNano ();
@@ -139,6 +162,11 @@ private long getLastEntryInLedgerInternal(long ledgerId) throws IOException {
139162 .attr ("ledgerId" , ledgerId )
140163 .attr ("lastEntryId" , lastEntryId )
141164 .log ("Found last page in storage db for ledger" );
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 );
@@ -173,6 +201,18 @@ public void addLocation(Batch batch, long ledgerId, long entryId, long location)
173201 key .recycle ();
174202 value .recycle ();
175203 }
204+
205+ // Update the last entry cache if this entry is newer.
206+ // ConcurrentLongLongHashMap.get() returns -1 if not found.
207+ long cachedLastEntry = lastEntryCache .get (ledgerId );
208+ if (cachedLastEntry < entryId ) {
209+ // Clear the cache if it exceeds the max size to bound memory usage.
210+ // The cache will quickly repopulate with hot ledgers on subsequent reads.
211+ if (cachedLastEntry < 0 && lastEntryCache .size () >= lastEntryCacheMaxSize ) {
212+ lastEntryCache .clear ();
213+ }
214+ lastEntryCache .put (ledgerId , entryId );
215+ }
176216 }
177217
178218 public void updateLocations (Iterable <EntryLocation > newLocations ) throws IOException {
@@ -196,6 +236,7 @@ public void delete(long ledgerId) throws IOException {
196236 // We need to find all the LedgerIndexPage records belonging to one specific
197237 // ledgers
198238 deletedLedgers .add (ledgerId );
239+ lastEntryCache .remove (ledgerId );
199240 }
200241
201242 public String getEntryLocationDBPath () {
0 commit comments