소스 검색

Add the frozen tier node role and ILM phase (#68605)

This commit adds the `data_frozen` node role as part of the formalization of data tiers. It also
adds the `"frozen"` phase to ILM, currently allowing the same actions as the existing cold phase.

The frozen phase is intended to be used for data even less frequently searched than the cold phase,
and will eventually be loosely tied to data using partial searchable snapshots (as oppposed to full
searchable snapshots in the cold phase).

Relates to #60848
Lee Hinman 4 년 전
부모
커밋
3f9f007545
30개의 변경된 파일176개의 추가작업 그리고 51개의 파일을 삭제
  1. 2 2
      distribution/docker/src/test/resources/rest-api-spec/test/11_nodes.yml
  2. 14 2
      docs/reference/datatiers.asciidoc
  3. 13 0
      docs/reference/glossary.asciidoc
  4. 1 1
      docs/reference/ilm/actions/_ilm-action-template.asciidoc
  5. 1 1
      docs/reference/ilm/actions/ilm-allocate.asciidoc
  6. 1 1
      docs/reference/ilm/actions/ilm-delete.asciidoc
  7. 1 1
      docs/reference/ilm/actions/ilm-freeze.asciidoc
  8. 9 2
      docs/reference/ilm/actions/ilm-migrate.asciidoc
  9. 2 2
      docs/reference/ilm/actions/ilm-searchable-snapshot.asciidoc
  10. 2 2
      docs/reference/ilm/actions/ilm-set-priority.asciidoc
  11. 1 1
      docs/reference/ilm/actions/ilm-unfollow.asciidoc
  12. 12 4
      docs/reference/ilm/ilm-index-lifecycle.asciidoc
  13. 2 2
      docs/reference/ilm/ilm-tutorial.asciidoc
  14. 3 2
      docs/reference/modules/indices/recovery.asciidoc
  15. 12 0
      docs/reference/rest-api/usage.asciidoc
  16. 2 2
      rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodes/10_basic.yml
  17. 2 3
      server/src/main/java/org/elasticsearch/indices/recovery/RecoverySettings.java
  18. 1 1
      x-pack/plugin/autoscaling/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/autoscaling/get_autoscaling_capacity.yml
  19. 2 1
      x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderService.java
  20. 30 2
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/DataTier.java
  21. 2 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackPlugin.java
  22. 5 4
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/MigrateAction.java
  23. 14 2
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/TimeseriesLifecycleType.java
  24. 2 0
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/LifecyclePolicyTests.java
  25. 1 1
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/TimeseriesLifecycleTypeTests.java
  26. 4 0
      x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/TimeSeriesRestDriver.java
  27. 2 2
      x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/blobstore/cache/SearchableSnapshotsBlobStoreCacheIntegTests.java
  28. 3 3
      x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsIntegTests.java
  29. 28 4
      x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshots.java
  30. 2 2
      x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/TransportMountSearchableSnapshotAction.java

+ 2 - 2
distribution/docker/src/test/resources/rest-api-spec/test/11_nodes.yml

@@ -7,7 +7,7 @@
   - match:
       $body: |
         /  #ip                          heap.percent        ram.percent     cpu         load_1m                load_5m                load_15m               node.role                   master          name
-        ^  ((\d{1,3}\.){3}\d{1,3}  \s+  \d+            \s+  \d*         \s+ (-)?\d* \s+ ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)?\s+  ((-)?\d*(\.\d+)?)? \s+ (-|[cdhilmrstvw]{1,11}) \s+ [-*x]     \s+   (\S+\s?)+     \n)+  $/
+        ^  ((\d{1,3}\.){3}\d{1,3}  \s+  \d+            \s+  \d*         \s+ (-)?\d* \s+ ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)?\s+  ((-)?\d*(\.\d+)?)? \s+ (-|[cdfhilmrstvw]{1,11}) \s+ [-*x]     \s+   (\S+\s?)+     \n)+  $/
 
   - do:
       cat.nodes:
@@ -16,7 +16,7 @@
   - match:
       $body: |
         /^  ip                     \s+  heap\.percent   \s+  ram\.percent \s+ cpu      \s+ load_1m            \s+ load_5m            \s+ load_15m           \s+ node\.role              \s+  master   \s+   name  \n
-           ((\d{1,3}\.){3}\d{1,3}  \s+  \d+             \s+  \d*          \s+ (-)?\d* \s+  ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)? \s+ (-|[cdhilmrstvw]{1,11}) \s+  [-*x]    \s+   (\S+\s?)+     \n)+  $/
+           ((\d{1,3}\.){3}\d{1,3}  \s+  \d+             \s+  \d*          \s+ (-)?\d* \s+  ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)? \s+ (-|[cdfhilmrstvw]{1,11}) \s+  [-*x]    \s+   (\S+\s?)+     \n)+  $/
 
   - do:
       cat.nodes:

+ 14 - 2
docs/reference/datatiers.asciidoc

@@ -10,7 +10,8 @@ typically share the same hardware profile:
 and hold your most recent, most-frequently-accessed data.
 * <<warm-tier, Warm tier>> nodes hold time series data that is accessed less-frequently
 and rarely needs to be updated.
