Lifecycle Policies
Lifecycle policies are background tasks that automatically delete or compress old records from your buckets. You can configure policies to run at specific intervals and target records based on age, entry names, or custom conditions. This helps use your storage efficiently while keeping relevant data accessible.
How It Worksβ
A lifecycle policy runs at a configurable interval and scans the target bucket for records older than older_than. It applies the configured action to matching records and logs run diagnostics in the $system/lifecycle system entry. Delete policies remove matching records, while compress policies compress old persisted blocks with zstd. Compressed blocks remain transparently readable and are decompressed first if new writes target them. You can optionally scope policies to specific entries or filter records using conditions.
Policies are stored on disk and remain active after restarts. However, read-only (replica) instances cannot execute lifecycle policies. Every policy run is recorded in the $system/lifecycle system entry for transparency and debugging.
Supported Actionsβ
ReductStore supports these action types for lifecycle policies:
| Type | Description |
|---|---|
delete | Removes records older than older_than from the bucket |
compress | Compresses persisted blocks containing records older than older_than with zstd |
Compress policies do not delete records. A common retention pattern is to compress old data first, then delete very old data later with a separate delete policy.
Compression is applied per storage block for efficiency, not per individual record. Because of this block-level behavior, the when condition is not supported for compress policies.
Policy Settingsβ
You can configure lifecycle policies using the following settings:
| Setting | Description | Required | Default |
|---|---|---|---|
type | Action type (e.g., delete or compress) | Yes | - |
bucket | Target bucket name | Yes | - |
entries | List of entries to scope the policy (wildcard * supported) | No | - |
older_than | Maximum age of records before action is applied (e.g., 30d, 1h, 5m) | Yes | - |
interval | Policy execution interval (e.g., 3600s, 10m) | No | 3600s |
when | JSON condition to filter records (see Conditional Query Reference) | No | - |
mode | Policy mode: enabled (default), dry_run, or disabled | No | enabled |
Conditional Policiesβ
You can refine lifecycle policies using the entries and when parameters:
entries: Limit the policy to specific entries (e.g.,sensor-1,sensor-2orsensor-*)when: Apply adeletepolicy only to records matching a condition (e.g.,{"&sensor_type": {"$eq": "temperature"}}). This condition is not supported forcompresspolicies.
For more details, see the Conditional Query Reference.
Exampleβ
Imagine you store telemetry data from temperature sensors in a bucket called telemetry. You want to automatically delete records older than 30 days, but only for specific sensors (sensor-1 and sensor-2) and only if the sensor_type label equals temperature. Hereβs how youβd configure the policy:
version: "3"
services:
reductstore:
image: reduct/store:latest
ports:
- "8383:8383"
volumes:
- ./data:/data
environment:
RS_API_TOKEN: my-api-token
RS_BUCKET_1_NAME: telemetry
RS_LIFECYCLE_1_NAME: purge-sensors-30d
RS_LIFECYCLE_1_BUCKET: telemetry
RS_LIFECYCLE_1_TYPE: delete
RS_LIFECYCLE_1_OLDER_THAN: 30d
RS_LIFECYCLE_1_INTERVAL: 10m
RS_LIFECYCLE_1_ENTRIES: "sensor-1,sensor-2"
RS_LIFECYCLE_1_WHEN: |
{
"&sensor_type": { "$eq": "temperature" }
}
Managing Lifecycle Policiesβ
Here you will find examples of how to create, list, retrieve, update modes, and delete lifecycle policies using the ReductStore SDKs, REST API, CLI and Web Console.
All examples are written for a local ReductStore instance available at http://127.0.0.1:8383 with API token my-token.
For more information on setting up a local ReductStore instance, see the Getting Started guide.
Creating a Lifecycle Policyβ
To create a lifecycle policy, you must provide at least the action type, target bucket, and older_than. The examples below also include optional filters (entries and when) and a custom interval.
- CLI
- Web Console
- Python
- JavaScript
- Go
- Rust
- C++
- cURL
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
reduct-cli bucket create local/my-bucket
reduct-cli lifecycle create local/my-lifecycle my-bucket --type delete --older-than 30d --interval 1h --entries cli-example --when '{"&anomaly":{"$eq":1}}'
Steps to create a lifecycle policy using the Web Console:
- Open the Web Console at
http://127.0.0.1:8383in your browser. - Enter the API token if authorization is enabled.
- Click "Lifecycles" in the left sidebar.
- Click the plus icon in the top-right corner.
- Fill in the lifecycle name, type, bucket,
older_than, optionalinterval, and optional filters (entriesandwhen). - Click "Create Lifecycle".
import asyncio
from reduct import Client, LifecycleSettings, LifecycleType
async def main():
async with Client("http://127.0.0.1:8383", api_token="my-token") as client:
await client.create_bucket("my-bucket", exist_ok=True)
settings = LifecycleSettings(
type=LifecycleType.DELETE,
bucket="my-bucket",
entries=["py-example"],
older_than="30d",
interval="1h",
when={"&anomaly": {"$eq": 1}},
)
await client.create_lifecycle("my-lifecycle", settings)
asyncio.run(main())
import { Client, LifecycleType } from "reduct-js";
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
await client.getOrCreateBucket("my-bucket");
const settings = {
lifecycleType: LifecycleType.DELETE,
bucket: "my-bucket",
entries: ["js-example"],
olderThan: "30d",
interval: "1h",
when: { "&anomaly": { $eq: 1 } },
};
await client.createLifecycle("my-lifecycle", settings);
package main
import (
"context"
reduct "github.com/reductstore/reduct-go"
model "github.com/reductstore/reduct-go/model"
)
func main() {
client := reduct.NewClient("http://localhost:8383", reduct.ClientOptions{APIToken: "my-token"})
_, err := client.CreateOrGetBucket(context.Background(), "my-bucket", nil)
if err != nil {
panic(err)
}
settings := model.LifecycleSettings{
LifecycleType: model.LifecycleTypeDelete,
Bucket: "my-bucket",
Entries: []string{"go-example"},
OlderThan: "30d",
Interval: "1h",
When: map[string]any{
"&anomaly": map[string]any{"$eq": 1},
},
}
err = client.CreateLifecycle(context.Background(), "my-lifecycle", settings)
if err != nil {
panic(err)
}
}
use reduct_rs::{condition, LifecycleType, ReductClient, ReductError};
#[tokio::main]
async fn main() -> Result<(), ReductError> {
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
let _ = client
.create_bucket("my-bucket")
.exist_ok(true)
.send()
.await?;
client
.create_lifecycle("my-lifecycle")
.lifecycle_type(LifecycleType::Delete)
.bucket("my-bucket")
.entries(vec!["rs-example".to_string()])
.older_than("30d")
.interval("1h")
.when(condition!({ "&anomaly": { "$eq": 1 } }))
.send()
.await?;
Ok(())
}
#include <cassert>
#include <reduct/client.h>
using reduct::Error;
using reduct::IClient;
int main() {
auto client = IClient::Build("http://127.0.0.1:8383", {.api_token = "my-token"});
auto [bucket, bucket_err] = client->GetOrCreateBucket("my-bucket");
assert(bucket_err == Error::kOk);
auto err = client->CreateLifecycle("my-lifecycle", IClient::LifecycleSettings{
.type = IClient::LifecycleType::kDelete,
.bucket = "my-bucket",
.entries = {"cpp-example"},
.older_than = "30d",
.interval = "1h",
.when = R"({"&anomaly":{"$eq":1}})",
});
assert(err == Error::kOk);
}
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
curl -X POST \
-H "${AUTH_HEADER}" \
-d '{"bucket":"my-bucket","entries":["curl-example"],"older_than":"30d","interval":"1h","when":{"&anomaly":{"$eq":1}}}' \
"${API_PATH}"/lifecycles/my-lifecycle
Browsing Lifecycle Policiesβ
You can list all lifecycle policies and inspect full settings/details of a specific policy. Lifecycle list responses include the action type and the last run timestamp when available.
- CLI
- Web Console
- Python
- JavaScript
- Go
- Rust
- C++
- cURL
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
reduct-cli lifecycle ls local --full
reduct-cli lifecycle show local/my-lifecycle
Steps to browse lifecycle policies in the Web Console:
- Open the Web Console at
http://127.0.0.1:8383in your browser. - Enter the API token if authorization is enabled.
- Click "Lifecycles" in the left sidebar.
- Open any lifecycle policy in the list to inspect settings and status.
import asyncio
from reduct import Client
async def main():
async with Client("http://127.0.0.1:8383", api_token="my-token") as client:
for lifecycle in await client.get_lifecycles():
print("Lifecycle:", lifecycle.name)
print("Mode:", lifecycle.mode)
print("Running:", lifecycle.is_running)
print("Provisioned:", lifecycle.is_provisioned)
details = await client.get_lifecycle_detail("my-lifecycle")
print("Lifecycle:", details.info.name)
print("Settings:", details.settings)
asyncio.run(main())
import { Client } from "reduct-js";
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
for (const lifecycle of await client.getLifecycleList()) {
console.log("Lifecycle:", lifecycle.name);
console.log("Mode:", lifecycle.mode);
console.log("Running:", lifecycle.isRunning);
console.log("Provisioned:", lifecycle.isProvisioned);
}
const details = await client.getLifecycle("my-lifecycle");
console.log("Lifecycle:", details.info.name);
console.log("Settings:", details.settings);
package main
import (
"context"
reduct "github.com/reductstore/reduct-go"
)
func main() {
client := reduct.NewClient("http://localhost:8383", reduct.ClientOptions{APIToken: "my-token"})
lifecycles, err := client.GetLifecycles(context.Background())
if err != nil {
panic(err)
}
for _, lifecycle := range lifecycles {
println("Lifecycle:", lifecycle.Name)
println("Mode:", string(lifecycle.Mode))
println("Running:", lifecycle.IsRunning)
println("Provisioned:", lifecycle.IsProvisioned)
}
detail, err := client.GetLifecycle(context.Background(), "my-lifecycle")
if err != nil {
panic(err)
}
println("Lifecycle:", detail.Info.Name)
println("Bucket:", detail.Settings.Bucket)
}
use reduct_rs::{ReductClient, ReductError};
#[tokio::main]
async fn main() -> Result<(), ReductError> {
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
for lifecycle in client.list_lifecycles().await? {
println!("Lifecycle: {}", lifecycle.name);
println!("Mode: {:?}", lifecycle.mode);
println!("Running: {}", lifecycle.is_running);
println!("Provisioned: {}", lifecycle.is_provisioned);
}
let detail = client.get_lifecycle("my-lifecycle").await?;
println!("Lifecycle: {}", detail.info.name);
println!("Settings: {:?}", detail.settings);
Ok(())
}
#include <iostream>
#include <reduct/client.h>
using reduct::IClient;
int main() {
auto client = IClient::Build("http://127.0.0.1:8383", {.api_token = "my-token"});
auto [lifecycles, list_err] = client->GetLifecycleList();
if (list_err != reduct::Error::kOk) {
return 1;
}
for (const auto& lifecycle : lifecycles) {
std::cout << "Lifecycle: " << lifecycle.name << std::endl;
std::cout << "Running: " << lifecycle.is_running << std::endl;
std::cout << "Provisioned: " << lifecycle.is_provisioned << std::endl;
}
auto [detail, detail_err] = client->GetLifecycle("my-lifecycle");
if (detail_err != reduct::Error::kOk) {
return 1;
}
std::cout << "Lifecycle: " << detail.info.name << std::endl;
std::cout << "Bucket: " << detail.settings.bucket << std::endl;
}
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
curl -H "${AUTH_HEADER}" \
"${API_PATH}"/lifecycles
curl -H "${AUTH_HEADER}" \
"${API_PATH}"/lifecycles/my-lifecycle
Lifecycle Modesβ
Lifecycle policies can be switched between modes without recreating the policy.
- CLI
- Web Console
- Python
- JavaScript
- Go
- Rust
- C++
- cURL
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
reduct-cli lifecycle dry-run local/my-lifecycle
reduct-cli lifecycle disable local/my-lifecycle
reduct-cli lifecycle enable local/my-lifecycle
Steps to update lifecycle policy mode using the Web Console:
- Open the Web Console at
http://127.0.0.1:8383in your browser. - Enter the API token if authorization is enabled.
- Click "Lifecycles" in the left sidebar.
- Update the policy mode by clicking the dropdown button next to the policy name and selecting the desired mode (
enabled,dry_run, ordisabled).
import asyncio
from reduct import Client, LifecycleMode
async def main():
async with Client("http://127.0.0.1:8383", api_token="my-token") as client:
await client.set_lifecycle_mode("my-lifecycle", LifecycleMode.DRY_RUN)
await client.set_lifecycle_mode("my-lifecycle", LifecycleMode.DISABLED)
await client.set_lifecycle_mode("my-lifecycle", LifecycleMode.ENABLED)
asyncio.run(main())
import { Client, LifecycleMode } from "reduct-js";
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
await client.setLifecycleMode("my-lifecycle", LifecycleMode.DRY_RUN);
await client.setLifecycleMode("my-lifecycle", LifecycleMode.DISABLED);
await client.setLifecycleMode("my-lifecycle", LifecycleMode.ENABLED);
package main
import (
"context"
reduct "github.com/reductstore/reduct-go"
model "github.com/reductstore/reduct-go/model"
)
func main() {
client := reduct.NewClient("http://localhost:8383", reduct.ClientOptions{APIToken: "my-token"})
if err := client.SetLifecycleMode(context.Background(), "my-lifecycle", model.LifecycleModeDryRun); err != nil {
panic(err)
}
if err := client.SetLifecycleMode(context.Background(), "my-lifecycle", model.LifecycleModeDisabled); err != nil {
panic(err)
}
if err := client.SetLifecycleMode(context.Background(), "my-lifecycle", model.LifecycleModeEnabled); err != nil {
panic(err)
}
}
use reduct_rs::{LifecycleMode, ReductClient, ReductError};
#[tokio::main]
async fn main() -> Result<(), ReductError> {
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
client
.set_lifecycle_mode("my-lifecycle", LifecycleMode::DryRun)
.await?;
client
.set_lifecycle_mode("my-lifecycle", LifecycleMode::Disabled)
.await?;
client
.set_lifecycle_mode("my-lifecycle", LifecycleMode::Enabled)
.await?;
Ok(())
}
#include <cassert>
#include <reduct/client.h>
using reduct::Error;
using reduct::IClient;
int main() {
auto client = IClient::Build("http://127.0.0.1:8383", {.api_token = "my-token"});
auto err = client->SetLifecycleMode("my-lifecycle", IClient::LifecycleMode::kDryRun);
assert(err == Error::kOk);
err = client->SetLifecycleMode("my-lifecycle", IClient::LifecycleMode::kDisabled);
assert(err == Error::kOk);
err = client->SetLifecycleMode("my-lifecycle", IClient::LifecycleMode::kEnabled);
assert(err == Error::kOk);
}
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
curl -X PATCH \
-H "${AUTH_HEADER}" \
-d '{"mode":"dry_run"}' \
"${API_PATH}"/lifecycles/my-lifecycle/mode
curl -X PATCH \
-H "${AUTH_HEADER}" \
-d '{"mode":"disabled"}' \
"${API_PATH}"/lifecycles/my-lifecycle/mode
curl -X PATCH \
-H "${AUTH_HEADER}" \
-d '{"mode":"enabled"}' \
"${API_PATH}"/lifecycles/my-lifecycle/mode
Removing a Lifecycle Policyβ
You can remove a lifecycle policy when it is no longer needed.
Provisioned lifecycle policies cannot be modified or deleted through the API. To change or remove them, update environment variables and restart ReductStore.
- CLI
- Web Console
- Python
- JavaScript
- Go
- Rust
- C++
- cURL
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
reduct-cli lifecycle rm local/lifecycle-to-remove --yes
Steps to remove a lifecycle policy using the Web Console:
- Open the Web Console at
http://127.0.0.1:8383in your browser. - Enter the API token if authorization is enabled.
- Click "Lifecycles" in the left sidebar.
- Open the lifecycle policy details.
- Click the trash icon in the bottom-right corner and confirm by typing the policy name.
import asyncio
from reduct import Client
async def main():
async with Client("http://127.0.0.1:8383", api_token="my-token") as client:
await client.delete_lifecycle("lifecycle-to-remove")
asyncio.run(main())
import { Client } from "reduct-js";
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
await client.deleteLifecycle("lifecycle-to-remove");
package main
import (
"context"
reduct "github.com/reductstore/reduct-go"
)
func main() {
client := reduct.NewClient("http://localhost:8383", reduct.ClientOptions{APIToken: "my-token"})
if err := client.RemoveLifecycle(context.Background(), "lifecycle-to-remove"); err != nil {
panic(err)
}
}
use reduct_rs::{ReductClient, ReductError};
#[tokio::main]
async fn main() -> Result<(), ReductError> {
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
client.delete_lifecycle("lifecycle-to-remove").await?;
Ok(())
}
#include <cassert>
#include <reduct/client.h>
using reduct::Error;
using reduct::IClient;
int main() {
auto client = IClient::Build("http://127.0.0.1:8383", {.api_token = "my-token"});
auto err = client->DeleteLifecycle("lifecycle-to-remove");
assert(err == Error::kOk);
}
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
curl -X DELETE \
-H "${AUTH_HEADER}" \
"${API_PATH}"/lifecycles/lifecycle-to-remove
Run Diagnosticsβ
Every lifecycle run writes a diagnostic record to the $system bucket under the lifecycle/<instance>/<policy-name> entry. For example, diagnostics for a policy named purge-sensors-30d on instance node-1 are stored in $system/lifecycle/node-1/purge-sensors-30d.
Lifecycle diagnostics are written as lifecycle_run system events. The stored JSON record contains these fields:
| Field | Description |
|---|---|
timestamp | Event timestamp in Unix microseconds. |
instance | ReductStore instance name that emitted the diagnostic record. |
status | HTTP-style status code for the lifecycle run result. |
message | Error message for the lifecycle run, or an empty string on success. |
policy_name | Lifecycle policy name. |
action_type | Lifecycle action type, such as delete or compress. |
bucket | Target bucket name. |
duration | Lifecycle run duration in seconds. |
processed_records | Number of records processed by the lifecycle action, or null on failure. |
error_code | Error status code. Present only when the lifecycle run fails. |
error_message | Error details. Present only when the lifecycle run fails. |
The record also has a status label with the same value as the status field, so you can filter diagnostics by result code.