Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/configuration/provisioning.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,37 @@ For more information about the replication settings, see the **[Data Replication

TLS verification for replication destinations is configured globally for the source instance with `RS_REPLICATION_VERIFY_SSL` and `RS_REPLICATION_CA_PATH`.
These settings apply to all replication tasks created through provisioning, the HTTP API, SDKs, CLI, or the Web Console.

## Lifecycle Provisioning

You can provision lifecycle policies by setting environment variables. Lifecycle policies run in the background and currently support deleting old records from a bucket.

| Name | Default | Description |
| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| `RS_LIFECYCLE_<ID>_NAME` | | Provisioned lifecycle policy name (required) |
| `RS_LIFECYCLE_<ID>_TYPE` | delete | Lifecycle action type. Currently only `delete` is supported. |
| `RS_LIFECYCLE_<ID>_BUCKET` | | Bucket to apply the lifecycle policy to (required) |
| `RS_LIFECYCLE_<ID>_MAX_AGE` | | Maximum record age before deletion, for example `30d`, `24h`, or `3600s` (required). Minimum value is `1h`. |
| `RS_LIFECYCLE_<ID>_INTERVAL` | 3600s | Interval between lifecycle runs, for example `10m`, `1h`, or `3600s`. Minimum value is `10m`. |
| `RS_LIFECYCLE_<ID>_ENTRIES` | | Comma-separated list of entries to clean. If empty, all entries are matched. Prefix wildcards are supported. |
| `RS_LIFECYCLE_<ID>_WHEN` | | JSON condition for records to delete. Uses the same condition syntax as queries, but `#ext` is not supported. |
| `RS_LIFECYCLE_<ID>_MODE` | enabled | Policy mode: `enabled`, `disabled`, or `dry_run`. |

Example:

```env
RS_LIFECYCLE_A_NAME=purge-sensors-30d
RS_LIFECYCLE_A_TYPE=delete
RS_LIFECYCLE_A_BUCKET=telemetry
RS_LIFECYCLE_A_ENTRIES=sensors/*,env/temp,env/humidity
RS_LIFECYCLE_A_MAX_AGE=30d
RS_LIFECYCLE_A_INTERVAL=10m
RS_LIFECYCLE_A_WHEN={"$eq":["&label","true"]}
RS_LIFECYCLE_A_MODE=dry_run
```

Use `dry_run` mode to count matching records and write lifecycle audit events without deleting data. This is useful for validating `max_age`, `entries`, and `when` filters before switching the policy to `enabled`.

Provisioned lifecycle policies cannot be updated or removed through the API. Change their environment variables and restart ReductStore to update them. If `RS_LIFECYCLE_<ID>_MODE` is not set, ReductStore preserves the existing stored mode for an already provisioned lifecycle policy.

For more information about lifecycle policies, see the **[Lifecycle Policies Guide](../guides/lifecycle-policies.mdx)**.
5 changes: 4 additions & 1 deletion docs/configuration/settings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,10 @@ ReductStore can persist aggregated API interactions in the `$audit` system bucke

Notes:

- If `RS_AUDIT_ENABLED` is unset, audit defaults to `true` when `RS_API_TOKEN` is set, otherwise `false`.
- If `RS_AUDIT_ENABLED` is unset, audit defaults to `true` when `RS_API_TOKEN` is set or lifecycle policies are configured, otherwise `false`.
- `RS_AUDIT_ENABLED` explicitly overrides the token-based default.
- `RS_AUDIT_QUOTA_SIZE` is applied only when audit is enabled.
- If `RS_AUDIT_QUOTA_SIZE` is unset, `$audit` capacity is unlimited.
- Audit records are stored as structured events with common fields: `type`, `timestamp`, `instance`, `entry_name`, `status`, `message`, and `payload`.
- API audit events use `type: "api_call"` and keep request details (`token_name`, `method`, `path`, `client_ip`, `call_count`, and `duration`) in `payload`.
- Lifecycle audit events use `type: "lifecycle_run"` and keep run details (`policy_name`, `action_type`, `bucket`, `duration`, `processed_records`, `error_code`, and `error_message`) in `payload`.
3 changes: 3 additions & 0 deletions docs/examples/cli/lifecycle_policy_browse.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
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
3 changes: 3 additions & 0 deletions docs/examples/cli/lifecycle_policy_create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
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 --max-age 30d --interval 1h --entries cli-example --when '{"&anomaly":{"$eq":1}}'
4 changes: 4 additions & 0 deletions docs/examples/cli/lifecycle_policy_mode.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
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
2 changes: 2 additions & 0 deletions docs/examples/cli/lifecycle_policy_remove.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
reduct-cli lifecycle rm local/lifecycle-to-remove --yes
27 changes: 27 additions & 0 deletions docs/examples/cpp/src/lifecycle_policy_browse.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#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;
}
22 changes: 22 additions & 0 deletions docs/examples/cpp/src/lifecycle_policy_create.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#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"},
.max_age = "30d",
.interval = "1h",
.when = R"({"&anomaly":{"$eq":1}})",
});
assert(err == Error::kOk);
}
18 changes: 18 additions & 0 deletions docs/examples/cpp/src/lifecycle_policy_mode.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#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);
}
12 changes: 12 additions & 0 deletions docs/examples/cpp/src/lifecycle_policy_remove.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#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);
}
11 changes: 11 additions & 0 deletions docs/examples/curl/lifecycle_policy_browse.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/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
10 changes: 10 additions & 0 deletions docs/examples/curl/lifecycle_policy_create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/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"],"max_age":"30d","interval":"1h","when":{"&anomaly":{"$eq":1}}}' \
"${API_PATH}"/lifecycles/my-lifecycle
20 changes: 20 additions & 0 deletions docs/examples/curl/lifecycle_policy_mode.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/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
9 changes: 9 additions & 0 deletions docs/examples/curl/lifecycle_policy_remove.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/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
30 changes: 30 additions & 0 deletions docs/examples/go/src/lifecycle_policy_browse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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)
}
32 changes: 32 additions & 0 deletions docs/examples/go/src/lifecycle_policy_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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"},
MaxAge: "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)
}
}
22 changes: 22 additions & 0 deletions docs/examples/go/src/lifecycle_policy_mode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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)
}
}
15 changes: 15 additions & 0 deletions docs/examples/go/src/lifecycle_policy_remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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.DeleteLifecycle(context.Background(), "lifecycle-to-remove"); err != nil {
panic(err)
}
}
14 changes: 14 additions & 0 deletions docs/examples/js/src/lifecycle_policy_browse.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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);
15 changes: 15 additions & 0 deletions docs/examples/js/src/lifecycle_policy_create.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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"],
maxAge: "30d",
interval: "1h",
when: { "&anomaly": { $eq: 1 } },
};

await client.createLifecycle("my-lifecycle", settings);
7 changes: 7 additions & 0 deletions docs/examples/js/src/lifecycle_policy_mode.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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);
4 changes: 4 additions & 0 deletions docs/examples/js/src/lifecycle_policy_remove.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Client } from "reduct-js";

const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
await client.deleteLifecycle("lifecycle-to-remove");
22 changes: 22 additions & 0 deletions docs/examples/provisioning/lifecycle_create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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_MAX_AGE: 30d
RS_LIFECYCLE_1_INTERVAL: 10m
RS_LIFECYCLE_1_ENTRIES: "sensor-1,sensor-2"

RS_LIFECYCLE_1_WHEN: |
{
"&sensor_type": { "$eq": "temperature" }
}
19 changes: 19 additions & 0 deletions docs/examples/py/src/lifecycle_policy_browse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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())
Loading
Loading