Skip to content

Commit 37b2699

Browse files
committed
cli: add ZSTD compression support for tsdump generation and upload
This change adds native ZSTD compression support to the tsdump command, eliminating the need for manual compression/decompression of tsdump files. - Added `--format=raw-zstd` option to generate ZSTD-compressed tsdump files. - Enabled `tsdump upload` to automatically detect and decompress ZSTD files. Fixes #151727 Epic: CRDB-56325 Part-of: CRDB-53445 Release note (cli change): The `cockroach debug tsdump` command now supports ZSTD compression via `--format=raw-zstd`. This generates compressed tsdump files that are ~85% smaller than raw format. The `tsdump upload` command automatically detects and decompresses ZSTD files, allowing direct upload without manual decompression.
1 parent a7d0270 commit 37b2699

File tree

6 files changed

+113
-4
lines changed

6 files changed

+113
-4
lines changed

pkg/cli/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ go_library(
285285
"@com_github_gogo_protobuf//jsonpb",
286286
"@com_github_jackc_pgx_v5//pgconn",
287287
"@com_github_jackc_pgx_v5//pgtype",
288+
"@com_github_klauspost_compress//zstd",
288289
"@com_github_kr_pretty//:pretty",
289290
"@com_github_lestrrat_go_jwx_v2//jwk",
290291
"@com_github_marusama_semaphore//:semaphore",
@@ -498,6 +499,7 @@ go_test(
498499
"@com_github_datadog_datadog_api_client_go_v2//api/datadog",
499500
"@com_github_datadog_datadog_api_client_go_v2//api/datadogV2",
500501
"@com_github_google_pprof//profile",
502+
"@com_github_klauspost_compress//zstd",
501503
"@com_github_pmezard_go_difflib//difflib",
502504
"@com_github_spf13_cobra//:cobra",
503505
"@com_github_spf13_pflag//:pflag",

pkg/cli/testdata/tsdump_upload_e2e

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,17 @@ cr.store.rocksdb.block.cache.usage,2021-01-01T00:00:00Z,2,75.2
3939
[{"ddsource":"tsdump_upload","ddtags":"cluster_type:SELF_HOSTED,cluster_label:\"test-cluster\",cluster_id:test-cluster-id,zendesk_ticket:zd-test,org_name:test-org,user_name:test-user,upload_id:\"test-cluster\"-20241114000000,upload_timestamp:2024-11-14 00:00:00,upload_year:2024,upload_month:11,upload_day:14,series_uploaded:4","dry_run":"false","duration":"0","estimated_cost":"0.000186986301369863","hostname":"hostname","message":"tsdump upload completed: uploaded 4 series overall","series_uploaded":"4","service":"tsdump_upload","success":"true"}]
4040
----
4141
----
42+
43+
44+
upload-datadog compression=zstd
45+
cr.node.admission.admitted.elastic-cpu,2025-05-26T08:32:00Z,1,1
46+
cr.node.sql.query.count,2021-01-01T00:00:00Z,1,100.5
47+
cr.node.sql.query.count,2021-01-01T00:00:10Z,1,102.3
48+
cr.store.rocksdb.block.cache.usage,2021-01-01T00:00:00Z,2,75.2
49+
----
50+
----
51+
{"series":[{"interval":10,"metric":"cockroachdb.admission.admitted.elastic_cpu","points":[{"timestamp":1748248320,"value":1}],"tags":["node_id:1","cluster_type:SELF_HOSTED","cluster_label:\"test-cluster\"","cluster_id:test-cluster-id","zendesk_ticket:zd-test","org_name:test-org","user_name:test-user","upload_id:\"test-cluster\"-20241114000000","upload_timestamp:2024-11-14 00:00:00","upload_year:2024","upload_month:11","upload_day:14"],"type":1},{"interval":10,"metric":"cockroachdb.sql.query.count","points":[{"timestamp":1609459200,"value":100.5}],"tags":["node_id:1","cluster_type:SELF_HOSTED","cluster_label:\"test-cluster\"","cluster_id:test-cluster-id","zendesk_ticket:zd-test","org_name:test-org","user_name:test-user","upload_id:\"test-cluster\"-20241114000000","upload_timestamp:2024-11-14 00:00:00","upload_year:2024","upload_month:11","upload_day:14"],"type":1},{"interval":10,"metric":"cockroachdb.sql.query.count","points":[{"timestamp":1609459210,"value":1.8}],"tags":["node_id:1","cluster_type:SELF_HOSTED","cluster_label:\"test-cluster\"","cluster_id:test-cluster-id","zendesk_ticket:zd-test","org_name:test-org","user_name:test-user","upload_id:\"test-cluster\"-20241114000000","upload_timestamp:2024-11-14 00:00:00","upload_year:2024","upload_month:11","upload_day:14"],"type":1},{"interval":10,"metric":"cockroachdb.rocksdb.block.cache.usage","points":[{"timestamp":1609459200,"value":75.2}],"tags":["store:2","cluster_type:SELF_HOSTED","cluster_label:\"test-cluster\"","cluster_id:test-cluster-id","zendesk_ticket:zd-test","org_name:test-org","user_name:test-user","upload_id:\"test-cluster\"-20241114000000","upload_timestamp:2024-11-14 00:00:00","upload_year:2024","upload_month:11","upload_day:14"],"type":3}]}
52+
53+
[{"ddsource":"tsdump_upload","ddtags":"cluster_type:SELF_HOSTED,cluster_label:\"test-cluster\",cluster_id:test-cluster-id,zendesk_ticket:zd-test,org_name:test-org,user_name:test-user,upload_id:\"test-cluster\"-20241114000000,upload_timestamp:2024-11-14 00:00:00,upload_year:2024,upload_month:11,upload_day:14,series_uploaded:4","dry_run":"false","duration":"0","estimated_cost":"0.000186986301369863","hostname":"hostname","message":"tsdump upload completed: uploaded 4 series overall","series_uploaded":"4","service":"tsdump_upload","success":"true"}]
54+
----
55+
----

pkg/cli/tsdump.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/cockroachdb/cockroach/pkg/util/netutil/addr"
3737
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
3838
"github.com/cockroachdb/errors"
39+
"github.com/klauspost/compress/zstd"
3940
"github.com/spf13/cobra"
4041
)
4142

@@ -127,7 +128,7 @@ will then convert it to the --format requested in the current invocation.
127128

128129
var w tsWriter
129130
switch cmd := debugTimeSeriesDumpOpts.format; cmd {
130-
case tsDumpRaw:
131+
case tsDumpRaw, tsDumpRawZstd:
131132
if convertFile != "" {
132133
return errors.Errorf("input file is already in raw format")
133134
}
@@ -263,7 +264,7 @@ will then convert it to the --format requested in the current invocation.
263264
}
264265

265266
tsClient := conn.NewTimeSeriesClient()
266-
if debugTimeSeriesDumpOpts.format == tsDumpRaw {
267+
if debugTimeSeriesDumpOpts.format == tsDumpRaw || debugTimeSeriesDumpOpts.format == tsDumpRawZstd {
267268
// get the node details so that we can get the SQL port
268269
statusClient := conn.NewStatusClient()
269270
resp, err := statusClient.Details(ctx, &serverpb.DetailsRequest{NodeId: "local"})
@@ -298,7 +299,18 @@ will then convert it to the --format requested in the current invocation.
298299

299300
// Buffer the writes since we're going to
300301
// be writing potentially a lot of data.
301-
w := bufio.NewWriterSize(output, 1024*1024)
302+
bufWriter := bufio.NewWriterSize(output, 1024*1024)
303+
304+
// Wrap with ZSTD compressor if raw-zstd format
305+
var w io.Writer = bufWriter
306+
var zstdWriter *zstd.Encoder
307+
if debugTimeSeriesDumpOpts.format == tsDumpRawZstd {
308+
zstdWriter, err = zstd.NewWriter(bufWriter)
309+
if err != nil {
310+
return errors.Wrap(err, "creating zstd writer")
311+
}
312+
w = zstdWriter
313+
}
302314

303315
// Write embedded metadata first
304316
if err := tsdumpmeta.Write(w, metadata); err != nil {
@@ -309,7 +321,14 @@ will then convert it to the --format requested in the current invocation.
309321
return err
310322
}
311323

312-
if err := w.Flush(); err != nil {
324+
// Close ZSTD writer before flushing buffer
325+
if zstdWriter != nil {
326+
if err := zstdWriter.Close(); err != nil {
327+
return err
328+
}
329+
}
330+
331+
if err := bufWriter.Flush(); err != nil {
313332
return err
314333
}
315334

@@ -839,6 +858,8 @@ const (
839858
// to push older timestamps. There's no way to enable historical
840859
// ingestion if DD doesn't already know your metric name.
841860
tsDumpDatadogInit
861+
// tsDumpRawZstd is like tsDumpRaw but with ZSTD compression.
862+
tsDumpRawZstd
842863
)
843864

844865
// Type implements the pflag.Value interface.
@@ -863,6 +884,8 @@ func (m *tsDumpFormat) String() string {
863884
return "datadog"
864885
case tsDumpDatadogInit:
865886
return "datadoginit"
887+
case tsDumpRawZstd:
888+
return "raw-zstd"
866889
}
867890
return ""
868891
}
@@ -886,6 +909,8 @@ func (m *tsDumpFormat) Set(s string) error {
886909
*m = tsDumpDatadog
887910
case "datadoginit":
888911
*m = tsDumpDatadogInit
912+
case "raw-zstd":
913+
*m = tsDumpRawZstd
889914

890915
default:
891916
return fmt.Errorf("invalid value for --format: %s", s)

pkg/cli/tsdump_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/cockroachdb/cockroach/pkg/util/log"
3030
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
3131
"github.com/cockroachdb/datadriven"
32+
"github.com/klauspost/compress/zstd"
3233
"github.com/stretchr/testify/require"
3334
)
3435

@@ -485,6 +486,54 @@ func TestTSDumpRawGenerationWithEmbeddedMetadata(t *testing.T) {
485486
require.Equal(t, "1", readMetadata.StoreToNodeMap["1"])
486487
}
487488

489+
// TestTSDumpRawZstdGenerationWithEmbeddedMetadata tests that raw-zstd format tsdump generation
490+
// produces valid ZSTD-compressed output with embedded metadata.
491+
func TestTSDumpRawZstdGenerationWithEmbeddedMetadata(t *testing.T) {
492+
defer leaktest.AfterTest(t)()
493+
defer log.Scope(t).Close(t)
494+
495+
c := NewCLITest(TestCLIParams{})
496+
defer c.Cleanup()
497+
498+
tmpFile, err := os.CreateTemp("", "tsdump_*.gob.zst")
499+
require.NoError(t, err)
500+
defer func() {
501+
_ = os.Remove(tmpFile.Name())
502+
}()
503+
tmpFile.Close()
504+
505+
_, err = c.RunWithCapture(fmt.Sprintf(
506+
"debug tsdump --format=raw-zstd --output=%s --cluster-name=test-cluster-1 --disable-cluster-name-verification",
507+
tmpFile.Name(),
508+
))
509+
require.NoError(t, err)
510+
511+
file, err := os.Open(tmpFile.Name())
512+
require.NoError(t, err)
513+
defer file.Close()
514+
515+
// Read first 4 bytes to verify ZSTD magic number
516+
magic := make([]byte, 4)
517+
_, err = file.Read(magic)
518+
require.NoError(t, err)
519+
require.Equal(t, []byte{0x28, 0xB5, 0x2F, 0xFD}, magic, "file should have ZSTD magic number")
520+
521+
_, err = file.Seek(0, io.SeekStart)
522+
require.NoError(t, err)
523+
524+
zstdReader, err := zstd.NewReader(file)
525+
require.NoError(t, err)
526+
defer zstdReader.Close()
527+
528+
dec := gob.NewDecoder(zstdReader)
529+
readMetadata, err := tsdumpmeta.Read(dec)
530+
require.NoError(t, err)
531+
532+
// Verify store-to-node mapping is embedded
533+
require.NotNil(t, readMetadata.StoreToNodeMap)
534+
require.Equal(t, "1", readMetadata.StoreToNodeMap["1"])
535+
}
536+
488537
func makeTS(name, source string, dataPointsNum int) *tspb.TimeSeriesData {
489538
dps := make([]tspb.TimeSeriesDatapoint, dataPointsNum)
490539
for i := range dps {

pkg/cli/tsdump_upload.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
3636
"github.com/cockroachdb/cockroach/pkg/util/yamlutil"
3737
"github.com/cockroachdb/errors"
38+
"github.com/klauspost/compress/zstd"
3839
"go.yaml.in/yaml/v4"
3940
)
4041

@@ -61,6 +62,7 @@ var (
6162
"tpl_var_cluster=%s&tpl_var_upload_id=%s&tpl_var_upload_day=%d&tpl_var_upload_month=%d&tpl_var_upload_year=%d&from_ts=%d&to_ts=%d"
6263
zipFileSignature = []byte{0x50, 0x4B, 0x03, 0x04}
6364
gzipFileSignature = []byte{0x1f, 0x8b}
65+
zstdFileSignature = []byte{0x28, 0xB5, 0x2F, 0xFD}
6466
logMessageFormat = "tsdump upload to datadog is partially failed for metric: %s"
6567
partialFailureMessageFormat = "The Tsdump upload to Datadog succeeded but %d metrics partially failed to upload." +
6668
" These failures can be due to transient network errors.\nMetrics:\n%s\n" +
@@ -1026,6 +1028,13 @@ func getFileReader(fileName string) (io.Reader, error) {
10261028
}
10271029
return gzipReader, nil
10281030

1031+
case bytes.HasPrefix(buf, zstdFileSignature):
1032+
zstdReader, err := zstd.NewReader(file)
1033+
if err != nil {
1034+
return nil, err
1035+
}
1036+
return zstdReader, nil
1037+
10291038
default:
10301039
return file, nil
10311040
}

pkg/cli/tsdump_upload_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/cockroachdb/cockroach/pkg/util/log"
3737
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
3838
"github.com/cockroachdb/datadriven"
39+
"github.com/klauspost/compress/zstd"
3940
"github.com/stretchr/testify/require"
4041
)
4142

@@ -416,6 +417,15 @@ func generateMockTSDumpFromCSV(t *testing.T, csvInput string, options ...mockTSD
416417
require.NoError(t, err)
417418
encoder = gob.NewEncoder(writer)
418419

420+
case "zstd":
421+
filePattern = "mock_tsdump_*.gob.zst"
422+
zstdWriter, err := zstd.NewWriter(tmpFile)
423+
require.NoError(t, err)
424+
defer func() {
425+
require.NoError(t, zstdWriter.Close(), "failed to close zstd writer")
426+
}()
427+
encoder = gob.NewEncoder(zstdWriter)
428+
419429
default:
420430
filePattern = "mock_tsdump_*.gob"
421431
encoder = gob.NewEncoder(tmpFile)

0 commit comments

Comments
 (0)