swift/doc/source/overview_policies.rst
Nandini Tata 6f230c7ea0 Fixed inconsistent naming conventions
Fixed naming conventions of Keystone, Swift and proxy servers in
the docs.

Change-Id: I294afd8d7bffa8c1fc299f5812effacb9ad08910
2016-07-07 21:40:21 +00:00

629 lines
32 KiB
ReStructuredText

================
Storage Policies
================
Storage Policies allow for some level of segmenting the cluster for various
purposes through the creation of multiple object rings. The Storage Policies
feature is implemented throughout the entire code base so it is an important
concept in understanding Swift architecture.
As described in :doc:`overview_ring`, Swift uses modified hashing rings to
determine where data should reside in the cluster. There is a separate ring for
account databases, container databases, and there is also one object ring per
storage policy. Each object ring behaves exactly the same way and is maintained
in the same manner, but with policies, different devices can belong to different
rings. By supporting multiple object rings, Swift allows the application and/or
deployer to essentially segregate the object storage within a single cluster.
There are many reasons why this might be desirable:
* Different levels of durability: If a provider wants to offer, for example,
2x replication and 3x replication but doesn't want to maintain 2 separate
clusters, they would setup a 2x and a 3x replication policy and assign the
nodes to their respective rings. Furthermore, if a provider wanted to offer a
cold storage tier, they could create an erasure coded policy.
* Performance: Just as SSDs can be used as the exclusive members of an account
or database ring, an SSD-only object ring can be created as well and used to
implement a low-latency/high performance policy.
* Collecting nodes into group: Different object rings may have different
physical servers so that objects in specific storage policies are always
placed in a particular data center or geography.
* Different Storage implementations: Another example would be to collect
together a set of nodes that use a different Diskfile (e.g., Kinetic,
GlusterFS) and use a policy to direct traffic just to those nodes.
.. note::
Today, Swift supports two different policy types: Replication and Erasure
Code. See :doc:`overview_erasure_code` for details.
Also note that Diskfile refers to backend object storage plug-in
architecture. See :doc:`development_ondisk_backends` for details.
-----------------------
Containers and Policies
-----------------------
Policies are implemented at the container level. There are many advantages to
this approach, not the least of which is how easy it makes life on
applications that want to take advantage of them. It also ensures that
Storage Policies remain a core feature of Swift independent of the auth
implementation. Policies were not implemented at the account/auth layer
because it would require changes to all auth systems in use by Swift
deployers. Each container has a new special immutable metadata element called
the storage policy index. Note that internally, Swift relies on policy
indexes and not policy names. Policy names exist for human readability and
translation is managed in the proxy. When a container is created, one new
optional header is supported to specify the policy name. If no name is
specified, the default policy is used (and if no other policies defined,
Policy-0 is considered the default). We will be covering the difference
between default and Policy-0 in the next section.
Policies are assigned when a container is created. Once a container has been
assigned a policy, it cannot be changed (unless it is deleted/recreated). The
implications on data placement/movement for large datasets would make this a
task best left for applications to perform. Therefore, if a container has an
existing policy of, for example 3x replication, and one wanted to migrate that
data to an Erasure Code policy, the application would create another container
specifying the other policy parameters and then simply move the data from one
container to the other. Policies apply on a per container basis allowing for
minimal application awareness; once a container has been created with a specific
policy, all objects stored in it will be done so in accordance with that policy.
If a container with a specific name is deleted (requires the container be empty)
a new container may be created with the same name without any restriction on
storage policy enforced by the deleted container which previously shared the
same name.
Containers have a many-to-one relationship with policies meaning that any number
of containers can share one policy. There is no limit to how many containers
can use a specific policy.
The notion of associating a ring with a container introduces an interesting
scenario: What would happen if 2 containers of the same name were created with
different Storage Policies on either side of a network outage at the same time?
Furthermore, what would happen if objects were placed in those containers, a
whole bunch of them, and then later the network outage was restored? Well,
without special care it would be a big problem as an application could end up
using the wrong ring to try and find an object. Luckily there is a solution for
this problem, a daemon known as the Container Reconciler works tirelessly to
identify and rectify this potential scenario.
--------------------
Container Reconciler
--------------------
Because atomicity of container creation cannot be enforced in a
distributed eventually consistent system, object writes into the wrong
storage policy must be eventually merged into the correct storage policy
by an asynchronous daemon. Recovery from object writes during a network
partition which resulted in a split brain container created with
different storage policies are handled by the
`swift-container-reconciler` daemon.
The container reconciler works off a queue similar to the
object-expirer. The queue is populated during container-replication.
It is never considered incorrect to enqueue an object to be evaluated by
the container-reconciler because if there is nothing wrong with the location
of the object the reconciler will simply dequeue it. The
container-reconciler queue is an indexed log for the real location of an
object for which a discrepancy in the storage policy of the container was
discovered.
To determine the correct storage policy of a container, it is necessary
to update the status_changed_at field in the container_stat table when a
container changes status from deleted to re-created. This transaction
log allows the container-replicator to update the correct storage policy
both when replicating a container and handling REPLICATE requests.
Because each object write is a separate distributed transaction it is
not possible to determine the correctness of the storage policy for each
object write with respect to the entire transaction log at a given
container database. As such, container databases will always record the
object write regardless of the storage policy on a per object row basis.
Object byte and count stats are tracked per storage policy in each
container and reconciled using normal object row merge semantics.
The object rows are ensured to be fully durable during replication using
the normal container replication. After the container
replicator pushes its object rows to available primary nodes any
misplaced object rows are bulk loaded into containers based off the
object timestamp under the ``.misplaced_objects`` system account. The
rows are initially written to a handoff container on the local node, and
at the end of the replication pass the ``.misplaced_objects`` containers are
replicated to the correct primary nodes.
The container-reconciler processes the ``.misplaced_objects`` containers in
descending order and reaps its containers as the objects represented by
the rows are successfully reconciled. The container-reconciler will
always validate the correct storage policy for enqueued objects using
direct container HEAD requests which are accelerated via caching.
Because failure of individual storage nodes in aggregate is assumed to
be common at scale, the container-reconciler will make forward progress
with a simple quorum majority. During a combination of failures and
rebalances it is possible that a quorum could provide an incomplete
record of the correct storage policy - so an object write may have to be
applied more than once. Because storage nodes and container databases
will not process writes with an ``X-Timestamp`` less than or equal to
their existing record when objects writes are re-applied their timestamp
is slightly incremented. In order for this increment to be applied
transparently to the client a second vector of time has been added to
Swift for internal use. See :class:`~swift.common.utils.Timestamp`.
As the reconciler applies object writes to the correct storage policy it
cleans up writes which no longer apply to the incorrect storage policy
and removes the rows from the ``.misplaced_objects`` containers. After all
rows have been successfully processed it sleeps and will periodically
check for newly enqueued rows to be discovered during container
replication.
.. _default-policy:
-------------------------
Default versus 'Policy-0'
-------------------------
Storage Policies is a versatile feature intended to support both new and
pre-existing clusters with the same level of flexibility. For that reason, we
introduce the ``Policy-0`` concept which is not the same as the "default"
policy. As you will see when we begin to configure policies, each policy has
a single name and an arbitrary number of aliases (human friendly,
configurable) as well as an index (or simply policy number). Swift reserves
index 0 to map to the object ring that's present in all installations
(e.g., ``/etc/swift/object.ring.gz``). You can name this policy anything you
like, and if no policies are defined it will report itself as ``Policy-0``,
however you cannot change the index as there must always be a policy with
index 0.
Another important concept is the default policy which can be any policy
in the cluster. The default policy is the policy that is automatically
chosen when a container creation request is sent without a storage
policy being specified. :ref:`configure-policy` describes how to set the
default policy. The difference from ``Policy-0`` is subtle but
extremely important. ``Policy-0`` is what is used by Swift when
accessing pre-storage-policy containers which won't have a policy - in
this case we would not use the default as it might not have the same
policy as legacy containers. When no other policies are defined, Swift
will always choose ``Policy-0`` as the default.
In other words, default means "create using this policy if nothing else is
specified" and ``Policy-0`` means "use the legacy policy if a container doesn't
have one" which really means use ``object.ring.gz`` for lookups.
.. note::
With the Storage Policy based code, it's not possible to create a
container that doesn't have a policy. If nothing is provided, Swift will
still select the default and assign it to the container. For containers
created before Storage Policies were introduced, the legacy Policy-0 will
be used.
.. _deprecate-policy:
--------------------
Deprecating Policies
--------------------
There will be times when a policy is no longer desired; however simply
deleting the policy and associated rings would be problematic for existing
data. In order to ensure that resources are not orphaned in the cluster (left
on disk but no longer accessible) and to provide proper messaging to
applications when a policy needs to be retired, the notion of deprecation is
used. :ref:`configure-policy` describes how to deprecate a policy.
Swift's behavior with deprecated policies is as follows:
* The deprecated policy will not appear in /info
* PUT/GET/DELETE/POST/HEAD are still allowed on the pre-existing containers
created with a deprecated policy
* Clients will get an ''400 Bad Request'' error when trying to create a new
container using the deprecated policy
* Clients still have access to policy statistics via HEAD on pre-existing
containers
.. note::
A policy cannot be both the default and deprecated. If you deprecate the
default policy, you must specify a new default.
You can also use the deprecated feature to rollout new policies. If you
want to test a new storage policy before making it generally available
you could deprecate the policy when you initially roll it the new
configuration and rings to all nodes. Being deprecated will render it
innate and unable to be used. To test it you will need to create a
container with that storage policy; which will require a single proxy
instance (or a set of proxy-servers which are only internally
accessible) that has been one-off configured with the new policy NOT
marked deprecated. Once the container has been created with the new
storage policy any client authorized to use that container will be able
to add and access data stored in that container in the new storage
policy. When satisfied you can roll out a new ``swift.conf`` which does
not mark the policy as deprecated to all nodes.
.. _configure-policy:
--------------------
Configuring Policies
--------------------
Policies are configured in ``swift.conf`` and it is important that the deployer
have a solid understanding of the semantics for configuring policies. Recall
that a policy must have a corresponding ring file, so configuring a policy is a
two-step process. First, edit your ``/etc/swift/swift.conf`` file to add your
new policy and, second, create the corresponding policy object ring file.
See :doc:`policies_saio` for a step by step guide on adding a policy to the SAIO
setup.
Note that each policy has a section starting with ``[storage-policy:N]`` where N
is the policy index. There's no reason other than readability that these be
sequential but there are a number of rules enforced by Swift when parsing this
file:
* If a policy with index 0 is not declared and no other policies defined,
Swift will create one
* The policy index must be a non-negative integer
* If no policy is declared as the default and no other policies are
defined, the policy with index 0 is set as the default
* Policy indexes must be unique
* Policy names are required
* Policy names are case insensitive
* Policy names must contain only letters, digits or a dash
* Policy names must be unique
* The policy name 'Policy-0' can only be used for the policy with index 0
* Multiple names can be assigned to one policy using aliases. All names
must follow the Swift naming rules.
* If any policies are defined, exactly one policy must be declared default
* Deprecated policies cannot be declared the default
* If no ``policy_type`` is provided, ``replication`` is the default value.
The following is an example of a properly configured ``swift.conf`` file. See
:doc:`policies_saio` for full instructions on setting up an all-in-one with this
example configuration.::
[swift-hash]
# random unique strings that can never change (DO NOT LOSE)
# Use only printable chars (python -c "import string; print(string.printable)")
swift_hash_path_prefix = changeme
swift_hash_path_suffix = changeme
[storage-policy:0]
name = gold
aliases = yellow, orange
policy_type = replication
default = yes
[storage-policy:1]
name = silver
policy_type = replication
deprecated = yes
Review :ref:`default-policy` and :ref:`deprecate-policy` for more
information about the ``default`` and ``deprecated`` options.
There are some other considerations when managing policies:
* Policy names can be changed.
* Aliases are supported and can be added and removed. If the primary name
of a policy is removed the next available alias will be adopted as the
primary name. A policy must always have at least one name.
* You cannot change the index of a policy once it has been created
* The default policy can be changed at any time, by adding the
default directive to the desired policy section
* Any policy may be deprecated by adding the deprecated directive to
the desired policy section, but a deprecated policy may not also
be declared the default, and you must specify a default - so you
must have policy which is not deprecated at all times.
* The option ``policy_type`` is used to distinguish between different
policy types. The default value is ``replication``. When defining an EC
policy use the value ``erasure_coding``.
* The EC policy has additional required parameters. See
:doc:`overview_erasure_code` for details.
Once ``swift.conf`` is configured for a new policy, a new ring must be created.
The ring tools are not policy name aware so it's critical that the
correct policy index be used when creating the new policy's ring file.
Additional object rings are created in the same manner as the legacy ring
except that '-N' is appended after the word ``object`` where N matches the
policy index used in ``swift.conf``. This naming convention follows the pattern
for per-policy storage node data directories as well. So, to create the ring
for policy 1::
swift-ring-builder object-1.builder create 10 3 1
<and add devices, rebalance using the same naming convention>
.. note::
The same drives can indeed be used for multiple policies and the details
of how that's managed on disk will be covered in a later section, it's
important to understand the implications of such a configuration before
setting one up. Make sure it's really what you want to do, in many cases
it will be, but in others maybe not.
--------------
Using Policies
--------------
Using policies is very simple - a policy is only specified when a container is
initially created. There are no other API changes. Creating a container can
be done without any special policy information::
curl -v -X PUT -H 'X-Auth-Token: <your auth token>' \
http://127.0.0.1:8080/v1/AUTH_test/myCont0
Which will result in a container created that is associated with the
policy name 'gold' assuming we're using the swift.conf example from
above. It would use 'gold' because it was specified as the default.
Now, when we put an object into this container, it will get placed on
nodes that are part of the ring we created for policy 'gold'.
If we wanted to explicitly state that we wanted policy 'gold' the command
would simply need to include a new header as shown below::
curl -v -X PUT -H 'X-Auth-Token: <your auth token>' \
-H 'X-Storage-Policy: gold' http://127.0.0.1:8080/v1/AUTH_test/myCont0
And that's it! The application does not need to specify the policy name ever
again. There are some illegal operations however:
* If an invalid (typo, non-existent) policy is specified: 400 Bad Request
* if you try to change the policy either via PUT or POST: 409 Conflict
If you'd like to see how the storage in the cluster is being used, simply HEAD
the account and you'll see not only the cumulative numbers, as before, but
per policy statistics as well. In the example below there's 3 objects total
with two of them in policy 'gold' and one in policy 'silver'::
curl -i -X HEAD -H 'X-Auth-Token: <your auth token>' \
http://127.0.0.1:8080/v1/AUTH_test
and your results will include (some output removed for readability)::
X-Account-Container-Count: 3
X-Account-Object-Count: 3
X-Account-Bytes-Used: 21
X-Storage-Policy-Gold-Object-Count: 2
X-Storage-Policy-Gold-Bytes-Used: 14
X-Storage-Policy-Silver-Object-Count: 1
X-Storage-Policy-Silver-Bytes-Used: 7
--------------
Under the Hood
--------------
Now that we've explained a little about what Policies are and how to
configure/use them, let's explore how Storage Policies fit in at the
nuts-n-bolts level.
Parsing and Configuring
-----------------------
The module, :ref:`storage_policy`, is responsible for parsing the
``swift.conf`` file, validating the input, and creating a global collection of
configured policies via class :class:`.StoragePolicyCollection`. This
collection is made up of policies of class :class:`.StoragePolicy`. The
collection class includes handy functions for getting to a policy either by
name or by index , getting info about the policies, etc. There's also one
very important function, :meth:`~.StoragePolicyCollection.get_object_ring`.
Object rings are members of the :class:`.StoragePolicy` class and are
actually not instantiated until the :meth:`~.StoragePolicy.load_ring`
method is called. Any caller anywhere in the code base that needs to access
an object ring must use the :data:`.POLICIES` global singleton to access the
:meth:`~.StoragePolicyCollection.get_object_ring` function and provide the
policy index which will call :meth:`~.StoragePolicy.load_ring` if
needed; however, when starting request handling services such as the
:ref:`proxy-server` rings are proactively loaded to provide moderate
protection against a mis-configuration resulting in a run time error. The
global is instantiated when Swift starts and provides a mechanism to patch
policies for the test code.
Middleware
----------
Middleware can take advantage of policies through the :data:`.POLICIES` global
and by importing :func:`.get_container_info` to gain access to the policy index
associated with the container in question. From the index it can then use the
:data:`.POLICIES` singleton to grab the right ring. For example,
:ref:`list_endpoints` is policy aware using the means just described. Another
example is :ref:`recon` which will report the md5 sums for all of the rings.
Proxy Server
------------
The :ref:`proxy-server` module's role in Storage Policies is essentially to make
sure the correct ring is used as its member element. Before policies, the one
object ring would be instantiated when the :class:`.Application` class was
instantiated and could be overridden by test code via init parameter. With
policies, however, there is no init parameter and the :class:`.Application`
class instead depends on the :data:`.POLICIES` global singleton to retrieve the
ring which is instantiated the first time it's needed. So, instead of an object
ring member of the :class:`.Application` class, there is an accessor function,
:meth:`~.Application.get_object_ring`, that gets the ring from
:data:`.POLICIES`.
In general, when any module running on the proxy requires an object ring, it
does so via first getting the policy index from the cached container info. The
exception is during container creation where it uses the policy name from the
request header to look up policy index from the :data:`.POLICIES` global. Once
the proxy has determined the policy index, it can use the
:meth:`~.Application.get_object_ring` method described earlier to gain access to
the correct ring. It then has the responsibility of passing the index
information, not the policy name, on to the back-end servers via the header ``X
-Backend-Storage-Policy-Index``. Going the other way, the proxy also strips the
index out of headers that go back to clients, and makes sure they only see the
friendly policy names.
On Disk Storage
---------------
Policies each have their own directories on the back-end servers and are
identified by their storage policy indexes. Organizing the back-end directory
structures by policy index helps keep track of things and also allows for
sharing of disks between policies which may or may not make sense depending on
the needs of the provider. More on this later, but for now be aware of the
following directory naming convention:
* ``/objects`` maps to objects associated with Policy-0
* ``/objects-N`` maps to storage policy index #N
* ``/async_pending`` maps to async pending update for Policy-0
* ``/async_pending-N`` maps to async pending update for storage policy index #N
* ``/tmp`` maps to the DiskFile temporary directory for Policy-0
* ``/tmp-N`` maps to the DiskFile temporary directory for policy index #N
* ``/quarantined/objects`` maps to the quarantine directory for Policy-0
* ``/quarantined/objects-N`` maps to the quarantine directory for policy index #N
Note that these directory names are actually owned by the specific Diskfile
implementation, the names shown above are used by the default Diskfile.
Object Server
-------------
The :ref:`object-server` is not involved with selecting the storage policy
placement directly. However, because of how back-end directory structures are
setup for policies, as described earlier, the object server modules do play a
role. When the object server gets a :class:`.Diskfile`, it passes in the
policy index and leaves the actual directory naming/structure mechanisms to
:class:`.Diskfile`. By passing in the index, the instance of
:class:`.Diskfile` being used will assure that data is properly located in the
tree based on its policy.
For the same reason, the :ref:`object-updater` also is policy aware. As
previously described, different policies use different async pending directories
so the updater needs to know how to scan them appropriately.
The :ref:`object-replicator` is policy aware in that, depending on the policy,
it may have to do drastically different things, or maybe not. For example, the
difference in handling a replication job for 2x versus 3x is trivial; however,
the difference in handling replication between 3x and erasure code is most
definitely not. In fact, the term 'replication' really isn't appropriate for
some policies like erasure code; however, the majority of the framework for
collecting and processing jobs is common. Thus, those functions in the
replicator are leveraged for all policies and then there is policy specific code
required for each policy, added when the policy is defined if needed.
The ssync functionality is policy aware for the same reason. Some of the
other modules may not obviously be affected, but the back-end directory
structure owned by :class:`.Diskfile` requires the policy index
parameter. Therefore ssync being policy aware really means passing the
policy index along. See :class:`~swift.obj.ssync_sender` and
:class:`~swift.obj.ssync_receiver` for more information on ssync.
For :class:`.Diskfile` itself, being policy aware is all about managing the
back-end structure using the provided policy index. In other words, callers who
get a :class:`.Diskfile` instance provide a policy index and
:class:`.Diskfile`'s job is to keep data separated via this index (however it
chooses) such that policies can share the same media/nodes if desired. The
included implementation of :class:`.Diskfile` lays out the directory structure
described earlier but that's owned within :class:`.Diskfile`; external modules
have no visibility into that detail. A common function is provided to map
various directory names and/or strings based on their policy index. For example
:class:`.Diskfile` defines :func:`.get_data_dir` which builds off of a generic
:func:`.get_policy_string` to consistently build policy aware strings for
various usage.
Container Server
----------------
The :ref:`container-server` plays a very important role in Storage Policies, it
is responsible for handling the assignment of a policy to a container and the
prevention of bad things like changing policies or picking the wrong policy to
use when nothing is specified (recall earlier discussion on Policy-0 versus
default).
The :ref:`container-updater` is policy aware, however its job is very simple, to
pass the policy index along to the :ref:`account-server` via a request header.
The :ref:`container-backend` is responsible for both altering existing DB
schema as well as assuring new DBs are created with a schema that supports
storage policies. The "on-demand" migration of container schemas allows Swift
to upgrade without downtime (sqlite's alter statements are fast regardless of
row count). To support rolling upgrades (and downgrades) the incompatible
schema changes to the ``container_stat`` table are made to a
``container_info`` table, and the ``container_stat`` table is replaced with a
view that includes an ``INSTEAD OF UPDATE`` trigger which makes it behave like
the old table.
The policy index is stored here for use in reporting information
about the container as well as managing split-brain scenario induced
discrepancies between containers and their storage policies. Furthermore,
during split-brain, containers must be prepared to track object updates from
multiple policies so the object table also includes a
``storage_policy_index`` column. Per-policy object counts and bytes are
updated in the ``policy_stat`` table using ``INSERT`` and ``DELETE`` triggers
similar to the pre-policy triggers that updated ``container_stat`` directly.
The :ref:`container-replicator` daemon will pro-actively migrate legacy
schemas as part of its normal consistency checking process when it updates the
``reconciler_sync_point`` entry in the ``container_info`` table. This ensures
that read heavy containers which do not encounter any writes will still get
migrated to be fully compatible with the post-storage-policy queries without
having to fall back and retry queries with the legacy schema to service
container read requests.
The :ref:`container-sync-daemon` functionality only needs to be policy aware in
that it accesses the object rings. Therefore, it needs to pull the policy index
out of the container information and use it to select the appropriate object
ring from the :data:`.POLICIES` global.
Account Server
--------------
The :ref:`account-server`'s role in Storage Policies is really limited to
reporting. When a HEAD request is made on an account (see example provided
earlier), the account server is provided with the storage policy index and
builds the ``object_count`` and ``byte_count`` information for the client on a
per policy basis.
The account servers are able to report per-storage-policy object and byte
counts because of some policy specific DB schema changes. A policy specific
table, ``policy_stat``, maintains information on a per policy basis (one row
per policy) in the same manner in which the ``account_stat`` table does. The
``account_stat`` table still serves the same purpose and is not replaced by
``policy_stat``, it holds the total account stats whereas ``policy_stat`` just
has the break downs. The backend is also responsible for migrating
pre-storage-policy accounts by altering the DB schema and populating the
``policy_stat`` table for Policy-0 with current ``account_stat`` data at that
point in time.
The per-storage-policy object and byte counts are not updated with each object
PUT and DELETE request, instead container updates to the account server are
performed asynchronously by the ``swift-container-updater``.
.. _upgrade-policy:
Upgrading and Confirming Functionality
--------------------------------------
Upgrading to a version of Swift that has Storage Policy support is not
difficult, in fact, the cluster administrator isn't required to make any special
configuration changes to get going. Swift will automatically begin using the
existing object ring as both the default ring and the Policy-0 ring. Adding the
declaration of policy 0 is totally optional and in its absence, the name given
to the implicit policy 0 will be 'Policy-0'. Let's say for testing purposes
that you wanted to take an existing cluster that already has lots of data on it
and upgrade to Swift with Storage Policies. From there you want to go ahead and
create a policy and test a few things out. All you need to do is:
#. Upgrade all of your Swift nodes to a policy-aware version of Swift
#. Define your policies in ``/etc/swift/swift.conf``
#. Create the corresponding object rings
#. Create containers and objects and confirm their placement is as expected
For a specific example that takes you through these steps, please see
:doc:`policies_saio`
.. note::
If you downgrade from a Storage Policy enabled version of Swift to an
older version that doesn't support policies, you will not be able to
access any data stored in policies other than the policy with index 0 but
those objects WILL appear in container listings (possibly as duplicates if
there was a network partition and un-reconciled objects). It is EXTREMELY
important that you perform any necessary integration testing on the
upgraded deployment before enabling an additional storage policy to ensure
a consistent API experience for your clients. DO NOT downgrade to a
version of Swift that does not support storage policies once you expose
multiple storage policies.