Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Commit 51042d1

Browse files
authored
Merge pull request #1173 from grafana/mt-update-ttl-updates
mt-update-ttl : use standard store, specify TTL's not tables, auto-create tables + misc
2 parents fb1478e + 5f52ccb commit 51042d1

File tree

3 files changed

+106
-130
lines changed

3 files changed

+106
-130
lines changed

cmd/mt-update-ttl/main.go

+74-105
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,27 @@ import (
55
"fmt"
66
"math"
77
"os"
8-
"strings"
98
"sync"
109
"sync/atomic"
1110
"time"
1211

13-
"github.com/gocql/gocql"
1412
"github.com/grafana/metrictank/logger"
13+
"github.com/grafana/metrictank/stats"
1514
"github.com/grafana/metrictank/store/cassandra"
16-
hostpool "github.com/hailocab/go-hostpool"
1715
"github.com/raintank/dur"
1816
log "github.com/sirupsen/logrus"
1917
)
2018

2119
const maxToken = math.MaxInt64 // 9223372036854775807
2220

2321
var (
24-
cassandraAddrs = flag.String("cassandra-addrs", "localhost", "cassandra host (may be given multiple times as comma-separated list)")
25-
cassandraKeyspace = flag.String("cassandra-keyspace", "metrictank", "cassandra keyspace to use for storing the metric data table")
26-
cassandraConsistency = flag.String("cassandra-consistency", "one", "write consistency (any|one|two|three|quorum|all|local_quorum|each_quorum|local_one")
27-
cassandraHostSelectionPolicy = flag.String("cassandra-host-selection-policy", "tokenaware,hostpool-epsilon-greedy", "")
28-
cassandraTimeout = flag.String("cassandra-timeout", "1s", "cassandra timeout")
29-
cassandraConcurrency = flag.Int("cassandra-concurrency", 20, "max number of concurrent reads to cassandra.")
30-
cassandraRetries = flag.Int("cassandra-retries", 0, "how many times to retry a query before failing it")
31-
cqlProtocolVersion = flag.Int("cql-protocol-version", 4, "cql protocol version to use")
32-
33-
cassandraSSL = flag.Bool("cassandra-ssl", false, "enable SSL connection to cassandra")
34-
cassandraCaPath = flag.String("cassandra-ca-path", "/etc/metrictank/ca.pem", "cassandra CA certificate path when using SSL")
35-
cassandraHostVerification = flag.Bool("cassandra-host-verification", true, "host (hostname and server cert) verification when using SSL")
36-
37-
cassandraAuth = flag.Bool("cassandra-auth", false, "enable cassandra authentication")
38-
cassandraUsername = flag.String("cassandra-username", "cassandra", "username for authentication")
39-
cassandraPassword = flag.String("cassandra-password", "cassandra", "password for authentication")
40-
41-
cassandraDisableInitialHostLookup = flag.Bool("cassandra-disable-initial-host-lookup", false, "instruct the driver to not attempt to get host info from the system.peers table")
42-
43-
startTs = flag.Int("start-timestamp", 0, "timestamp at which to start, defaults to 0")
44-
endTs = flag.Int("end-timestamp", math.MaxInt32, "timestamp at which to stop, defaults to int max")
45-
numThreads = flag.Int("threads", 10, "number of workers to use to process data")
46-
47-
verbose = flag.Bool("verbose", false, "show every record being processed")
48-
49-
doneKeys uint64
50-
doneRows uint64
22+
doneKeys uint64
23+
doneRows uint64
24+
startTs int
25+
endTs int
26+
numThreads int
27+
statusEvery int
28+
verbose bool
5129
)
5230

