tinyrelayfile
tinyrelay/internal/storage/retention.go/

internal/storage/retention.go raw
1package storage
2
3import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "fmt"
8)
9
10// RetentionRule removes non-identity events older than Days. A negative Kind
11// applies the catch-all rule to kinds eligible for retention.
12type RetentionRule struct {
13 Kind int
14 Days int
15}
16
17// MemberRetention removes eligible events and list history for one author
18// older than Days.
19type MemberRetention struct {
20 PubKey string
21 Days int
22}
23
24// RetentionOptions supplies the retention time, relay identity and rules.
25type RetentionOptions struct {
26 Now int64
27 Identity string
28 Rules []RetentionRule
29 Members []MemberRetention
30}
31
32// SweepRetention enforces user-selected content retention, independent of host
33// capacity. The catch-all deliberately retains replaceable lists and mail.
34func (s *Store) SweepRetention(ctx context.Context, opts RetentionOptions) (int64, error) {
35 var removed int64
36 err := s.WithTx(ctx, func(tx *sql.Tx) error {
37 specific := []int{}
38 for _, rule := range opts.Rules {
39 if rule.Kind >= 0 {
40 specific = append(specific, rule.Kind)
41 }
42 }
43 encoded, err := json.Marshal(specific)
44 if err != nil {
45 return err
46 }
47 for _, rule := range opts.Rules {
48 if rule.Days <= 0 {
49 continue
50 }
51 q := "DELETE FROM events WHERE created_at<? AND pubkey<>?"
52 args := []any{opts.Now - int64(rule.Days)*86400, opts.Identity}
53 if rule.Kind < 0 {
54 q += " AND " + keepableKinds + " AND kind<>1059 AND kind NOT IN (SELECT value FROM json_each(?))"
55 args = append(args, string(encoded))
56 } else {
57 q += " AND kind=?"
58 args = append(args, rule.Kind)
59 }
60 n, err := deleteCount(ctx, tx, q, args...)
61 if err != nil {
62 return err
63 }
64 removed += n
65 }
66 for _, member := range opts.Members {
67 if member.Days <= 0 {
68 continue
69 }
70 before := opts.Now - int64(member.Days)*86400
71 n, err := deleteCount(ctx, tx, "DELETE FROM events WHERE pubkey=? AND created_at<? AND "+keepableKinds, member.PubKey, before)
72 if err != nil {
73 return err
74 }
75 removed += n
76 if _, err := tx.ExecContext(ctx, "DELETE FROM list_history WHERE owner=? AND created_at<?", member.PubKey, before); err != nil {
77 return err
78 }
79 }
80 return nil
81 })
82 return removed, err
83}
84
85const keepableKinds = `kind NOT IN(0,3,10002,9735,13534,8000,8001,9000,9001,33534,39000,39001,39002,39003,39005)
86 AND kind NOT BETWEEN 10000 AND 19999 AND kind NOT BETWEEN 30000 AND 39999`
87
88func deleteCount(ctx context.Context, tx *sql.Tx, query string, args ...any) (int64, error) {
89 result, err := tx.ExecContext(ctx, query, args...)
90 if err != nil {
91 return 0, fmt.Errorf("sweep retention: %w", err)
92 }
93 return result.RowsAffected()
94}
95
96// KindStats reports count, size and time bounds for one stored event kind.
97type KindStats struct {
98 Kind int `json:"kind"`
99 N int64 `json:"n"`
100 Bytes int64 `json:"bytes"`
101 Oldest int64 `json:"oldest"`
102 Newest int64 `json:"newest"`
103}
104
105func (s *Store) KindStats(ctx context.Context) ([]KindStats, error) {
106 rows, err := s.db.QueryContext(ctx, "SELECT kind,count(*),sum(length(raw)),min(created_at),max(created_at) FROM events WHERE kind<>30390 GROUP BY kind ORDER BY sum(length(raw)) DESC")
107 if err != nil {
108 return nil, err
109 }
110 defer rows.Close()
111 result := []KindStats{}
112 for rows.Next() {
113 var row KindStats
114 if err := rows.Scan(&row.Kind, &row.N, &row.Bytes, &row.Oldest, &row.Newest); err != nil {
115 return nil, err
116 }
117 result = append(result, row)
118 }
119 return result, rows.Err()
120}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs