Repository: tikv/tikv
File: components/raftstore/src/store/entry_storage.rs (Rust)

Code before:
            }
        }
        // New term.
        self.cache.push_back((term, index));
        if self.cache.len() > self.capacity {
            self.cache.pop_front();
        }
    }

    /// Push entries to the left of the cache.
    fn prepend(&mut self, entries: &[Entry]) {
        for e in entries.iter().rev() {
            self.append(e.get_index(), e.get_term());
            if self.cache.len() == self.capacity {
                // NOTE: no need to check the previous term as the cached
                // terms are fresh and the term is not allowed to decrease.
                break;
            }
        }
    }

    /// Compact all terms whose term_id are less than the given term.
    fn compact_to(&mut self, term: u64) {
        if let Some((first_term, _)) = self.cache.front() {
            self.cache
                .drain(0..term.saturating_sub(*first_term) as usize);
        }
    }

    /// Return the term of the given index.
    ///
    /// If the term is not found in the cache, return None.
    fn entry(&self, idx: u64) -> Option<u64> {
        if self.cache.is_empty() {
            return None;
        }
        let (_, start_idx) = self.cache.front().unwrap();
        if idx < *start_idx {
            return None;
        }
        let (end_term, end_idx) = self.cache.back().unwrap();
        if idx >= *end_idx {
            return Some(*end_term);
        }
        // Traverse the cache in reverse order.
        // The cost is low since the cache is small and the term is likely to be
        // found in the recent terms.
        let mut candidate = None;
        for (term, start_index) in self.cache.iter().rev() {
            candidate = Some(*term);
            if idx >= *start_index {

Review comment (performance):
The drain operation uses `term.saturating_sub(*first_term)` which could drain more elements than the cache contains if `term` is much larger than `first_term`. This should be bounded by the cache length: `std::cmp::min(term.saturating_sub(*first_term) as usize, self.cache.len())`.
```suggestion
            let drain_range = std::cmp::min(term.saturating_sub(*first_term) as usize, self.cache.len());
            self.cache.drain(0..drain_range);
```

Code after:
                        if cached_start_idx > index {
                            self.cache[pos].1 = index;
                        }
                        return;
                    } else if self.cache.back().unwrap().0 + 1 < term {
                        // Detected a term jump indicating potential network partition.
                        // Clear the cache to prevent serving stale term information.
                        self.cache.clear();
                    }
                }
            }
        }
        // New term.
        self.cache.push_back((term, index));
        if self.cache.len() > self.capacity {
            self.cache.pop_front();
        }
    }

    /// Push entries to the left of the cache.
    #[cfg(test)]
    fn prepend(&mut self, entries: &[Entry]) {
        for e in entries.iter().rev() {
            self.append(e.get_index(), e.get_term());
            if self.cache.len() == self.capacity {
                // NOTE: no need to check the previous term as the cached
                // terms are fresh and the term is not allowed to decrease.
                break;
            }
        }
    }

    /// Compact all terms whose start_index are less than the given index.
    fn compact_to(&mut self, index: u64) {
        // This operation has low cost.
        // Since consistency and safety have been assured, compaction of the
        // `TermCache` is only called when `UnsafeForceCompact` is triggered.
        // Meanwhile, the capacity of `TermCache` is limited to 8, so the
        // traversal cost is low.
        self.cache.retain(|(_, start_idx)| *start_idx >= index);
    }

    /// Return the term of the given index.
    ///
    /// If the term is not found in the cache, return None.
    fn entry(&self, idx: u64) -> Option<u64> {
        if self.cache.is_empty() {
            return None;
        }
        let (_, start_idx) = self.cache.front().unwrap();
        if idx < *start_idx {