SigNoz/signoz · error · model.ApiError
SetTTLV2 only supported
Error message
SetTTLV2 only supported
What it means
ApiError (ErrorExec) from setTTLLogs when the logs table has a custom_retention column (hasCustomRetention == true). Legacy per-org TTL setting via this path is refused because the schema is on the newer custom-retention model where SetTTLV2 must be used instead.
Source
Thrown at pkg/query-service/app/clickhouseReader/reader.go:930
return &response, nil
}
func getLocalTableName(tableName string) string {
tableNameSplit := strings.Split(tableName, ".")
return tableNameSplit[0] + "." + strings.Split(tableNameSplit[1], "distributed_")[1]
}
func (r *ClickHouseReader) setTTLLogs(ctx context.Context, orgID string, params *retentiontypes.TTLParams) (*retentiontypes.SetTTLResponseItem, *model.ApiError) {
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
instrumentationtypes.TelemetrySignal: telemetrytypes.SignalLogs.StringValue(),
instrumentationtypes.CodeNamespace: "clickhouse-reader",
instrumentationtypes.CodeFunctionName: "setTTLLogs",
})
hasCustomRetention, err := r.hasCustomRetentionColumn(ctx)
if hasCustomRetention {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("SetTTLV2 only supported")}
}
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in processing TTL")}
}
// uuid is used as transaction id
uuidWithHyphen := uuid.New()
uuid := strings.Replace(uuidWithHyphen.String(), "-", "", -1)
coldStorageDuration := -1
if len(params.ColdStorageVolume) > 0 {
coldStorageDuration = int(params.ToColdStorageDuration)
}
tableNameArray := []string{
r.logsDB + "." + r.logsLocalTableV2,
r.logsDB + "." + r.logsResourceLocalTableV2,
getLocalTableName(r.logsDB + "." + r.logsAttributeKeys),
getLocalTableName(r.logsDB + "." + r.logsResourceKeys),View on GitHub (pinned to 5069bf80b0)
Solutions
- Switch the caller to the SetTTLV2 API for logs retention
- Upgrade any scripts/UI to the v2 endpoints
- If legacy behavior is truly required, consult migration docs rather than forcing v1
Example fix
// before apiErr := reader.SetTTL(ctx, orgID, ttlParams) // legacy logs TTL // after apiErr := reader.SetTTLV2(ctx, orgID, v2Params) // custom-retention schema
Defensive patterns
Strategy: type-guard
Validate before calling
// Probe schema once and route to the right API
hasV2, err := schemaHasCustomRetention(ctx, ch)
if err != nil { return err }
if hasV2 { return reader.SetTTLV2(ctx, orgID, v2Params) }
return reader.SetTTL(ctx, orgID, v1Params) Type guard
func requiresTTLV2(apiErr *model.ApiError) bool {
return apiErr != nil && strings.Contains(apiErr.Err.Error(), "SetTTLV2 only supported")
} Try / catch
apiErr := reader.SetTTL(ctx, orgID, params)
if requiresTTLV2(apiErr) {
apiErr = reader.SetTTLV2(ctx, orgID, convertToV2(params))
} Prevention
- Detect the custom-retention schema at integration time
- Migrate retention automation to v2 endpoints during upgrades
- Never assume v1 TTL works across schema migrations
When it happens
Trigger: Invoking the logs TTL endpoint (SetTTL/setTTLLogs) against a SigNoz schema that has already migrated to custom retention — the guard clause returns immediately with 'SetTTLV2 only supported'.
Common situations: Older clients/automation still calling the v1 TTL API after the SigNoz upgrade added custom_retention; mixed versions during rolling upgrades.
Related errors
- error while getting ttl. Err=%v
- type and duration param cannot be empty from the query
- type param should be metrics|traces|logs, got %v
- not a valid TTL duration %v
- type param cannot be empty from the query
AI-assisted analysis of SigNoz/signoz@5069bf80b0 (2026-08-28).
Data as JSON: /api/errors/15f5edd6658a81a7.
Report an issue: GitHub.