Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Size based retention #7927

Open
wants to merge 74 commits into
base: main
Choose a base branch
from

Conversation

Abuelodelanada
Copy link

@Abuelodelanada Abuelodelanada commented Dec 13, 2022

What this PR does / why we need it:

This WorkInProgress RP is the first approach to try to solve #6876.
I'm opening the PR at this stage to share the progress we made and fundamentally to read suggestions about it!

Which issue(s) this PR fixes:

Fixes #6876

Special notes for your reviewer:

To activate the size_based_retention policy you have to add the size_based_retention_percentage in the storage.filesystem section:

common:
  path_prefix: /tmp/loki
  storage:
    filesystem:
      chunks_directory: /tmp/loki/chunks
      rules_directory: /tmp/loki/rules
      size_based_retention_percentage: 2

Besides in the compactor section, the value for retention_enabled must be true, for instance:

compactor:
  working_directory: /tmp/loki/retention
  shared_store: filesystem
  compaction_interval: 30s
  retention_enabled: true
  retention_delete_delay: 1m
  retention_delete_worker_count: 150

How to test:

  1. Build loki: make loki
  2. Let's say loki will store chunks in a filesystem with a disk usage near 2%, use a loki config like this:
auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  instance_addr: 127.0.0.1
  path_prefix: /tmp/loki
  storage:
    filesystem:
      chunks_directory: /tmp/loki/chunks
      rules_directory: /tmp/loki/rules
      size_based_retention_percentage: 2
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory


storage_config:
  boltdb_shipper:
    active_index_directory: /tmp/loki/boltdb-shipper-active
    shared_store: filesystem
  filesystem:
    directory: /tmp/loki/chunks


schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h

ingester:
  wal:
    enabled: true
    dir: /tmp/loki/chunks/wal
    flush_on_shutdown: true

compactor:
  working_directory: /tmp/loki/retention
  shared_store: filesystem
  compaction_interval: 30s
  retention_enabled: true
  retention_delete_delay: 1m
  retention_delete_worker_count: 150

limits_config:
  ingestion_rate_mb: 10
  1. Run loki: ./cmd/loki/loki -config.file=cmd/loki/loki-local-config.yaml
  2. Send logs to loki using for instance promtail.
  3. Check that Loki actually receives the logs, for instance using Grafana:

image

  1. When disk usage reaches (or exceeds) 2% Loki will start deleting chunk files and you will see in the logs:
level=info ts=2023-04-14T17:17:59.614606507Z caller=retention.go:135 msg="Detected disk usage percentage" diskUsage=52.61%
level=info ts=2023-04-14T17:17:59.614636609Z caller=retention.go:156 msg="Block size retention exceeded, removing chunks from file" filepath=miguelito-1681483439590834149-1681491600.gz
level=info ts=2023-04-14T17:18:59.611874456Z caller=retention.go:135 msg="Detected disk usage percentage" diskUsage=52.60%
level=info ts=2023-04-14T17:18:59.614467894Z caller=retention.go:135 msg="Detected disk usage percentage" diskUsage=52.60%
level=info ts=2023-04-14T17:18:59.614573707Z caller=retention.go:156 msg="Block size retention exceeded, removing chunks from file" filepath=compactor-1681492684.gz
  1. Loki will check for disk usage every minute.

Doubts and considerations

Settings

Despite of the fact it is working seems the solution may be improved a lot, for instance we need 2 settings in the config file:

  • size_based_retention_percentage in storage.filesystem section
  • retention_enabled: true in compactor section.

I would love to avoid the need for retention_enabled: true....

About the author

Since I'm mostly a Python/PHP Software Engineer writing my first lines in Go and it's my first contact with the Loki codebase, there are surely many things that can be improved. Please feel free to comment!! 😄

Checklist

  • Reviewed the CONTRIBUTING.md guide
  • Documentation added
  • Tests updated
  • CHANGELOG.md updated
  • Changes that require user attention or interaction to upgrade are documented in docs/sources/upgrading/_index.md

@Abuelodelanada Abuelodelanada requested a review from a team as a code owner December 13, 2022 13:44
@CLAassistant
Copy link

CLAassistant commented Dec 13, 2022

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 3 committers have signed the CLA.

✅ MasslessParticle
✅ Abuelodelanada
❌ rbarry82
You have signed the CLA already but the status is still pending? Let us recheck it.

@Abuelodelanada
Copy link
Author

Hi @MasslessParticle

After some weeks of radio silence (we were working hard), we return to this PR.

