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

Add batch synchronisation #19

Merged
merged 9 commits into from
Nov 8, 2016
Merged

Add batch synchronisation #19

merged 9 commits into from
Nov 8, 2016

Conversation

Sytten
Copy link
Contributor

@Sytten Sytten commented Nov 3, 2016

This is a first implementation of a new async storage that sync in batch. Suggestions and critiques are welcome.
This storage was necessary since we just can't make a query to our database (even async) for each query that arrives, that does not scale.

public AsyncBatchLimitUsageStorage(
LimitUsageStorage wrappedLimitUsageStorage,
int timeBetweenSynchronisations,
int delayBeforeFirstSync) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I see a real usefulness for this param - why not just use timeBetweenSynchronization ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed for tests :(

Copy link
Contributor

Choose a reason for hiding this comment

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

Alright then.

Copy link
Contributor

Choose a reason for hiding this comment

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

package protected then ?

@@ -146,6 +148,10 @@ public void asyncPerformance() {
lastResponse =
asyncStorage.incrementAndGet(RESOURCE1, LIMIT1, PROPERTY1, EXPIRATION, TIMESTAMP);
}
asyncStorage.shutdownStorage();
while (!asyncStorage.isTerminated()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should provide a method that blocks until terminated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I just wasn't sure of the implementation. Is a Thread sleep of like 10ms between checks good enough?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well it's a start, but have a look at awaitTermination on ExecutorService

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes thats true, forgot about that

.withCost(cost)
.build();

Pair<LimitKey, Integer> reponse = storage.addAndGet(request);
Copy link
Contributor

Choose a reason for hiding this comment

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

Until there is something I got wrong you're sending the events using separate requests here right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup. Why not send them in one go through the other overload?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think its possible since we iterate over the map and we sync each value one by one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that pipelining the queries would be better, but I don't really know how to do that while iterating over the map. We have to remember that it is less critical since its an async job and we don't have that many entries.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest to go with that first version for now. It's already a good improvement over the previous version. Further optimizations can be done in a separate issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#21

valueEntry.getValue().setTotal(reponse.getValue());
});
} catch (IllegalStateException e) {
logger.warn("Key was deleted during synchronisation, ignoring");
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't we catch this particular error in a more precise location?

Also, maybe provide a catch all handler that logs an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean by a more precise location? It makes no sens to catch it inside the inner forEach since if the key is deleted, all values in the inner map will be deleted too.
This exception should not be thrown by Oracle JVM anyway, I included it since the javadoc says that some implement might throw it.

Yes I will include a catch for runtime exception, maybe surrounding the whole function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed that for a catch Exception and log the error.

* @author Emile Fugulin
* @since 1.0.0
*/
public class CacheSynchronisation extends TimerTask {
Copy link
Contributor

Choose a reason for hiding this comment

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

Pretty sure that Synchronization should take a Z


@Override
public void run() {
cache.applyOnEach(
Copy link
Contributor

Choose a reason for hiding this comment

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

I would expect that we'd somehow clear the cache after we flush it's content downstream, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what you mean by that...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add a removeExpiredEntry after, good point

@@ -78,6 +78,14 @@ public AsyncLimitUsageStorage(LimitUsageStorage wrappedLimitUsageStorage) {
return wrappedLimitUsageStorage.debugCurrentLimitCounters();
}

public void shutdownStorage() {
executorService.shutdown();
Copy link
Contributor

Choose a reason for hiding this comment

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

No guarantee that we flush the remaining data to the underlying storage?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not necessary since the keys will expire by themselves

Copy link
Contributor

Choose a reason for hiding this comment

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

Well yes, but it does mean that those events will never get counted. Probably not a problem but still something that could be better easily no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ho I see what you mean. Well the shutdown isn't meant to be called during the life of the async storage. Its something that I would have put in a destructor, but Java doesn't have one...

Copy link
Contributor

Choose a reason for hiding this comment

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

Why shouldn't it be called? Makes sense to do it (maybe indirectly?) when shutting down the app no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes you have to do it when you shutdown the app, but I don't see your point of counting the events on the remote storage if you are shutting down everything :)

Copy link
Contributor

Choose a reason for hiding this comment

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

You might be shutting down a single server in a cluster that has 10s of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

well the shutdown method is only to stop gracefully the sync between the local and the remote storage, it won't actually stop the remote storage

@Sytten
Copy link
Contributor Author

Sytten commented Nov 7, 2016

I also replied to some comments marked as outdated because I renamed the class, but I would still like your feedback @malaporte

@Sytten
Copy link
Contributor Author

Sytten commented Nov 8, 2016

If you want to take a look too @GuiSim

public AsyncBatchLimitUsageStorage(
LimitUsageStorage wrappedLimitUsageStorage,
int timeBetweenSynchronisations,
int delayBeforeFirstSync) {
Copy link
Contributor

Choose a reason for hiding this comment

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

package protected then ?

private Timer timer;

public AsyncBatchLimitUsageStorage(
LimitUsageStorage wrappedLimitUsageStorage, int timeBetweenSynchronisations) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Duration instead of a int ? or we should specify the unit somehow...


@Override
public Map<LimitKey, Integer> addAndGet(Collection<AddAndGetRequest> requests) {
Map<LimitKey, Integer> cachedEntries = cache.addAndGet(requests);
Copy link
Contributor

Choose a reason for hiding this comment

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

return directly

valueEntry.getValue().setTotal(reponse.getValue());
});
} catch (IllegalStateException e) {
logger.warn("Key was deleted during synchronisation, ignoring");
Copy link
Contributor

Choose a reason for hiding this comment

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

"synchronization"
add a "." at the end


Pair<LimitKey, Integer> reponse = storage.addAndGet(request);

valueEntry.getValue().addAndGet(-cost);
Copy link
Contributor

Choose a reason for hiding this comment

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

should we add a decrementAndGet method ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could, but under it it will still be an addAndGet

Copy link
Contributor

Choose a reason for hiding this comment

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

why not a decrementAndGet under also ?

@Sytten Sytten mentioned this pull request Nov 8, 2016
@Sytten
Copy link
Contributor Author

Sytten commented Nov 8, 2016

If everybody approve, i will merge and create a V1 published on maven central.

@Sytten Sytten merged commit 21a738a into master Nov 8, 2016
@Sytten Sytten deleted the add-batch-async branch November 8, 2016 17:25
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants