nstream.persist.kv.state.MapChangeset Maven / Gradle / Ivy
// Copyright 2015-2024 Nstream, inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nstream.persist.kv.state;
import java.util.Collection;
import java.util.Objects;
import swim.collections.HashTrieMap;
import swim.recon.Recon;
import swim.structure.Value;
/**
* Records the changes made to a map lane since its most recent commit. The state of a map lane has a monotonic epoch
* counter that is incremented each time the map is cleared. The changeset keeps track of this epoch and will
* disregard changes that were made in a previous epoch (as they can be guaranteed to be irrelevant).
*
* Change-sets are immutable and all methods will produce a new instance.
*/
public final class MapChangeset {
// The current monotonically increasing epoch of the map lane.
private final long currentEpoch;
// Whether the map has been cleared (this will be applied to the data store before any updates).
private final boolean cleared;
// The total size of the change-set in bytes.
private final long totalSize;
// Keys that have been updated.
private final HashTrieMap keys;
private MapChangeset(long currentEpoch, boolean cleared, HashTrieMap keys, long totalSize) {
this.currentEpoch = currentEpoch;
this.cleared = cleared;
this.keys = keys;
this.totalSize = totalSize;
}
public MapChangeset(long currentEpoch) {
this.currentEpoch = currentEpoch;
this.cleared = false;
this.keys = HashTrieMap.empty();
this.totalSize = 0;
}
/**
* Indicate that an entry in the map has been updated.
*
* @param epoch The epoch of the update.
* @param key The key of the modified entry.
* @param valueSize The size of the value ({@code 0} for a removal).
* @return The updated changeset.
*/
public MapChangeset added(long epoch, Value key, long valueSize) {
if (epoch < this.currentEpoch) {
return this;
}
final var oldSize = this.keys.get(key);
final long delta;
if (oldSize == null) {
delta = Recon.sizeOf(key) + valueSize;
} else {
if (valueSize == oldSize) {
return this;
}
delta = valueSize - oldSize;
}
return new MapChangeset(this.currentEpoch, this.cleared, this.keys.updated(key, valueSize), this.totalSize + delta);
}
/**
* Indicate that the map state has been cleared.
*
* @param epoch The epoch of the change.
* @return The updated change-set.
*/
public MapChangeset cleared(long epoch) {
if (epoch < this.currentEpoch) {
return this;
} else {
return new MapChangeset(epoch, true, HashTrieMap.empty(), 0);
}
}
public boolean isCleared() {
return this.cleared;
}
public Collection keys() {
return this.keys.keySet();
}
/**
* @return The total size of all changes in this change-set in bytes.
*/
public long getCommitSize() {
return this.totalSize;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final MapChangeset that = (MapChangeset) o;
return this.currentEpoch == that.currentEpoch && this.cleared == that.cleared && this.totalSize == that.totalSize && Objects.equals(this.keys, that.keys);
}
@Override
public int hashCode() {
return Objects.hash(this.currentEpoch, this.cleared, this.totalSize, this.keys);
}
}