Line data Source code
1 : //! See docs/rfcs/031-sharding-static.md for an overview of sharding.
2 : //!
3 : //! This module contains a variety of types used to represent the concept of sharding
4 : //! a Neon tenant across multiple physical shards. Since there are quite a few of these,
5 : //! we provide an summary here.
6 : //!
7 : //! Types used to describe shards:
8 : //! - [`ShardCount`] describes how many shards make up a tenant, plus the magic `unsharded` value
9 : //! which identifies a tenant which is not shard-aware. This means its storage paths do not include
10 : //! a shard suffix.
11 : //! - [`ShardNumber`] is simply the zero-based index of a shard within a tenant.
12 : //! - [`ShardIndex`] is the 2-tuple of `ShardCount` and `ShardNumber`, it's just like a `TenantShardId`
13 : //! without the tenant ID. This is useful for things that are implicitly scoped to a particular
14 : //! tenant, such as layer files.
15 : //! - [`ShardIdentity`]` is the full description of a particular shard's parameters, in sufficient
16 : //! detail to convert a [`Key`] to a [`ShardNumber`] when deciding where to write/read.
17 : //! - The [`ShardSlug`] is a terse formatter for ShardCount and ShardNumber, written as
18 : //! four hex digits. An unsharded tenant is `0000`.
19 : //! - [`TenantShardId`] is the unique ID of a particular shard within a particular tenant
20 : //!
21 : //! Types used to describe the parameters for data distribution in a sharded tenant:
22 : //! - [`ShardStripeSize`] controls how long contiguous runs of [`Key`]s (stripes) are when distributed across
23 : //! multiple shards. Its value is given in 8kiB pages.
24 : //! - [`ShardLayout`] describes the data distribution scheme, and at time of writing is
25 : //! always zero: this is provided for future upgrades that might introduce different
26 : //! data distribution schemes.
27 : //!
28 : //! Examples:
29 : //! - A legacy unsharded tenant has one shard with ShardCount(0), ShardNumber(0), and its slug is 0000
30 : //! - A single sharded tenant has one shard with ShardCount(1), ShardNumber(0), and its slug is 0001
31 : //! - In a tenant with 4 shards, each shard has ShardCount(N), ShardNumber(i) where i in 0..N-1 (inclusive),
32 : //! and their slugs are 0004, 0104, 0204, and 0304.
33 :
34 : use crate::{key::Key, models::ShardParameters};
35 : use postgres_ffi::relfile_utils::INIT_FORKNUM;
36 : use serde::{Deserialize, Serialize};
37 :
38 : #[doc(inline)]
39 : pub use ::utils::shard::*;
40 :
41 : /// The ShardIdentity contains enough information to map a [`Key`] to a [`ShardNumber`],
42 : /// and to check whether that [`ShardNumber`] is the same as the current shard.
43 0 : #[derive(Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Debug)]
44 : pub struct ShardIdentity {
45 : pub number: ShardNumber,
46 : pub count: ShardCount,
47 : pub stripe_size: ShardStripeSize,
48 : layout: ShardLayout,
49 : }
50 :
51 : /// Stripe size in number of pages
52 0 : #[derive(Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Debug)]
53 : pub struct ShardStripeSize(pub u32);
54 :
55 : impl Default for ShardStripeSize {
56 2 : fn default() -> Self {
57 2 : DEFAULT_STRIPE_SIZE
58 2 : }
59 : }
60 :
61 : /// Layout version: for future upgrades where we might change how the key->shard mapping works
62 0 : #[derive(Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Debug)]
63 : pub struct ShardLayout(u8);
64 :
65 : const LAYOUT_V1: ShardLayout = ShardLayout(1);
66 : /// ShardIdentity uses a magic layout value to indicate if it is unusable
67 : const LAYOUT_BROKEN: ShardLayout = ShardLayout(255);
68 :
69 : /// Default stripe size in pages: 256MiB divided by 8kiB page size.
70 : const DEFAULT_STRIPE_SIZE: ShardStripeSize = ShardStripeSize(256 * 1024 / 8);
71 :
72 : #[derive(thiserror::Error, Debug, PartialEq, Eq)]
73 : pub enum ShardConfigError {
74 : #[error("Invalid shard count")]
75 : InvalidCount,
76 : #[error("Invalid shard number")]
77 : InvalidNumber,
78 : #[error("Invalid stripe size")]
79 : InvalidStripeSize,
80 : }
81 :
82 : impl ShardIdentity {
83 : /// An identity with number=0 count=0 is a "none" identity, which represents legacy
84 : /// tenants. Modern single-shard tenants should not use this: they should
85 : /// have number=0 count=1.
86 910 : pub const fn unsharded() -> Self {
87 910 : Self {
88 910 : number: ShardNumber(0),
89 910 : count: ShardCount(0),
90 910 : layout: LAYOUT_V1,
91 910 : stripe_size: DEFAULT_STRIPE_SIZE,
92 910 : }
93 910 : }
94 :
95 : /// A broken instance of this type is only used for `TenantState::Broken` tenants,
96 : /// which are constructed in code paths that don't have access to proper configuration.
97 : ///
98 : /// A ShardIdentity in this state may not be used for anything, and should not be persisted.
99 : /// Enforcement is via assertions, to avoid making our interface fallible for this
100 : /// edge case: it is the Tenant's responsibility to avoid trying to do any I/O when in a broken
101 : /// state, and by extension to avoid trying to do any page->shard resolution.
102 0 : pub fn broken(number: ShardNumber, count: ShardCount) -> Self {
103 0 : Self {
104 0 : number,
105 0 : count,
106 0 : layout: LAYOUT_BROKEN,
107 0 : stripe_size: DEFAULT_STRIPE_SIZE,
108 0 : }
109 0 : }
110 :
111 : /// The "unsharded" value is distinct from simply having a single shard: it represents
112 : /// a tenant which is not shard-aware at all, and whose storage paths will not include
113 : /// a shard suffix.
114 0 : pub fn is_unsharded(&self) -> bool {
115 0 : self.number == ShardNumber(0) && self.count == ShardCount(0)
116 0 : }
117 :
118 : /// Count must be nonzero, and number must be < count. To construct
119 : /// the legacy case (count==0), use Self::unsharded instead.
120 13034 : pub fn new(
121 13034 : number: ShardNumber,
122 13034 : count: ShardCount,
123 13034 : stripe_size: ShardStripeSize,
124 13034 : ) -> Result<Self, ShardConfigError> {
125 13034 : if count.0 == 0 {
126 1 : Err(ShardConfigError::InvalidCount)
127 13033 : } else if number.0 > count.0 - 1 {
128 3 : Err(ShardConfigError::InvalidNumber)
129 13030 : } else if stripe_size.0 == 0 {
130 1 : Err(ShardConfigError::InvalidStripeSize)
131 : } else {
132 13029 : Ok(Self {
133 13029 : number,
134 13029 : count,
135 13029 : layout: LAYOUT_V1,
136 13029 : stripe_size,
137 13029 : })
138 : }
139 13034 : }
140 :
141 : /// For use when creating ShardIdentity instances for new shards, where a creation request
142 : /// specifies the ShardParameters that apply to all shards.
143 198 : pub fn from_params(number: ShardNumber, params: &ShardParameters) -> Self {
144 198 : Self {
145 198 : number,
146 198 : count: params.count,
147 198 : layout: LAYOUT_V1,
148 198 : stripe_size: params.stripe_size,
149 198 : }
150 198 : }
151 :
152 6440390 : fn is_broken(&self) -> bool {
153 6440390 : self.layout == LAYOUT_BROKEN
154 6440390 : }
155 :
156 3082 : pub fn get_shard_number(&self, key: &Key) -> ShardNumber {
157 3082 : assert!(!self.is_broken());
158 3082 : key_to_shard_number(self.count, self.stripe_size, key)
159 3082 : }
160 :
161 : /// Return true if the key is stored only on this shard. This does not include
162 : /// global keys, see is_key_global().
163 : ///
164 : /// Shards must ingest _at least_ keys which return true from this check.
165 6437308 : pub fn is_key_local(&self, key: &Key) -> bool {
166 6437308 : assert!(!self.is_broken());
167 6437308 : if self.count < ShardCount(2) || (key_is_shard0(key) && self.number == ShardNumber(0)) {
168 5387254 : true
169 : } else {
170 1050054 : key_to_shard_number(self.count, self.stripe_size, key) == self.number
171 : }
172 6437308 : }
173 :
174 : /// Return true if the key should be stored on all shards, not just one.
175 1050057 : pub fn is_key_global(&self, key: &Key) -> bool {
176 1050057 : if key.is_slru_block_key() || key.is_slru_segment_size_key() || key.is_aux_file_key() {
177 : // Special keys that are only stored on shard 0
178 0 : false
179 1050057 : } else if key.is_rel_block_key() {
180 : // Ordinary relation blocks are distributed across shards
181 1050052 : false
182 5 : } else if key.is_rel_size_key() {
183 : // All shards maintain rel size keys (although only shard 0 is responsible for
184 : // keeping it strictly accurate, other shards just reflect the highest block they've ingested)
185 5 : true
186 : } else {
187 : // For everything else, we assume it must be kept everywhere, because ingest code
188 : // might assume this -- this covers functionality where the ingest code has
189 : // not (yet) been made fully shard aware.
190 0 : true
191 : }
192 1050057 : }
193 :
194 : /// Return true if the key should be discarded if found in this shard's
195 : /// data store, e.g. during compaction after a split.
196 : ///
197 : /// Shards _may_ drop keys which return false here, but are not obliged to.
198 3745542 : pub fn is_key_disposable(&self, key: &Key) -> bool {
199 3745542 : if self.count < ShardCount(2) {
200 : // Fast path: unsharded tenant doesn't dispose of anything
201 2695485 : return false;
202 1050057 : }
203 1050057 :
204 1050057 : if self.is_key_global(key) {
205 5 : false
206 : } else {
207 1050052 : !self.is_key_local(key)
208 : }
209 3745542 : }
210 :
211 : /// Obtains the shard number and count combined into a `ShardIndex`.
212 282 : pub fn shard_index(&self) -> ShardIndex {
213 282 : ShardIndex {
214 282 : shard_count: self.count,
215 282 : shard_number: self.number,
216 282 : }
217 282 : }
218 :
219 8 : pub fn shard_slug(&self) -> String {
220 8 : if self.count > ShardCount(0) {
221 8 : format!("-{:02x}{:02x}", self.number.0, self.count.0)
222 : } else {
223 0 : String::new()
224 : }
225 8 : }
226 :
227 : /// Convenience for checking if this identity is the 0th shard in a tenant,
228 : /// for special cases on shard 0 such as ingesting relation sizes.
229 0 : pub fn is_shard_zero(&self) -> bool {
230 0 : self.number == ShardNumber(0)
231 0 : }
232 : }
233 :
234 : /// Whether this key is always held on shard 0 (e.g. shard 0 holds all SLRU keys
235 : /// in order to be able to serve basebackup requests without peer communication).
236 2100127 : fn key_is_shard0(key: &Key) -> bool {
237 2100127 : // To decide what to shard out to shards >0, we apply a simple rule that only
238 2100127 : // relation pages are distributed to shards other than shard zero. Everything else gets
239 2100127 : // stored on shard 0. This guarantees that shard 0 can independently serve basebackup
240 2100127 : // requests, and any request other than those for particular blocks in relations.
241 2100127 : //
242 2100127 : // The only exception to this rule is "initfork" data -- this relates to postgres's UNLOGGED table
243 2100127 : // type. These are special relations, usually with only 0 or 1 blocks, and we store them on shard 0
244 2100127 : // because they must be included in basebackups.
245 2100127 : let is_initfork = key.field5 == INIT_FORKNUM;
246 2100127 :
247 2100127 : !key.is_rel_block_key() || is_initfork
248 2100127 : }
249 :
250 : /// Provide the same result as the function in postgres `hashfn.h` with the same name
251 2100147 : fn murmurhash32(mut h: u32) -> u32 {
252 2100147 : h ^= h >> 16;
253 2100147 : h = h.wrapping_mul(0x85ebca6b);
254 2100147 : h ^= h >> 13;
255 2100147 : h = h.wrapping_mul(0xc2b2ae35);
256 2100147 : h ^= h >> 16;
257 2100147 : h
258 2100147 : }
259 :
260 : /// Provide the same result as the function in postgres `hashfn.h` with the same name
261 1050074 : fn hash_combine(mut a: u32, mut b: u32) -> u32 {
262 1050074 : b = b.wrapping_add(0x9e3779b9);
263 1050074 : b = b.wrapping_add(a << 6);
264 1050074 : b = b.wrapping_add(a >> 2);
265 1050074 :
266 1050074 : a ^= b;
267 1050074 : a
268 1050074 : }
269 :
270 : /// Where a Key is to be distributed across shards, select the shard. This function
271 : /// does not account for keys that should be broadcast across shards.
272 : ///
273 : /// The hashing in this function must exactly match what we do in postgres smgr
274 : /// code. The resulting distribution of pages is intended to preserve locality within
275 : /// `stripe_size` ranges of contiguous block numbers in the same relation, while otherwise
276 : /// distributing data pseudo-randomly.
277 : ///
278 : /// The mapping of key to shard is not stable across changes to ShardCount: this is intentional
279 : /// and will be handled at higher levels when shards are split.
280 1053137 : fn key_to_shard_number(count: ShardCount, stripe_size: ShardStripeSize, key: &Key) -> ShardNumber {
281 1053137 : // Fast path for un-sharded tenants or broadcast keys
282 1053137 : if count < ShardCount(2) || key_is_shard0(key) {
283 3064 : return ShardNumber(0);
284 1050073 : }
285 1050073 :
286 1050073 : // relNode
287 1050073 : let mut hash = murmurhash32(key.field4);
288 1050073 : // blockNum/stripe size
289 1050073 : hash = hash_combine(hash, murmurhash32(key.field6 / stripe_size.0));
290 1050073 :
291 1050073 : ShardNumber((hash % count.0 as u32) as u8)
292 1053137 : }
293 :
294 : /// For debugging, while not exposing the internals.
295 : #[derive(Debug)]
296 : #[allow(unused)] // used by debug formatting by pagectl
297 : struct KeyShardingInfo {
298 : shard0: bool,
299 : shard_number: ShardNumber,
300 : }
301 :
302 0 : pub fn describe(
303 0 : key: &Key,
304 0 : shard_count: ShardCount,
305 0 : stripe_size: ShardStripeSize,
306 0 : ) -> impl std::fmt::Debug {
307 0 : KeyShardingInfo {
308 0 : shard0: key_is_shard0(key),
309 0 : shard_number: key_to_shard_number(shard_count, stripe_size, key),
310 0 : }
311 0 : }
312 :
313 : #[cfg(test)]
314 : mod tests {
315 : use std::str::FromStr;
316 :
317 : use utils::{id::TenantId, Hex};
318 :
319 : use super::*;
320 :
321 : const EXAMPLE_TENANT_ID: &str = "1f359dd625e519a1a4e8d7509690f6fc";
322 :
323 : #[test]
324 1 : fn tenant_shard_id_string() -> Result<(), hex::FromHexError> {
325 1 : let example = TenantShardId {
326 1 : tenant_id: TenantId::from_str(EXAMPLE_TENANT_ID).unwrap(),
327 1 : shard_count: ShardCount(10),
328 1 : shard_number: ShardNumber(7),
329 1 : };
330 1 :
331 1 : let encoded = format!("{example}");
332 1 :
333 1 : let expected = format!("{EXAMPLE_TENANT_ID}-070a");
334 1 : assert_eq!(&encoded, &expected);
335 :
336 1 : let decoded = TenantShardId::from_str(&encoded)?;
337 :
338 1 : assert_eq!(example, decoded);
339 :
340 1 : Ok(())
341 1 : }
342 :
343 : #[test]
344 1 : fn tenant_shard_id_binary() -> Result<(), hex::FromHexError> {
345 1 : let example = TenantShardId {
346 1 : tenant_id: TenantId::from_str(EXAMPLE_TENANT_ID).unwrap(),
347 1 : shard_count: ShardCount(10),
348 1 : shard_number: ShardNumber(7),
349 1 : };
350 1 :
351 1 : let encoded = bincode::serialize(&example).unwrap();
352 1 : let expected: [u8; 18] = [
353 1 : 0x1f, 0x35, 0x9d, 0xd6, 0x25, 0xe5, 0x19, 0xa1, 0xa4, 0xe8, 0xd7, 0x50, 0x96, 0x90,
354 1 : 0xf6, 0xfc, 0x07, 0x0a,
355 1 : ];
356 1 : assert_eq!(Hex(&encoded), Hex(&expected));
357 :
358 1 : let decoded = bincode::deserialize(&encoded).unwrap();
359 1 :
360 1 : assert_eq!(example, decoded);
361 :
362 1 : Ok(())
363 1 : }
364 :
365 : #[test]
366 1 : fn tenant_shard_id_backward_compat() -> Result<(), hex::FromHexError> {
367 1 : // Test that TenantShardId can decode a TenantId in human
368 1 : // readable form
369 1 : let example = TenantId::from_str(EXAMPLE_TENANT_ID).unwrap();
370 1 : let encoded = format!("{example}");
371 1 :
372 1 : assert_eq!(&encoded, EXAMPLE_TENANT_ID);
373 :
374 1 : let decoded = TenantShardId::from_str(&encoded)?;
375 :
376 1 : assert_eq!(example, decoded.tenant_id);
377 1 : assert_eq!(decoded.shard_count, ShardCount(0));
378 1 : assert_eq!(decoded.shard_number, ShardNumber(0));
379 :
380 1 : Ok(())
381 1 : }
382 :
383 : #[test]
384 1 : fn tenant_shard_id_forward_compat() -> Result<(), hex::FromHexError> {
385 1 : // Test that a legacy TenantShardId encodes into a form that
386 1 : // can be decoded as TenantId
387 1 : let example_tenant_id = TenantId::from_str(EXAMPLE_TENANT_ID).unwrap();
388 1 : let example = TenantShardId::unsharded(example_tenant_id);
389 1 : let encoded = format!("{example}");
390 1 :
391 1 : assert_eq!(&encoded, EXAMPLE_TENANT_ID);
392 :
393 1 : let decoded = TenantId::from_str(&encoded)?;
394 :
395 1 : assert_eq!(example_tenant_id, decoded);
396 :
397 1 : Ok(())
398 1 : }
399 :
400 : #[test]
401 1 : fn tenant_shard_id_legacy_binary() -> Result<(), hex::FromHexError> {
402 1 : // Unlike in human readable encoding, binary encoding does not
403 1 : // do any special handling of legacy unsharded TenantIds: this test
404 1 : // is equivalent to the main test for binary encoding, just verifying
405 1 : // that the same behavior applies when we have used `unsharded()` to
406 1 : // construct a TenantShardId.
407 1 : let example = TenantShardId::unsharded(TenantId::from_str(EXAMPLE_TENANT_ID).unwrap());
408 1 : let encoded = bincode::serialize(&example).unwrap();
409 1 :
410 1 : let expected: [u8; 18] = [
411 1 : 0x1f, 0x35, 0x9d, 0xd6, 0x25, 0xe5, 0x19, 0xa1, 0xa4, 0xe8, 0xd7, 0x50, 0x96, 0x90,
412 1 : 0xf6, 0xfc, 0x00, 0x00,
413 1 : ];
414 1 : assert_eq!(Hex(&encoded), Hex(&expected));
415 :
416 1 : let decoded = bincode::deserialize::<TenantShardId>(&encoded).unwrap();
417 1 : assert_eq!(example, decoded);
418 :
419 1 : Ok(())
420 1 : }
421 :
422 : #[test]
423 1 : fn shard_identity_validation() -> Result<(), ShardConfigError> {
424 1 : // Happy cases
425 1 : ShardIdentity::new(ShardNumber(0), ShardCount(1), DEFAULT_STRIPE_SIZE)?;
426 1 : ShardIdentity::new(ShardNumber(0), ShardCount(1), ShardStripeSize(1))?;
427 1 : ShardIdentity::new(ShardNumber(254), ShardCount(255), ShardStripeSize(1))?;
428 :
429 1 : assert_eq!(
430 1 : ShardIdentity::new(ShardNumber(0), ShardCount(0), DEFAULT_STRIPE_SIZE),
431 1 : Err(ShardConfigError::InvalidCount)
432 1 : );
433 1 : assert_eq!(
434 1 : ShardIdentity::new(ShardNumber(10), ShardCount(10), DEFAULT_STRIPE_SIZE),
435 1 : Err(ShardConfigError::InvalidNumber)
436 1 : );
437 1 : assert_eq!(
438 1 : ShardIdentity::new(ShardNumber(11), ShardCount(10), DEFAULT_STRIPE_SIZE),
439 1 : Err(ShardConfigError::InvalidNumber)
440 1 : );
441 1 : assert_eq!(
442 1 : ShardIdentity::new(ShardNumber(255), ShardCount(255), DEFAULT_STRIPE_SIZE),
443 1 : Err(ShardConfigError::InvalidNumber)
444 1 : );
445 1 : assert_eq!(
446 1 : ShardIdentity::new(ShardNumber(0), ShardCount(1), ShardStripeSize(0)),
447 1 : Err(ShardConfigError::InvalidStripeSize)
448 1 : );
449 :
450 1 : Ok(())
451 1 : }
452 :
453 : #[test]
454 1 : fn shard_index_human_encoding() -> Result<(), hex::FromHexError> {
455 1 : let example = ShardIndex {
456 1 : shard_number: ShardNumber(13),
457 1 : shard_count: ShardCount(17),
458 1 : };
459 1 : let expected: String = "0d11".to_string();
460 1 : let encoded = format!("{example}");
461 1 : assert_eq!(&encoded, &expected);
462 :
463 1 : let decoded = ShardIndex::from_str(&encoded)?;
464 1 : assert_eq!(example, decoded);
465 1 : Ok(())
466 1 : }
467 :
468 : #[test]
469 1 : fn shard_index_binary_encoding() -> Result<(), hex::FromHexError> {
470 1 : let example = ShardIndex {
471 1 : shard_number: ShardNumber(13),
472 1 : shard_count: ShardCount(17),
473 1 : };
474 1 : let expected: [u8; 2] = [0x0d, 0x11];
475 1 :
476 1 : let encoded = bincode::serialize(&example).unwrap();
477 1 : assert_eq!(Hex(&encoded), Hex(&expected));
478 1 : let decoded = bincode::deserialize(&encoded).unwrap();
479 1 : assert_eq!(example, decoded);
480 :
481 1 : Ok(())
482 1 : }
483 :
484 : // These are only smoke tests to spot check that our implementation doesn't
485 : // deviate from a few examples values: not aiming to validate the overall
486 : // hashing algorithm.
487 : #[test]
488 1 : fn murmur_hash() {
489 1 : assert_eq!(murmurhash32(0), 0);
490 :
491 1 : assert_eq!(hash_combine(0xb1ff3b40, 0), 0xfb7923c9);
492 1 : }
493 :
494 : #[test]
495 1 : fn shard_mapping() {
496 1 : let key = Key {
497 1 : field1: 0x00,
498 1 : field2: 0x67f,
499 1 : field3: 0x5,
500 1 : field4: 0x400c,
501 1 : field5: 0x00,
502 1 : field6: 0x7d06,
503 1 : };
504 1 :
505 1 : let shard = key_to_shard_number(ShardCount(10), DEFAULT_STRIPE_SIZE, &key);
506 1 : assert_eq!(shard, ShardNumber(8));
507 1 : }
508 :
509 : #[test]
510 1 : fn shard_id_split() {
511 1 : let tenant_id = TenantId::generate();
512 1 : let parent = TenantShardId::unsharded(tenant_id);
513 1 :
514 1 : // Unsharded into 2
515 1 : assert_eq!(
516 1 : parent.split(ShardCount(2)),
517 1 : vec![
518 1 : TenantShardId {
519 1 : tenant_id,
520 1 : shard_count: ShardCount(2),
521 1 : shard_number: ShardNumber(0)
522 1 : },
523 1 : TenantShardId {
524 1 : tenant_id,
525 1 : shard_count: ShardCount(2),
526 1 : shard_number: ShardNumber(1)
527 1 : }
528 1 : ]
529 1 : );
530 :
531 : // Unsharded into 4
532 1 : assert_eq!(
533 1 : parent.split(ShardCount(4)),
534 1 : vec![
535 1 : TenantShardId {
536 1 : tenant_id,
537 1 : shard_count: ShardCount(4),
538 1 : shard_number: ShardNumber(0)
539 1 : },
540 1 : TenantShardId {
541 1 : tenant_id,
542 1 : shard_count: ShardCount(4),
543 1 : shard_number: ShardNumber(1)
544 1 : },
545 1 : TenantShardId {
546 1 : tenant_id,
547 1 : shard_count: ShardCount(4),
548 1 : shard_number: ShardNumber(2)
549 1 : },
550 1 : TenantShardId {
551 1 : tenant_id,
552 1 : shard_count: ShardCount(4),
553 1 : shard_number: ShardNumber(3)
554 1 : }
555 1 : ]
556 1 : );
557 :
558 : // count=1 into 2 (check this works the same as unsharded.)
559 1 : let parent = TenantShardId {
560 1 : tenant_id,
561 1 : shard_count: ShardCount(1),
562 1 : shard_number: ShardNumber(0),
563 1 : };
564 1 : assert_eq!(
565 1 : parent.split(ShardCount(2)),
566 1 : vec![
567 1 : TenantShardId {
568 1 : tenant_id,
569 1 : shard_count: ShardCount(2),
570 1 : shard_number: ShardNumber(0)
571 1 : },
572 1 : TenantShardId {
573 1 : tenant_id,
574 1 : shard_count: ShardCount(2),
575 1 : shard_number: ShardNumber(1)
576 1 : }
577 1 : ]
578 1 : );
579 :
580 : // count=2 into count=8
581 1 : let parent = TenantShardId {
582 1 : tenant_id,
583 1 : shard_count: ShardCount(2),
584 1 : shard_number: ShardNumber(1),
585 1 : };
586 1 : assert_eq!(
587 1 : parent.split(ShardCount(8)),
588 1 : vec![
589 1 : TenantShardId {
590 1 : tenant_id,
591 1 : shard_count: ShardCount(8),
592 1 : shard_number: ShardNumber(1)
593 1 : },
594 1 : TenantShardId {
595 1 : tenant_id,
596 1 : shard_count: ShardCount(8),
597 1 : shard_number: ShardNumber(3)
598 1 : },
599 1 : TenantShardId {
600 1 : tenant_id,
601 1 : shard_count: ShardCount(8),
602 1 : shard_number: ShardNumber(5)
603 1 : },
604 1 : TenantShardId {
605 1 : tenant_id,
606 1 : shard_count: ShardCount(8),
607 1 : shard_number: ShardNumber(7)
608 1 : },
609 1 : ]
610 1 : );
611 1 : }
612 : }
|