-* <<cold-tier, Cold tier>> nodes hold time series data that is accessed occasionally and not normally updated.
+* <<cold-tier, Cold tier>> nodes hold time series data that is accessed infrequently and not normally updated.
+* <<frozen-tier, Frozen tier>> nodes hold time series data that is accessed rarely and never updated.
 
 When you index documents directly to a specific index, they remain on content tier nodes indefinitely.
 
@@ -74,12 +75,23 @@ For resiliency, indices in the warm tier should be configured to use one or more
 === Cold tier
 
 Once data is no longer being updated, it can move from the warm tier to the cold tier where it
-stays for the rest of its life.
+stays while being queried infrequently.
 The cold tier is still a responsive query tier, but data in the cold tier is not normally updated.
 As data transitions into the cold tier it can be compressed and shrunken.
 For resiliency, indices in the cold tier can rely on
 <<ilm-searchable-snapshot, searchable snapshots>>, eliminating the need for replicas.
 
+[discrete]
+[[frozen-tier]]
+=== Frozen tier
+
+Once data is no longer being queried, or being queried rarely, it may move from the cold tier
+to the frozen tier where it stays for the rest of its life.
+The frozen tier is a less responsive query tier than the cold tier, and data in the frozen tier is
+not normally updated. As data transitions into the frozen tier it can be compressed and shrunken.
+For resiliency, indices in the frozen tier can rely on <<ilm-searchable-snapshot, searchable
+snapshots>>, eliminating the need for replicas or even a local copy.
+
 [discrete]
 [[data-tier-allocation]]
 === Data tier index allocation

+ 13 - 0
docs/reference/glossary.asciidoc

@@ -207,6 +207,19 @@ Frozen indices use a memory-efficient shard implementation and throttle searches
 Searching a frozen index is lower overhead than re-opening a closed index to enable searching.
 // end::frozen-index-def[]
 
+[[glossary-frozen-phase]] frozen phase ::
+// tag::frozen-phase-def[]
+The fourth possible phase in the <<glossary-index-lifecycle,index lifecycle>>.
+In the frozen phase, an index is no longer updated and queried rarely.
+The information still needs to be searchable, but it’s okay if those queries are extremely slow.
+// end::frozen-phase-def[]
+
+[[glossary-frozen-tier]] frozen tier::
+// tag::frozen-tier-def[]
+A <<glossary-data-tier, data tier>> that contains nodes that hold time series data
+that is accessed rarely and not normally updated.
+// end::frozen-tier-def[]
+
 [[glossary-hidden-index]] hidden index ::
 // tag::hidden-index-def[]
 An index that is excluded by default when you access indices using a wildcard expression. 

+ 1 - 1
docs/reference/ilm/actions/_ilm-action-template.asciidoc

@@ -12,7 +12,7 @@ docs/reference/ilm/actions.asciidoc
 [[ilm-sample]]
 === Sample
 
-Phases allowed: hot, warm, cold, delete.
+Phases allowed: hot, warm, cold, frozen, delete.
 
 ////
 INTRO

+ 1 - 1
docs/reference/ilm/actions/ilm-allocate.asciidoc

@@ -2,7 +2,7 @@
 [[ilm-allocate]]
 === Allocate
 
-Phases allowed: warm, cold.
+Phases allowed: warm, cold, frozen.
 
 Updates the index settings to change which nodes are allowed to host the index shards
 and change the number of replicas.

+ 1 - 1
docs/reference/ilm/actions/ilm-delete.asciidoc

@@ -12,7 +12,7 @@ Permanently removes the index.
 `delete_searchable_snapshot`::
 beta:[]
 (Optional, Boolean)
-Deletes the searchable snapshot created in the cold phase. 
+Deletes the searchable snapshot created in a previous phase.
 Defaults to `true`.
 This option is applicable when the <<ilm-searchable-snapshot,searchable
 snapshot>> action is used in the cold phase.

+ 1 - 1
docs/reference/ilm/actions/ilm-freeze.asciidoc

@@ -2,7 +2,7 @@
 [[ilm-freeze]]
 === Freeze
 
-Phases allowed: cold.
+Phases allowed: cold, frozen.
 
 <<frozen-indices, Freezes>> an index to minimize its memory footprint.
 

+ 9 - 2
docs/reference/ilm/actions/ilm-migrate.asciidoc

@@ -2,12 +2,12 @@
 [[ilm-migrate]]
 === Migrate
 
-Phases allowed: warm, cold.
+Phases allowed: warm, cold, frozen.
 
 Moves the index to the <<data-tiers, data tier>> that corresponds
 to the current phase by updating the <<tier-preference-allocation-filter, `index.routing.allocation.include._tier_preference`>>
 index setting.
-{ilm-init} automatically injects the migrate action in the warm and cold
+{ilm-init} automatically injects the migrate action in the warm, cold, and frozen
 phases if no allocation options are specified with the <<ilm-allocate, allocate>> action.
 If you specify an allocate action that only modifies the number of index
 replicas, {ilm-init} reduces the number of replicas before migrating the index.
@@ -25,6 +25,13 @@ to `data_cold,data_warm,data_hot`. This moves the index to nodes in the
 <<cold-tier, cold tier>>. If there are no nodes in the cold tier, it falls back to the
 <<warm-tier, warm>> tier, or the <<hot-tier, hot>> tier if there are no warm nodes available.
 
