Skip to content

Commit c7059f9

Browse files
authored
Merge pull request #532 from tidesdb/ttl-article
ttl article on using it in tidesql
2 parents e3e45a9 + c3076f7 commit c7059f9

3 files changed

Lines changed: 170 additions & 0 deletions

File tree

astro.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ export default defineConfig({
167167
{
168168
label: 'Articles',
169169
items: [
170+
{
171+
172+
label: 'TTL (Time to live) In MariaDB',
173+
link: 'articles/ttl-time-to-live-using-tidesql-in-mariadb'
174+
},
170175
{
171176

172177
label: 'Configuring TideSQL in MariaDB',
119 KB
Loading
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
title: "TTL (Time to live) using TideSQL In MariaDB"
3+
description: "TTL, what is it? how to use it, in MariaDB using TidesDB's TideSQL plugin engine."
4+
head:
5+
- tag: meta
6+
attrs:
7+
property: og:image
8+
content: https://tidesdb.com/pexels-sami-aksu-48867324-9213725.jpg
9+
- tag: meta
10+
attrs:
11+
name: twitter:image
12+
content: https://tidesdb.com/pexels-sami-aksu-48867324-9213725.jpg
13+
---
14+
15+
<div class="article-image">
16+
17+
![](/pexels-sami-aksu-48867324-9213725.jpg)
18+
19+
</div>
20+
21+
*by <a target="_blank" href="https://alexpadula.com">Alex Gaetano Padula</a>*
22+
23+
*published on June 17th, 2026*
24+
25+
Some of you may not know but TideSQL has a built in TTL (time to live) capability. Say I want a row to live only a short amount of time and then disappear, this is what you'd use. You configure TTL in seconds. You can set it automatically for every new row in a table, or per insert, and if you want to update a row that hasn't expired yet to live longer, you can do that too.
26+
27+
To do this in TideSQL is rather easy, first, obviously, you need the engine installed and running.
28+
29+
There are three places you can set a TTL, and they stack in a clear order of priority. A per-row TTL column wins over the session variable, which wins over the table default, and if none of them apply the row just never expires.
30+
31+
The simplest is a table-level default, every row written to the table inherits it. Here I give a sessions table an 8 second TTL.
32+
33+
```sql
34+
CREATE TABLE sessions (
35+
id INT PRIMARY KEY,
36+
token VARCHAR(40)
37+
) ENGINE=TidesDB TTL=8;
38+
39+
INSERT INTO sessions VALUES (1,'tok-a'), (2,'tok-b'), (3,'tok-c');
40+
SELECT * FROM sessions;
41+
```
42+
43+
Right after the insert all three rows are there.
44+
45+
```
46+
+----+-------+
47+
| id | token |
48+
+----+-------+
49+
| 1 | tok-a |
50+
| 2 | tok-b |
51+
| 3 | tok-c |
52+
+----+-------+
53+
```
54+
55+
Wait eight seconds, run the same select, and they're gone.
56+
57+
```sql
58+
SELECT * FROM sessions;
59+
```
60+
```
61+
Empty set
62+
```
63+
64+
If different rows need different lifetimes, you can point TTL at a column instead. You mark an integer column with the `TTL` field option and its value becomes that row's lifetime in seconds, with 0 meaning never expire.
65+
66+
```sql
67+
CREATE TABLE events (
68+
id INT PRIMARY KEY,
69+
name VARCHAR(20),
70+
ttl_s INT `TTL`=1
71+
) ENGINE=TidesDB;
72+
73+
INSERT INTO events VALUES (1,'short',8), (2,'long',86400), (3,'forever',0);
74+
```
75+
76+
All three rows are visible at first.
77+
78+
```
79+
+----+---------+-------+
80+
| id | name | ttl_s |
81+
+----+---------+-------+
82+
| 1 | short | 8 |
83+
| 2 | long | 86400 |
84+
| 3 | forever | 0 |
85+
+----+---------+-------+
86+
```
87+
88+
Eight seconds later only the long-lived and the never-expiring rows remain, row 1 aged out on its own.
89+
90+
```
91+
+----+---------+
92+
| id | name |
93+
+----+---------+
94+
| 2 | long |
95+
| 3 | forever |
96+
+----+---------+
97+
```
98+
99+
And if you don't want to bake TTL into the schema at all, there's a session variable, `tidesdb_ttl`, that applies to whatever you insert while it's set. Handy for a one-off load, or scoped to a single statement with `SET STATEMENT tidesdb_ttl=N FOR ...`.
100+
101+
```sql
102+
CREATE TABLE apicache (k VARCHAR(20) PRIMARY KEY, v VARCHAR(40)) ENGINE=TidesDB;
103+
104+
SET SESSION tidesdb_ttl = 8;
105+
INSERT INTO apicache VALUES ('a','val-a'), ('b','val-b');
106+
107+
SET SESSION tidesdb_ttl = 0;
108+
INSERT INTO apicache VALUES ('c','permanent');
109+
```
110+
111+
Rows a and b carry the 8 second TTL, c was inserted after I set it back to 0 so it stays. After the wait only c is left.
112+
113+
```
114+
+---+-----------+
115+
| k | v |
116+
+---+-----------+
117+
| c | permanent |
118+
+---+-----------+
119+
```
120+
121+
So what's actually happening underneath. TTL is set through the transaction and lives with the key-value pair within a TidesDB column family, every row in TideSQL is just a key-value pair in the engine, and the expiry rides along with it as an absolute timestamp.
122+
123+
A key-value pair is checked against its TTL in real time, the skip list treats it as expired the moment its time passes, so it reads as gone exactly like a tombstone would, without anything having to delete it. That's why the rows vanished from the selects above the instant their time was up, nothing ran a DELETE, no event scheduler fired, the engine just stops handing them back.
124+
125+
The bytes themselves are dropped later, when compaction merges the sorted runs. When a sorted run occurs, taking an l0 file and writing it out as an sstable, that sstable holds the latest version of the key which is now expired, and as compactions occur the system garbage collects it. The thing to know here is that a single sstable on its own won't shrink, the expired data is only physically removed when two or more sstables get merged together and the expired entries are dropped from the merged output.
126+
127+
You can watch that happen. Here I load 400,000 rows with a 20 second TTL, with a small write buffer so the load spills into several sstables instead of one.
128+
129+
```sql
130+
CREATE TABLE gc_demo (id INT PRIMARY KEY, payload CHAR(220)) ENGINE=TidesDB TTL=20;
131+
INSERT INTO gc_demo SELECT seq, REPEAT('y',220) FROM seq_1_to_400000;
132+
```
133+
134+
Right after the load the rows are all there, sitting across eight sstables and about 149M on disk.
135+
136+
```
137+
rows visible : 400000
138+
sstables : 8
139+
on disk : 149M
140+
```
141+
142+
Once the 20 seconds pass, every row is gone from queries instantly, but the disk hasn't moved, the eight sstables and 149M are still sitting there. This is the real-time read filter at work, the bytes just haven't been collected yet.
143+
144+
```
145+
rows visible : 0
146+
sstables : 8
147+
on disk : 149M
148+
```
149+
150+
Now I force the sstables to merge. Compaction does this on its own over time, `OPTIMIZE TABLE` just makes it happen right now. The merge drops every expired entry and the space comes back.
151+
152+
```sql
153+
OPTIMIZE TABLE gc_demo;
154+
```
155+
```
156+
rows visible : 0
157+
sstables : 3
158+
on disk : 84K
159+
```
160+
161+
149M down to 84K, the expired rows are physically gone.
162+
163+
And that's TTL in TideSQL. Set it on the table, on a column, or per session, and the engine handles expiry for you. Rows drop out of your queries the moment their time is up, and the disk space is reclaimed in the background as compaction runs. It's a clean fit for sessions, caches, rate limits, anything with a natural shelf life, no cleanup jobs to write and nothing to schedule.
164+
165+
Thanks for reading!

0 commit comments

Comments
 (0)