LCOV - code coverage report
Current view: top level - libs/pageserver_api/src - shard.rs (source / functions) Coverage Total Hit
Test: 1e20c4f2b28aa592527961bb32170ebbd2c9172f.info Lines: 89.5 % 363 325
Test Date: 2025-07-16 12:29:03 Functions: 53.7 % 54 29

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

Generated by: LCOV version 2.1-beta