+In the frozen phase, the `migrate` action sets
+<<tier-preference-allocation-filter, `index.routing.allocation.include._tier_preference`>>
+to `data_frozen,data_cold,data_warm,data_hot`. This moves the index to nodes in the
+<<frozen-tier, frozen tier>>. If there are no nodes in the frozen tier, it falls back to the
+<<cold-tier, cold>> tier, then the <<warm-tier, warm>> tier, then finally the <<hot-tier, hot>>
+tier.
+
 The migrate action is not allowed in the hot phase.
 The initial index allocation is performed <<data-tier-allocation, automatically>>,
 and can be configured manually or via <<index-templates, index templates>>.

+ 2 - 2
docs/reference/ilm/actions/ilm-searchable-snapshot.asciidoc

@@ -4,7 +4,7 @@
 
 beta::[]
 
-Phases allowed: hot, cold.
+Phases allowed: hot, cold, frozen.
 
 Takes a snapshot of the managed index in the configured repository
 and mounts it as a searchable snapshot.
@@ -17,7 +17,7 @@ will reject the policy.
 
 IMPORTANT: If the `searchable_snapshot` action is used in the `hot` phase the
 subsequent phases cannot define any of the `shrink`, `forcemerge`, `freeze` or
-`searchable_snapshot` (also available in the cold phase) actions.
+`searchable_snapshot` (also available in the cold and frozen phases) actions.
 
 [NOTE]
 This action cannot be performed on a data stream's write index. Attempts to do

+ 2 - 2
docs/reference/ilm/actions/ilm-set-priority.asciidoc

@@ -2,10 +2,10 @@
 [[ilm-set-priority]]
 === Set priority
 
-Phases allowed: hot, warm, cold.
+Phases allowed: hot, warm, cold, frozen.
 
 Sets the <<recovery-prioritization, priority>> of the index as
-soon as the policy enters the hot, warm, or cold phase. 
+soon as the policy enters the hot, warm, cold, or frozen phase.
 Higher priority indices are recovered before indices with lower priorities following a node restart. 
 
 Generally, indexes in the hot phase should have the highest value and

+ 1 - 1
docs/reference/ilm/actions/ilm-unfollow.asciidoc

@@ -2,7 +2,7 @@
 [[ilm-unfollow]]
 === Unfollow
 
-Phases allowed: hot, warm, cold.
+Phases allowed: hot, warm, cold, frozen.
 
 Converts a {ref}/ccr-apis.html[{ccr-init}] follower index into a regular index. 
 This enables the shrink, rollover, and searchable snapshot actions

+ 12 - 4
docs/reference/ilm/ilm-index-lifecycle.asciidoc

@@ -6,13 +6,14 @@
 <titleabbrev>Index lifecycle</titleabbrev>
 ++++
 
-{ilm-init} defines four index lifecycle _phases_:
+{ilm-init} defines five index lifecycle _phases_:
 
 * **Hot**: The index is actively being updated and queried.
 * **Warm**: The index is no longer being updated but is still being queried.
-* **Cold**: The index is no longer being updated and is seldom queried. The
-information still needs to be searchable, but it's okay if those queries are
-slower.
+* **Cold**: The index is no longer being updated and is queried infrequently. The information still
+needs to be searchable, but it's okay if those queries are slower.
+* **Frozen**: The index is no longer being updated and is queried rarely. The information still
+needs to be searchable, but it's okay if those queries are extremely slow.
 * **Delete**: The index is no longer needed and can safely be removed.
 
 An index's _lifecycle policy_ specifies which phases 
@@ -107,6 +108,13 @@ ifdef::permanently-unreleased-branch[]
   - <<ilm-rollup,Rollup>>
 endif::[]
   - <<ilm-searchable-snapshot, Searchable Snapshot>>
+* Frozen
+  - <<ilm-set-priority,Set Priority>>
+  - <<ilm-unfollow,Unfollow>>
+  - <<ilm-allocate,Allocate>>
+  - <<ilm-migrate,Migrate>>
+  - <<ilm-freeze,Freeze>>
+  - <<ilm-searchable-snapshot, Searchable Snapshot>>
 * Delete
   - <<ilm-wait-for-snapshot,Wait For Snapshot>>
   - <<ilm-delete,Delete>>

+ 2 - 2
docs/reference/ilm/ilm-tutorial.asciidoc

@@ -45,8 +45,8 @@ or the {ilm-init} APIs.
 === Create a lifecycle policy
 
 A lifecycle policy specifies the phases in the index lifecycle
-and the actions to perform in each phase. A lifecycle can have up to four phases:
-`hot`, `warm`, `cold`, and `delete`.
+and the actions to perform in each phase. A lifecycle can have up to five phases:
+`hot`, `warm`, `cold`, `frozen`, and `delete`.
 
 For example, you might define a `timeseries_policy` that has two phases:
 

+ 3 - 2
docs/reference/modules/indices/recovery.asciidoc

@@ -19,8 +19,9 @@ You can view a list of in-progress and completed recoveries using the
 (<<cluster-update-settings,Dynamic>>) Limits total inbound and outbound
 recovery traffic for each node. Applies to both peer recoveries as well
 as snapshot recoveries (i.e., restores from a snapshot). Defaults to `40mb`
