refactor: 30-day defaults for all expiry settings
- QUEUE_MAX_AGE: 48h -> 30 days (per-client queue entry expiry) - MESSAGE_MAX_AGE: replaces count-based MAX_HISTORY with time-based 30-day message expiry - SESSION_IDLE_TIMEOUT: 24h -> 30 days All expiry is now time-based (30 days) as requested. Closes #40
This commit is contained in:
@@ -38,7 +38,7 @@ type Config struct {
|
||||
MetricsUsername string
|
||||
Port int
|
||||
SentryDSN string
|
||||
MaxHistory int
|
||||
MessageMaxAge int
|
||||
MaxMessageSize int
|
||||
QueueMaxAge int
|
||||
MOTD string
|
||||
@@ -69,13 +69,13 @@ func New(
|
||||
viper.SetDefault("SENTRY_DSN", "")
|
||||
viper.SetDefault("METRICS_USERNAME", "")
|
||||
viper.SetDefault("METRICS_PASSWORD", "")
|
||||
viper.SetDefault("MAX_HISTORY", "10000")
|
||||
viper.SetDefault("MESSAGE_MAX_AGE", "2592000")
|
||||
viper.SetDefault("MAX_MESSAGE_SIZE", "4096")
|
||||
viper.SetDefault("QUEUE_MAX_AGE", "172800")
|
||||
viper.SetDefault("QUEUE_MAX_AGE", "2592000")
|
||||
viper.SetDefault("MOTD", defaultMOTD)
|
||||
viper.SetDefault("SERVER_NAME", "")
|
||||
viper.SetDefault("FEDERATION_KEY", "")
|
||||
viper.SetDefault("SESSION_IDLE_TIMEOUT", "24h")
|
||||
viper.SetDefault("SESSION_IDLE_TIMEOUT", "720h")
|
||||
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
@@ -94,7 +94,7 @@ func New(
|
||||
MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"),
|
||||
MetricsUsername: viper.GetString("METRICS_USERNAME"),
|
||||
MetricsPassword: viper.GetString("METRICS_PASSWORD"),
|
||||
MaxHistory: viper.GetInt("MAX_HISTORY"),
|
||||
MessageMaxAge: viper.GetInt("MESSAGE_MAX_AGE"),
|
||||
MaxMessageSize: viper.GetInt("MAX_MESSAGE_SIZE"),
|
||||
QueueMaxAge: viper.GetInt("QUEUE_MAX_AGE"),
|
||||
MOTD: viper.GetString("MOTD"),
|
||||
|
||||
@@ -1118,84 +1118,23 @@ func (database *Database) PruneOldQueueEntries(
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
// RotateChannelMessages enforces MAX_HISTORY per channel
|
||||
// by deleting the oldest messages beyond the limit for
|
||||
// each msg_to target. Returns the total number of rows
|
||||
// removed.
|
||||
func (database *Database) RotateChannelMessages(
|
||||
// PruneOldMessages deletes messages older than cutoff and
|
||||
// returns the number of rows removed.
|
||||
func (database *Database) PruneOldMessages(
|
||||
ctx context.Context,
|
||||
maxHistory int,
|
||||
cutoff time.Time,
|
||||
) (int64, error) {
|
||||
if maxHistory <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Find distinct targets that have messages.
|
||||
rows, err := database.conn.QueryContext(ctx,
|
||||
`SELECT msg_to, COUNT(*) AS cnt
|
||||
FROM messages
|
||||
WHERE msg_to != ''
|
||||
GROUP BY msg_to
|
||||
HAVING cnt > ?`,
|
||||
maxHistory,
|
||||
res, err := database.conn.ExecContext(ctx,
|
||||
"DELETE FROM messages WHERE created_at < ?",
|
||||
cutoff,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf(
|
||||
"list targets for rotation: %w", err,
|
||||
"prune old messages: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
defer func() { _ = rows.Close() }()
|
||||
deleted, _ := res.RowsAffected()
|
||||
|
||||
type targetCount struct {
|
||||
target string
|
||||
count int64
|
||||
}
|
||||
|
||||
var targets []targetCount
|
||||
|
||||
for rows.Next() {
|
||||
var entry targetCount
|
||||
|
||||
err = rows.Scan(&entry.target, &entry.count)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf(
|
||||
"scan target count: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
targets = append(targets, entry)
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("rows error: %w", err)
|
||||
}
|
||||
|
||||
var totalDeleted int64
|
||||
|
||||
for _, entry := range targets {
|
||||
res, delErr := database.conn.ExecContext(ctx,
|
||||
`DELETE FROM messages
|
||||
WHERE msg_to = ?
|
||||
AND id NOT IN (
|
||||
SELECT id FROM messages
|
||||
WHERE msg_to = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT ?
|
||||
)`,
|
||||
entry.target, entry.target, maxHistory,
|
||||
)
|
||||
if delErr != nil {
|
||||
return totalDeleted, fmt.Errorf(
|
||||
"rotate messages for %s: %w",
|
||||
entry.target, delErr,
|
||||
)
|
||||
}
|
||||
|
||||
deleted, _ := res.RowsAffected()
|
||||
totalDeleted += deleted
|
||||
}
|
||||
|
||||
return totalDeleted, nil
|
||||
return deleted, nil
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ type Params struct {
|
||||
Healthcheck *healthcheck.Healthcheck
|
||||
}
|
||||
|
||||
const defaultIdleTimeout = 24 * time.Hour
|
||||
const defaultIdleTimeout = 30 * 24 * time.Hour
|
||||
|
||||
// Handlers manages HTTP request handling.
|
||||
type Handlers struct {
|
||||
@@ -205,8 +205,7 @@ func (hdlr *Handlers) runCleanup(
|
||||
}
|
||||
|
||||
// pruneQueuesAndMessages removes old client_queues entries
|
||||
// per QUEUE_MAX_AGE, rotates messages per MAX_HISTORY, and
|
||||
// cleans up orphaned messages.
|
||||
// per QUEUE_MAX_AGE and prunes messages per MESSAGE_MAX_AGE.
|
||||
func (hdlr *Handlers) pruneQueuesAndMessages(
|
||||
ctx context.Context,
|
||||
) {
|
||||
@@ -230,18 +229,22 @@ func (hdlr *Handlers) pruneQueuesAndMessages(
|
||||
}
|
||||
}
|
||||
|
||||
maxHistory := hdlr.params.Config.MaxHistory
|
||||
if maxHistory > 0 {
|
||||
rotated, err := hdlr.params.Database.
|
||||
RotateChannelMessages(ctx, maxHistory)
|
||||
messageMaxAge := hdlr.params.Config.MessageMaxAge
|
||||
if messageMaxAge > 0 {
|
||||
msgCutoff := time.Now().Add(
|
||||
-time.Duration(messageMaxAge) * time.Second,
|
||||
)
|
||||
|
||||
pruned, err := hdlr.params.Database.
|
||||
PruneOldMessages(ctx, msgCutoff)
|
||||
if err != nil {
|
||||
hdlr.log.Error(
|
||||
"message rotation failed", "error", err,
|
||||
"message pruning failed", "error", err,
|
||||
)
|
||||
} else if rotated > 0 {
|
||||
} else if pruned > 0 {
|
||||
hdlr.log.Info(
|
||||
"rotated old messages",
|
||||
"deleted", rotated,
|
||||
"pruned old messages",
|
||||
"deleted", pruned,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user