{"record":{"id":"5361adc4b61aa1e1","repo":"crowdsecurity/crowdsec","slug":"marking-usage-metrics-as-sent-w","errorCode":null,"errorMessage":"marking usage metrics as sent: %w","messagePattern":"marking usage metrics as sent: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/database/metrics.go","lineNumber":71,"sourceCode":"\t\t\tmetric.PushedAtIsNil(),\n\t\t).\n\t\tAll(ctx)\n\tif err != nil {\n\t\tc.Log.Warningf(\"GetBouncerUsageMetricsByName: %s\", err)\n\t\treturn nil, fmt.Errorf(\"getting bouncer usage metrics by name %s: %w\", bouncerName, err)\n\t}\n\n\treturn metrics, nil\n}\n\nfunc (c *Client) MarkUsageMetricsAsSent(ctx context.Context, ids []int) error {\n\t_, err := c.Ent.Metric.Update().\n\t\tWhere(metric.IDIn(ids...)).\n\t\tSetPushedAt(time.Now().UTC()).\n\t\tSave(ctx)\n\tif err != nil {\n\t\tc.Log.Warningf(\"MarkUsageMetricsAsSent: %s\", err)\n\t\treturn fmt.Errorf(\"marking usage metrics as sent: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// GetUnsentMetrics returns metrics not pushed to CAPI yet, across all sources, ordered by id.\n// Callers walk the backlog by passing back the id of the last row they consumed.\nfunc (c *Client) GetUnsentMetrics(ctx context.Context, afterID int, limit int) ([]*ent.Metric, error) {\n\tmetrics, err := c.Ent.Metric.Query().\n\t\tWhere(\n\t\t\tmetric.PushedAtIsNil(),\n\t\t\tmetric.IDGT(afterID),\n\t\t).\n\t\tOrder(ent.Asc(metric.FieldID)).\n\t\tLimit(limit).\n\t\tAll(ctx)\n\tif err != nil {\n\t\tc.Log.Warningf(\"GetUnsentMetrics: %s\", err)","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/database/metrics.go#L53-L89","documentation":"MarkUsageMetricsAsSent sets pushed_at on the metric rows with the given IDs. This error wraps a failing bulk UPDATE. Note the Save count is ignored, so a zero-row update is still 'success' — only a database-level failure produces this error.","triggerScenarios":"Calling MarkUsageMetricsAsSent (from sendUsageMetricsBatch after a successful CAPI push) when the UPDATE fails: DB write lock held by another process, disk full, corrupted DB, or context cancellation.","commonSituations":"SQLite 'database is locked' while the agent writes alert rows concurrently; disk quota exceeded on the metrics volume after a large usage batch.","solutions":["Check the Warningf log 'MarkUsageMetricsAsSent: <err>' for the real cause.","Resolve writer contention (WAL mode / single crowdsec process per DB).","Free disk space; verify the DB integrity.","Re-run the send loop — unsent rows will be re-fetched by GetUnsentMetrics, so a failed mark just risks a duplicate push, not data loss."],"exampleFix":"// before: error only\nif err := c.MarkUsageMetricsAsSent(ctx, ids); err != nil { log.Error(err) }\n// after: tolerate failure so push loop keeps going (rows may be pushed twice at worst)\nif err := c.MarkUsageMetricsAsSent(ctx, ids); err != nil {\n    log.Warnf(\"could not mark metrics sent, they may be re-pushed: %v\", err)\n}","handlingStrategy":"retry","validationCode":"// ensure the ids are non-empty before issuing the update\nif len(ids) == 0 { return nil }","typeGuard":null,"tryCatchPattern":"err := client.MarkUsageMetricsAsSent(ctx, ids)\nif err != nil {\n    log.Warnf(\"mark-sent failed; rows may be re-pushed next cycle: %v\", err)\n    // do not abort the loop — at worst metrics get pushed twice\n}","preventionTips":["Make the push loop idempotent — a failed mark is recoverable.","Resolve SQLite writer contention (WAL, single process).","Monitor disk space; full disks cause UPDATE failures.","Log the unwrapped cause from the Warningf line."],"tags":["database","sqlite","update-failure","metrics"],"backgroundTag":"database-write-failed","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}