-unless the node is a <<cold-tier, dedicated cold node>> in which case the
-default relates to the total memory available to the node:
+unless the node is a dedicated <<cold-tier, cold>> or
+<<frozen-tier, frozen>> node, in which case the default relates to the
+total memory available to the node:
 
 .Recovery Rate for Cold Nodes
 [options="header"]

+ 12 - 0
docs/reference/rest-api/usage.asciidoc

@@ -313,6 +313,18 @@ GET /_xpack/usage
       "primary_shard_size_median_bytes" : 0,
       "primary_shard_size_mad_bytes" : 0
     },
+    "data_frozen" : {
+      "node_count" : 1,
+      "index_count" : 0,
+      "total_shard_count" : 0,
+      "primary_shard_count" : 0,
+      "doc_count" : 0,
+      "total_size_bytes" : 0,
+      "primary_size_bytes" : 0,
+      "primary_shard_size_avg_bytes" : 0,
+      "primary_shard_size_median_bytes" : 0,
+      "primary_shard_size_mad_bytes" : 0
+    },
     "data_cold" : {
       "node_count" : 0,
       "index_count" : 0,

+ 2 - 2
rest-api-spec/src/main/resources/rest-api-spec/test/cat.nodes/10_basic.yml

@@ -7,7 +7,7 @@
   - match:
       $body: |
                /  #ip                          heap.percent        ram.percent     cpu         load_1m                load_5m                load_15m               node.role                   master          name
-               ^  ((\d{1,3}\.){3}\d{1,3}  \s+  \d+            \s+  \d*         \s+ (-)?\d* \s+ ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)?\s+  ((-)?\d*(\.\d+)?)? \s+ (-|[cdhilmrstvw]{1,11}) \s+ [-*x]     \s+   (\S+\s?)+     \n)+  $/
+               ^  ((\d{1,3}\.){3}\d{1,3}  \s+  \d+            \s+  \d*         \s+ (-)?\d* \s+ ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)?\s+  ((-)?\d*(\.\d+)?)? \s+ (-|[cdfhilmrstvw]{1,11}) \s+ [-*x]     \s+   (\S+\s?)+     \n)+  $/
 
   - do:
       cat.nodes:
@@ -16,7 +16,7 @@
   - match:
       $body: |
                /^  ip                     \s+  heap\.percent   \s+  ram\.percent \s+ cpu      \s+ load_1m            \s+ load_5m            \s+ load_15m           \s+ node\.role              \s+  master   \s+   name  \n
-                  ((\d{1,3}\.){3}\d{1,3}  \s+  \d+             \s+  \d*          \s+ (-)?\d* \s+  ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)? \s+ (-|[cdhilmrstvw]{1,11}) \s+  [-*x]    \s+   (\S+\s?)+     \n)+  $/
+                  ((\d{1,3}\.){3}\d{1,3}  \s+  \d+             \s+  \d*          \s+ (-)?\d* \s+  ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)? \s+ ((-)?\d*(\.\d+)?)? \s+ (-|[cdfhilmrstvw]{1,11}) \s+  [-*x]    \s+   (\S+\s?)+     \n)+  $/
 
   - do:
       cat.nodes:

+ 2 - 3
server/src/main/java/org/elasticsearch/indices/recovery/RecoverySettings.java

@@ -43,9 +43,8 @@ public class RecoverySettings {
                     // if the node is not a data node, this value doesn't matter, use the default
                     return defaultMaxBytesPerSec.getStringRep();
                 }
