Make sure group searches are cached and entries invalidate accordingly
Closes #26983 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
6c0f7444b5
commit
326d63ce74
4 changed files with 94 additions and 39 deletions
|
@ -32,6 +32,7 @@ import org.keycloak.storage.StorageId;
|
|||
import org.keycloak.storage.client.ClientStorageProviderModel;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -1016,40 +1017,63 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public Stream<GroupModel> getTopLevelGroupsStream(RealmModel realm, String search, Boolean exact, Integer first, Integer max) {
|
||||
String cacheKey = getTopGroupsQueryCacheKey(realm.getId() + search + first + max);
|
||||
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(cacheKey)
|
||||
|| listInvalidations.contains(realm.getId());
|
||||
if (queryDB) {
|
||||
String cacheKey = getTopGroupsQueryCacheKey(realm.getId());
|
||||
|
||||
if (hasInvalidation(realm, cacheKey)) {
|
||||
return getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max);
|
||||
}
|
||||
|
||||
GroupListQuery query = cache.get(cacheKey, GroupListQuery.class);
|
||||
if (Objects.nonNull(query)) {
|
||||
logger.tracev("getTopLevelGroups cache hit: {0}", realm.getName());
|
||||
}
|
||||
String searchKey = Optional.ofNullable(search).orElse("") + "." + Optional.ofNullable(first).orElse(-1) + "." + Optional.ofNullable(max).orElse(-1);
|
||||
Set<String> cached;
|
||||
|
||||
if (Objects.isNull(query)) {
|
||||
// not cached yet
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
List<GroupModel> model = getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max).collect(Collectors.toList());
|
||||
if (model.isEmpty()) return Stream.empty();
|
||||
Set<String> ids = new HashSet<>();
|
||||
for (GroupModel client : model) ids.add(client.getId());
|
||||
query = new GroupListQuery(loaded, cacheKey, realm, ids);
|
||||
cached = getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max).map(GroupModel::getId).collect(Collectors.toSet());
|
||||
query = new GroupListQuery(loaded, cacheKey, realm, searchKey, cached);
|
||||
logger.tracev("adding realm getTopLevelGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model.stream();
|
||||
}
|
||||
List<GroupModel> list = new LinkedList<>();
|
||||
for (String id : query.getGroups()) {
|
||||
GroupModel group = session.groups().getGroupById(realm, id);
|
||||
if (Objects.isNull(group)) {
|
||||
invalidations.add(cacheKey);
|
||||
return getGroupDelegate().getTopLevelGroupsStream(realm);
|
||||
} else {
|
||||
logger.tracev("getTopLevelGroups cache hit: {0}", realm.getName());
|
||||
|
||||
cached = query.getGroups(searchKey);
|
||||
|
||||
if (hasInvalidation(realm, cacheKey) || cached == null) {
|
||||
// there is a cache entry, but the current search is not yet cached
|
||||
cache.invalidateObject(cacheKey);
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
cached = getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max).map(GroupModel::getId).collect(Collectors.toSet());
|
||||
query = new GroupListQuery(loaded, cacheKey, realm, searchKey, cached, query);
|
||||
logger.tracev("adding realm getTopLevelGroups search cache miss: realm {0} key {1}", realm.getName(), searchKey);
|
||||
cache.addRevisioned(query, cache.getCurrentCounter());
|
||||
}
|
||||
list.add(group);
|
||||
}
|
||||
|
||||
return list.stream().sorted(GroupModel.COMPARE_BY_NAME);
|
||||
AtomicBoolean invalidate = new AtomicBoolean(false);
|
||||
Stream<GroupModel> groups = cached.stream()
|
||||
.map((id) -> session.groups().getGroupById(realm, id))
|
||||
.takeWhile(group -> {
|
||||
if (Objects.isNull(group)) {
|
||||
invalidate.set(true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.sorted(GroupModel.COMPARE_BY_NAME);
|
||||
|
||||
if (!invalidate.get()) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
invalidations.add(cacheKey);
|
||||
|
||||
return getGroupDelegate().getTopLevelGroupsStream(realm, search, exact, first, max);
|
||||
}
|
||||
|
||||
private boolean hasInvalidation(RealmModel realm, String cacheKey) {
|
||||
return invalidations.contains(cacheKey) || listInvalidations.contains(cacheKey)
|
||||
|| listInvalidations.contains(realm.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -2,27 +2,59 @@ package org.keycloak.models.cache.infinispan.entities;
|
|||
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class GroupListQuery extends AbstractRevisioned implements GroupQuery {
|
||||
private final Set<String> groups;
|
||||
private final String realm;
|
||||
private final String realmName;
|
||||
private Map<String, Set<String>> searchKeys;
|
||||
|
||||
public GroupListQuery(Long revisioned, String id, RealmModel realm, Set<String> groups) {
|
||||
public GroupListQuery(Long revisioned, String id, RealmModel realm, String searchKey, Set<String> result) {
|
||||
super(revisioned, id);
|
||||
this.realm = realm.getId();
|
||||
this.realmName = realm.getName();
|
||||
this.groups = groups;
|
||||
this.searchKeys = new HashMap<>();
|
||||
this.searchKeys.put(searchKey, result);
|
||||
}
|
||||
|
||||
public GroupListQuery(Long revisioned, String id, RealmModel realm, String searchKey, Set<String> result, GroupListQuery previous) {
|
||||
super(revisioned, id);
|
||||
this.realm = realm.getId();
|
||||
this.realmName = realm.getName();
|
||||
this.searchKeys = new HashMap<>();
|
||||
this.searchKeys.putAll(previous.searchKeys);
|
||||
this.searchKeys.put(searchKey, result);
|
||||
}
|
||||
|
||||
public GroupListQuery(Long revisioned, String id, RealmModel realm, Set<String> ids) {
|
||||
super(revisioned, id);
|
||||
this.realm = realm.getId();
|
||||
this.realmName = realm.getName();
|
||||
this.searchKeys = new HashMap<>();
|
||||
this.searchKeys.put(id, ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getGroups() {
|
||||
return groups;
|
||||
Collection<Set<String>> values = searchKeys.values();
|
||||
|
||||
if (values.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
return values.stream().flatMap(Set::stream).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public Set<String> getGroups(String searchKey) {
|
||||
return searchKeys.get(searchKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -30,6 +62,13 @@ public class GroupListQuery extends AbstractRevisioned implements GroupQuery {
|
|||
return realm;
|
||||
}
|
||||
|
||||
public Map<String, Set<String>> getSearchKeys() {
|
||||
if (searchKeys == null) {
|
||||
searchKeys = new HashMap<>();
|
||||
}
|
||||
return searchKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GroupListQuery{" +
|
||||
|
|
|
@ -20,8 +20,6 @@ package org.keycloak.models;
|
|||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.storage.group.GroupLookupProvider;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
|
|
|
@ -235,17 +235,11 @@ public class ConcurrencyTest extends AbstractConcurrencyTest {
|
|||
c = realm.groups().group(id).toRepresentation();
|
||||
assertNotNull(c);
|
||||
|
||||
boolean retry = true;
|
||||
int i = 0;
|
||||
do {
|
||||
List<String> groups = realm.groups().groups().stream()
|
||||
.map(GroupRepresentation::getName)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
retry = !groups.contains(name);
|
||||
i++;
|
||||
} while(retry && i < 3);
|
||||
assertFalse("Group " + name + " [" + id + "] " + " not found in group list", retry);
|
||||
assertTrue("Group " + name + " [" + id + "] " + " not found in group list",
|
||||
realm.groups().groups().stream()
|
||||
.map(GroupRepresentation::getName)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(name::equals));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue