@@ -202,7 +202,6 @@ defmodule Kafkaesque.Topic.RetentionController do
202202 cutoff_time = current_time - state . retention_ms
203203
204204 # Find the offset of the first message after the cutoff time
205- # In a real implementation, this would scan the index
206205 find_offset_after_timestamp ( state , cutoff_time )
207206 end
208207 end
@@ -212,7 +211,6 @@ defmodule Kafkaesque.Topic.RetentionController do
212211 0
213212 else
214213 # Calculate total size and find offset where we exceed retention
215- # In a real implementation, this would use the index and file stats
216214 find_offset_for_size_limit ( state , state . retention_bytes )
217215 end
218216 end
@@ -231,8 +229,19 @@ defmodule Kafkaesque.Topic.RetentionController do
231229 # Get offsets range first
232230 case SingleFile . get_offsets ( state . topic , state . partition ) do
233231 { :ok , % { earliest: earliest , latest: latest } } when latest > earliest ->
234- # Use binary search to find the first offset after cutoff time
235- binary_search_timestamp ( state . topic , state . partition , earliest , latest , cutoff_time )
232+ # Try to get index table for more efficient search
233+ index_table = get_index_table ( state )
234+
235+ # Use index to narrow search if available
236+ { search_start , search_end } =
237+ if index_table do
238+ narrow_search_with_index ( index_table , earliest , latest , cutoff_time )
239+ else
240+ { earliest , latest }
241+ end
242+
243+ # Binary search to find the first offset after cutoff time
244+ binary_search_timestamp ( state . topic , state . partition , search_start , search_end , cutoff_time )
236245
237246 _ ->
238247 # No messages or error
@@ -290,19 +299,27 @@ defmodule Kafkaesque.Topic.RetentionController do
290299 # We need to keep only size_limit_bytes, so calculate how much to remove
291300 bytes_to_remove = file_size - size_limit_bytes
292301
302+ # Try to use index for more efficient size calculation
303+ index_table = get_index_table ( state )
304+
293305 # Scan through messages to find where we've accumulated enough bytes to remove
294- find_size_based_offset ( state . topic , state . partition , bytes_to_remove )
306+ find_size_based_offset ( state . topic , state . partition , bytes_to_remove , index_table )
295307
296308 _ ->
297309 # File is smaller than limit, keep everything
298310 0
299311 end
300312 end
301313
302- defp find_size_based_offset ( topic , partition , bytes_to_remove ) do
314+ defp find_size_based_offset ( topic , partition , bytes_to_remove , index_table ) do
303315 case SingleFile . get_offsets ( topic , partition ) do
304316 { :ok , % { earliest: earliest , latest: latest } } when latest > earliest ->
305- scan_for_size_limit_recursive ( topic , partition , earliest , latest , bytes_to_remove , 0 )
317+ # Use index entries to jump more efficiently if available
318+ if index_table do
319+ scan_with_index ( topic , partition , index_table , bytes_to_remove , earliest , latest )
320+ else
321+ scan_for_size_limit_recursive ( topic , partition , earliest , latest , bytes_to_remove , 0 )
322+ end
306323
307324 _ ->
308325 0
@@ -475,4 +492,85 @@ defmodule Kafkaesque.Topic.RetentionController do
475492 newest_timestamp: System . system_time ( :millisecond )
476493 } )
477494 end
495+
496+ defp get_index_table ( state ) do
497+ # Get the index table from SingleFile
498+ case SingleFile . get_index_table ( state . topic , state . partition ) do
499+ { :ok , table } -> table
500+ _ -> nil
501+ end
502+ rescue
503+ _ -> nil
504+ end
505+
506+ defp narrow_search_with_index ( index_table , earliest , latest , _cutoff_time ) do
507+ # Use index entries to narrow binary search range
508+ # Since we don't have timestamps in index yet, we can at least use
509+ # the index to skip large ranges
510+ entries = :ets . tab2list ( index_table ) |> Enum . sort ( )
511+
512+ # For now, just use index to segment the search space
513+ # TODO: We could later enhance index to store timestamps
514+ if length ( entries ) > 10 do
515+ # Use middle third of the range for search (heuristic)
516+ range = latest - earliest
517+ search_start = earliest + div ( range , 3 )
518+ search_end = latest - div ( range , 10 )
519+ { search_start , search_end }
520+ else
521+ { earliest , latest }
522+ end
523+ end
524+
525+ defp scan_with_index ( topic , partition , index_table , bytes_to_remove , earliest , latest ) do
526+ # Use index entries to jump through the file more efficiently
527+ entries = :ets . tab2list ( index_table ) |> Enum . sort ( )
528+
529+ # Calculate approximate bytes per offset using index positions
530+ if length ( entries ) > 1 do
531+ # Use index to estimate where size limit is reached
532+ scan_with_index_entries ( topic , partition , entries , bytes_to_remove , 0 )
533+ else
534+ # Fall back to regular scan
535+ scan_for_size_limit_recursive ( topic , partition , earliest , latest , bytes_to_remove , 0 )
536+ end
537+ end
538+
539+ defp scan_with_index_entries ( topic , partition , entries , bytes_to_remove , accumulated ) do
540+ case entries do
541+ [ { offset , { position , _frame_len } } | rest ] when rest != [ ] ->
542+ [ { next_offset , { next_position , _ } } | _ ] = rest
543+
544+ # Approximate size between these index entries
545+ segment_size = next_position - position
546+ new_accumulated = accumulated + segment_size
547+
548+ if new_accumulated >= bytes_to_remove do
549+ # Found the region, scan this segment in detail
550+ scan_for_exact_offset_in_segment ( topic , partition , offset , next_offset ,
551+ bytes_to_remove , accumulated )
552+ else
553+ scan_with_index_entries ( topic , partition , rest , bytes_to_remove , new_accumulated )
554+ end
555+
556+ _ ->
557+ # End of index or single entry
558+ 0
559+ end
560+ end
561+
562+ defp scan_for_exact_offset_in_segment ( topic , partition , start_offset , end_offset ,
563+ bytes_to_remove , accumulated_before ) do
564+ # Scan the specific segment to find exact offset
565+ batch_size = min ( 50 , end_offset - start_offset + 1 )
566+
567+ case SingleFile . read ( topic , partition , start_offset , batch_size * 100 ) do
568+ { :ok , messages } when messages != [ ] ->
569+ # Find exact cutoff in this batch
570+ find_exact_size_cutoff ( messages , start_offset , bytes_to_remove , accumulated_before )
571+
572+ _ ->
573+ start_offset
574+ end
575+ end
478576end
0 commit comments