limiting: Add Limiter #4

Merged
dstepanov-yadro merged 2 commits from a-savchuk/frostfs-qos:rps-limiting into master 2025-02-18 10:40:52 +00:00
Member

Added a Limiter to contol the number of concurrent operations. It can be used, for example, for active RPC limiting. It allows defining limits for both individual keys (e. g., RPC methods) and sets of keys (e. g., groups of RPC methods).

Added a `Limiter` to contol the number of concurrent operations. It can be used, for example, for active RPC limiting. It allows defining limits for both individual keys (e. g., RPC methods) and sets of keys (e. g., groups of RPC methods).
a-savchuk added 1 commit 2025-02-07 09:59:35 +00:00
[#xx] limiting: Add Limiter
Some checks failed
Tests and linters / Run gofumpt (pull_request) Failing after 45s
Vulncheck / Vulncheck (pull_request) Failing after 1m1s
DCO action / DCO (pull_request) Failing after 1m15s
Tests and linters / Tests (pull_request) Successful in 1m12s
Tests and linters / Tests with -race (pull_request) Successful in 1m16s
Tests and linters / Staticcheck (pull_request) Successful in 1m33s
Tests and linters / Lint (pull_request) Successful in 2m8s
Tests and linters / gopls check (pull_request) Successful in 2m14s
Pre-commit hooks / Pre-commit (pull_request) Successful in 2m27s
25fffe99cd
Signed-off-by: Aleksey Savchuk <a.savchuk@yadro.com>
a-savchuk force-pushed rps-limiting from 25fffe99cd to c99857c609 2025-02-07 10:00:51 +00:00 Compare
a-savchuk force-pushed rps-limiting from c99857c609 to d9bff390b5 2025-02-07 10:02:56 +00:00 Compare
a-savchuk force-pushed rps-limiting from d9bff390b5 to 52deeef627 2025-02-07 12:21:30 +00:00 Compare
a-savchuk changed title from WIP: limiting: Add Limiter to limiting: Add Limiter 2025-02-07 14:51:27 +00:00
requested review from fyrchik 2025-02-07 14:51:27 +00:00
aarifullin reviewed 2025-02-07 15:19:48 +00:00
@ -0,0 +35,4 @@
}
type Limiter struct {
m map[string]*semaphore
Member

It's enough to acquire semaphore by two concurrent goroutines and we're on panic :)

It's enough to acquire semaphore by two concurrent goroutines and we're on `panic` :)
Author
Member

Why? This map is used only for reading

Why? This map is used only for reading
Member
sem, ok := lr.m[key]
	if !ok {
		return func() {}, nil
	}

Yeah, okay. Then nevermind

```go sem, ok := lr.m[key] if !ok { return func() {}, nil } ``` Yeah, okay. Then nevermind
Author
Member

I think it'd be good if I leave a comment about it

I think it'd be good if I leave a comment about it
aarifullin marked this conversation as resolved
Member

This PR is absolutely OK with me. But, let's be honest, the PR is introducing rather a semaphore than a limiter. It's just a naïve implementation of a rate-limiter. Here I suggest to discuss one thing: what if we introduce an interface

type Limiter interface {
   /* Seize(context.Context) error */
}

and make this implementation as semaphoreLimiter. Probably, we could also try some new implementation in the great future. For example, we could try softer way to limit a goroutine execution by getting it slept with time.Sleep etc.

This is not change request, let's just discuss.

@dstepanov-yadro WDYT?

This PR is absolutely OK with me. But, let's be honest, the PR is introducing rather a semaphore than a limiter. It's just a naïve implementation of a rate-limiter. Here I suggest to discuss one thing: what if we introduce an interface ```go type Limiter interface { /* Seize(context.Context) error */ } ``` and make this implementation as `semaphoreLimiter`. Probably, we could also try some new implementation in the great future. For example, we could try softer way to limit a goroutine execution by getting it _slept_ with `time.Sleep` etc. This is not change request, let's just discuss. @dstepanov-yadro WDYT?
requested reviews from storage-core-developers, storage-core-committers 2025-02-07 17:46:50 +00:00
dstepanov-yadro reviewed 2025-02-10 09:18:17 +00:00
@ -0,0 +52,4 @@
type ReleaseFunc func()
func New(limits []KeyLimit) *Limiter {
lr := Limiter{m: make(map[string]*semaphore)}

Some kind of validation is needed. What happens if I specify two identical keys?

Some kind of validation is needed. What happens if I specify two identical keys?
Author
Member

Done

Done
dstepanov-yadro marked this conversation as resolved
dstepanov-yadro reviewed 2025-02-10 09:20:09 +00:00
@ -0,0 +5,4 @@
)
type semaphore struct {
ch chan struct{}

This option is generally good, but I have doubts that using channels is faster than atomics. Please provide another comparison (benchmark) with the atomic-based implementation.

This option is generally good, but I have doubts that using channels is faster than `atomic`s. Please provide another comparison (benchmark) with the `atomic`-based implementation.
Member

@dstepanov-yadro wrote in #4 (comment):

#4 (comment)

That's why I suggested to introduce an interface and try different implementations to compare

@dstepanov-yadro wrote in https://git.frostfs.info/TrueCloudLab/frostfs-qos/pulls/4#issuecomment-66989: https://git.frostfs.info/TrueCloudLab/frostfs-qos/pulls/4#issuecomment-66975 That's why I suggested to introduce an interface and try different implementations to compare
Author
Member

@aarifullin

  • Defined Limiter as an interface. After discussion with @dstepanov-yadro, left only TryAcquire method and renamed it to Acquire
  • Added semaphoreLimiter. Made it generic, so it can use different semaphore implementation, i. e. built with atomics, channels and etc.
  • Also added different semaphore implementation: with channels, with atomics, with atomics when allowing slight burst. Compared their performance. After we choose the best one, I can drop others

@dstepanov-yadro

  • Compared different semaphore implementation [here]
  • Measured average and maximum average limit for the atomic semaphore when allowing slight burst [here]
@aarifullin - Defined `Limiter` as an interface. After discussion with @dstepanov-yadro, left only `TryAcquire` method and renamed it to `Acquire` - Added `semaphoreLimiter`. Made it generic, so it can use different semaphore implementation, i. e. built with atomics, channels and etc. - Also added different semaphore implementation: with channels, with atomics, with atomics when allowing slight burst. Compared their performance. After we choose the best one, I can drop others @dstepanov-yadro - Compared different semaphore implementation [[here](/attachments/842933f2-fffc-4364-93f2-7e9b1ec5f476)] - Measured average and maximum average limit for the atomic semaphore when allowing slight burst [[here](/attachments/cc75ce83-0fef-4d53-97be-0cdadd4d8ca7)]
dstepanov-yadro marked this conversation as resolved
a-savchuk force-pushed rps-limiting from 52deeef627 to 455fec9b99 2025-02-10 10:15:31 +00:00 Compare
a-savchuk force-pushed rps-limiting from 455fec9b99 to ae6938c61c 2025-02-10 11:30:27 +00:00 Compare
a-savchuk added 1 commit 2025-02-10 21:09:33 +00:00
[#4] limiting: Add atomic limiter
Some checks failed
DCO action / DCO (pull_request) Successful in 31s
Tests and linters / Run gofumpt (pull_request) Successful in 50s
Vulncheck / Vulncheck (pull_request) Failing after 58s
Pre-commit hooks / Pre-commit (pull_request) Successful in 1m19s
Tests and linters / Staticcheck (pull_request) Successful in 1m14s
Tests and linters / Lint (pull_request) Successful in 1m37s
Tests and linters / gopls check (pull_request) Successful in 2m38s
Tests and linters / Tests (pull_request) Successful in 2m47s
Tests and linters / Tests with -race (pull_request) Successful in 9m47s
a1610ff987
Added atomic limiter for future benchmark. May drop this commit later.

Signed-off-by: Aleksey Savchuk <a.savchuk@yadro.com>
a-savchuk force-pushed rps-limiting from a1610ff987 to 6e4a6d7610 2025-02-12 20:07:06 +00:00 Compare
a-savchuk force-pushed rps-limiting from 6e4a6d7610 to 6e59e9c285 2025-02-13 07:26:15 +00:00 Compare
a-savchuk force-pushed rps-limiting from 6e59e9c285 to a37e3dc381 2025-02-13 07:30:37 +00:00 Compare
a-savchuk force-pushed rps-limiting from a37e3dc381 to 730583b3bb 2025-02-13 07:35:22 +00:00 Compare
aarifullin reviewed 2025-02-13 07:55:55 +00:00
@ -0,0 +5,4 @@
)
type atomicSemaphore struct {
countDown atomic.Int64
Member

countDown -> countdown as it's one word :)

`countDown` -> `countdown` as it's one word :)
Author
Member

Fixed

Fixed
aarifullin marked this conversation as resolved
aarifullin reviewed 2025-02-13 08:24:54 +00:00
@ -0,0 +114,4 @@
sizes := []int64{1, 10, 100, 1000, 10000}
lockDurations := []time.Duration{0, time.Microsecond, 10 * time.Microsecond, 100 * time.Microsecond}
// sizes := []int64{1}
Member

Leftovers?

Leftovers?
Author
Member

Fixed

Fixed
aarifullin marked this conversation as resolved
a-savchuk force-pushed rps-limiting from 730583b3bb to f7adcbb206 2025-02-13 08:30:16 +00:00 Compare
aarifullin approved these changes 2025-02-13 08:46:02 +00:00
Dismissed
aarifullin left a comment
Member

Excellent

Excellent
a-savchuk force-pushed rps-limiting from f7adcbb206 to 38482570f2 2025-02-13 11:27:16 +00:00 Compare
Author
Member

As discussed with @dstepanov-yadro, I left only atomic semaphore with burst allowed, also made it public to be reused later

As discussed with @dstepanov-yadro, I left only atomic semaphore with burst allowed, also made it public to be reused later
a-savchuk force-pushed rps-limiting from 38482570f2 to 11ea76879b 2025-02-13 11:35:05 +00:00 Compare
dstepanov-yadro requested changes 2025-02-13 11:44:29 +00:00
Dismissed
@ -0,0 +11,4 @@
type Limiter interface {
// Acquire attempts to reserve a slot without blocking.
//
// Returns a release function and true if successful, otherwise false.

Shoul I call release func if false returned? Please, extend the doc.

Shoul I call release func if `false` returned? Please, extend the doc.
Author
Member

Done

Done
dstepanov-yadro marked this conversation as resolved
@ -0,0 +23,4 @@
Release()
}
type semaphoreLimiter[T semaphore] struct {

To much abstractions: semaphore, generic type, BurstAtomic, etc.
Please, make the code simple.

To much abstractions: `semaphore`, generic type, `BurstAtomic`, etc. Please, make the code simple.
Author
Member

Done

Done
dstepanov-yadro marked this conversation as resolved
a-savchuk force-pushed rps-limiting from 11ea76879b to f2f98f6c2f 2025-02-13 12:40:54 +00:00 Compare
a-savchuk force-pushed rps-limiting from f2f98f6c2f to 51ff783b18 2025-02-13 12:51:17 +00:00 Compare
a-savchuk force-pushed rps-limiting from 51ff783b18 to 356851eed3 2025-02-13 12:52:11 +00:00 Compare
dstepanov-yadro approved these changes 2025-02-14 06:27:24 +00:00
requested reviews from aarifullin, acid-ant 2025-02-17 06:32:13 +00:00
aarifullin approved these changes 2025-02-18 09:06:08 +00:00
aarifullin left a comment
Member

👍

👍
dstepanov-yadro merged commit 356851eed3 into master 2025-02-18 10:40:52 +00:00
dstepanov-yadro referenced this pull request from a commit 2025-02-18 10:40:54 +00:00
a-savchuk deleted branch rps-limiting 2025-02-28 14:30:09 +00:00
Sign in to join this conversation.
No description provided.