{"id":5351,"date":"2026-01-28T13:26:43","date_gmt":"2026-01-28T19:26:43","guid":{"rendered":"https:\/\/neosmart.net\/blog\/?p=5351"},"modified":"2026-01-28T13:29:12","modified_gmt":"2026-01-28T19:29:12","slug":"sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function","status":"publish","type":"post","link":"https:\/\/neosmart.net\/blog\/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function\/","title":{"rendered":"Sharding UUIDv7 (and UUID v3, v4, and v5) values with one function"},"content":{"rendered":"<p>UUIDv7 (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Universally_unique_identifier#Version_7_(timestamp_and_random)\" rel=\"follow\">wiki link<\/a>) is seeing strong and eager adoption as a solution to problems that have long plagued the tech industry, providing a solution for generating collision-free IDs on the backend that could still be sorted chronologically to play nicer with database indexes and other needs.<sup id=\"rf1-5351\"><a href=\"#fn1-5351\" title=\"Of course, non-standardized solutions abound and UUIDv7 itself takes a lot of inspiration from predecessors like Ulid and others.\" rel=\"footnote\">1<\/a><\/sup> As a quick briefer, a UUIDv7 is essentially composed of two parts: a timestamp half and a randomized bytes half, and they&#8217;re sorted by the timestamp:<\/p>\n<p><a href=\"https:\/\/neosmart.net\/blog\/wp-content\/uploads\/2026\/01\/UUIDv7-Diagram-outlined.svg\" rel=\"follow\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-5352 colorbox-5351\" src=\"https:\/\/neosmart.net\/blog\/wp-content\/uploads\/2026\/01\/UUIDv7-Diagram-outlined.svg\" alt=\"\" \/><\/a><!--more--><\/p>\n<p>Let&#8217;s say you want to shard based off a UUIDv7 key: maybe to assign data across a horizontally-distributed database, or to avoid having a couple of million files in the same directory. You can&#8217;t use the timestamp portion to derive a shard key, as not even the microseconds are necessarily uniformly distributed, so you need to shard based off the random portion of the UUID.<\/p>\n<p>Lucky for us, the last eight bytes of the UUID contain random data not just in the case of a UUIDv7 \u2013 those bytes\u00a0<em>also<\/em> contain random data in the case of UUID v3 (MD5-hashed), UUID v4 (randomized), and UUID v5 (like v3 but using SHA-1), letting us use one approach to hash all four of these UUID types.<\/p>\n<p>With some judicious use of the stabilized subset of const generics, we can generate a shard key <code>N<\/code>-chars long without any allocation, and provide a wrapper <code>ShardKey<\/code> type to simplify both creating the shard key and passing it around in a way that lets it decompose to a <code>&amp;str<\/code> in a way that wouldn&#8217;t have been possible if we had returned a <code>[char; N]<\/code> instead.<sup id=\"rf2-5351\"><a href=\"#fn2-5351\" title=\"As rust is famously one of the very few languages that uses different-sized units\/types to represent a standalone char versus the smallest type used to compose a String; in rust, char is 4-bytes long and has UTF-32 encoding while a String or &amp;str uses an underlying 1-byte u8 type and are UTF8-encoded. This means you can&rsquo;t go from a String to a [char] without allocating, nor go from a [N; char] to an &amp;str without looping and allocating, either.\" rel=\"footnote\">2<\/a><\/sup><\/p>\n<pre><code class=\"language-rust\">\/\/\/ Derives a deterministic N-char sharding key from a UUID.\r\n\/\/\/\r\n\/\/\/ It extracts entropy starting from the last byte (the most random \r\n\/\/\/ part of a UUIDv7) moving backwards, converting nibbles to \r\n\/\/\/ hexadecimal characters. It uses only the last 8 bytes (the `rand_b`\r\n\/\/\/ section) to ensure the timestamp (first 6 bytes) is never touched, \r\n\/\/\/ preventing \"hot spot\" sharding issues based on time.\r\n\/\/\/\r\n\/\/\/ # Panics\r\n\/\/\/ * Panics if the provided UUID is not shardable based off the last \r\n\/\/\/   8 bytes (i.e. if the UUID is not one a v3, v4, v5, or v7 UUID).\r\n\/\/\/ * Panics if the requested shard key length `N` exceeds the \r\n\/\/\/   available entropy.\r\npub fn shard&lt;const N: usize&gt;(uuid: &amp;uuid::Uuid) -&gt; ShardKey&lt;N&gt; {\r\n    \/\/ Ensure UUID random bytes assumption\r\n    if !matches!(uuid.get_version_num(), 3 | 4 | 5 | 7) {\r\n        panic!(\"Provided UUID cannot be sharded with this interface!\");\r\n    }\r\n    \/\/ Validate shard key length\r\n    if N &gt; 15 {\r\n        panic!(\"Requested shard key length exceeds available entropy!\");\r\n    }\r\n\r\n    const HEX: &amp;[u8; 16] = b\"0123456789abcdef\";\r\n    let bytes = uuid.as_bytes();\r\n    let mut result = [b'\\0'; N];\r\n\r\n    \/\/ Generate N-char shard key\r\n    for i in 0..N {\r\n        \/\/ We extract data from the tail (byte 15) backwards to byte 8.\r\n        \/\/ Bytes 8-15 in UUIDv7 contain 62 bits of randomness + 2 bits \r\n        \/\/ to indicate variant. We avoid bytes 0-7 (Timestamp + Version) \r\n        \/\/ to ensure uniform distribution.\r\n\r\n        \/\/ Calculate byte index, consuming two nibbles per byte.\r\n        let inverse_offset = i \/ 2;\r\n        let byte_index = 15 - inverse_offset;\r\n\r\n        let byte = bytes[byte_index];\r\n\r\n        \/\/ Even indices take the low nibble; odd indices take the high nibble.\r\n        let nibble = if i % 2 == 0 {\r\n            byte &amp; 0x0F\r\n        } else {\r\n            (byte &gt;&gt; 4) &amp; 0x0F\r\n        };\r\n\r\n        result[i] = HEX[nibble as usize];\r\n    }\r\n\r\n    ShardKey { key: result }\r\n}\r\n\r\n#[repr(transparent)]\r\n#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]\r\npub struct ShardKey&lt;const N: usize&gt; {\r\n    key: [u8; N],\r\n}\r\n\r\nimpl&lt;const N: usize&gt; ShardKey&lt;N&gt; {\r\n    pub fn new(uuid: &amp;uuid::Uuid) -&gt; Self {\r\n        shard(uuid)\r\n    }\r\n\r\n    pub fn as_str(&amp;self) -&gt; &amp;str {\r\n        \/\/ Safe because only we have access to this field and we always\r\n        \/\/ initialize it fully with ASCII-only characters.\r\n        unsafe { std::str::from_utf8_unchecked(&amp;self.key) }\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>The <code>shard()<\/code> function can be used standalone, or the <code>ShardKey<\/code> wrapper type can be created from a reference to a <code>&Uuid<\/code> and then passed around until the point where you need to retrieve the key with a quick <code>.as_str()<\/code> call. As promised, the length of the shard key itself is variable and parameterized, by providing different values for <code>N<\/code> you can generate shard keys from one to fifteen (or sixteen, if you prefer) characters long (past which point the available uniformly-distributed entropy has been exhausted and the function will panic to ensure contract is upheld).<\/p>\n<hr class=\"footnotes\"><ol class=\"footnotes\"><li id=\"fn1-5351\"><p>Of course, non-standardized solutions abound and UUIDv7 itself takes a lot of inspiration from predecessors like <a href=\"https:\/\/github.com\/ulid\/spec\" rel=\"nofollow\">Ulid<\/a> and others.&nbsp;<a href=\"#rf1-5351\" class=\"backlink\" title=\"Jump back to footnote 1 in the text.\">&#8617;<\/a><\/p><\/li><li id=\"fn2-5351\"><p>As rust is famously one of the very few languages that uses different-sized units\/types to represent a standalone <code>char<\/code> versus the smallest type used to compose a <code>String<\/code>; in rust, <code>char<\/code> is 4-bytes long and has UTF-32 encoding while a <code>String<\/code> or <code>&str<\/code> uses an underlying 1-byte <code>u8<\/code> type and are UTF8-encoded. This means you can&#8217;t go from a <code>String<\/code> to a <code>[char]<\/code> without allocating, nor go from a <code>[N; char]<\/code> to an <code>&str<\/code> without looping and allocating, either.&nbsp;<a href=\"#rf2-5351\" class=\"backlink\" title=\"Jump back to footnote 2 in the text.\">&#8617;<\/a><\/p><\/li><\/ol>","protected":false},"excerpt":{"rendered":"<p>UUIDv7 (wiki link) is seeing strong and eager adoption as a solution to problems that have long plagued the tech industry, providing a solution for generating collision-free IDs on the backend that could still be sorted chronologically to play nicer &hellip; <a href=\"https:\/\/neosmart.net\/blog\/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":505,"featured_media":5359,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[936,1044,1042,1043],"class_list":["post-5351","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software","tag-rust","tag-sharding","tag-uuid","tag-uuidv7"],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/neosmart.net\/blog\/wp-content\/uploads\/2026\/01\/UUIDv7-Diagram.png","jetpack_shortlink":"https:\/\/wp.me\/p4xDa-1oj","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/5351","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/users\/505"}],"replies":[{"embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/comments?post=5351"}],"version-history":[{"count":7,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/5351\/revisions"}],"predecessor-version":[{"id":5360,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/5351\/revisions\/5360"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/media\/5359"}],"wp:attachment":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/media?parent=5351"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/categories?post=5351"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/tags?post=5351"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}