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

Split-brain merge policy design issues #12721

Closed
Donnerbart opened this issue Mar 27, 2018 · 1 comment
Closed

Split-brain merge policy design issues #12721

Donnerbart opened this issue Mar 27, 2018 · 1 comment

Comments

@Donnerbart
Copy link
Contributor

@Donnerbart Donnerbart commented Mar 27, 2018

The actual SPI design is very flexible, but requires downcasting in the merge policy classes. It's also quite unclear which merge policy is compatible with which data structure. This doesn't guide an implementer nicely through the process.

Example:

class HigherHitsMergePolicy implements SplitBrainMergePolicy {

    @Override
    public <V> V merge(MergingValue<V> mergingValue, MergingValue<V> existingValue) {
        checkInstanceOf(mergingValue, MergingHits.class);
        checkInstanceOf(existingValue, MergingHits.class);
        if (mergingValue == null) {
            return existingValue.getValue();
        }
        if (existingValue == null) {
            return mergingValue.getValue();
        }
        MergingHits merging = (MergingHits) mergingValue;
        MergingHits existing = (MergingHits) existingValue;
        if (merging.getHits() >= existing.getHits()) {
            return mergingValue.getValue();
        }
        return existingValue.getValue();
    }
}

Using Generics to avoid casting

A better solution would be to use generics throughout the system.

Example:

class HigherHitsMergePolicy<V, T extends MergingHits<V>>
        implements SplitBrainMergePolicy<V, T> {

    @Override
    public V merge(T mergingValue, T existingValue) {
        if (mergingValue == null) {
            return existingValue.getValue();
        }
        if (existingValue == null) {
            return mergingValue.getValue();
        }
        if (mergingValue.getHits() >= existingValue.getHits()) {
            return mergingValue.getValue();
        }
        return existingValue.getValue();
    }
}

Now it's clear that the merge policy requires MergingHits, which can be checked at the startup (fail fast). There is also no downcasting needed.

You can even compose complex merge policies with the generics:

class CustomMergePolicy<V, T extends MergingHits<V> & MergingCreationTime<V>>
        implements SplitBrainMergePolicy<V, T> {

    @Override
    public V merge(T mergingValue, T existingValue) {
        if (existingValue == null) {
            return mergingValue.getValue();
        }
        // the merging value wins, if it's older and has more hits
        if (mergingValue.getCreationTime() < existingValue.getCreationTime()
                && mergingValue.getHits() > existingValue.getHits()) {
            return mergingValue.getValue();
        }
        return existingValue.getValue();
    }
}

Using Generics for lookup

We can provide lookup interfaces, which show the provided merge types of each data structure:

interface MapMergeTypes extends MergingEntry<Data, Data>, MergingCreationTime<Data>,
    MergingHits<Data>, MergingLastAccessTime<Data>, MergingLastUpdateTime<Data>,
    MergingTTL<Data>, MergingCosts<Data>, MergingVersion<Data>,
    MergingExpirationTime<Data>, MergingLastStoredTime<Data> {
}

interface MultiMapMergeTypes extends MergingEntry<Data, Object>,
    MergingCreationTime<Object>, MergingHits<Object>, MergingLastAccessTime<Object>,
    MergingLastUpdateTime<Object> {
}

With these interfaces you can restrict a merge policy to a specific data structure:

class MapEntryCostsMergePolicy implements SplitBrainMergePolicy<Data, MapMergeTypes> {

    @Override
    public Data merge(MapMergeTypes mergingValue, MapMergeTypes existingValue) {
        if (existingValue == null) {
            return mergingValue.getValue();
        }
        // the merging value wins, if it's costs are higher
        if (mergingValue.getCost() > existingValue.getCost()) {
            return mergingValue.getValue();
        }
        return existingValue.getValue();
    }
}

The interfaces can also be used for config validation, to provide fail fast behavior, if a configured merge policy requires a merge type, which is not provided by the data structure.

@pveentjer
Copy link
Member

@pveentjer pveentjer commented Mar 28, 2018

If there is little time remaining, a potential solution would be to mark the API as beta so that in 3.11 we can have another look how to fix it. Also api's need to 'grow', designing an API without using it as an end user often is problematic.

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

Successfully merging a pull request may close this issue.

3 participants
You can’t perform that action at this time.