Skip to content

Commit 76503f4

Browse files
feat: add partition ring to dataobj consumer
1 parent 5b382fa commit 76503f4

File tree

6 files changed

+312
-156
lines changed

6 files changed

+312
-156
lines changed

docs/sources/shared/configuration.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,66 @@ dataobj:
12521252
# CLI flag: -dataobj-consumer.lifecycler.ID
12531253
[id: <string> | default = "<hostname>"]
12541254

1255+
partition_ring:
1256+
# The key-value store used to share the hash ring across multiple
1257+
# instances. This option needs be set on ingesters, distributors,
1258+
# queriers, and rulers when running in microservices mode.
1259+
kvstore:
1260+
# Backend storage to use for the ring. Supported values are: consul,
1261+
# etcd, inmemory, memberlist, multi.
1262+
# CLI flag: -dataobj-consumer.partition-ring.store
1263+
[store: <string> | default = "memberlist"]
1264+
1265+
# The prefix for the keys in the store. Should end with a /.
1266+
# CLI flag: -dataobj-consumer.partition-ring.prefix
1267+
[prefix: <string> | default = "collectors/"]
1268+
1269+
# Configuration for a Consul client. Only applies if the selected
1270+
# kvstore is consul.
1271+
# The CLI flags prefix for this block configuration is:
1272+
# dataobj-consumer.partition-ring
1273+
[consul: <consul>]
1274+
1275+
# Configuration for an ETCD v3 client. Only applies if the selected
1276+
# kvstore is etcd.
1277+
# The CLI flags prefix for this block configuration is:
1278+
# dataobj-consumer.partition-ring
1279+
[etcd: <etcd>]
1280+
1281+
multi:
1282+
# Primary backend storage used by multi-client.
1283+
# CLI flag: -dataobj-consumer.partition-ring.multi.primary
1284+
[primary: <string> | default = ""]
1285+
1286+
# Secondary backend storage used by multi-client.
1287+
# CLI flag: -dataobj-consumer.partition-ring.multi.secondary
1288+
[secondary: <string> | default = ""]
1289+
1290+
# Mirror writes to secondary store.
1291+
# CLI flag: -dataobj-consumer.partition-ring.multi.mirror-enabled
1292+
[mirror_enabled: <boolean> | default = false]
1293+
1294+
# Timeout for storing value to secondary store.
1295+
# CLI flag: -dataobj-consumer.partition-ring.multi.mirror-timeout
1296+
[mirror_timeout: <duration> | default = 2s]
1297+
1298+
# Minimum number of owners to wait before a PENDING partition gets
1299+
# switched to ACTIVE.
1300+
# CLI flag: -dataobj-consumer.partition-ring.min-partition-owners-count
1301+
[min_partition_owners_count: <int> | default = 1]
1302+
1303+
# How long the minimum number of owners are enforced before a PENDING
1304+
# partition gets switched to ACTIVE.
1305+
# CLI flag: -dataobj-consumer.partition-ring.min-partition-owners-duration
1306+
[min_partition_owners_duration: <duration> | default = 10s]
1307+
1308+
# How long to wait before an INACTIVE partition is eligible for deletion.
1309+
# The partition is deleted only if it has been in INACTIVE state for at
1310+
# least the configured duration and it has no owners registered. A value
1311+
# of 0 disables partitions deletion.
1312+
# CLI flag: -dataobj-consumer.partition-ring.delete-inactive-partition-after
1313+
[delete_inactive_partition_after: <duration> | default = 13h]
1314+
12551315
uploader:
12561316
# The size of the SHA prefix to use for generating object storage keys for
12571317
# data objects.
@@ -2824,6 +2884,7 @@ Configuration for a Consul client. Only applies if the selected kvstore is `cons
28242884
- `common.storage.ring`
28252885
- `compactor.ring`
28262886
- `dataobj-consumer`
2887+
- `dataobj-consumer.partition-ring`
28272888
- `distributor.ring`
28282889
- `index-gateway.ring`
28292890
- `ingest-limits`
@@ -3075,6 +3136,7 @@ Configuration for an ETCD v3 client. Only applies if the selected kvstore is `et
30753136
- `common.storage.ring`
30763137
- `compactor.ring`
30773138
- `dataobj-consumer`
3139+
- `dataobj-consumer.partition-ring`
30783140
- `distributor.ring`
30793141
- `index-gateway.ring`
30803142
- `ingest-limits`
@@ -7498,6 +7560,7 @@ The TLS configuration. The supported CLI flags `<prefix>` used to reference this
74987560
- `compactor.grpc-client`
74997561
- `compactor.ring.etcd`
75007562
- `dataobj-consumer.etcd`
7563+
- `dataobj-consumer.partition-ring.etcd`
75017564
- `distributor.ring.etcd`
75027565
- `etcd`
75037566
- `frontend.grpc-client-config`

