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

Dynamic configuration of capacity #76

Closed
nitenA opened this issue Jun 26, 2018 · 10 comments
Closed

Dynamic configuration of capacity #76

nitenA opened this issue Jun 26, 2018 · 10 comments
Labels

Comments

@nitenA
Copy link

nitenA commented Jun 26, 2018

Hi,

This is in reference to old issue #45 where i would like to create dynamic capacity per hour.

Earlier version has concept of BandwidthAdjuster which kind of I am looking for. Not sure if it actually adjust bucket during run time when hour changes as feature has been decommissioned so can't test.

Can you suggest any alternative way to achieve below using new 4.* version of Bucket4J?

BandwidthAdjuster adjuster = new BandwidthAdjuster() {
@OverRide
public long getCapacity(long currentTime) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(currentTime);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
if (hour >= 7 && hour <= 23) {
return 10;
} else {
return 2;
}
}
};
Buckets.withMillisTimePrecision()
.withLimitedBandwidth(adjuster, TimeUnit.MINUTES, 1, 10);

@vladimir-bukhtoyarov
Copy link
Collaborator

vladimir-bukhtoyarov commented Jun 26, 2018

Dynamic capacity was removed because of contradiction with other features. Currently there are no direct alternative.

replaceConfiguration method is single way to change capacity

@nitenA
Copy link
Author

nitenA commented Jun 26, 2018

Thanks for prompt answer. Do you suggest to keep 24 unique buckets per hour as alternative solution otherwise hour of duration has to be checked on every request to call replaceConfiguration before request is served. Please let me know your thoughts.

@vladimir-bukhtoyarov
Copy link
Collaborator

vladimir-bukhtoyarov commented Jun 26, 2018

@nitenA splitting to 24 buckets is unpractical because each bucket has own independent state, so there is a chance of over-consumption near to border of hour , because next bucket is not mentioned about amount of tokens that issued by previous bucket.

Try to think about problem in different way. I want to speculate that for mostly cases dynamic capacity can be emulated by dynamic weight of tokens. This example from bucket4j-1.0 can be directly emulated without any lost of user experience with bucket4-4.1 in following way:

Firstly, create bucket with maximum capacity that proposed to be:

LocalBucket bucket = Bucket4j.builder()
                .addLimit(Bandwidth.simple(10, Duration.ofMinutes(1)))
                .build();

Secondly, when consuming just multiply tokens if neccessary:

private static boolean tryConsume(Bucket bucket, int tokens) {
        int weightedTokens;
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        if (hour >= 7 && hour <= 23) {
            weightedTokens = tokens;
        } else {
            // at night tokens are more costly
            weightedTokens = tokens * 5;
        }
        return bucket.tryConsume(weightedTokens);
    }

@nitenA
Copy link
Author

nitenA commented Jun 26, 2018

Hi @vladimir-bukhtoyarov
I am clarifying my thoughts again as this is very critical from our solution standpoint.

  1. First of all, my use case demands fixed no of tokens for a given min under hour in distributed environment. Think from API Gateway standpoint like 5 hits per min for an IP from any server. I am utilizing Hazelcast for distributed grid persistence.

  2. The thought of keeping different bucket per hour is to have different bandwidth per min by having independent bucket state per hour. Like mentioned below in below example. Slight variation when hour changes won't be causing too much issue in our case.

just for ex, 12 am -8 am ---> bucket (Bandwidth.simple(10, Duration.ofMinutes(1)))
9 am -3 pm ---> bucket (Bandwidth.simple(5, Duration.ofMinutes(1)))
4 pm -8 pm ---> bucket (Bandwidth.simple(2, Duration.ofMinutes(1)))
9 pm -11 pm ---> bucket (Bandwidth.simple(8, Duration.ofMinutes(1)))

  1. Secondly, if i understand above weighted tokens, you are trying to consume weighted tokens per hour once. I would like to consume one token at point from bucket per min or so as per defined bucket configuration duration.

