@@ -169,6 +169,7 @@ mod data {
169169 pub ( crate ) const ETHEREUM_BLOCKS_TABLE_NAME : & str = "public.ethereum_blocks" ;
170170
171171 pub ( crate ) const ETHEREUM_CALL_CACHE_TABLE_NAME : & str = "public.eth_call_cache" ;
172+ pub ( crate ) const ETHEREUM_CALL_META_TABLE_NAME : & str = "public.eth_call_meta" ;
172173
173174 mod public {
174175 pub ( super ) use super :: super :: public:: ethereum_networks;
@@ -1742,6 +1743,56 @@ mod data {
17421743 . map_err ( StoreError :: from)
17431744 }
17441745
1746+ /// Given `ttl_days` and `max_contracts`, compute an effective
1747+ /// TTL in days such that at most `max_contracts` contracts would
1748+ /// be evicted. Returns a value >= `ttl_days`.
1749+ ///
1750+ /// We look at the (max_contracts+1)th oldest stale contract. If
1751+ /// it exists with `accessed_at = D`, the effective TTL is set to
1752+ /// `current_date - D` days, making the deletion cutoff exactly
1753+ /// `D`. Since the deletion query uses `accessed_at < cutoff`,
1754+ /// contracts with `accessed_at = D` are excluded, guaranteeing
1755+ /// at most `max_contracts` deletions.
1756+ ///
1757+ /// When multiple contracts share the boundary date, we may
1758+ /// delete fewer than `max_contracts` since we can only control
1759+ /// the cutoff at date granularity.
1760+ pub ( super ) async fn effective_ttl (
1761+ & self ,
1762+ conn : & mut AsyncPgConnection ,
1763+ ttl_days : i64 ,
1764+ max_contracts : i64 ,
1765+ ) -> Result < i64 , StoreError > {
1766+ #[ derive( QueryableByName ) ]
1767+ struct Row {
1768+ #[ diesel( sql_type = diesel:: sql_types:: BigInt ) ]
1769+ effective_ttl : i64 ,
1770+ }
1771+
1772+ let meta_table = match self {
1773+ Storage :: Shared => ETHEREUM_CALL_META_TABLE_NAME ,
1774+ Storage :: Private ( Schema { call_meta, .. } ) => call_meta. qname . as_str ( ) ,
1775+ } ;
1776+ let query = format ! (
1777+ "select extract(day from current_date - accessed_at)::int8 + 1\
1778+ as effective_ttl \
1779+ from {meta_table} \
1780+ where accessed_at < current_date - $1 * interval '1 day' \
1781+ order by accessed_at asc \
1782+ offset $2 \
1783+ limit 1"
1784+ ) ;
1785+ let effective_ttl: Option < i64 > = sql_query ( & query)
1786+ . bind :: < BigInt , _ > ( ttl_days)
1787+ . bind :: < BigInt , _ > ( max_contracts)
1788+ . get_result :: < Row > ( conn)
1789+ . await
1790+ . optional ( ) ?
1791+ . map ( |r| r. effective_ttl ) ;
1792+
1793+ Ok ( effective_ttl. unwrap_or ( ttl_days) )
1794+ }
1795+
17451796 /// Delete all call_meta entries with accessed_at older than
17461797 /// `ttl_days`. Should be called after all corresponding
17471798 /// call_cache rows have been deleted.
@@ -3226,7 +3277,11 @@ impl ChainStoreTrait for ChainStore {
32263277 Ok ( ( ) )
32273278 }
32283279
3229- async fn clear_stale_call_cache ( & self , ttl_days : usize ) -> Result < ( ) , Error > {
3280+ async fn clear_stale_call_cache (
3281+ & self ,
3282+ ttl_days : usize ,
3283+ max_contracts : Option < usize > ,
3284+ ) -> Result < ( ) , Error > {
32303285 const LOG_INTERVAL : Duration = Duration :: from_mins ( 5 ) ;
32313286
32323287 let conn = & mut self . pool . get_permitted ( ) . await ?;
@@ -3238,6 +3293,25 @@ impl ChainStoreTrait for ChainStore {
32383293 . await ?;
32393294
32403295 let ttl_days = ttl_days as i64 ;
3296+ let effective_ttl = match max_contracts {
3297+ Some ( max) => {
3298+ self . storage
3299+ . effective_ttl ( conn, ttl_days, max as i64 )
3300+ . await ?
3301+ }
3302+ None => ttl_days,
3303+ } ;
3304+
3305+ if effective_ttl != ttl_days {
3306+ info ! (
3307+ logger,
3308+ "adjusted ttl from {} to {} days to stay within {} contracts" ,
3309+ ttl_days,
3310+ effective_ttl,
3311+ max_contracts. unwrap( )
3312+ ) ;
3313+ }
3314+
32413315 let mut total_deleted: usize = 0 ;
32423316 let mut batch_count: usize = 0 ;
32433317 let mut batch_size = AdaptiveBatchSize :: with_size ( 100 ) ;
@@ -3248,7 +3322,7 @@ impl ChainStoreTrait for ChainStore {
32483322 let start = Instant :: now ( ) ;
32493323 let deleted = self
32503324 . storage
3251- . delete_stale_calls_batch ( conn, ttl_days , current_size)
3325+ . delete_stale_calls_batch ( conn, effective_ttl , current_size)
32523326 . await ?;
32533327
32543328 batch_size. adapt ( start. elapsed ( ) ) ;
@@ -3273,7 +3347,7 @@ impl ChainStoreTrait for ChainStore {
32733347 }
32743348 }
32753349
3276- let contracts_deleted = self . storage . delete_stale_meta ( conn, ttl_days ) . await ?;
3350+ let contracts_deleted = self . storage . delete_stale_meta ( conn, effective_ttl ) . await ?;
32773351
32783352 info ! (
32793353 logger,
0 commit comments