LCOV - code coverage report
Current view: top level - libs/tenant_size_model/src - calculation.rs (source / functions) Coverage Total Hit
Test: 2620485e474b48c32427149a5d91ef8fc2cd649e.info Lines: 98.3 % 121 119
Test Date: 2025-05-01 22:50:11 Functions: 100.0 % 3 3

            Line data    Source code
       1              : use crate::{SegmentMethod, SegmentSizeResult, SizeResult, StorageModel};
       2              : 
       3              : //
       4              : //                 *-g--*---D--->
       5              : //                /
       6              : //               /
       7              : //              /                 *---b----*-B--->
       8              : //             /                 /
       9              : //            /                 /
      10              : //      -----*--e---*-----f----* C
      11              : //           E                  \
      12              : //                               \
      13              : //                                *--a---*---A-->
      14              : //
      15              : // If A and B need to be retained, is it cheaper to store
      16              : // snapshot at C+a+b, or snapshots at A and B ?
      17              : //
      18              : // If D also needs to be retained, which is cheaper:
      19              : //
      20              : // 1. E+g+e+f+a+b
      21              : // 2. D+C+a+b
      22              : // 3. D+A+B
      23              : 
      24              : /// `Segment` which has had its size calculated.
      25              : #[derive(Clone, Debug)]
      26              : struct SegmentSize {
      27              :     method: SegmentMethod,
      28              : 
      29              :     // calculated size of this subtree, using this method
      30              :     accum_size: u64,
      31              : 
      32              :     seg_id: usize,
      33              :     children: Vec<SegmentSize>,
      34              : }
      35              : 
      36              : struct SizeAlternatives {
      37              :     /// cheapest alternative if parent is available.
      38              :     incremental: SegmentSize,
      39              : 
      40              :     /// cheapest alternative if parent node is not available
      41              :     non_incremental: Option<SegmentSize>,
      42              : }
      43              : 
      44              : impl StorageModel {
      45           30 :     pub fn calculate(&self) -> SizeResult {
      46           30 :         // Build adjacency list. 'child_list' is indexed by segment id. Each entry
      47           30 :         // contains a list of all child segments of the segment.
      48           30 :         let mut roots: Vec<usize> = Vec::new();
      49           30 :         let mut child_list: Vec<Vec<usize>> = Vec::new();
      50           30 :         child_list.resize(self.segments.len(), Vec::new());
      51              : 
      52          225 :         for (seg_id, seg) in self.segments.iter().enumerate() {
      53          225 :             if let Some(parent_id) = seg.parent {
      54          195 :                 child_list[parent_id].push(seg_id);
      55          195 :             } else {
      56           30 :                 roots.push(seg_id);
      57           30 :             }
      58              :         }
      59              : 
      60           30 :         let mut segment_results = Vec::new();
      61           30 :         segment_results.resize(
      62           30 :             self.segments.len(),
      63           30 :             SegmentSizeResult {
      64           30 :                 method: SegmentMethod::Skipped,
      65           30 :                 accum_size: 0,
      66           30 :             },
      67           30 :         );
      68           30 : 
      69           30 :         let mut total_size = 0;
      70           60 :         for root in roots {
      71           30 :             if let Some(selected) = self.size_here(root, &child_list).non_incremental {
      72           30 :                 StorageModel::fill_selected_sizes(&selected, &mut segment_results);
      73           30 :                 total_size += selected.accum_size;
      74           30 :             } else {
      75            0 :                 // Couldn't find any way to get this root. Error?
      76            0 :             }
      77              :         }
      78              : 
      79           30 :         SizeResult {
      80           30 :             // If total_size is 0, it means that the tenant has all timelines offloaded; we need to report 1
      81           30 :             // here so that the data point shows up in the s3 files.
      82           30 :             total_size: total_size.max(1),
      83           30 :             segments: segment_results,
      84           30 :         }
      85           30 :     }
      86              : 
      87          225 :     fn fill_selected_sizes(selected: &SegmentSize, result: &mut Vec<SegmentSizeResult>) {
      88          225 :         result[selected.seg_id] = SegmentSizeResult {
      89          225 :             method: selected.method,
      90          225 :             accum_size: selected.accum_size,
      91          225 :         };
      92              :         // recurse to children
      93          225 :         for child in selected.children.iter() {
      94          195 :             StorageModel::fill_selected_sizes(child, result);
      95          195 :         }
      96          225 :     }
      97              : 
      98              :     //
      99              :     // This is the core of the sizing calculation.
     100              :     //
     101              :     // This is a recursive function, that for each Segment calculates the best way
     102              :     // to reach all the Segments that are marked as needed in this subtree, under two
     103              :     // different conditions:
     104              :     // a) when the parent of this segment is available (as a snaphot or through WAL), and
     105              :     // b) when the parent of this segment is not available.
     106              :     //
     107          225 :     fn size_here(&self, seg_id: usize, child_list: &Vec<Vec<usize>>) -> SizeAlternatives {
     108          225 :         let seg = &self.segments[seg_id];
     109          225 :         // First figure out the best way to get each child
     110          225 :         let mut children = Vec::new();
     111          225 :         for child_id in &child_list[seg_id] {
     112          195 :             children.push(self.size_here(*child_id, child_list))
     113              :         }
     114              : 
     115              :         // Method 1. If this node is not needed, we can skip it as long as we
     116              :         // take snapshots later in each sub-tree
     117          225 :         let snapshot_later = if !seg.needed {
     118          152 :             let mut snapshot_later = SegmentSize {
     119          152 :                 seg_id,
     120          152 :                 method: SegmentMethod::Skipped,
     121          152 :                 accum_size: 0,
     122          152 :                 children: Vec::new(),
     123          152 :             };
     124          152 : 
     125          152 :             let mut possible = true;
     126          164 :             for child in children.iter() {
     127          164 :                 if let Some(non_incremental) = &child.non_incremental {
     128          106 :                     snapshot_later.accum_size += non_incremental.accum_size;
     129          106 :                     snapshot_later.children.push(non_incremental.clone())
     130              :                 } else {
     131           58 :                     possible = false;
     132           58 :                     break;
     133              :                 }
     134              :             }
     135          152 :             if possible { Some(snapshot_later) } else { None }
     136              :         } else {
     137           73 :             None
     138              :         };
     139              : 
     140              :         // Method 2. Get a snapshot here. This assumed to be possible, if the 'size' of
     141              :         // this Segment was given.
     142          225 :         let snapshot_here = if !seg.needed || seg.parent.is_none() {
     143          152 :             if let Some(snapshot_size) = seg.size {
     144          104 :                 let mut snapshot_here = SegmentSize {
     145          104 :                     seg_id,
     146          104 :                     method: SegmentMethod::SnapshotHere,
     147          104 :                     accum_size: snapshot_size,
     148          104 :                     children: Vec::new(),
     149          104 :                 };
     150          123 :                 for child in children.iter() {
     151          123 :                     snapshot_here.accum_size += child.incremental.accum_size;
     152          123 :                     snapshot_here.children.push(child.incremental.clone())
     153              :                 }
     154          104 :                 Some(snapshot_here)
     155              :             } else {
     156           48 :                 None
     157              :             }
     158              :         } else {
     159           73 :             None
     160              :         };
     161              : 
     162              :         // Method 3. Use WAL to get here from parent
     163          225 :         let wal_here = {
     164          225 :             let mut wal_here = SegmentSize {
     165          225 :                 seg_id,
     166          225 :                 method: SegmentMethod::Wal,
     167          225 :                 accum_size: if let Some(parent_id) = seg.parent {
     168          195 :                     seg.lsn - self.segments[parent_id].lsn
     169              :                 } else {
     170           30 :                     0
     171              :                 },
     172          225 :                 children: Vec::new(),
     173              :             };
     174          420 :             for child in children {
     175          195 :                 wal_here.accum_size += child.incremental.accum_size;
     176          195 :                 wal_here.children.push(child.incremental)
     177              :             }
     178          225 :             wal_here
     179          225 :         };
     180          225 : 
     181          225 :         // If the parent is not available, what's the cheapest method involving
     182          225 :         // a snapshot here or later?
     183          225 :         let mut cheapest_non_incremental: Option<SegmentSize> = None;
     184          225 :         if let Some(snapshot_here) = snapshot_here {
     185          104 :             cheapest_non_incremental = Some(snapshot_here);
     186          121 :         }
     187          225 :         if let Some(snapshot_later) = snapshot_later {
     188              :             // Use <=, to prefer skipping if the size is equal
     189           94 :             if let Some(parent) = &cheapest_non_incremental {
     190           46 :                 if snapshot_later.accum_size <= parent.accum_size {
     191           34 :                     cheapest_non_incremental = Some(snapshot_later);
     192           34 :                 }
     193           48 :             } else {
     194           48 :                 cheapest_non_incremental = Some(snapshot_later);
     195           48 :             }
     196          131 :         }
     197              : 
     198              :         // And what's the cheapest method, if the parent is available?
     199          225 :         let cheapest_incremental = if let Some(cheapest_non_incremental) = &cheapest_non_incremental
     200              :         {
     201              :             // Is it cheaper to use a snapshot here or later, anyway?
     202              :             // Use <, to prefer Wal over snapshot if the cost is the same
     203          152 :             if wal_here.accum_size < cheapest_non_incremental.accum_size {
     204          109 :                 wal_here
     205              :             } else {
     206           43 :                 cheapest_non_incremental.clone()
     207              :             }
     208              :         } else {
     209           73 :             wal_here
     210              :         };
     211              : 
     212          225 :         SizeAlternatives {
     213          225 :             incremental: cheapest_incremental,
     214          225 :             non_incremental: cheapest_non_incremental,
     215          225 :         }
     216          225 :     }
     217              : }
        

Generated by: LCOV version 2.1-beta