@vladimir-bukhtoyarov
Copy link
Collaborator

vladimir-bukhtoyarov commented Jun 26, 2018

Sorry if i was unclean, but i try to insist again that all you need already present in library.

just for ex, 12 am -8 am ---> bucket (Bandwidth.simple(10, Duration.ofMinutes(1)))
9 am -3 pm ---> bucket (Bandwidth.simple(5, Duration.ofMinutes(1)))
4 pm -8 pm ---> bucket (Bandwidth.simple(2, Duration.ofMinutes(1)))
9 pm -11 pm ---> bucket (Bandwidth.simple(8, Duration.ofMinutes(1)))

If duration is fixed(as in your case when duration is 1 minute), then solving your task is trivial:

  • Find minimum dividend.
  • Redefine limits according to minimum dividend.
  • Choose limit with maximum capacity as limit for bucket.
  • Use weighted tokens according to minimum dividend.

Lets do this exerciser together:

  • minimum dividend from 10, 5, 2, 8 is 40.
  • redefined limits would be: (multiplicator = minimum dividend / maxTokens = 40/10=4)
    12 am -8 am --> Bandwidth.simple(40, Duration.ofMinutes(1)))
    9 am -3 pm ---> bucket (Bandwidth.simple(20, Duration.ofMinutes(1)))
    4 pm -8 pm ---> bucket (Bandwidth.simple(8, Duration.ofMinutes(1)))
    9 pm -11 pm ---> bucket (Bandwidth.simple(32, Duration.ofMinutes(1)
  • then choose bandwidth with max capacity, it is obviously Bandwidth.simple(40, Duration.ofMinutes(1)))
  • then use following formula when calculating tokens weight:
public int getWeight(int tokens) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        if (hour < 9) {
            return 40/10*tokens
        } else if (hour < 14) {
            return 40/5*tokens
        } else if (hour >= 21) {
            return 40/2*tokens
        } else {
            return 40/8*tokens
        }
}

@nitenA
Copy link
Author

nitenA commented Jun 27, 2018

thanks @vladimir-bukhtoyarov for detailed explanation.
I will try out and will get back to you for any questions. For now i am good.

@nitenA nitenA closed this as completed Jun 27, 2018
@nitenA
Copy link
Author

nitenA commented Jun 27, 2018

Hi @vladimir-bukhtoyarov

One last question. When we use weighted bucket, the bucket will be created with max capacity like you mentioned..
Bandwidth.simple(40, Duration.ofMinutes(1)))

In our case, what will be the ideal BucketConfgiuration? I am using Refill.intervally to turn of greediness. Just to reiterate we don't want to go above capacity which is fixed for defined duration i.e. 1 minute.

To make it clear, let's go with your suggestion. For 12 am -8 am if i need to have 10 tokens per min (fixed), having below configuration doesn't produce right results. I tried tryConsume(4) to utilize weighted token but doesn't get desired results.

Refill refill = Refill.intervally(10, Duration.ofMinutes(1));
Bandwidth limit = Bandwidth.classic(30, refill);

As now you understand my problem and i understand weighted token concept, only remaining thing to decide right Bucket Configuration for fixed token per minute. Pls suggest

@nitenA nitenA reopened this Jun 27, 2018
@nitenA
Copy link
Author

nitenA commented Jun 27, 2018

I think below is right configuration for me which works fine.. Overall capacity and refill should be same and having Refill.intervally shutting of greediness. Just validate..

Refill newRefill = Refill.intervally(40, Duration.ofMinutes(1));
Bandwidth newLimit = Bandwidth.classic(40, newRefill);

@vladimir-bukhtoyarov
Copy link
Collaborator

@nitenA you are right, tokens of refill should be multiplied similar to capacity.

@nitenA
Copy link
Author

nitenA commented Jun 27, 2018

Thanks for all your help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants