From 8c532c40bc4f2264d9984b85c58793a410e90402 Mon Sep 17 00:00:00 2001 From: Jonathan Striebel Date: Thu, 4 Aug 2022 10:02:05 +0200 Subject: [PATCH 1/3] add ZEP0002 related files --- .../sharding/sharding.png | Bin 0 -> 28059 bytes .../storage-transformers/sharding/v1.0.rst | 293 ++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 docs/extensions/storage-transformers/sharding/sharding.png create mode 100644 docs/extensions/storage-transformers/sharding/v1.0.rst diff --git a/docs/extensions/storage-transformers/sharding/sharding.png b/docs/extensions/storage-transformers/sharding/sharding.png new file mode 100644 index 0000000000000000000000000000000000000000..85a3c331df18cfe2c0e591384f120fc9d855ee94 GIT binary patch literal 28059 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 rQbO%Xr##gGP6p=)X-lj37@}dZQFkVW{ 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 Ye+89=8oUq&c3T6<%P311Jx2umA2Ze4Z2$lO literal 0 HcmV?d00001 diff --git a/docs/extensions/storage-transformers/sharding/v1.0.rst b/docs/extensions/storage-transformers/sharding/v1.0.rst new file mode 100644 index 00000000..8e942449 --- /dev/null +++ b/docs/extensions/storage-transformers/sharding/v1.0.rst @@ -0,0 +1,293 @@ +.. _sharding-storage-transformer-v1: + +========================================== +Sharding storage transformer (version 1.0) +========================================== +----------------------------- + Editor's draft 18 02 2022 +----------------------------- + +Specification URI: + @@TODO + http://purl.org/zarr/spec/storage-transformers/sharding/1.0 + +Issue tracking: + `GitHub issues `- + +Suggest an edit for this spec: + `GitHub editor `_ + +Copyright 2022-Present `Zarr core development team +`_. This work +is licensed under a `Creative Commons Attribution 3.0 Unported License +`_. + +---- + + +Abstract +======== + +This specification defines an implementation of the Zarr storage transformer +specification for sharding. + +Sharding co-locates multiple chunks within a storage object, bundling them in +shards. + + +Motivation +========== + +In many cases, it becomes inefficient or impractical to store a large number of +chunks in single files or objects due to the design constraints of the +underlying storage. For example, the file block size and maximum inode number +restrict the usage of numerous small files for typical file systems, also cloud +storage such as S3, GCS, and various distributed filesystems do not efficiently +handle large numbers of small files or objects. + +Increasing the chunk size works only up to a certain point, as chunk sizes need +to be small for read and write efficiency requirements, for example to stream +data in browser-based visualization software. + +Therefore, chunks may need to be smaller than the minimum size of one storage +key. In those cases, it is efficient to store objects at a more coarse +granularity than reading chunks. + +**Sharding solves this by allowing to store multiple chunks in one storage key, +which is called a shard**: + +.. image:: sharding.png + + +Document conventions +==================== + +Conformance requirements are expressed with a combination of descriptive +assertions and [RFC2119]_ terminology. The key words "MUST", "MUST NOT", +"REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", +and "OPTIONAL" in the normative parts of this document are to be interpreted as +described in [RFC2119]_. However, for readability, these words do not appear in +all uppercase letters in this specification. + +All of the text of this specification is normative except sections explicitly +marked as non-normative, examples, and notes. Examples in this specification are +introduced with the words "for example". + + +Configuration +============= + +Sharding can be configured per array in the :ref:`array-metadata` as follows: + +.. code-block:: + + { + storage_transformers: [ + { + "extension": "https://purl.org/zarr/spec/storage_transformers/sharding/1.0", + "type": "indexed", + "configuration": { + "chunks_per_shard": [ + 2, + 2 + ] + } + ] + } + +``type`` + + Specifies a `Binary shard format`_. In this version, the only binary format + is the ``indexed`` format. + +``chunks_per_shard`` + + An array of integers providing the number of chunks that are combined in a + shard for each dimension of the Zarr array, where each chunk may only start + at a position that is divisible by ``chunks_per_shard`` per dimension, e.g. + starting at the zero-origin. The length of the array must match the length + of the array metadata ``shape`` entry. For example, a value ``[32, 2]`` + indicates that 64 chunks are combined in one shard, 32 along the first + dimension, and for each of those 2 along the second dimension. Some valid + starting positions for a shard in the chunk-grid are therefore `[0, 0]`, + `[32, 2]`, `[32, 4]`, `[64, 2]` or `[96, 18]`. + + +Storage transformer implementation +================================== + +Key & value transformation +-------------------------- + +The storage transformer specification defines the abstract interface to be the +same as the :ref:`abstract-store-interface`. + +The Zarr store interface is defined as a mapping of `keys` and `values`, where a +`key` is a sequence of characters and a `value` is a sequence of bytes. A +key-value pair is called `entry` in the following part. + +This sharding transformer only adapts entries where the key starts with +`data/root`, as they indicate data keys for array chunks, see +:ref:`storage-keys`. All other entries are simply passed on. + +Entries starting with ``data/root`` are grouped by their common shard, assuming +storage keys from a regular chunk grid which may use a custom configured +``chunk separator``: For all entries that are part of the same shard the key is +changed to the shard-key and the values are combined in the +`Binary shard format`_ as described below. The new shard-key is the chunk key +divided by ``chunks_per_shard`` and floored per dimension. For example for +``chunks_per_shard=[32, 2]``, the chunk grid position ``[96, 18]`` (e.g. key +"data/root/foo/baz/c96/18") is transformed to the shard grid position +``[3, 9]`` and reassigned to the respective new key, honoring the original chunk +separator (e.g. "data/root/foo/baz/c3/9"). Chunk grid positions ``[96, 19]``, +``[97, 18]``, …, up to ``[127, 19]`` will also have the same shard grid position +``[3, 9]``. + + +Binary shard format +------------------- + +The only binary format is the ``indexed`` format, as specified by the ``type`` +configuration key. Other binary formats might be added in future versions. + +In the indexed binary format, chunks are written successively in a shard, where +unused space between them is allowed, followed by an index referencing them. The +index is placed at the end of the file and has a size of 16 bytes multiplied by +the number of chunks in a shard, for example ``16 bytes * 64 = 1014 bytes`` for +``chunks_per_shard=[32, 2]``. The index holds an `offset, nbytes` pair of +little-endian uint64 per chunk, the chunks-order in the index is row-major (C) +order, for example for ``chunks_per_shard=[2, 2]`` an index would look like: + +.. code-block:: + + | chunk (0, 0) | chunk (0, 1) | chunk (1, 0) | chunk (1, 1) | + | offset | nbytes | offset | nbytes | offset | nbytes | offset | nbytes | + | uint64 | uint64 | uint64 | uint64 | uint64 | uint64 | uint64 | uint64 | + + +Empty chunks are denoted by setting both offset and nbytes to ``2^64 - 1``. The +index always has the full shape of all possible chunks per shard, even if they +are outside of the array size. + +The actual order of the chunk content is not fixed and may be chosen by the +implementation as all possible write orders are valid according to this +specification and therefore can be read by any other implementation. When +writing partial chunks into an existing shard no specific order of the existing +chunks may be expected. Some writing strategies might be + +* **Fixed order**: Specify a fixed order (e.g. row-, column-major, or Morton + order). When replacing existing chunks larger or equal-sized chunks may be + replaced in-place, leaving unused space up to an upper limit that might + possibly be specified. Please note that for regular-sized uncompressed data + all chunks have the same size and can therefore be replaced in-place. > * +* **Append-only**: Any chunk to write is appended to the existing shard, + followed by an updated index. If previous chunks are updated, their storage + space becomes unused, as well as the previous index. This might be useful for + storage that only allows append-only updates. +* **Other formats**: Other formats that accept additional bytes at the end of + the file (such as HDF) could be used for storing shards, by writing the chunks + in the order the format prescribes and appending a binary index derived from + the byte offsets and lengths at the end of the file. + +Any configuration parameters for the write strategy must not be part of the +metadata document, they need to be configured at runtime, as this is +implementation specific. + + +API implementation +------------------ + +The section below defines an implementation of the +:ref:`abstract-store-interface` in terms of the operations of this storage +transformer as a ``StoreWithPartialAccess``. The term `underlying store` +references either the next storage transformer in the stack or the actual store +if this transformer is the last one in the stack. Any operations with keys not +starting with ``data/root`` are simply relayed to the underlying store and not +described explicitly. + +* ``get_partial_values(key_ranges) -> values``: For each referenced key, request + the indices from the underlying store using ``get_partial_values``. For each + `key`, `range` pair in in `key_ranges`, check if the chunk exists by checking + if the index offset and nbytes are both ``2^64 - 1``. For existing keys, + request the actual chunks by their ranges as read from the index using + ``get_partial_values``. This operation should be implemented using two + ``get_partial_values`` operations on the underlying store, one for retrieving + the indices and one for retrieving existing chunks. + +* ``set_partial_values(key_start_values)``: For each referenced key, check if + all available chunks in a shard are referenced. In this case, a shard can be + constructed according to the `Binary shard format`_ directly. For all other + keys, request the indices from the underlying store using + ``get_partial_values``. All chunks that are not updated completely and exist + according to the index (index offset and nbytes are both ``2^64 - 1``) need to + be read via ``get_partial_values`` from the underlying store. For + simplification purposes a shard may also be read completely, combining the + previous two `get` operations into one. Based on the existing chunks and value + ranges that need to be updated new shards are constructed according to the + `Binary shard format`_. All shards that need to be updated must now be set via + ``set`` or ``set_partial_values(key_start_values)``, depending on the chosen + writing strategy provided by the implementation. Specialized store + implementations that allow appending to a storage object may only need to read + the index to update it. + +* ``erase_values(keys)``: For each referenced key, check if all available chunks + in a shard are referenced. In this case, the full shard is removed using + ``erase_values`` on the underlying store. For all other keys, request the + indices from the underlying store using ``get_partial_values``. Update the + index using an offset and nbytes of ``2^64 - 1`` to mark missing chunks. The + updated index may be written in-place using + ``set_partial_values(key_start_values)``, or a larger rewrite of the shard may + be done including the index update, but also removing value ranges + corresponding to the erased chunks. + +* ``erase_prefix()``: If the prefix contains a part of the chunk-grid key, this + part is translated to the referenced shard and contained chunks. For affected + shards where all contained chunks are erased the prefix is rewritten to the + corresponding shard key and the operation is relayed to the underlying store. + For all shards where only some chunks are erased the affected chunks are + removed by invoking the operation ``erase_values`` on this storage transformer + with the respective chunk keys. + +* ``list()``: See ``list_prefix`` with the prefix ``/``. + +* ``list_prefix(prefix)``: If the prefix contains a part of the chunk-grid key, + this part is translated to the referenced shard and contained chunks. Then, + ``list_prefix`` is called on the underlying store with the translated prefix. + For all listed shards request the indices from the underlying store using + ``get_partial_values``. Existing chunks, where the index offset or nbytes are + not ``2^64 - 1`` are then listed by their original key. + +* ``list_dir(prefix)``: If the prefix contains a part of the chunk-grid key, + this part is translated to the referenced shard and contained chunks. Then, + ``list_dir`` is called on the underlying store with the translated prefix. For + all *retrieved prefixes* (not full keys) with partial shard keys, the + corresponding original prefixes covering all possible chunks in the shard are + listed. For *retrieved full keys* the indices from the underlying store are + requested using ``get_partial_values``. Existing chunks, where the index + offset or nbytes are not ``2^64 - 1`` are then listed by their original key. + + .. note:: + + Not all listed prefixes must necessarily contain keys, as shard prefixes + with partially available chunks return prefixes for all possible chunks + without verifying their existence for performance reasons. Listing those + prefixes is still safe as some chunks in their corresponding shard exist, + but not necessarily in the requested prefix, possibly leading to empty + responses. Please note that this only applies to returned prefixes, *not* + for full keys referencing storage objects. Returned full keys always reflect + the available chunks and are safe to request. + + +References +========== + +.. [RFC2119] S. Bradner. Key words for use in RFCs to Indicate + Requirement Levels. March 1997. Best Current Practice. URL: + https://tools.ietf.org/html/rfc2119 + + +Change log +========== + +This section is a placeholder for keeping a log of the snapshots of this +document that are tagged in GitHub and what changed between them. From 4f1b9816213acff0bcbd6866ac23b1c2357efa6b Mon Sep 17 00:00:00 2001 From: Jonathan Striebel Date: Mon, 15 Aug 2022 11:16:00 +0200 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Ryan Abernathey --- docs/extensions/storage-transformers/sharding/v1.0.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/extensions/storage-transformers/sharding/v1.0.rst b/docs/extensions/storage-transformers/sharding/v1.0.rst index 8e942449..9657b1db 100644 --- a/docs/extensions/storage-transformers/sharding/v1.0.rst +++ b/docs/extensions/storage-transformers/sharding/v1.0.rst @@ -153,7 +153,7 @@ configuration key. Other binary formats might be added in future versions. In the indexed binary format, chunks are written successively in a shard, where unused space between them is allowed, followed by an index referencing them. The index is placed at the end of the file and has a size of 16 bytes multiplied by -the number of chunks in a shard, for example ``16 bytes * 64 = 1014 bytes`` for +the number of chunks in a shard, for example ``16 bytes * 64 = 1024 bytes`` for ``chunks_per_shard=[32, 2]``. The index holds an `offset, nbytes` pair of little-endian uint64 per chunk, the chunks-order in the index is row-major (C) order, for example for ``chunks_per_shard=[2, 2]`` an index would look like: @@ -170,15 +170,15 @@ index always has the full shape of all possible chunks per shard, even if they are outside of the array size. The actual order of the chunk content is not fixed and may be chosen by the -implementation as all possible write orders are valid according to this +implementation. All possible write orders are valid according to this specification and therefore can be read by any other implementation. When -writing partial chunks into an existing shard no specific order of the existing +writing partial chunks into an existing shard, no specific order of the existing chunks may be expected. Some writing strategies might be * **Fixed order**: Specify a fixed order (e.g. row-, column-major, or Morton order). When replacing existing chunks larger or equal-sized chunks may be replaced in-place, leaving unused space up to an upper limit that might - possibly be specified. Please note that for regular-sized uncompressed data + possibly be specified. Please note that, for regular-sized uncompressed data, all chunks have the same size and can therefore be replaced in-place. > * * **Append-only**: Any chunk to write is appended to the existing shard, followed by an updated index. If previous chunks are updated, their storage @@ -190,7 +190,7 @@ chunks may be expected. Some writing strategies might be the byte offsets and lengths at the end of the file. Any configuration parameters for the write strategy must not be part of the -metadata document, they need to be configured at runtime, as this is +metadata document; instead they need to be configured at runtime, as this is implementation specific. From 9b3237c7a83f20da52d3c1f0aeddd143a6306192 Mon Sep 17 00:00:00 2001 From: Jonathan Striebel Date: Mon, 15 Aug 2022 12:04:38 +0200 Subject: [PATCH 3/3] update operation descriptions: list_dir and note --- .../storage-transformers/sharding/v1.0.rst | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/extensions/storage-transformers/sharding/v1.0.rst b/docs/extensions/storage-transformers/sharding/v1.0.rst index 9657b1db..c174bd8f 100644 --- a/docs/extensions/storage-transformers/sharding/v1.0.rst +++ b/docs/extensions/storage-transformers/sharding/v1.0.rst @@ -109,7 +109,7 @@ Sharding can be configured per array in the :ref:`array-metadata` as follows: of the array metadata ``shape`` entry. For example, a value ``[32, 2]`` indicates that 64 chunks are combined in one shard, 32 along the first dimension, and for each of those 2 along the second dimension. Some valid - starting positions for a shard in the chunk-grid are therefore `[0, 0]`, + starting positions for a shard in the chunk-grid therefore are `[0, 0]`, `[32, 2]`, `[32, 4]`, `[64, 2]` or `[96, 18]`. @@ -197,7 +197,7 @@ implementation specific. API implementation ------------------ -The section below defines an implementation of the +The section below defines a non-normative implementation of the :ref:`abstract-store-interface` in terms of the operations of this storage transformer as a ``StoreWithPartialAccess``. The term `underlying store` references either the next storage transformer in the stack or the actual store @@ -262,20 +262,20 @@ described explicitly. ``list_dir`` is called on the underlying store with the translated prefix. For all *retrieved prefixes* (not full keys) with partial shard keys, the corresponding original prefixes covering all possible chunks in the shard are - listed. For *retrieved full keys* the indices from the underlying store are - requested using ``get_partial_values``. Existing chunks, where the index - offset or nbytes are not ``2^64 - 1`` are then listed by their original key. + listed, which might include non-existing prefixes. For *retrieved full keys* + the indices from the underlying store are requested using + ``get_partial_values``. Existing chunks, where the index offset or nbytes are + not ``2^64 - 1`` are then listed by their original key. .. note:: - Not all listed prefixes must necessarily contain keys, as shard prefixes - with partially available chunks return prefixes for all possible chunks - without verifying their existence for performance reasons. Listing those - prefixes is still safe as some chunks in their corresponding shard exist, - but not necessarily in the requested prefix, possibly leading to empty - responses. Please note that this only applies to returned prefixes, *not* - for full keys referencing storage objects. Returned full keys always reflect - the available chunks and are safe to request. + For performance reasons, one might also implement ``list_prefix`` and + ``list_dir`` without any index lookups, always assuming that all possible + chunks in a shard are available. Alternatively, for many use-cases, + implementations of these operations might also be omitted with sharding, + since array access does not use them. Global operations like copying can + operate directly on the underlying store, not on the sharded transformed + store. References