5331
func init() {
@@ -58,89 +36,71 @@ func init() {
5836
}
5937

6038
func main() {
39+
cfg := cassandra.CliConfig
40+
flag.StringVar(&cfg.Addrs, "cassandra-addrs", cfg.Addrs, "cassandra host (may be given multiple times as comma-separated list)")
41+
flag.StringVar(&cfg.Keyspace, "cassandra-keyspace", cfg.Keyspace, "cassandra keyspace to use for storing the metric data table")
42+
flag.StringVar(&cfg.Consistency, "cassandra-consistency", cfg.Consistency, "write consistency (any|one|two|three|quorum|all|local_quorum|each_quorum|local_one")
43+
flag.StringVar(&cfg.HostSelectionPolicy, "host-selection-policy", cfg.HostSelectionPolicy, "")
44+
flag.StringVar(&cfg.Timeout, "cassandra-timeout", cfg.Timeout, "cassandra timeout")
45+
flag.IntVar(&cfg.WriteConcurrency, "cassandra-concurrency", 20, "number of concurrent connections to cassandra.") // this will launch idle write goroutines which we don't need but we can clean this up later.
46+
flag.IntVar(&cfg.Retries, "cassandra-retries", cfg.Retries, "how many times to retry a query before failing it")
47+
flag.IntVar(&cfg.WindowFactor, "window-factor", cfg.WindowFactor, "size of compaction window relative to TTL")
48+
flag.IntVar(&cfg.CqlProtocolVersion, "cql-protocol-version", cfg.CqlProtocolVersion, "cql protocol version to use")
49+
flag.BoolVar(&cfg.CreateKeyspace, "create-keyspace", cfg.CreateKeyspace, "enable the creation of the keyspace and tables")
50+
flag.BoolVar(&cfg.SSL, "cassandra-ssl", cfg.SSL, "enable SSL connection to cassandra")
51+
flag.StringVar(&cfg.CaPath, "cassandra-ca-path", cfg.CaPath, "cassandra CA certificate path when using SSL")
52+
flag.BoolVar(&cfg.HostVerification, "cassandra-host-verification", cfg.HostVerification, "host (hostname and server cert) verification when using SSL")
53+
flag.BoolVar(&cfg.Auth, "cassandra-auth", cfg.Auth, "enable cassandra authentication")
54+
flag.StringVar(&cfg.Username, "cassandra-username", cfg.Username, "username for authentication")
55+
flag.StringVar(&cfg.Password, "cassandra-password", cfg.Password, "password for authentication")
56+
flag.StringVar(&cfg.SchemaFile, "schema-file", cfg.SchemaFile, "File containing the needed schemas in case database needs initializing")
57+
flag.BoolVar(&cfg.DisableInitialHostLookup, "cassandra-disable-initial-host-lookup", cfg.DisableInitialHostLookup, "instruct the driver to not attempt to get host info from the system.peers table")
58+
59+
cfg.ReadConcurrency = 0
60+
cfg.ReadQueueSize = 0
61+
cfg.WriteQueueSize = 0
62+
63+
flag.IntVar(&startTs, "start-timestamp", 0, "timestamp at which to start, defaults to 0")
64+
flag.IntVar(&endTs, "end-timestamp", math.MaxInt32, "timestamp at which to stop, defaults to int max")
65+
flag.IntVar(&numThreads, "threads", 10, "number of workers to use to process data")
66+
flag.IntVar(&statusEvery, "status-every", 100000, "print status every x keys")
67+
68+
flag.BoolVar(&verbose, "verbose", false, "show every record being processed")
69+
6170
flag.Usage = func() {
62-
fmt.Fprintln(os.Stderr, "mt-update-ttl [flags] ttl table-in [table-out]")
71+
fmt.Fprintln(os.Stderr, "mt-update-ttl [flags] ttl-old ttl-new")
6372
fmt.Fprintln(os.Stderr)
6473
fmt.Fprintln(os.Stderr, "Adjusts the data in Cassandra to use a new TTL value. The TTL is applied counting from the timestamp of the data")
65-
fmt.Fprintln(os.Stderr, "If table-out not specified or same as table-in, will update in place. Otherwise will not touch input table and store results in table-out")
66-
fmt.Fprintln(os.Stderr, "In that case, it is up to you to assure table-out exists before running this tool")
67-
fmt.Fprintln(os.Stderr, "Not supported yet: for the per-ttl tables as of 0.7, automatically putting data in the right table")
74+
fmt.Fprintln(os.Stderr, "Automatically resolves the corresponding tables based on ttl value. If the table stays the same, will update in place. Otherwise will copy to the new table, not touching the input data")
75+
fmt.Fprintln(os.Stderr, "Unless you disable create-keyspace, tables are created as needed")
6876
fmt.Println("Flags:")
6977
flag.PrintDefaults()
7078
os.Exit(-1)
7179
}
7280
flag.Parse()
7381

74-
if flag.NArg() < 2 || flag.NArg() > 3 {
82+
stats.NewDevnull() // make sure metrics don't pile up without getting discarded
83+
84+
if flag.NArg() != 2 {
7585
flag.Usage()
7686
os.Exit(2)
7787
}
7888

79-
ttl := int(dur.MustParseNDuration("ttl", flag.Arg(0)))
80-
tableIn, tableOut := flag.Arg(1), flag.Arg(1)
81-
if flag.NArg() == 3 {
82-
tableOut = flag.Arg(2)
83-
}
89+
ttlIn := dur.MustParseNDuration("ttl", flag.Arg(0))
90+
ttlOut := dur.MustParseNDuration("ttl", flag.Arg(1))
8491

85-
session, err := NewCassandraStore()
92+
// note: cassandraStore will not be aware via its TTLTables attribute of the other, pre-existing tables,
93+
// only of the table we're copying to. but that's ok because we don't exercise any functionality that
94+
// needs that
95+
store, err := cassandra.NewCassandraStore(cassandra.CliConfig, []uint32{ttlIn, ttlOut})
8696

8797
if err != nil {
8898
log.Fatalf("Failed to instantiate cassandra: %s", err)
8999
}
90100

91-
update(session, ttl, tableIn, tableOut)
92-
}
93-
94-
func NewCassandraStore() (*gocql.Session, error) {
95-
cluster := gocql.NewCluster(strings.Split(*cassandraAddrs, ",")...)
96-
if *cassandraSSL {
97-
cluster.SslOpts = &gocql.SslOptions{
98-
CaPath: *cassandraCaPath,
99-
EnableHostVerification: *cassandraHostVerification,
100-
}
101-
}
102-
if *cassandraAuth {
103-
cluster.Authenticator = gocql.PasswordAuthenticator{
104-
Username: *cassandraUsername,
105-
Password: *cassandraPassword,
106-
}
107-
}
108-
cluster.Consistency = gocql.ParseConsistency(*cassandraConsistency)
109-
cluster.Timeout = cassandra.ConvertTimeout(*cassandraTimeout, time.Millisecond)
110-
cluster.NumConns = *cassandraConcurrency
111-
cluster.ProtoVersion = *cqlProtocolVersion
112-
cluster.Keyspace = *cassandraKeyspace
113-
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: *cassandraRetries}
114-
cluster.DisableInitialHostLookup = *cassandraDisableInitialHostLookup
115-
116-
switch *cassandraHostSelectionPolicy {
117-
case "roundrobin":
118-
cluster.PoolConfig.HostSelectionPolicy = gocql.RoundRobinHostPolicy()
119-
case "hostpool-simple":
120-
cluster.PoolConfig.HostSelectionPolicy = gocql.HostPoolHostPolicy(hostpool.New(nil))
121-
case "hostpool-epsilon-greedy":
122-
cluster.PoolConfig.HostSelectionPolicy = gocql.HostPoolHostPolicy(
123-
hostpool.NewEpsilonGreedy(nil, 0, &hostpool.LinearEpsilonValueCalculator{}),
124-
)
125-
case "tokenaware,roundrobin":
126-
cluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(
127-
gocql.RoundRobinHostPolicy(),
128-
)
129-
case "tokenaware,hostpool-simple":
130-
cluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(
131-
gocql.HostPoolHostPolicy(hostpool.New(nil)),
132-
)
133-
case "tokenaware,hostpool-epsilon-greedy":
134-
cluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(
135-
gocql.HostPoolHostPolicy(
136-
hostpool.NewEpsilonGreedy(nil, 0, &hostpool.LinearEpsilonValueCalculator{}),
137-
),
138-
)
139-
default:
140-
return nil, fmt.Errorf("unknown HostSelectionPolicy '%q'", *cassandraHostSelectionPolicy)
141-
}
101+
tableIn, tableOut := store.TTLTables[ttlIn].Name, store.TTLTables[ttlOut].Name
142102

143-
return cluster.CreateSession()
103+
update(store, int(ttlOut), tableIn, tableOut)
144104
}
145105

146106
func getTTL(now, ts, ttl int) int {
@@ -164,37 +124,46 @@ func completenessEstimate(token int64) float64 {
164124
return ((float64(token) / float64(maxToken)) + 1) / 2
165125
}
166126

167-
func worker(id int, jobs <-chan string, wg *sync.WaitGroup, session *gocql.Session, startTime, endTime, ttl int, tableIn, tableOut string) {
127+
func worker(id int, jobs <-chan string, wg *sync.WaitGroup, store *cassandra.CassandraStore, startTime, endTime, ttlOut int, tableIn, tableOut string) {
168128
defer wg.Done()
169129
var token int64
170130
var ts int
171131
var data []byte
172132
var query string
133+
pre := time.Now()
173134
queryTpl := fmt.Sprintf("SELECT token(key), ts, data FROM %s where key=? AND ts>=? AND ts<?", tableIn)
174135

175136
for key := range jobs {
176-
iter := session.Query(queryTpl, key, startTime, endTime).Iter()
137+
iter := store.Session.Query(queryTpl, key, startTime, endTime).Iter()
177138
for iter.Scan(&token, &ts, &data) {
178-
newTTL := getTTL(int(time.Now().Unix()), ts, ttl)
139+
newTTL := getTTL(int(time.Now().Unix()), ts, ttlOut)
179140
if tableIn == tableOut {
180141
query = fmt.Sprintf("UPDATE %s USING TTL %d SET data = ? WHERE key = ? AND ts = ?", tableIn, newTTL)
181142
} else {
182143
query = fmt.Sprintf("INSERT INTO %s (data, key, ts) values(?,?,?) USING TTL %d", tableOut, newTTL)
183144
}
184-
if *verbose {
145+
if verbose {
185146
log.Infof("id=%d processing rownum=%d table=%q key=%q ts=%d query=%q data='%x'\n", id, atomic.LoadUint64(&doneRows)+1, tableIn, key, ts, query, data)
186147
}
187148

188-
err := session.Query(query, data, key, ts).Exec()
149+
err := store.Session.Query(query, data, key, ts).Exec()
189150
if err != nil {
190151
log.Errorf("id=%d failed updating %s %s %d: %q", id, tableOut, key, ts, err)
191152
}
192153

193154
doneRowsSnap := atomic.AddUint64(&doneRows, 1)
194-
if doneRowsSnap%10000 == 0 {
155+
if doneRowsSnap%uint64(statusEvery) == 0 {
195156
doneKeysSnap := atomic.LoadUint64(&doneKeys)
196157
completeness := completenessEstimate(token)
197-
log.Infof("WORKING: id=%d processed %d keys, %d rows. (last token: %d, completeness estimate %.1f%%)", id, doneKeysSnap, doneRowsSnap, token, completeness*100)
158+
if completeness == 0 {
159+
completeness = math.SmallestNonzeroFloat64
160+
}
161+
162+
doneDur := time.Since(pre)
163+
totalDur := doneDur.Seconds() / completeness
164+
leftDur := (time.Second*time.Duration(int64(totalDur)) - doneDur).Round(time.Second)
165+
eta := time.Now().Add(leftDur).Round(time.Second).Format("2006-1-2 15:04:05")
166+
log.Infof("WORKING: id=%d processed %d keys, %d rows. (last token: %d, estimates: completeness %.1f%% - remaining %s - ETA %s)", id, doneKeysSnap, doneRowsSnap, token, completeness*100, leftDur, eta)
198167
}
199168
}
200169
err := iter.Close()
@@ -207,16 +176,16 @@ func worker(id int, jobs <-chan string, wg *sync.WaitGroup, session *gocql.Sessi
207176
}
208177
}
209178

210-
func update(session *gocql.Session, ttl int, tableIn, tableOut string) {
179+
func update(store *cassandra.CassandraStore, ttlOut int, tableIn, tableOut string) {
211180

212-
keyItr := session.Query(fmt.Sprintf("SELECT distinct key FROM %s", tableIn)).Iter()
181+
keyItr := store.Session.Query(fmt.Sprintf("SELECT distinct key FROM %s", tableIn)).Iter()
213182

214183
jobs := make(chan string, 100)
215184

216185
var wg sync.WaitGroup
217-
wg.Add(*numThreads)
218-
for i := 0; i < *numThreads; i++ {
219-
go worker(i, jobs, &wg, session, *startTs, *endTs, ttl, tableIn, tableOut)
186+
wg.Add(numThreads)
187+
for i := 0; i < numThreads; i++ {
188+
go worker(i, jobs, &wg, store, startTs, endTs, ttlOut, tableIn, tableOut)
220189
}
221190

222191
var key string

docs/tools.md

+14-7
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,11 @@ Flags:
511511
## mt-update-ttl
512512

513513
```
514-
mt-update-ttl [flags] ttl table-in [table-out]
514+
mt-update-ttl [flags] ttl-old ttl-new
515515
516516
Adjusts the data in Cassandra to use a new TTL value. The TTL is applied counting from the timestamp of the data
517-
If table-out not specified or same as table-in, will update in place. Otherwise will not touch input table and store results in table-out
518-
In that case, it is up to you to assure table-out exists before running this tool
519-
Not supported yet: for the per-ttl tables as of 0.7, automatically putting data in the right table
517+
Automatically resolves the corresponding tables based on ttl value. If the table stays the same, will update in place. Otherwise will copy to the new table, not touching the input data
518+
Unless you disable create-keyspace, tables are created as needed
520519
Flags:
521520
-cassandra-addrs string
522521
cassandra host (may be given multiple times as comma-separated list) (default "localhost")
@@ -525,13 +524,11 @@ Flags:
525524
-cassandra-ca-path string
526525
cassandra CA certificate path when using SSL (default "/etc/metrictank/ca.pem")
527526
-cassandra-concurrency int
528-
max number of concurrent reads to cassandra. (default 20)
527+
number of concurrent connections to cassandra. (default 20)
529528
-cassandra-consistency string
530529
write consistency (any|one|two|three|quorum|all|local_quorum|each_quorum|local_one (default "one")
531530
-cassandra-disable-initial-host-lookup
532531
instruct the driver to not attempt to get host info from the system.peers table
533-
-cassandra-host-selection-policy string
534-
(default "tokenaware,hostpool-epsilon-greedy")
535532
-cassandra-host-verification
536533
host (hostname and server cert) verification when using SSL (default true)
537534
-cassandra-keyspace string
@@ -548,14 +545,24 @@ Flags:
548545
username for authentication (default "cassandra")
549546
-cql-protocol-version int
550547
cql protocol version to use (default 4)
548+
-create-keyspace
549+
enable the creation of the keyspace and tables (default true)
551550
-end-timestamp int
552551
timestamp at which to stop, defaults to int max (default 2147483647)
552+
-host-selection-policy string
553+
(default "tokenaware,hostpool-epsilon-greedy")
554+
-schema-file string
555+
File containing the needed schemas in case database needs initializing (default "/etc/metrictank/schema-store-cassandra.toml")
553556
-start-timestamp int
554557
timestamp at which to start, defaults to 0
558+
-status-every int
559+
print status every x keys (default 100000)
555560
-threads int
556561
number of workers to use to process data (default 10)
557562
-verbose
558563
show every record being processed
564+
-window-factor int
565+
size of compaction window relative to TTL (default 20)
559566
```
560567

561568

store/cassandra/table.go

+18-18
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,25 @@ func GetTable(ttl uint32, windowFactor int, nameFormat string) Table {
4242
* generated with: https://gist.github.com/replay/69ad7cfd523edfa552cd12851fa74c58
4343
*
4444
* +------------------------+---------------+---------------------+----------+
45-
* | TTL hours | table_name | window_size (hours) | sstables |
45+
* | TTL hours | table_name | window_size (hours) | sstables |
4646
* +------------------------+---------------+---------------------+----------+
47-
* | 0 <= hours < 1 | metrics_0 | 1 | 0 - 2 |
48-
* | 1 <= hours < 2 | metrics_1 | 1 | 1 - 3 |
49-
* | 2 <= hours < 4 | metrics_2 | 1 | 2 - 5 |
50-
* | 4 <= hours < 8 | metrics_4 | 1 | 4 - 9 |
51-
* | 8 <= hours < 16 | metrics_8 | 1 | 8 - 17 |
52-
* | 16 <= hours < 32 | metrics_16 | 1 | 16 - 33 |
53-
* | 32 <= hours < 64 | metrics_32 | 2 | 16 - 33 |
54-
* | 64 <= hours < 128 | metrics_64 | 4 | 16 - 33 |
55-
* | 128 <= hours < 256 | metrics_128 | 7 | 19 - 38 |
56-
* | 256 <= hours < 512 | metrics_256 | 13 | 20 - 41 |
57-
* | 512 <= hours < 1024 | metrics_512 | 26 | 20 - 41 |
58-
* | 1024 <= hours < 2048 | metrics_1024 | 52 | 20 - 41 |
59-
* | 2048 <= hours < 4096 | metrics_2048 | 103 | 20 - 41 |
60-
* | 4096 <= hours < 8192 | metrics_4096 | 205 | 20 - 41 |
61-
* | 8192 <= hours < 16384 | metrics_8192 | 410 | 20 - 41 |
62-
* | 16384 <= hours < 32768 | metrics_16384 | 820 | 20 - 41 |
63-
* | 32768 <= hours < 65536 | metrics_32768 | 1639 | 20 - 41 |
47+
* | 0 <= hours < 1 | metric_0 | 1 | 0 - 2 |
48+
* | 1 <= hours < 2 | metric_1 | 1 | 1 - 3 |
49+
* | 2 <= hours < 4 | metric_2 | 1 | 2 - 5 |
50+
* | 4 <= hours < 8 | metric_4 | 1 | 4 - 9 |
51+
* | 8 <= hours < 16 | metric_8 | 1 | 8 - 17 |
52+
* | 16 <= hours < 32 | metric_16 | 1 | 16 - 33 |
53+
* | 32 <= hours < 64 | metric_32 | 2 | 16 - 33 |
54+
* | 64 <= hours < 128 | metric_64 | 4 | 16 - 33 |
55+
* | 128 <= hours < 256 | metric_128 | 7 | 19 - 38 |
56+
* | 256 <= hours < 512 | metric_256 | 13 | 20 - 41 |
57+
* | 512 <= hours < 1024 | metric_512 | 26 | 20 - 41 |
58+
* | 1024 <= hours < 2048 | metric_1024 | 52 | 20 - 41 |
59+
* | 2048 <= hours < 4096 | metric_2048 | 103 | 20 - 41 |
60+
* | 4096 <= hours < 8192 | metric_4096 | 205 | 20 - 41 |
61+
* | 8192 <= hours < 16384 | metric_8192 | 410 | 20 - 41 |
62+
* | 16384 <= hours < 32768 | metric_16384 | 820 | 20 - 41 |
63+
* | 32768 <= hours < 65536 | metric_32768 | 1639 | 20 - 41 |
6464
* +------------------------+---------------+---------------------+----------+
6565
*/
6666

0 commit comments

Comments
 (0)