LCOV - code coverage report
Current view: top level - libs/pageserver_api/src - shard.rs (source / functions) Coverage Total Hit
Test: b4ae4c4857f9ef3e144e982a35ee23bc84c71983.info Lines: 92.3 % 377 348
Test Date: 2024-10-22 22:13:45 Functions: 44.6 % 56 25

            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            0 : #[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          896 :     pub const fn unsharded() -> Self {
      87          896 :         Self {
      88          896 :             number: ShardNumber(0),
      89          896 :             count: ShardCount(0),
      90          896 :             layout: LAYOUT_V1,
      91          896 :             stripe_size: DEFAULT_STRIPE_SIZE,
      92          896 :         }
      93          896 :     }
      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        13028 :     pub fn new(
     121        13028 :         number: ShardNumber,
     122        13028 :         count: ShardCount,
     123        13028 :         stripe_size: ShardStripeSize,
     124        13028 :     ) -> Result<Self, ShardConfigError> {
     125        13028 :         if count.0 == 0 {
     126            1 :             Err(ShardConfigError::InvalidCount)
     127        13027 :         } else if number.0 > count.0 - 1 {
     128            3 :             Err(ShardConfigError::InvalidNumber)
     129        13024 :         } else if stripe_size.0 == 0 {
     130            1 :             Err(ShardConfigError::InvalidStripeSize)
     131              :         } else {
     132        13023 :             Ok(Self {
     133        13023 :                 number,
     134        13023 :                 count,
     135        13023 :                 layout: LAYOUT_V1,
     136        13023 :                 stripe_size,
     137        13023 :             })
     138              :         }
     139        13028 :     }
     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          190 :     pub fn from_params(number: ShardNumber, params: &ShardParameters) -> Self {
     144          190 :         Self {
     145          190 :             number,
     146          190 :             count: params.count,
     147          190 :             layout: LAYOUT_V1,
     148          190 :             stripe_size: params.stripe_size,
     149          190 :         }
     150          190 :     }
     151              : 
     152      1223865 :     fn is_broken(&self) -> bool {
     153      1223865 :         self.layout == LAYOUT_BROKEN
     154      1223865 :     }
     155              : 
     156         3046 :     pub fn get_shard_number(&self, key: &Key) -> ShardNumber {
     157         3046 :         assert!(!self.is_broken());
     158         3046 :         key_to_shard_number(self.count, self.stripe_size, key)
     159         3046 :     }
     160              : 
     161              :     /// Return true if the key should be ingested by this shard
     162              :     ///
     163              :     /// Shards must ingest _at least_ keys which return true from this check.
     164      1220819 :     pub fn is_key_local(&self, key: &Key) -> bool {
     165      1220819 :         assert!(!self.is_broken());
     166      1220819 :         if self.count < ShardCount(2) || (key_is_shard0(key) && self.number == ShardNumber(0)) {
     167       170765 :             true
     168              :         } else {
     169      1050054 :             key_to_shard_number(self.count, self.stripe_size, key) == self.number
     170              :         }
     171      1220819 :     }
     172              : 
     173              :     /// Return true if the key should be discarded if found in this shard's
     174              :     /// data store, e.g. during compaction after a split.
     175              :     ///
     176              :     /// Shards _may_ drop keys which return false here, but are not obliged to.
     177      3744676 :     pub fn is_key_disposable(&self, key: &Key) -> bool {
     178      3744676 :         if key_is_shard0(key) {
     179              :             // Q: Why can't we dispose of shard0 content if we're not shard 0?
     180              :             // A1: because the WAL ingestion logic currently ingests some shard 0
     181              :             //     content on all shards, even though it's only read on shard 0.  If we
     182              :             //     dropped it, then subsequent WAL ingest to these keys would encounter
     183              :             //     an error.
     184              :             // A2: because key_is_shard0 also covers relation size keys, which are written
     185              :             //     on all shards even though they're only maintained accurately on shard 0.
     186      2674971 :             false
     187              :         } else {
     188      1069705 :             !self.is_key_local(key)
     189              :         }
     190      3744676 :     }
     191              : 
     192              :     /// Obtains the shard number and count combined into a `ShardIndex`.
     193          276 :     pub fn shard_index(&self) -> ShardIndex {
     194          276 :         ShardIndex {
     195          276 :             shard_count: self.count,
     196          276 :             shard_number: self.number,
     197          276 :         }
     198          276 :     }
     199              : 
     200            8 :     pub fn shard_slug(&self) -> String {
     201            8 :         if self.count > ShardCount(0) {
     202            8 :             format!("-{:02x}{:02x}", self.number.0, self.count.0)
     203              :         } else {
     204            0 :             String::new()
     205              :         }
     206            8 :     }
     207              : 
     208              :     /// Convenience for checking if this identity is the 0th shard in a tenant,
     209              :     /// for special cases on shard 0 such as ingesting relation sizes.
     210            0 :     pub fn is_shard_zero(&self) -> bool {
     211            0 :         self.number == ShardNumber(0)
     212            0 :     }
     213              : }
     214              : 
     215              : /// Whether this key is always held on shard 0 (e.g. shard 0 holds all SLRU keys
     216              : /// in order to be able to serve basebackup requests without peer communication).
     217      5844787 : fn key_is_shard0(key: &Key) -> bool {
     218      5844787 :     // To decide what to shard out to shards >0, we apply a simple rule that only
     219      5844787 :     // relation pages are distributed to shards other than shard zero. Everything else gets
     220      5844787 :     // stored on shard 0.  This guarantees that shard 0 can independently serve basebackup
     221      5844787 :     // requests, and any request other than those for particular blocks in relations.
     222      5844787 :     //
     223      5844787 :     // The only exception to this rule is "initfork" data -- this relates to postgres's UNLOGGED table
     224      5844787 :     // type. These are special relations, usually with only 0 or 1 blocks, and we store them on shard 0
     225      5844787 :     // because they must be included in basebackups.
     226      5844787 :     let is_initfork = key.field5 == INIT_FORKNUM;
     227      5844787 : 
     228      5844787 :     !key.is_rel_block_key() || is_initfork
     229      5844787 : }
     230              : 
     231              : /// Provide the same result as the function in postgres `hashfn.h` with the same name
     232      2100115 : fn murmurhash32(mut h: u32) -> u32 {
     233      2100115 :     h ^= h >> 16;
     234      2100115 :     h = h.wrapping_mul(0x85ebca6b);
     235      2100115 :     h ^= h >> 13;
     236      2100115 :     h = h.wrapping_mul(0xc2b2ae35);
     237      2100115 :     h ^= h >> 16;
     238      2100115 :     h
     239      2100115 : }
     240              : 
     241              : /// Provide the same result as the function in postgres `hashfn.h` with the same name
     242      1050058 : fn hash_combine(mut a: u32, mut b: u32) -> u32 {
     243      1050058 :     b = b.wrapping_add(0x9e3779b9);
     244      1050058 :     b = b.wrapping_add(a << 6);
     245      1050058 :     b = b.wrapping_add(a >> 2);
     246      1050058 : 
     247      1050058 :     a ^= b;
     248      1050058 :     a
     249      1050058 : }
     250              : 
     251              : /// Where a Key is to be distributed across shards, select the shard.  This function
     252              : /// does not account for keys that should be broadcast across shards.
     253              : ///
     254              : /// The hashing in this function must exactly match what we do in postgres smgr
     255              : /// code.  The resulting distribution of pages is intended to preserve locality within
     256              : /// `stripe_size` ranges of contiguous block numbers in the same relation, while otherwise
     257              : /// distributing data pseudo-randomly.
     258              : ///
     259              : /// The mapping of key to shard is not stable across changes to ShardCount: this is intentional
     260              : /// and will be handled at higher levels when shards are split.
     261      1053101 : fn key_to_shard_number(count: ShardCount, stripe_size: ShardStripeSize, key: &Key) -> ShardNumber {
     262      1053101 :     // Fast path for un-sharded tenants or broadcast keys
     263      1053101 :     if count < ShardCount(2) || key_is_shard0(key) {
     264         3044 :         return ShardNumber(0);
     265      1050057 :     }
     266      1050057 : 
     267      1050057 :     // relNode
     268      1050057 :     let mut hash = murmurhash32(key.field4);
     269      1050057 :     // blockNum/stripe size
     270      1050057 :     hash = hash_combine(hash, murmurhash32(key.field6 / stripe_size.0));
     271      1050057 : 
     272      1050057 :     ShardNumber((hash % count.0 as u32) as u8)
     273      1053101 : }
     274              : 
     275              : /// For debugging, while not exposing the internals.
     276              : #[derive(Debug)]
     277              : #[allow(unused)] // used by debug formatting by pagectl
     278              : struct KeyShardingInfo {
     279              :     shard0: bool,
     280              :     shard_number: ShardNumber,
     281              : }
     282              : 
     283            0 : pub fn describe(
     284            0 :     key: &Key,
     285            0 :     shard_count: ShardCount,
     286            0 :     stripe_size: ShardStripeSize,
     287            0 : ) -> impl std::fmt::Debug {
     288            0 :     KeyShardingInfo {
     289            0 :         shard0: key_is_shard0(key),
     290            0 :         shard_number: key_to_shard_number(shard_count, stripe_size, key),
     291            0 :     }
     292            0 : }
     293              : 
     294              : #[cfg(test)]
     295              : mod tests {
     296              :     use std::str::FromStr;
     297              : 
     298              :     use utils::{id::TenantId, Hex};
     299              : 
     300              :     use super::*;
     301              : 
     302              :     const EXAMPLE_TENANT_ID: &str = "1f359dd625e519a1a4e8d7509690f6fc";
     303              : 
     304              :     #[test]
     305            1 :     fn tenant_shard_id_string() -> Result<(), hex::FromHexError> {
     306            1 :         let example = TenantShardId {
     307            1 :             tenant_id: TenantId::from_str(EXAMPLE_TENANT_ID).unwrap(),
     308            1 :             shard_count: ShardCount(10),
     309            1 :             shard_number: ShardNumber(7),
     310            1 :         };
     311            1 : 
     312            1 :         let encoded = format!("{example}");
     313            1 : 
     314            1 :         let expected = format!("{EXAMPLE_TENANT_ID}-070a");
     315            1 :         assert_eq!(&encoded, &expected);
     316              : 
     317            1 :         let decoded = TenantShardId::from_str(&encoded)?;
     318              : 
     319            1 :         assert_eq!(example, decoded);
     320              : 
     321            1 :         Ok(())
     322            1 :     }
     323              : 
     324              :     #[test]
     325            1 :     fn tenant_shard_id_binary() -> Result<(), hex::FromHexError> {
     326            1 :         let example = TenantShardId {
     327            1 :             tenant_id: TenantId::from_str(EXAMPLE_TENANT_ID).unwrap(),
     328            1 :             shard_count: ShardCount(10),
     329            1 :             shard_number: ShardNumber(7),
     330            1 :         };
     331            1 : 
     332            1 :         let encoded = bincode::serialize(&example).unwrap();
     333            1 :         let expected: [u8; 18] = [
     334            1 :             0x1f, 0x35, 0x9d, 0xd6, 0x25, 0xe5, 0x19, 0xa1, 0xa4, 0xe8, 0xd7, 0x50, 0x96, 0x90,
     335            1 :             0xf6, 0xfc, 0x07, 0x0a,
     336            1 :         ];
     337            1 :         assert_eq!(Hex(&encoded), Hex(&expected));
     338              : 
     339            1 :         let decoded = bincode::deserialize(&encoded).unwrap();
     340            1 : 
     341            1 :         assert_eq!(example, decoded);
     342              : 
     343            1 :         Ok(())
     344            1 :     }
     345              : 
     346              :     #[test]
     347            1 :     fn tenant_shard_id_backward_compat() -> Result<(), hex::FromHexError> {
     348            1 :         // Test that TenantShardId can decode a TenantId in human
     349            1 :         // readable form
     350            1 :         let example = TenantId::from_str(EXAMPLE_TENANT_ID).unwrap();
     351            1 :         let encoded = format!("{example}");
     352            1 : 
     353            1 :         assert_eq!(&encoded, EXAMPLE_TENANT_ID);
     354              : 
     355            1 :         let decoded = TenantShardId::from_str(&encoded)?;
     356              : 
     357            1 :         assert_eq!(example, decoded.tenant_id);
     358            1 :         assert_eq!(decoded.shard_count, ShardCount(0));
     359            1 :         assert_eq!(decoded.shard_number, ShardNumber(0));
     360              : 
     361            1 :         Ok(())
     362            1 :     }
     363              : 
     364              :     #[test]
     365            1 :     fn tenant_shard_id_forward_compat() -> Result<(), hex::FromHexError> {
     366            1 :         // Test that a legacy TenantShardId encodes into a form that
     367            1 :         // can be decoded as TenantId
     368            1 :         let example_tenant_id = TenantId::from_str(EXAMPLE_TENANT_ID).unwrap();
     369            1 :         let example = TenantShardId::unsharded(example_tenant_id);
     370            1 :         let encoded = format!("{example}");
     371            1 : 
     372            1 :         assert_eq!(&encoded, EXAMPLE_TENANT_ID);
     373              : 
     374            1 :         let decoded = TenantId::from_str(&encoded)?;
     375              : 
     376            1 :         assert_eq!(example_tenant_id, decoded);
     377              : 
     378            1 :         Ok(())
     379            1 :     }
     380              : 
     381              :     #[test]
     382            1 :     fn tenant_shard_id_legacy_binary() -> Result<(), hex::FromHexError> {
     383            1 :         // Unlike in human readable encoding, binary encoding does not
     384            1 :         // do any special handling of legacy unsharded TenantIds: this test
     385            1 :         // is equivalent to the main test for binary encoding, just verifying
     386            1 :         // that the same behavior applies when we have used `unsharded()` to
     387            1 :         // construct a TenantShardId.
     388            1 :         let example = TenantShardId::unsharded(TenantId::from_str(EXAMPLE_TENANT_ID).unwrap());
     389            1 :         let encoded = bincode::serialize(&example).unwrap();
     390            1 : 
     391            1 :         let expected: [u8; 18] = [
     392            1 :             0x1f, 0x35, 0x9d, 0xd6, 0x25, 0xe5, 0x19, 0xa1, 0xa4, 0xe8, 0xd7, 0x50, 0x96, 0x90,
     393            1 :             0xf6, 0xfc, 0x00, 0x00,
     394            1 :         ];
     395            1 :         assert_eq!(Hex(&encoded), Hex(&expected));
     396              : 
     397            1 :         let decoded = bincode::deserialize::<TenantShardId>(&encoded).unwrap();
     398            1 :         assert_eq!(example, decoded);
     399              : 
     400            1 :         Ok(())
     401            1 :     }
     402              : 
     403              :     #[test]
     404            1 :     fn shard_identity_validation() -> Result<(), ShardConfigError> {
     405            1 :         // Happy cases
     406            1 :         ShardIdentity::new(ShardNumber(0), ShardCount(1), DEFAULT_STRIPE_SIZE)?;
     407            1 :         ShardIdentity::new(ShardNumber(0), ShardCount(1), ShardStripeSize(1))?;
     408            1 :         ShardIdentity::new(ShardNumber(254), ShardCount(255), ShardStripeSize(1))?;
     409              : 
     410            1 :         assert_eq!(
     411            1 :             ShardIdentity::new(ShardNumber(0), ShardCount(0), DEFAULT_STRIPE_SIZE),
     412            1 :             Err(ShardConfigError::InvalidCount)
     413            1 :         );
     414            1 :         assert_eq!(
     415            1 :             ShardIdentity::new(ShardNumber(10), ShardCount(10), DEFAULT_STRIPE_SIZE),
     416            1 :             Err(ShardConfigError::InvalidNumber)
     417            1 :         );
     418            1 :         assert_eq!(
     419            1 :             ShardIdentity::new(ShardNumber(11), ShardCount(10), DEFAULT_STRIPE_SIZE),
     420            1 :             Err(ShardConfigError::InvalidNumber)
     421            1 :         );
     422            1 :         assert_eq!(
     423            1 :             ShardIdentity::new(ShardNumber(255), ShardCount(255), DEFAULT_STRIPE_SIZE),
     424            1 :             Err(ShardConfigError::InvalidNumber)
     425            1 :         );
     426            1 :         assert_eq!(
     427            1 :             ShardIdentity::new(ShardNumber(0), ShardCount(1), ShardStripeSize(0)),
     428            1 :             Err(ShardConfigError::InvalidStripeSize)
     429            1 :         );
     430              : 
     431            1 :         Ok(())
     432            1 :     }
     433              : 
     434              :     #[test]
     435            1 :     fn shard_index_human_encoding() -> Result<(), hex::FromHexError> {
     436            1 :         let example = ShardIndex {
     437            1 :             shard_number: ShardNumber(13),
     438            1 :             shard_count: ShardCount(17),
     439            1 :         };
     440            1 :         let expected: String = "0d11".to_string();
     441            1 :         let encoded = format!("{example}");
     442            1 :         assert_eq!(&encoded, &expected);
     443              : 
     444            1 :         let decoded = ShardIndex::from_str(&encoded)?;
     445            1 :         assert_eq!(example, decoded);
     446            1 :         Ok(())
     447            1 :     }
     448              : 
     449              :     #[test]
     450            1 :     fn shard_index_binary_encoding() -> Result<(), hex::FromHexError> {
     451            1 :         let example = ShardIndex {
     452            1 :             shard_number: ShardNumber(13),
     453            1 :             shard_count: ShardCount(17),
     454            1 :         };
     455            1 :         let expected: [u8; 2] = [0x0d, 0x11];
     456            1 : 
     457            1 :         let encoded = bincode::serialize(&example).unwrap();
     458            1 :         assert_eq!(Hex(&encoded), Hex(&expected));
     459            1 :         let decoded = bincode::deserialize(&encoded).unwrap();
     460            1 :         assert_eq!(example, decoded);
     461              : 
     462            1 :         Ok(())
     463            1 :     }
     464              : 
     465              :     // These are only smoke tests to spot check that our implementation doesn't
     466              :     // deviate from a few examples values: not aiming to validate the overall
     467              :     // hashing algorithm.
     468              :     #[test]
     469            1 :     fn murmur_hash() {
     470            1 :         assert_eq!(murmurhash32(0), 0);
     471              : 
     472            1 :         assert_eq!(hash_combine(0xb1ff3b40, 0), 0xfb7923c9);
     473            1 :     }
     474              : 
     475              :     #[test]
     476            1 :     fn shard_mapping() {
     477            1 :         let key = Key {
     478            1 :             field1: 0x00,
     479            1 :             field2: 0x67f,
     480            1 :             field3: 0x5,
     481            1 :             field4: 0x400c,
     482            1 :             field5: 0x00,
     483            1 :             field6: 0x7d06,
     484            1 :         };
     485            1 : 
     486            1 :         let shard = key_to_shard_number(ShardCount(10), DEFAULT_STRIPE_SIZE, &key);
     487            1 :         assert_eq!(shard, ShardNumber(8));
     488            1 :     }
     489              : 
     490              :     #[test]
     491            1 :     fn shard_id_split() {
     492            1 :         let tenant_id = TenantId::generate();
     493            1 :         let parent = TenantShardId::unsharded(tenant_id);
     494            1 : 
     495            1 :         // Unsharded into 2
     496            1 :         assert_eq!(
     497            1 :             parent.split(ShardCount(2)),
     498            1 :             vec![
     499            1 :                 TenantShardId {
     500            1 :                     tenant_id,
     501            1 :                     shard_count: ShardCount(2),
     502            1 :                     shard_number: ShardNumber(0)
     503            1 :                 },
     504            1 :                 TenantShardId {
     505            1 :                     tenant_id,
     506            1 :                     shard_count: ShardCount(2),
     507            1 :                     shard_number: ShardNumber(1)
     508            1 :                 }
     509            1 :             ]
     510            1 :         );
     511              : 
     512              :         // Unsharded into 4
     513            1 :         assert_eq!(
     514            1 :             parent.split(ShardCount(4)),
     515            1 :             vec![
     516            1 :                 TenantShardId {
     517            1 :                     tenant_id,
     518            1 :                     shard_count: ShardCount(4),
     519            1 :                     shard_number: ShardNumber(0)
     520            1 :                 },
     521            1 :                 TenantShardId {
     522            1 :                     tenant_id,
     523            1 :                     shard_count: ShardCount(4),
     524            1 :                     shard_number: ShardNumber(1)
     525            1 :                 },
     526            1 :                 TenantShardId {
     527            1 :                     tenant_id,
     528            1 :                     shard_count: ShardCount(4),
     529            1 :                     shard_number: ShardNumber(2)
     530            1 :                 },
     531            1 :                 TenantShardId {
     532            1 :                     tenant_id,
     533            1 :                     shard_count: ShardCount(4),
     534            1 :                     shard_number: ShardNumber(3)
     535            1 :                 }
     536            1 :             ]
     537            1 :         );
     538              : 
     539              :         // count=1 into 2 (check this works the same as unsharded.)
     540            1 :         let parent = TenantShardId {
     541            1 :             tenant_id,
     542            1 :             shard_count: ShardCount(1),
     543            1 :             shard_number: ShardNumber(0),
     544            1 :         };
     545            1 :         assert_eq!(
     546            1 :             parent.split(ShardCount(2)),
     547            1 :             vec![
     548            1 :                 TenantShardId {
     549            1 :                     tenant_id,
     550            1 :                     shard_count: ShardCount(2),
     551            1 :                     shard_number: ShardNumber(0)
     552            1 :                 },
     553            1 :                 TenantShardId {
     554            1 :                     tenant_id,
     555            1 :                     shard_count: ShardCount(2),
     556            1 :                     shard_number: ShardNumber(1)
     557            1 :                 }
     558            1 :             ]
     559            1 :         );
     560              : 
     561              :         // count=2 into count=8
     562            1 :         let parent = TenantShardId {
     563            1 :             tenant_id,
     564            1 :             shard_count: ShardCount(2),
     565            1 :             shard_number: ShardNumber(1),
     566            1 :         };
     567            1 :         assert_eq!(
     568            1 :             parent.split(ShardCount(8)),
     569            1 :             vec![
     570            1 :                 TenantShardId {
     571            1 :                     tenant_id,
     572            1 :                     shard_count: ShardCount(8),
     573            1 :                     shard_number: ShardNumber(1)
     574            1 :                 },
     575            1 :                 TenantShardId {
     576            1 :                     tenant_id,
     577            1 :                     shard_count: ShardCount(8),
     578            1 :                     shard_number: ShardNumber(3)
     579            1 :                 },
     580            1 :                 TenantShardId {
     581            1 :                     tenant_id,
     582            1 :                     shard_count: ShardCount(8),
     583            1 :                     shard_number: ShardNumber(5)
     584            1 :                 },
     585            1 :                 TenantShardId {
     586            1 :                     tenant_id,
     587            1 :                     shard_count: ShardCount(8),
     588            1 :                     shard_number: ShardNumber(7)
     589            1 :                 },
     590            1 :             ]
     591            1 :         );
     592            1 :     }
     593              : }
        

Generated by: LCOV version 2.1-beta