1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright 2021 Twitter, Inc.
// Copyright 2023 Pelikan Cache contributors
// Licensed under the MIT and Apache-2.0 licenses
//! A builder for configuring a new [`Segcache`] instance.
use crate::*;
/// A builder that is used to construct a new [`Segcache`] instance.
pub struct Builder {
hash_power: u8,
overflow_factor: f64,
segments_builder: SegmentsBuilder,
}
// Defines the default parameters
impl Default for Builder {
fn default() -> Self {
Self {
hash_power: 16,
overflow_factor: 0.0,
segments_builder: SegmentsBuilder::default(),
}
}
}
impl Builder {
/// Specify the hash power, which limits the size of the hashtable to 2^N
/// entries. 1/8th of these are used for metadata storage, meaning that the
/// total number of items which can be held in the cache is limited to
/// `7 * 2^(N - 3)` items. The hash table will have a total size of
/// `2^(N + 3)` bytes.
///
/// ```
/// use segcache::Segcache;
///
/// // create a cache with a small hashtable that has room for ~114k items
/// // without using any overflow buckets.
/// let cache = Segcache::builder().hash_power(17).build();
///
/// // create a cache with a larger hashtable with room for ~1.8M items
/// let cache = Segcache::builder().hash_power(21).build();
/// ```
pub fn hash_power(mut self, hash_power: u8) -> Self {
assert!(hash_power >= 3, "hash power must be at least 3");
self.hash_power = hash_power;
self
}
/// Specify an overflow factor which is used to scale the hashtable and
/// provide additional capacity for chaining item buckets. A factor of 1.0
/// will result in a hash table that is 100% larger.
///
/// ```
/// use segcache::Segcache;
///
/// // create a cache with a hashtable with room for ~228k items, which is
/// // about the same as using a hash power of 18, but is more tolerant of
/// // hash collisions.
/// let cache = Segcache::builder()
/// .hash_power(17)
/// .overflow_factor(1.0)
/// .build();
///
/// // smaller overflow factors may be specified, meaning only some buckets
/// // can ever be chained
/// let cache = Segcache::builder()
/// .hash_power(17)
/// .overflow_factor(0.2)
/// .build();
/// ```
pub fn overflow_factor(mut self, percent: f64) -> Self {
self.overflow_factor = percent;
self
}
/// Specify the total number of bytes to be used for heap storage of items.
/// This includes, key, value, and per-item overheads.
///
/// ```
/// use segcache::Segcache;
///
/// const MB: usize = 1024 * 1024;
///
/// // create a cache with a 64MB heap
/// let cache = Segcache::builder().heap_size(64 * MB).build();
///
/// // create a cache with a 256MB heap
/// let cache = Segcache::builder().heap_size(256 * MB).build();
/// ```
pub fn heap_size(mut self, bytes: usize) -> Self {
self.segments_builder = self.segments_builder.heap_size(bytes);
self
}
/// Specify the segment size for item storage. The largest item which can be
/// held is `size - 5` bytes for builds without the `debug` or `magic` build
/// features enabled. Smaller segment sizes reduce the number of items which
/// would be evicted/expired at one time, at the cost of additional memory
/// and book-keeping overheads compared to using larger segments for the
/// same total size.
///
/// ```
/// use segcache::Segcache;
///
/// const MB: i32 = 1024 * 1024;
///
/// // create a cache using 1MB segments
/// let cache = Segcache::builder().segment_size(1 * MB).build();
///
/// // create a cache using 4MB segments
/// let cache = Segcache::builder().segment_size(4 * MB).build();
/// ```
pub fn segment_size(mut self, size: i32) -> Self {
self.segments_builder = self.segments_builder.segment_size(size);
self
}
/// Specify the eviction policy to be used. See the `Policy` documentation
/// for more details about each strategy.
///
/// ```
/// use segcache::{Policy, Segcache};
///
/// // create a cache using random segment eviction
/// let cache = Segcache::builder().eviction(Policy::Random).build();
///
/// // create a cache using a merge based eviction policy
/// let policy = Policy::Merge { max: 8, merge: 4, compact: 2};
/// let cache = Segcache::builder().eviction(policy).build();
///
/// // create an S3-Segcache with 10% admission pool
/// let cache = Segcache::builder()
/// .eviction(Policy::S3Fifo { admission_ratio: 0.10 })
/// .build();
/// ```
pub fn eviction(mut self, policy: Policy) -> Self {
self.segments_builder = self.segments_builder.eviction_policy(policy);
self
}
/// Consumes the builder and returns a fully-allocated `Segcache` instance.
///
/// ```
/// use segcache::{Policy, Segcache};
///
/// const MB: usize = 1024 * 1024;
///
/// let cache = Segcache::builder()
/// .heap_size(64 * MB)
/// .segment_size(1 * MB as i32)
/// .hash_power(16)
/// .eviction(Policy::Random).build();
/// ```
pub fn build(self) -> Result<Segcache, std::io::Error> {
let hashtable = HashTable::new(self.hash_power, self.overflow_factor);
let segments = self.segments_builder.build()?;
let ttl_buckets = TtlBuckets::default();
Ok(Segcache {
hashtable,
segments,
ttl_buckets,
time: Instant::now(),
})
}
}