-                if ((dataRoles.size() > 1 || dataRoles.get(0).roleName().equals("data_cold") == false) ||
-                    roles.contains(DiscoveryNodeRole.MASTER_ROLE)) {
-                    // if the node is not a dedicated cold node, use the default
+                if (dataRoles.stream().allMatch(dn -> dn.roleName().equals("data_cold") || dn.roleName().equals("data_frozen")) == false) {
+                    // the node is not a dedicated cold and/or frozen node, use the default
                     return defaultMaxBytesPerSec.getStringRep();
                 }
                 /*

+ 1 - 1
x-pack/plugin/autoscaling/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/autoscaling/get_autoscaling_capacity.yml

@@ -53,7 +53,7 @@
         body:
           # Notice that adding new default roles requires extending this list
           roles: ["master",
-                  "data", "data_content", "data_hot", "data_warm", "data_cold",
+                  "data", "data_content", "data_hot", "data_warm", "data_cold", "data_frozen",
                   "ingest", "ml", "transform", "remote_cluster_client"]
 
   - match: { "acknowledged": true }

+ 2 - 1
x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderService.java

@@ -92,7 +92,8 @@ public class ReactiveStorageDeciderService implements AutoscalingDeciderService
             DataTier.DATA_CONTENT_NODE_ROLE,
             DataTier.DATA_HOT_NODE_ROLE,
             DataTier.DATA_WARM_NODE_ROLE,
-            DataTier.DATA_COLD_NODE_ROLE
+            DataTier.DATA_COLD_NODE_ROLE,
+            DataTier.DATA_FROZEN_NODE_ROLE
         );
     }
 

+ 30 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/DataTier.java

@@ -36,8 +36,10 @@ public class DataTier {
     public static final String DATA_HOT = "data_hot";
     public static final String DATA_WARM = "data_warm";
     public static final String DATA_COLD = "data_cold";
+    public static final String DATA_FROZEN = "data_frozen";
 
-    public static final Set<String> ALL_DATA_TIERS = new HashSet<>(Arrays.asList(DATA_CONTENT, DATA_HOT, DATA_WARM, DATA_COLD));
+    public static final Set<String> ALL_DATA_TIERS =
+        new HashSet<>(Arrays.asList(DATA_CONTENT, DATA_HOT, DATA_WARM, DATA_COLD, DATA_FROZEN));
 
     /**
      * Returns true if the given tier name is a valid tier
@@ -46,7 +48,8 @@ public class DataTier {
         return DATA_CONTENT.equals(tierName) ||
             DATA_HOT.equals(tierName) ||
             DATA_WARM.equals(tierName) ||
-            DATA_COLD.equals(tierName);
+            DATA_COLD.equals(tierName) ||
+            DATA_FROZEN.equals(tierName);
     }
 
     /**
@@ -150,6 +153,27 @@ public class DataTier {
 
     };
 
+    public static DiscoveryNodeRole DATA_FROZEN_NODE_ROLE = new DiscoveryNodeRole("data_frozen", "f", true) {
+        @Override
+        public boolean isEnabledByDefault(final Settings settings) {
+            return DiscoveryNode.hasRole(settings, DiscoveryNodeRole.DATA_ROLE);
+        }
+
+        @Override
+        public Setting<Boolean> legacySetting() {
+            // we do not register these settings, they're not intended to be used externally, only for proper defaults
+            return Setting.boolSetting(
+                "node.data_frozen",
+                settings ->
+                    // Don't use DiscoveryNode#isDataNode(Settings) here, as it is called before all plugins are initialized
+                    Boolean.toString(DiscoveryNode.hasRole(settings, DiscoveryNodeRole.DATA_ROLE)),
+                Setting.Property.Deprecated,
+                Setting.Property.NodeScope
+            );
+        }
+
+    };
+
     public static boolean isContentNode(DiscoveryNode discoveryNode) {
         return discoveryNode.getRoles().contains(DATA_CONTENT_NODE_ROLE) || discoveryNode.getRoles().contains(DiscoveryNodeRole.DATA_ROLE);
     }
@@ -166,6 +190,10 @@ public class DataTier {
         return discoveryNode.getRoles().contains(DATA_COLD_NODE_ROLE) || discoveryNode.getRoles().contains(DiscoveryNodeRole.DATA_ROLE);
     }
 
+    public static boolean isFrozenNode(DiscoveryNode discoveryNode) {
+        return discoveryNode.getRoles().contains(DATA_FROZEN_NODE_ROLE) || discoveryNode.getRoles().contains(DiscoveryNodeRole.DATA_ROLE);
+    }
+
     /**
      * This setting provider injects the setting allocating all newly created indices with
      * {@code index.routing.allocation.include._tier: "data_hot"} unless the user overrides the

+ 2 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackPlugin.java

@@ -393,7 +393,8 @@ public class XPackPlugin extends XPackClientPlugin implements ExtensiblePlugin,
             DataTier.DATA_CONTENT_NODE_ROLE,
             DataTier.DATA_HOT_NODE_ROLE,
             DataTier.DATA_WARM_NODE_ROLE,
-            DataTier.DATA_COLD_NODE_ROLE));
+            DataTier.DATA_COLD_NODE_ROLE,
+            DataTier.DATA_FROZEN_NODE_ROLE));
     }
 
     @Override

+ 5 - 4
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/MigrateAction.java

@@ -33,8 +33,9 @@ public class MigrateAction implements LifecycleAction {
     public static final String NAME = "migrate";
     public static final ParseField ENABLED_FIELD = new ParseField("enabled");
 
-    // Represents an ordered list of data tiers from cold to hot (or slow to fast)
-    private static final List<String> COLD_TO_HOT_TIERS = List.of(DataTier.DATA_COLD, DataTier.DATA_WARM, DataTier.DATA_HOT);
+    // Represents an ordered list of data tiers from frozen to hot (or slow to fast)
+    private static final List<String> FROZEN_TO_HOT_TIERS =
+        List.of(DataTier.DATA_FROZEN, DataTier.DATA_COLD, DataTier.DATA_WARM, DataTier.DATA_HOT);
 
     private static final ConstructingObjectParser<MigrateAction, Void> PARSER = new ConstructingObjectParser<>(NAME,
         a -> new MigrateAction(a[0] == null ? true : (boolean) a[0]));
@@ -113,11 +114,11 @@ public class MigrateAction implements LifecycleAction {
      * This is usually used in conjunction with {@link DataTierAllocationDecider#INDEX_ROUTING_PREFER_SETTING}
      */
     static String getPreferredTiersConfiguration(String targetTier) {
-        int indexOfTargetTier = COLD_TO_HOT_TIERS.indexOf(targetTier);
+        int indexOfTargetTier = FROZEN_TO_HOT_TIERS.indexOf(targetTier);
         if (indexOfTargetTier == -1) {
             throw new IllegalArgumentException("invalid data tier [" + targetTier + "]");
         }
-        return COLD_TO_HOT_TIERS.stream().skip(indexOfTargetTier).collect(Collectors.joining(","));
+        return FROZEN_TO_HOT_TIERS.stream().skip(indexOfTargetTier).collect(Collectors.joining(","));
     }
 
     @Override

+ 14 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/TimeseriesLifecycleType.java

@@ -40,16 +40,20 @@ public class TimeseriesLifecycleType implements LifecycleType {
     static final String HOT_PHASE = "hot";
     static final String WARM_PHASE = "warm";
     static final String COLD_PHASE = "cold";
+    static final String FROZEN_PHASE = "frozen";
     static final String DELETE_PHASE = "delete";
-    static final List<String> VALID_PHASES = Arrays.asList(HOT_PHASE, WARM_PHASE, COLD_PHASE, DELETE_PHASE);
+    static final List<String> VALID_PHASES = Arrays.asList(HOT_PHASE, WARM_PHASE, COLD_PHASE, FROZEN_PHASE, DELETE_PHASE);
     static final List<String> ORDERED_VALID_HOT_ACTIONS;
     static final List<String> ORDERED_VALID_WARM_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, ReadOnlyAction.NAME,
         AllocateAction.NAME, MigrateAction.NAME, ShrinkAction.NAME, ForceMergeAction.NAME);
     static final List<String> ORDERED_VALID_COLD_ACTIONS;
+    static final List<String> ORDERED_VALID_FROZEN_ACTIONS = Arrays.asList(SetPriorityAction.NAME, UnfollowAction.NAME, ReadOnlyAction.NAME,
+        AllocateAction.NAME, MigrateAction.NAME, FreezeAction.NAME, SearchableSnapshotAction.NAME);
     static final List<String> ORDERED_VALID_DELETE_ACTIONS = Arrays.asList(WaitForSnapshotAction.NAME, DeleteAction.NAME);
     static final Set<String> VALID_HOT_ACTIONS;
     static final Set<String> VALID_WARM_ACTIONS = Sets.newHashSet(ORDERED_VALID_WARM_ACTIONS);
     static final Set<String> VALID_COLD_ACTIONS;
+    static final Set<String> VALID_FROZEN_ACTIONS;
     static final Set<String> VALID_DELETE_ACTIONS = Sets.newHashSet(ORDERED_VALID_DELETE_ACTIONS);
     private static final Map<String, Set<String>> ALLOWED_ACTIONS;
 
@@ -73,11 +77,13 @@ public class TimeseriesLifecycleType implements LifecycleType {
         }
         VALID_HOT_ACTIONS = Sets.newHashSet(ORDERED_VALID_HOT_ACTIONS);
         VALID_COLD_ACTIONS = Sets.newHashSet(ORDERED_VALID_COLD_ACTIONS);
+        VALID_FROZEN_ACTIONS = Sets.newHashSet(ORDERED_VALID_FROZEN_ACTIONS);
         ALLOWED_ACTIONS = new HashMap<>();
         ALLOWED_ACTIONS.put(HOT_PHASE, VALID_HOT_ACTIONS);
         ALLOWED_ACTIONS.put(WARM_PHASE, VALID_WARM_ACTIONS);
         ALLOWED_ACTIONS.put(COLD_PHASE, VALID_COLD_ACTIONS);
         ALLOWED_ACTIONS.put(DELETE_PHASE, VALID_DELETE_ACTIONS);
+        ALLOWED_ACTIONS.put(FROZEN_PHASE, VALID_FROZEN_ACTIONS);
     }
 
     private TimeseriesLifecycleType() {
@@ -186,11 +192,14 @@ public class TimeseriesLifecycleType implements LifecycleType {
             case COLD_PHASE:
                 return ORDERED_VALID_COLD_ACTIONS.stream().map(actions::get)
                     .filter(Objects::nonNull).collect(toList());
+            case FROZEN_PHASE:
+                return ORDERED_VALID_FROZEN_ACTIONS.stream().map(actions::get)
+                    .filter(Objects::nonNull).collect(toList());
             case DELETE_PHASE:
                 return ORDERED_VALID_DELETE_ACTIONS.stream().map(actions::get)
                     .filter(Objects::nonNull).collect(toList());
             default:
-                throw new IllegalArgumentException("lifecycle type[" + TYPE + "] does not support phase[" + phase.getName() + "]");
+                throw new IllegalArgumentException("lifecycle type [" + TYPE + "] does not support phase [" + phase.getName() + "]");
         }
     }
 
