{"record":{"id":"114753b74d3e7212","repo":"microsoft/aspire","slug":"instrument-limit-of-telemetryrepositorylimits","errorCode":null,"errorMessage":"Instrument limit of {TelemetryRepositoryLimits.MaxInstrumentCount} reached. Instrument '{metric.Name}' will not be added.","messagePattern":"Instrument limit of (.+?) reached\\. Instrument '(.+?)' will not be added\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/Aspire.Dashboard/Otlp/Storage/SqliteTelemetryRepository.Caching.cs","lineNumber":293,"sourceCode":"        {\n            if (resourceScope.Instruments.TryGetValue(metric.Name, out var instrument))\n            {\n                return instrument;\n            }\n\n            EnsureCachedInstrumentsLoaded(connection, transaction, resource, resourceScope);\n            if (resourceScope.Instruments.TryGetValue(metric.Name, out instrument))\n            {\n                return instrument;\n            }\n\n            var instrumentCount = resource.InstrumentCount ??= connection.QuerySingle<int>(\n                    \"SELECT COUNT(*) FROM telemetry_metric_instruments WHERE resource_id = @ResourceId;\",\n                    new { resource.ResourceId },\n                    transaction);\n            if (instrumentCount >= TelemetryRepositoryLimits.MaxInstrumentCount)\n            {\n                throw new InvalidOperationException($\"Instrument limit of {TelemetryRepositoryLimits.MaxInstrumentCount} reached. Instrument '{metric.Name}' will not be added.\");\n            }\n\n            var instrumentId = connection.QuerySingle<long>(\"\"\"\n                INSERT INTO telemetry_metric_instruments (\n                    resource_id, resource_view_id, scope_id, instrument_name, description, unit, instrument_type,\n                    aggregation_temporality, is_monotonic)\n                VALUES (\n                    @ResourceId, @ResourceViewId, @ScopeId, @InstrumentName, @Description, @Unit, @InstrumentType,\n                    @AggregationTemporality, @IsMonotonic)\n                RETURNING instrument_id;\n                \"\"\", new\n            {\n                resource.ResourceId,\n                resourceView.ResourceViewId,\n                ScopeId = resourceScope.Scope.ScopeId,\n                InstrumentName = metric.Name,\n                metric.Description,\n                metric.Unit,","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Dashboard/Otlp/Storage/SqliteTelemetryRepository.Caching.cs#L275-L311","documentation":"The Dashboard repository caps the number of distinct metric instruments stored per resource at TelemetryRepositoryLimits.MaxInstrumentCount. Before inserting a new instrument row, GetOrAddCachedInstrument counts existing instruments for the resource and throws when the cap is already reached, preventing unbounded cardinality from a faulty emitter.","triggerScenarios":"AddMetricToDatabase -> GetOrAddCachedInstrument runs for a resource that already has MaxInstrumentCount instruments in telemetry_metric_instruments, and the incoming metric.Name is not yet among the cached instruments.","commonSituations":"A service emitting metrics with high-cardinality names (e.g. names embedding ids or timestamps); a long-running dashboard that accumulated instruments from many deployments of the same resource; a runaway loop generating new instrument names.","solutions":["Fix the emitting app to stop creating instruments with unbounded/dynamic names (create instruments once at startup, not per request).","Increase TelemetryRepositoryLimits.MaxInstrumentCount if the app legitimately needs more instruments per resource.","Restart or clear dashboard telemetry storage to reset the count after fixing the emitter.","Use the dashboard UI (Limits) to view configured limits and confirm the cap that was hit."],"exampleFix":"// before: instrument per request\nvar counter = meter.CreateCounter<long>($\"requests.{orderId}\");\n// after: fixed instrument, order id as a tag/attribute\nvar counter = meter.CreateCounter<long>(\"app.requests\");\ncounter.Add(1, new KeyValuePair<string, object?>(\"order.id\", orderId));","handlingStrategy":"validation","validationCode":"if (instrumentNameIsDynamic) // names derived from request data\n{\n    throw new InvalidOperationException(\"Instrument names must be static; use attributes for variability.\");\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    await repository.AddMetricsAsync(...);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"Instrument limit\"))\n{\n    logger.LogWarning(ex, \"Instrument cardinality limit hit; fix emitting app.\");\n}","preventionTips":["Create instruments once at startup with static names.","Never embed ids/timestamps in instrument names; use attributes instead.","Monitor distinct instrument names per service in dev before production."],"tags":["otlp","metrics","cardinality","limits"],"backgroundTag":"value-out-of-range","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"}