use of org.opensearch.ad.ml.EntityModel in project anomaly-detection by opensearch-project.
the class PriorityCache method maintainInactiveCache.
private void maintainInactiveCache() {
if (lastInActiveEntityMaintenance.plus(this.modelTtl).isAfter(clock.instant())) {
// don't scan inactive cache too frequently as it is costly
return;
}
// force maintenance of the cache. ref: https://tinyurl.com/pyy3p9v6
inActiveEntities.cleanUp();
// // make sure no model has been stored due to bugs
for (ModelState<EntityModel> state : inActiveEntities.asMap().values()) {
EntityModel model = state.getModel();
if (model != null && model.getTrcf().isPresent()) {
LOG.warn(new ParameterizedMessage("Inactive entity's model is null: [{}]. Maybe there are bugs.", state.getModelId()));
state.setModel(null);
}
}
lastInActiveEntityMaintenance = clock.instant();
}
use of org.opensearch.ad.ml.EntityModel in project anomaly-detection by opensearch-project.
the class PriorityCache method maintenance.
/**
* Maintain active entity's cache and door keepers.
*
* inActiveEntities is a Guava's LRU cache. The data structure itself is
* gonna evict items if they are inactive for 3 days or its maximum size
* reached (1 million entries)
*/
@Override
public void maintenance() {
try {
// clean up memory if we allocate more memory than we should
tryClearUpMemory();
activeEnities.entrySet().stream().forEach(cacheBufferEntry -> {
String detectorId = cacheBufferEntry.getKey();
CacheBuffer cacheBuffer = cacheBufferEntry.getValue();
// remove expired cache buffer
if (cacheBuffer.expired(modelTtl)) {
activeEnities.remove(detectorId);
cacheBuffer.clear();
} else {
List<ModelState<EntityModel>> removedStates = cacheBuffer.maintenance();
for (ModelState<EntityModel> state : removedStates) {
addIntoInactiveCache(state);
}
}
});
maintainInactiveCache();
doorKeepers.entrySet().stream().forEach(doorKeeperEntry -> {
String detectorId = doorKeeperEntry.getKey();
DoorKeeper doorKeeper = doorKeeperEntry.getValue();
// doorKeeper has its own state ttl
if (doorKeeper.expired(null)) {
doorKeepers.remove(detectorId);
} else {
doorKeeper.maintenance();
}
});
} catch (Exception e) {
// will be thrown to ES's transport broadcast handler
throw new AnomalyDetectionException("Fail to maintain cache", e);
}
}
use of org.opensearch.ad.ml.EntityModel in project anomaly-detection by opensearch-project.
the class PriorityCache method getModelProfile.
/**
* Gets an entity's model state
*
* @param detectorId detector id
* @param entityModelId entity model id
* @return the model state
*/
@Override
public Optional<ModelProfile> getModelProfile(String detectorId, String entityModelId) {
CacheBuffer cacheBuffer = activeEnities.get(detectorId);
if (cacheBuffer != null && cacheBuffer.getModel(entityModelId).isPresent()) {
EntityModel model = cacheBuffer.getModel(entityModelId).get();
Entity entity = null;
if (model != null && model.getEntity().isPresent()) {
entity = model.getEntity().get();
}
return Optional.of(new ModelProfile(entityModelId, entity, cacheBuffer.getMemoryConsumptionPerEntity()));
}
return Optional.empty();
}
use of org.opensearch.ad.ml.EntityModel in project anomaly-detection by opensearch-project.
the class PriorityCache method selectUpdateCandidate.
@Override
public Pair<List<Entity>, List<Entity>> selectUpdateCandidate(Collection<Entity> cacheMissEntities, String detectorId, AnomalyDetector detector) {
List<Entity> hotEntities = new ArrayList<>();
List<Entity> coldEntities = new ArrayList<>();
CacheBuffer buffer = activeEnities.get(detectorId);
if (buffer == null) {
// Since this method is public, need to deal with this case in case of misuse.
return Pair.of(hotEntities, coldEntities);
}
Iterator<Entity> cacheMissEntitiesIter = cacheMissEntities.iterator();
// current buffer's dedicated cache has free slots
while (cacheMissEntitiesIter.hasNext() && buffer.dedicatedCacheAvailable()) {
addEntity(hotEntities, cacheMissEntitiesIter.next(), detectorId);
}
while (cacheMissEntitiesIter.hasNext() && memoryTracker.canAllocate(buffer.getMemoryConsumptionPerEntity())) {
// can allocate in shared cache
// race conditions can happen when multiple threads evaluating this condition.
// This is a problem as our AD memory usage is close to full and we put
// more things than we planned. One model in HCAD is small,
// it is fine we exceed a little. We have regular maintenance to remove
// extra memory usage.
addEntity(hotEntities, cacheMissEntitiesIter.next(), detectorId);
}
// check if we can replace anything in dedicated or shared cache
// have a copy since we need to do the iteration twice: one for
// dedicated cache and one for shared cache
List<Entity> otherBufferReplaceCandidates = new ArrayList<>();
while (cacheMissEntitiesIter.hasNext()) {
// can replace an entity in the same CacheBuffer living in reserved
// or shared cache
// thread safe as each detector has one thread at one time and only the
// thread can access its buffer.
Entity entity = cacheMissEntitiesIter.next();
Optional<String> modelId = entity.getModelId(detectorId);
if (false == modelId.isPresent()) {
continue;
}
Optional<ModelState<EntityModel>> state = getStateFromInactiveEntiiyCache(modelId.get());
if (false == state.isPresent()) {
// not even recorded in inActiveEntities yet because of doorKeeper
continue;
}
ModelState<EntityModel> modelState = state.get();
float priority = modelState.getPriority();
if (buffer.canReplaceWithinDetector(priority)) {
addEntity(hotEntities, entity, detectorId);
} else {
// re-evaluate replacement condition in other buffers
otherBufferReplaceCandidates.add(entity);
}
}
// record current minimum priority among all detectors to save redundant
// scanning of all CacheBuffers
CacheBuffer bufferToRemove = null;
float minPriority = Float.MIN_VALUE;
// check if we can replace in other CacheBuffer
cacheMissEntitiesIter = otherBufferReplaceCandidates.iterator();
while (cacheMissEntitiesIter.hasNext()) {
// If two threads try to remove the same entity and add their own state, the 2nd remove
// returns null and only the first one succeeds.
Entity entity = cacheMissEntitiesIter.next();
Optional<String> modelId = entity.getModelId(detectorId);
if (false == modelId.isPresent()) {
continue;
}
Optional<ModelState<EntityModel>> inactiveState = getStateFromInactiveEntiiyCache(modelId.get());
if (false == inactiveState.isPresent()) {
// empty state should not stand a chance to replace others
continue;
}
ModelState<EntityModel> state = inactiveState.get();
float priority = state.getPriority();
float scaledPriority = buffer.getPriorityTracker().getScaledPriority(priority);
if (scaledPriority <= minPriority) {
// not even larger than the minPriority, we can put this to coldEntities
addEntity(coldEntities, entity, detectorId);
continue;
}
// Float.MIN_VALUE means we need to re-iterate through all CacheBuffers
if (minPriority == Float.MIN_VALUE) {
Triple<CacheBuffer, String, Float> bufferToRemoveEntity = canReplaceInSharedCache(buffer, scaledPriority);
bufferToRemove = bufferToRemoveEntity.getLeft();
minPriority = bufferToRemoveEntity.getRight();
}
if (bufferToRemove != null) {
addEntity(hotEntities, entity, detectorId);
// reset minPriority after the replacement so that we need to iterate all CacheBuffer
// again
minPriority = Float.MIN_VALUE;
} else {
// after trying everything, we can now safely put this to cold entities list
addEntity(coldEntities, entity, detectorId);
}
}
return Pair.of(hotEntities, coldEntities);
}
use of org.opensearch.ad.ml.EntityModel in project anomaly-detection by opensearch-project.
the class CacheBuffer method put.
/**
* Insert the model state associated with a model Id to the cache. Update priority.
* @param entityModelId the model Id
* @param value the ModelState
* @param priority the priority
*/
private void put(String entityModelId, ModelState<EntityModel> value, float priority) {
ModelState<EntityModel> contentNode = items.get(entityModelId);
if (contentNode == null) {
priorityTracker.addPriority(entityModelId, priority);
items.put(entityModelId, value);
Instant now = clock.instant();
value.setLastUsedTime(now);
lastUsedTime = now;
// skip bookkeeping.
if (!sharedCacheEmpty()) {
memoryTracker.consumeMemory(memoryConsumptionPerEntity, false, Origin.HC_DETECTOR);
}
} else {
update(entityModelId);
items.put(entityModelId, value);
}
}
Aggregations