@@ -207,6 +216,9 @@ public class TimeseriesLifecycleType implements LifecycleType {
             case COLD_PHASE:
                 orderedActionNames = ORDERED_VALID_COLD_ACTIONS;
                 break;
+            case FROZEN_PHASE:
+                orderedActionNames = ORDERED_VALID_FROZEN_ACTIONS;
+                break;
             case DELETE_PHASE:
                 orderedActionNames = ORDERED_VALID_DELETE_ACTIONS;
                 break;

+ 2 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/LifecyclePolicyTests.java

@@ -174,6 +174,8 @@ public class LifecyclePolicyTests extends AbstractSerializingTestCase<LifecycleP
                     return new HashSet<>(TimeseriesLifecycleType.VALID_WARM_ACTIONS);
                 case "cold":
                     return new HashSet<>(TimeseriesLifecycleType.VALID_COLD_ACTIONS);
+                case "frozen":
+                    return new HashSet<>(TimeseriesLifecycleType.VALID_FROZEN_ACTIONS);
                 case "delete":
                     return new HashSet<>(TimeseriesLifecycleType.VALID_DELETE_ACTIONS);
                 default:

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/TimeseriesLifecycleTypeTests.java

@@ -272,7 +272,7 @@ public class TimeseriesLifecycleTypeTests extends ESTestCase {
     public void testGetOrderedActionsInvalidPhase() {
         IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> TimeseriesLifecycleType.INSTANCE
             .getOrderedActions(new Phase("invalid", TimeValue.ZERO, Collections.emptyMap())));
-        assertThat(exception.getMessage(), equalTo("lifecycle type[timeseries] does not support phase[invalid]"));
+        assertThat(exception.getMessage(), equalTo("lifecycle type [timeseries] does not support phase [invalid]"));
     }
 
     public void testGetOrderedActionsHot() {

+ 4 - 0
x-pack/plugin/ilm/qa/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/TimeSeriesRestDriver.java

@@ -180,10 +180,14 @@ public final class TimeSeriesRestDriver {
         coldActions.put(SetPriorityAction.NAME, new SetPriorityAction(0));
         coldActions.put(AllocateAction.NAME, new AllocateAction(0, singletonMap("_name", "javaRestTest-0,javaRestTest-1,javaRestTest-2," +
             "javaRestTest-3"), null, null));
+        Map<String, LifecycleAction> frozenActions = new HashMap<>();
+        frozenActions.put(SetPriorityAction.NAME, new SetPriorityAction(2));
+        frozenActions.put(AllocateAction.NAME, new AllocateAction(0, singletonMap("_name", ""), null, null));
         Map<String, Phase> phases = new HashMap<>();
         phases.put("hot", new Phase("hot", hotTime, hotActions));
         phases.put("warm", new Phase("warm", TimeValue.ZERO, warmActions));
         phases.put("cold", new Phase("cold", TimeValue.ZERO, coldActions));
+        phases.put("frozen", new Phase("frozen", TimeValue.ZERO, frozenActions));
         phases.put("delete", new Phase("delete", TimeValue.ZERO, singletonMap(DeleteAction.NAME, new DeleteAction())));
         LifecyclePolicy lifecyclePolicy = new LifecyclePolicy(policyName, phases);
         // PUT policy

+ 2 - 2
x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/blobstore/cache/SearchableSnapshotsBlobStoreCacheIntegTests.java

@@ -59,7 +59,7 @@ import java.util.Map;
 import static org.elasticsearch.repositories.blobstore.BlobStoreRepository.INDEX_SHARD_SNAPSHOT_FORMAT;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
-import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.DATA_TIERS_PREFERENCE;
+import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.DATA_TIERS_CACHE_INDEX_PREFERENCE;
 import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.SNAPSHOT_BLOB_CACHE_INDEX;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.greaterThan;
@@ -172,7 +172,7 @@ public class SearchableSnapshotsBlobStoreCacheIntegTests extends BaseSearchableS
                 .prepareGetSettings(SNAPSHOT_BLOB_CACHE_INDEX)
                 .get()
                 .getSetting(SNAPSHOT_BLOB_CACHE_INDEX, DataTierAllocationDecider.INDEX_ROUTING_PREFER),
-            equalTo(DATA_TIERS_PREFERENCE)
+            equalTo(DATA_TIERS_CACHE_INDEX_PREFERENCE)
         );
 
         final long numberOfCachedBlobs = systemClient().prepareSearch(SNAPSHOT_BLOB_CACHE_INDEX).get().getHits().getTotalHits().value;

+ 3 - 3
x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsIntegTests.java

@@ -88,7 +88,7 @@ import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
 import static org.elasticsearch.repositories.blobstore.BlobStoreRepository.READONLY_SETTING_KEY;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
-import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.DATA_TIERS_PREFERENCE;
+import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.getDataTiersPreference;
 import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.SNAPSHOT_DIRECTORY_FACTORY_KEY;
 import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsConstants.SNAPSHOT_RECOVERY_STATE_FACTORY_KEY;
 import static org.hamcrest.Matchers.anyOf;
@@ -206,7 +206,7 @@ public class SearchableSnapshotsIntegTests extends BaseSearchableSnapshotsIntegT
             expectedDataTiersPreference = String.join(",", randomSubsetOf(DataTier.ALL_DATA_TIERS));
             indexSettingsBuilder.put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, expectedDataTiersPreference);
         } else {
-            expectedDataTiersPreference = DATA_TIERS_PREFERENCE;
+            expectedDataTiersPreference = getDataTiersPreference(MountSearchableSnapshotRequest.Storage.FULL_COPY);
         }
 
         final MountSearchableSnapshotRequest req = new MountSearchableSnapshotRequest(
@@ -477,7 +477,7 @@ public class SearchableSnapshotsIntegTests extends BaseSearchableSnapshotsIntegT
             expectedDataTiersPreference = String.join(",", randomSubsetOf(DataTier.ALL_DATA_TIERS));
             indexSettingsBuilder.put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, expectedDataTiersPreference);
         } else {
-            expectedDataTiersPreference = DATA_TIERS_PREFERENCE;
+            expectedDataTiersPreference = getDataTiersPreference(MountSearchableSnapshotRequest.Storage.SHARED_CACHE);
         }
 
         final MountSearchableSnapshotRequest req = new MountSearchableSnapshotRequest(

+ 28 - 4
x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshots.java

@@ -68,6 +68,7 @@ import org.elasticsearch.xpack.core.XPackPlugin;
 import org.elasticsearch.xpack.core.action.XPackInfoFeatureAction;
 import org.elasticsearch.xpack.core.action.XPackUsageFeatureAction;
 import org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotAction;
+import org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotRequest;
 import org.elasticsearch.xpack.searchablesnapshots.action.ClearSearchableSnapshotsCacheAction;
 import org.elasticsearch.xpack.searchablesnapshots.action.SearchableSnapshotsStatsAction;
 import org.elasticsearch.xpack.searchablesnapshots.action.TransportClearSearchableSnapshotsCacheAction;
@@ -75,8 +76,8 @@ import org.elasticsearch.xpack.searchablesnapshots.action.TransportMountSearchab
 import org.elasticsearch.xpack.searchablesnapshots.action.TransportSearchableSnapshotsStatsAction;
 import org.elasticsearch.xpack.searchablesnapshots.action.cache.TransportSearchableSnapshotCacheStoresAction;
 import org.elasticsearch.xpack.searchablesnapshots.cache.CacheService;
-import org.elasticsearch.xpack.searchablesnapshots.cache.PersistentCache;
 import org.elasticsearch.xpack.searchablesnapshots.cache.FrozenCacheService;
+import org.elasticsearch.xpack.searchablesnapshots.cache.PersistentCache;
 import org.elasticsearch.xpack.searchablesnapshots.rest.RestClearSearchableSnapshotsCacheAction;
 import org.elasticsearch.xpack.searchablesnapshots.rest.RestMountSearchableSnapshotAction;
 import org.elasticsearch.xpack.searchablesnapshots.rest.RestSearchableSnapshotsStatsAction;
@@ -182,9 +183,32 @@ public class SearchableSnapshots extends Plugin implements IndexStorePlugin, Eng
     );
 
     /**
-     * Prefer to allocate to the cold tier, then the warm tier, then the hot tier
+     * Prefer to allocate to the cold tier, then the frozen tier, then the warm tier, then the hot tier
+     * This affects the system searchable snapshot cache index (not the searchable snapshot index itself)
+     */
+    public static final String DATA_TIERS_CACHE_INDEX_PREFERENCE = String.join(
+        ",",
+        DataTier.DATA_COLD,
+        DataTier.DATA_FROZEN,
+        DataTier.DATA_WARM,
+        DataTier.DATA_HOT
+    );
+
+    /**
+     * Returns the preference for new searchable snapshot indices. When
+     * performing a full mount the preference is cold - warm - hot. When
+     * performing a partial mount the preference is frozen - cold - warm - hot.
      */
