Skip to main content

Cache values with a time to live

In this tutorial, you cache values that expire on their own. You write entries with a time to live (TTL), read the remaining TTL back, watch an entry disappear when its TTL elapses, and learn how to change or remove a TTL on a key that already exists. This is the pattern behind session tokens, short-lived credentials, and any cached value that should not outlive its usefulness. You need the .NET 8 SDK or later and a text editor.

You set a TTL at write time with Put.WithTtl, and you read what is left with GetTimeToLiveAsync. Both work against an in-process store, so you can complete the core of this tutorial with nothing but the SDK.

1. Create a project and add the SDK​

Create a console project and add the Zaris SDK.

dotnet new console -o zaris-ttl
cd zaris-ttl
dotnet add package Clustron.Zaris.SDK

2. Get a client​

In Program.cs, register an in-process store and resolve the client. TTL on writes and reads is available on the base client, so you do not need the IZaris cast for this tutorial.

using Clustron.Zaris.Client;
using Clustron.Zaris.Client.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection()
.AddClustronZaris("demo", "zaris://inproc/demo")
.BuildServiceProvider();

var client = (IZaris)await services
.GetRequiredService<IZarisClientProvider>()
.GetAsync("demo");

3. Write a value with a TTL​

Pass Put.WithTtl as the put options to store a value that expires. Here a session token is cached for two seconds so the example stays short; in real code you would use minutes or hours.

var put = await client.PutAsync(
"session:alice",
"token-abc123",
Put.WithTtl(TimeSpan.FromSeconds(2)));

Console.WriteLine($"Stored: {put.IsSuccess}");

Put.WithTtl returns a PutOptions carrying the TTL, and the server stamps the expiry when it stores the entry. You do not schedule anything yourself.

4. Read the remaining time to live​

Use GetTimeToLiveAsync to see how long a key has left. It returns a KvResult<TimeSpan?>: on success, Value is the remaining time, or null when the key exists but has no TTL. If the key is absent, IsSuccess is false with Status set to KvStatus.NotFound.

var ttl = await client.GetTimeToLiveAsync("session:alice");

if (ttl.IsSuccess)
{
Console.WriteLine(ttl.Value.HasValue
? $"Expires in {ttl.Value.Value.TotalSeconds:0.0}s"
: "No expiry set (permanent)");
}

5. Observe the value expire​

The value is readable while the TTL is running and gone once it elapses. Read it immediately, wait past the two-second window, then read again. After expiry, GetAsync reports KvStatus.NotFound.

var before = await client.GetAsync<string>("session:alice");
Console.WriteLine($"Before: success={before.IsSuccess}, value={before.Value}");

await Task.Delay(TimeSpan.FromSeconds(2.5));

var after = await client.GetAsync<string>("session:alice");
Console.WriteLine($"After: success={after.IsSuccess}, status={after.Status}");

The first read succeeds and prints the token; the second read fails with Status == KvStatus.NotFound because the entry has expired. You never delete the key yourself.

6. Change or remove a TTL on an existing key​

Sometimes you want to re-arm the expiry on a key you already stored, or make a key permanent, without rewriting its value. ExpireAsync sets or replaces a key's TTL, and PersistAsync removes it. Both return a KvResult, so check IsSuccess before assuming they took effect.

var expire = await client.ExpireAsync("session:alice", TimeSpan.FromMinutes(30));

if (expire.IsSuccess)
{
Console.WriteLine("TTL extended to 30 minutes");
}
else
{
Console.WriteLine($"ExpireAsync unavailable here: {expire.Status} - {expire.Error}");
}

ExpireAsync and PersistAsync route the TTL change through the cluster's partition map. On a running cluster node that check passes and the call succeeds. In a bare single-process demo like this one the partition map is not initialized, so these two calls return KvStatus.Unavailable with the message "partition map not initialized" — that is expected, not a bug. Guarding on IsSuccess, as above, keeps the sample honest whether you run it in-process or against a cluster. To set a TTL that always works in-process, set it at write time with Put.WithTtl, as in step 3.

What you built​

You built a cache whose entries clean themselves up. You set expiry at write time with Put.WithTtl, inspected the remaining time with GetTimeToLiveAsync (reading Value as a TimeSpan?, where null means no expiry), and watched a key vanish once its TTL elapsed, after which GetAsync returns KvStatus.NotFound. You also saw that ExpireAsync and PersistAsync re-arm or remove a TTL on an existing key against a running cluster, and how to detect the KvStatus.Unavailable they return in a single-process demo. Prefer Put.WithTtl when you write the value anyway; reach for ExpireAsync/PersistAsync when you need to adjust the lifetime of a key you already stored.

Next steps​