pkg/dataobj/consumer/config.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import (
88

99
"github.com/grafana/loki/v3/pkg/dataobj/consumer/logsobj"
1010
"github.com/grafana/loki/v3/pkg/dataobj/uploader"
11+
"github.com/grafana/loki/v3/pkg/kafka/partitionring"
1112
util_log "github.com/grafana/loki/v3/pkg/util/log"
1213
)
1314

1415
type Config struct {
15-
BuilderConfig logsobj.BuilderConfig `yaml:"builder,omitempty"`
16-
LifecyclerConfig ring.LifecyclerConfig `yaml:"lifecycler,omitempty"`
17-
UploaderConfig uploader.Config `yaml:"uploader"`
18-
IdleFlushTimeout time.Duration `yaml:"idle_flush_timeout"`
16+
BuilderConfig logsobj.BuilderConfig `yaml:"builder,omitempty"`
17+
LifecyclerConfig ring.LifecyclerConfig `yaml:"lifecycler,omitempty"`
18+
PartitionRingConfig partitionring.Config `yaml:"partition_ring" category:"experimental"`
19+
UploaderConfig uploader.Config `yaml:"uploader"`
20+
IdleFlushTimeout time.Duration `yaml:"idle_flush_timeout"`
1921
}
2022

2123
func (cfg *Config) Validate() error {
@@ -38,6 +40,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
3840
func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
3941
cfg.BuilderConfig.RegisterFlagsWithPrefix(prefix, f)
4042
cfg.LifecyclerConfig.RegisterFlagsWithPrefix(prefix, f, util_log.Logger)
43+
cfg.PartitionRingConfig.RegisterFlagsWithPrefix(prefix, f)
4144
cfg.UploaderConfig.RegisterFlagsWithPrefix(prefix, f)
4245

4346
f.DurationVar(&cfg.IdleFlushTimeout, prefix+"idle-flush-timeout", 60*60*time.Second, "The maximum amount of time to wait in seconds before flushing an object that is no longer receiving new writes")

pkg/dataobj/consumer/service.go

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/go-kit/log"
88
"github.com/go-kit/log/level"
9+
"github.com/grafana/dskit/kv"
910
"github.com/grafana/dskit/ring"
1011
"github.com/grafana/dskit/services"
1112
"github.com/prometheus/client_golang/prometheus"
@@ -15,22 +16,26 @@ import (
1516
"github.com/grafana/loki/v3/pkg/dataobj/metastore"
1617
"github.com/grafana/loki/v3/pkg/kafka"
1718
"github.com/grafana/loki/v3/pkg/kafka/client"
19+
"github.com/grafana/loki/v3/pkg/kafka/partitionring"
1820
"github.com/grafana/loki/v3/pkg/scratch"
1921
)
2022

2123
const (
22-
RingKey = "dataobj-consumer"
23-
RingName = "dataobj-consumer"
24+
RingKey = "dataobj-consumer"
25+
RingName = "dataobj-consumer"
26+
PartitionRingKey = "dataobj-consumer-partitions-key"
27+
PartitionRingName = "dataobj-consumer-partitions"
2428
)
2529

2630
type Service struct {
2731
services.Service
28-
cfg Config
29-
metastoreEvents *kgo.Client
30-
lifecycler *ring.Lifecycler
31-
watcher *services.FailureWatcher
32-
logger log.Logger
33-
reg prometheus.Registerer
32+
cfg Config
33+
metastoreEvents *kgo.Client
34+
lifecycler *ring.Lifecycler
35+
partitionInstanceLifecycler *ring.PartitionInstanceLifecycler
36+
watcher *services.FailureWatcher
37+
logger log.Logger
38+
reg prometheus.Registerer
3439
}
3540

3641
func New(kafkaCfg kafka.Config, cfg Config, _ metastore.Config, _ objstore.Bucket, _ scratch.Store, _ string, _ ring.PartitionRingReader, reg prometheus.Registerer, logger log.Logger) (*Service, error) {
@@ -69,20 +74,57 @@ func New(kafkaCfg kafka.Config, cfg Config, _ metastore.Config, _ objstore.Bucke
6974
}
7075
s.lifecycler = lifecycler
7176

77+
// An instance must register itself in the partition ring. Each instance
78+
// is responsible for consuming exactly one partition determined by
79+
// its partition ID. Once ready, the instance will declare its partition
80+
// as active in the partition ring. This is how distributors know which
81+
// partitions have a ready consumer.
82+
partitionID, err := partitionring.ExtractPartitionID(cfg.LifecyclerConfig.ID)
83+
if err != nil {
84+
return nil, fmt.Errorf("failed to extract partition ID from lifecycler configuration: %w", err)
85+
}
86+
partitionRingKV := cfg.PartitionRingConfig.KVStore.Mock
87+
// The mock KV is used in tests. If this is not a test then we must
88+
// initialize a real kv.
89+
if partitionRingKV == nil {
90+
partitionRingKV, err = kv.NewClient(
91+
cfg.PartitionRingConfig.KVStore,
92+
ring.GetPartitionRingCodec(),
93+
kv.RegistererWithKVName(reg, "dataobj-consumer-lifecycler"),
94+
logger,
95+
)
96+
if err != nil {
97+
return nil, fmt.Errorf("failed to set up partition ring: %w", err)
98+
}
99+
}
100+
partitionInstanceLifecycler := ring.NewPartitionInstanceLifecycler(
101+
cfg.PartitionRingConfig.ToLifecyclerConfig(partitionID, cfg.LifecyclerConfig.ID),
102+
PartitionRingName,
103+
PartitionRingKey,
104+
partitionRingKV,
105+
logger,
106+
prometheus.WrapRegistererWithPrefix("loki_", reg))
107+
s.partitionInstanceLifecycler = partitionInstanceLifecycler
108+
72109
watcher := services.NewFailureWatcher()
73110
watcher.WatchService(lifecycler)
111+
watcher.WatchService(partitionInstanceLifecycler)
74112
s.watcher = watcher
75113

76114
s.Service = services.NewBasicService(s.starting, s.running, s.stopping)
77115
return s, nil
78116
}
79117

80118
// starting implements the Service interface's starting method.
81-
func (s *Service) starting(ctx context.Context) (err error) {
119+
func (s *Service) starting(ctx context.Context) error {
82120
level.Info(s.logger).Log("msg", "starting")
83121
if err := services.StartAndAwaitRunning(ctx, s.lifecycler); err != nil {
84122
return fmt.Errorf("failed to start lifecycler: %w", err)
85123
}
124+
if err := services.StartAndAwaitRunning(ctx, s.partitionInstanceLifecycler); err != nil {
125+
return fmt.Errorf("failed to start partition instance lifecycler: %w", err)
126+
}
127+
86128
return nil
87129
}
88130

@@ -96,6 +138,9 @@ func (s *Service) running(ctx context.Context) error {
96138
func (s *Service) stopping(failureCase error) error {
97139
level.Info(s.logger).Log("msg", "stopping")
98140
ctx := context.TODO()
141+
if err := services.StopAndAwaitTerminated(ctx, s.partitionInstanceLifecycler); err != nil {
142+
level.Warn(s.logger).Log("msg", "failed to stop partition instance lifecycler", "err", err)
143+
}
99144
if err := services.StopAndAwaitTerminated(ctx, s.lifecycler); err != nil {
100145
level.Warn(s.logger).Log("msg", "failed to stop lifecycler", "err", err)
101146
}

pkg/loki/config_wrapper.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ func applyConfigToRings(r, defaults *ConfigWrapper, rc lokiring.RingConfig, merg
379379
r.DataObj.Consumer.LifecyclerConfig.ListenPort = rc.ListenPort
380380
r.DataObj.Consumer.LifecyclerConfig.ObservePeriod = rc.ObservePeriod
381381
r.DataObj.Consumer.LifecyclerConfig.EnableInet6 = rc.EnableIPv6
382+
r.DataObj.Consumer.PartitionRingConfig.KVStore = rc.KVStore
382383
}
383384
}
384385

0 commit comments

Comments
 (0)