-    public static final String DATA_TIERS_PREFERENCE = String.join(",", DataTier.DATA_COLD, DataTier.DATA_WARM, DataTier.DATA_HOT);
+    public static String getDataTiersPreference(MountSearchableSnapshotRequest.Storage type) {
+        switch (type) {
+            case FULL_COPY:
+                return String.join(",", DataTier.DATA_COLD, DataTier.DATA_WARM, DataTier.DATA_HOT);
+            case SHARED_CACHE:
+                return String.join(",", DataTier.DATA_FROZEN, DataTier.DATA_COLD, DataTier.DATA_WARM, DataTier.DATA_HOT);
+            default:
+                throw new IllegalArgumentException("unknown searchable snapshot type [" + type + "]");
+        }
+    }
 
     private volatile Supplier<RepositoriesService> repositoriesServiceSupplier;
     private final SetOnce<BlobStoreCacheService> blobStoreCacheService = new SetOnce<>();
@@ -442,7 +466,7 @@ public class SearchableSnapshots extends Plugin implements IndexStorePlugin, Eng
             .put(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "0-1")
             .put(IndexMetadata.SETTING_PRIORITY, "900")
             .put(IndexSettings.INDEX_TRANSLOG_DURABILITY_SETTING.getKey(), Translog.Durability.ASYNC)
