{"record":{"id":"b2c7fa733cc130e2","repo":"microsoft/aspire","slug":"histogram-data-point-bucket-count-length-changed-b2c7fa","errorCode":null,"errorMessage":"Histogram data point bucket count length changed.","messagePattern":"Histogram data point bucket count length changed\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/Aspire.Dashboard/Otlp/Storage/SqliteTelemetryRepository.Metrics.Writes.cs","lineNumber":262,"sourceCode":"        IDbTransaction transaction,\n        AddContext context,\n        long instrumentId,\n        HistogramDataPoint point,\n        MetricIngestionState ingestionState,\n        MetricPointBatch pointBatch)\n    {\n        try\n        {\n            OtlpHelpers.ValidateHistogramDataPoint(point);\n            var dimension = GetOrAddMetricDimension(connection, transaction, instrumentId, point.Attributes, ingestionState);\n            var pendingLatest = dimension.PendingPoint;\n            var latest = dimension.LatestPoint;\n            var latestPointType = pendingLatest?.PointType ?? latest?.PointType;\n            var latestEndTimeTicks = pendingLatest?.EndTimeTicks ?? latest?.EndTimeTicks;\n            var latestBucketCountLength = pendingLatest?.HistogramBucketCounts?.Length ?? latest?.HistogramBucketCountLength;\n            if (latestPointType == HistogramPointType && latestBucketCountLength != point.BucketCounts.Count)\n            {\n                throw new InvalidOperationException(\"Histogram data point bucket count length changed.\");\n            }\n            var histogramCount = checked((long)point.Count);\n            var sameCount = latestPointType == HistogramPointType &&\n                (pendingLatest?.HistogramCount ?? latest?.HistogramCount) == histogramCount;\n            var endTimeTicks = OtlpHelpers.UnixNanoSecondsToDateTime(point.TimeUnixNano).Ticks;\n            if (sameCount)\n            {\n                if (pendingLatest is not null)\n                {\n                    pendingLatest.EndTimeTicks = endTimeTicks;\n                    pendingLatest.SourcePointCount++;\n                    pendingLatest.Exemplars.AddRange(point.Exemplars);\n                }\n                else\n                {\n                    pointBatch.AddUpdate(latest!.PointId, endTimeTicks, incrementRepeatCount: false);\n                    latest.EndTimeTicks = endTimeTicks;\n                    QueueMetricExemplars(pointBatch, latest.PointId, point.Exemplars);","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Dashboard/Otlp/Storage/SqliteTelemetryRepository.Metrics.Writes.cs#L244-L280","documentation":"The repository models histograms as deltas keyed by fixed bucket layouts. When a new histogram point arrives whose bucket count array length differs from the previously stored point for the same dimension, the bucket boundaries changed mid-stream, so AddHistogramMetricPoint throws rather than producing inconsistent series.","triggerScenarios":"AddMetricToDatabase -> AddHistogramMetricPoint sees latestPointType == HistogramPointType and the stored latestBucketCountLength differs from point.BucketCounts.Count.","commonSituations":"The emitting app changed its histogram bucket boundaries (advice or explicit boundaries) while running; switching instrumentation libraries or SDK config without restarting the app; a rebuilt service reusing the same resource identity with a new bucket layout, sending to a dashboard with retained state.","solutions":["Restart the emitting app after changing histogram bucket boundaries, or keep boundaries stable for the process lifetime.","Clear/reset dashboard telemetry state (or restart the dashboard) so the old bucket layout is discarded.","Keep explicit bucket boundaries consistent across deployments of the same service.","If using SDK auto bucket advice, pin explicit boundaries to avoid layout changes across SDK versions."],"exampleFix":"// before: boundaries changed at runtime\nvar hist = meter.CreateHistogram<double>(\"latency\");\n// SDK advice changes after config reload -> bucket count changes\n// after: pin explicit boundaries and restart the app\nvar hist = meter.CreateHistogram<double>(\"latency\",\n    advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = new[] { 0d, 10, 50, 100, 500, 1000 } });","handlingStrategy":"try-catch","validationCode":"if (lastBucketCounts is { } prev && prev.Length != point.BucketCounts.Count)\n{\n    // bucket layout changed; reset resource state before sending\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    await repo.AddMetricsAsync(request);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"bucket count length changed\"))\n{\n    logger.LogWarning(ex, \"Histogram bucket layout changed; restarting emitter is required.\");\n}","preventionTips":["Pin histogram bucket boundaries via InstrumentAdvice.HistogramBucketBoundaries.","Restart the app whenever SDK/bucket configuration changes.","Keep bucket boundaries consistent across deployments of the same resource."],"tags":["metrics","histogram","ingest","cardinality"],"backgroundTag":"invalid-state-transition","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T09:17:21.228Z"}