We are at a point this is kind of working as expected, using a config file like this one:

auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  instance_addr: 127.0.0.1
  path_prefix: /tmp/loki
  storage:
    filesystem:
      chunks_directory: /tmp/loki/chunks
      rules_directory: /tmp/loki/rules
      size_based_retention_percentage: 2
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory


storage_config:
  boltdb_shipper:
    active_index_directory: /tmp/loki/boltdb-shipper-active
    shared_store: filesystem
  filesystem:
    directory: /tmp/loki/chunks


schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h

ingester:
  wal:
    enabled: true
    dir: /tmp/loki/chunks/wal
    flush_on_shutdown: true

compactor:
  working_directory: /tmp/loki/retention
  shared_store: filesystem
  compaction_interval: 30s
  retention_enabled: true
  retention_delete_delay: 1m
  retention_delete_worker_count: 150

limits_config:
  ingestion_rate_mb: 10

Note the size_based_retention_percentage under common section and retention_enabled: true under compactor.

May you give it a try and let us known your thoughts?

@MasslessParticle
Copy link
Contributor

@Abuelodelanada 🎉

I'm a buried at the moment but I will test when I get the chance!

@simskij
Copy link

simskij commented May 31, 2023

@Abuelodelanada tada

I'm a buried at the moment but I will test when I get the chance!

@MasslessParticle

Hey mate :)

Sorry for being a pain in the ass. We're really keen on getting this functionality into shape. Would you mind giving it another review?

Thanks,
Simme

Copy link
Contributor

@MasslessParticle MasslessParticle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry again for the delay to look at this. I can't run it because things have drifted but I have done a review.

This is looking good and makes sense. I've added comments about a couple of concerns. I'm also concerned there there's a lot of logic here and very few tests. I notice that you merged my original suggestions. That's fair to avoid rework but also those were illustrative and should probably be tested

pkg/loki/modules.go Outdated Show resolved Hide resolved
return nil
}