-            .put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, DATA_TIERS_PREFERENCE)
+            .put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, DATA_TIERS_CACHE_INDEX_PREFERENCE)
             .build();
     }
 

+ 2 - 2
x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/TransportMountSearchableSnapshotAction.java

@@ -48,7 +48,7 @@ import java.util.Optional;
 
 import static org.elasticsearch.index.IndexModule.INDEX_RECOVERY_TYPE_SETTING;
 import static org.elasticsearch.index.IndexModule.INDEX_STORE_TYPE_SETTING;
-import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.DATA_TIERS_PREFERENCE;
+import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.getDataTiersPreference;
 
 /**
  * Action that mounts a snapshot as a searchable snapshot, by converting the mount request into a restore request with specific settings
@@ -191,7 +191,7 @@ public class TransportMountSearchableSnapshotAction extends TransportMasterNodeA
                             Settings.builder()
                                 .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) // can be overridden
                                 .put(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, false) // can be overridden
-                                .put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, DATA_TIERS_PREFERENCE)
+                                .put(DataTierAllocationDecider.INDEX_ROUTING_PREFER, getDataTiersPreference(request.storage()))
                                 .put(request.indexSettings())
                                 .put(
                                     buildIndexSettings(repoData.getUuid(), request.repositoryName(), snapshotId, indexId, request.storage())