func (c *Compactor) sizeBasedCompactionInterval(ctx context.Context) error {
if exceeded, err := c.sizeBasedRetention.ThresholdExceeded(); !exceeded {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't handle the error when exceeded == true

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @MasslessParticle

We are not handling this situation, because the function ThresholdExceeded() does not return this combination: true, err. The possible returns are:

  • false, err
  • false, nil
  • true, nil

Do you think we should modify this anyway?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For uniform error handling, I think it would be best to modify this to handle true, err. This guards against future changes to ThresholdExceeded

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing the code again seems that there is no point in checking all the combinations.

We need just 2 guards:

  • Log a message and return nil if there is an error, or
  • return nil if doesn't exceed threshold.

In other situations we should continue with the execution.

Pushing these changes with less nesting, let me know your thoughts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense. Do we return nil on error to not exit compactions? That makes sense, but we should probably add a metric here so an alert can be raised on successive failures.

pkg/storage/stores/indexshipper/compactor/compactor.go Outdated Show resolved Hide resolved
@@ -90,7 +227,17 @@ type Marker struct {
markTimeout time.Duration
}

var mu sync.Mutex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the mutex here. The compactor should probably own the marker metrics and pass it to markers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not implement this.
This section of the code was written by @rbarry82 who sadly passed away a month ago.

Do you mean something like this?

func NewMarker(workingDirectory string, expiration ExpirationChecker, markTimeout time.Duration, chunkClient client.Client, metrics *markerMetrics) (*Marker, error) {
	return &Marker{
		workingDirectory: workingDirectory,
		expiration:       expiration,
		markerMetrics:    metrics,
		chunkClient:      chunkClient,
		markTimeout:      markTimeout,
	}, nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry to hear about the loss of your colleague 🙁

Metrics can only be registered once. If we try to register them a second time, a runtime panic occurs. The code, as is prevents registering a second time but it's a little akward.

I think we could change

func NewMarker(workingDirectory string, expiration ExpirationChecker, markTimeout time.Duration, chunkClient client.Client, r prometheus.Registerer) (*Marker, error)

to take metrics like this

func NewMarker(workingDirectory string, expiration ExpirationChecker, markTimeout time.Duration, chunkClient client.Client, metrics *markerMetrics) (*Marker, error)

But then we need a place to instantiate the metrics. We could probably do it in the compactor's init function and make it a field on the compactor.

If we do this, the mutex can be deleted altogether.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About mutex, now I remember that Ryan added the following to Compactor struct:

	// Size based compaction means that two compactions might try to happen at the same time.
	// Use this to ensure size-based and normal compaction can't step on eachother.
	compactionMtx sync.Mutex

Does make sense or he was wrong?

About your suggestion, I have this draft right now (didn't push it yet)... Is it close to what you suggest?

modified   pkg/storage/stores/indexshipper/compactor/compactor.go
@@ -182,6 +182,7 @@ type Compactor struct {
 	// Size based compaction means that two compactions might try to happen at the same time.
 	// Use this to ensure size-based and normal compaction can't step on eachother.
 	compactionMtx sync.Mutex
+	markerMetrics *retention.markerMetrics
 
 	// one for each object store
 	storeContainers map[string]storeContainer
@@ -310,6 +311,8 @@ func (c *Compactor) init(objectStoreClients map[string]client.ObjectClient, sche
 		}
 	}
 
+	c.markerMetrics = retention.newMarkerMetrics(r)
+
 	c.storeContainers = make(map[string]storeContainer, len(objectStoreClients))
 	for objectStoreType, objectClient := range objectStoreClients {
 		var sc storeContainer
@@ -338,7 +341,7 @@ func (c *Compactor) init(objectStoreClients map[string]client.ObjectClient, sche
 				return fmt.Errorf("failed to init sweeper: %w", err)
 			}
 
-			sc.tableMarker, err = retention.NewMarker(retentionWorkDir, c.expirationChecker, c.cfg.RetentionTableTimeout, chunkClient, r)
+			sc.tableMarker, err = retention.NewMarker(retentionWorkDir, c.expirationChecker, c.cfg.RetentionTableTimeout, chunkClient, c.markerMetrics)
 			if err != nil {
 				return fmt.Errorf("failed to init table marker: %w", err)
 			}
modified   pkg/storage/stores/indexshipper/compactor/retention/retention.go
@@ -5,9 +5,9 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"sync"
 	"os"
 	"path/filepath"
+	"sync"
 
 	"time"
 
@@ -227,21 +227,11 @@ type Marker struct {
 	markTimeout      time.Duration
 }
 
-var mu sync.Mutex
-var metrics *markerMetrics
-
-func NewMarker(workingDirectory string, expiration ExpirationChecker, markTimeout time.Duration, chunkClient client.Client, r prometheus.Registerer) (*Marker, error) {
-	mu.Lock()
-	defer mu.Unlock()
-
-	if metrics == nil {
-		metrics = newMarkerMetrics(r)
-	}
-
+func NewMarker(workingDirectory string, expiration ExpirationChecker, markTimeout time.Duration, chunkClient client.Client, metrics *markerMetrics) (*Marker, error) {
 	return &Marker{
 		workingDirectory: workingDirectory,
 		expiration:       expiration,
-		markerMetrics:    newMarkerMetrics(r),
+		markerMetrics:    metrics,
 		chunkClient:      chunkClient,
 		markTimeout:      markTimeout,
 	}, nil

Copy link
Contributor

@MasslessParticle MasslessParticle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry again for the delay to look at this. I can't run it because things have drifted but I have done a review.

This is looking good and makes sense. I've added comments about a couple of concerns. I'm also concerned there there's a lot of logic here and very few tests. I notice that you merged my original suggestions. That's fair to avoid rework but also those were illustrative and should probably be tested

@Abuelodelanada
Copy link
Author

Sorry again for the delay to look at this. I can't run it because things have drifted but I have done a review.

This is looking good and makes sense. I've added comments about a couple of concerns. I'm also concerned there there's a lot of logic here and very few tests. I notice that you merged my original suggestions. That's fair to avoid rework but also those were illustrative and should probably be tested

I'm working on the test right now.

@simskij
Copy link

simskij commented Jul 10, 2023

@MasslessParticle

This is taking a lot longer than we expected as we've bumped into quite a lot of issues with the delta between when we originally forked and the current state. In combination with @rbarry82's unfortunate passing, things are going slower than usual. Work is however still ongoing and we're still looking to get this across the finish line.

@VenkateswaranJ
Copy link

Any update on this?

@simskij
Copy link

simskij commented Dec 12, 2023

Any update on this?

Some, but nothing substantial. Loki moves quite rapidly, and as this feature touches multiple parts of the code base, it's been somewhat of a moving target. I'll get back to you all at the beginning of next year with more details.

@mkjpryor
Copy link

We would absolutely love this feature. Any progress so far this year?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size/XL type/docs Issues related to technical documentation; the Docs Squad uses this label across many repositories
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Loki Block Disk Space Cleanup Proposal
9 participants