У меня есть приложение, работающее на сервере Wildfly 10.0.0. Я сохраняю свои сущности в базе данных, но для повышения производительности я решил использовать кеп Infinispan (шаблон для кэширования). производительности приложений увеличилась, но все еще есть проблемы с отношением расчета родителя - дети (Foo и SubFoos):Кэш как индекс базы данных для отношения родитель-ребенок в Infinispan
Это лицо код:
@Entity
class Foo
{
Long id;
}
@Entity
class SubFoo
{
Long id;
Long fooId;
}
Это код услуги:
public class FooService
{
@Inject
EntityManger em;
@Inject
Cache<Long, Foo> fooCache;
@Inject
Cache<Long, SubFoo> subFooCache;
public void save(Foo foo)
{
em.save(foo);
fooCache.put(foo.id, foo);
}
public void save(SubFoo subFoo)
{
em.save(subFoo);
subFooCache.put(subFoo.id, subFoo);
}
public void remove ....
public void update ....
public Foo get(Long fooId)
{
return fooCache.get(fooId);
}
public SubFoo get(Long subFooId)
{
return subFooCache.get(subFooId);
}
public List<SubFoo> findSubFoo(Long fooId)
{
return subFooCache.values().stream().filter(subFoo -> subFoo.fooId == fooId).collect(Collector.list());
}
}
Проблема findSubFoo способ. Каждый раз приходится проверять все коллекции subFoos. И этот метод все еще сильно влияет на производительность приложения.
Можно ли бесконечно использовать имитацию использования индексов базы данных или решить эту проблему?
подход 1
Я пытался использовать TreeCache для хранения списка в качестве значения кэша, и сохранить параллельность и поддержку транзакций. TreeNode сохраняет FooId как корневой путь узла и subFooId как листья. Этот подход был ОК, когда количество запросов было небольшим. Во многих запросах, где состояния без согласованности, где короткие состояния с отсутствием согласованности. Казалось, что Tx был зачислен, и один кеш (нормальный для сущности subFoo) был обновлен, но второй (foo2subFoo) еще не был. Но через короткий промежуток времени все было в порядке, и согласованность данных вернулась.
Исходный код:
поставщик Cache с производителями:
@ApplicationScoped
public class CacheProvider
{
private EmbeddedCacheManager cacheManager;
@PostConstruct
public void init()
{
final GlobalConfiguration globalConfig =
new GlobalConfigurationBuilder().nonClusteredDefault().globalJmxStatistics()
.allowDuplicateDomains(true).build();
final Configuration entityDefaultConfig =
new ConfigurationBuilder().transaction().transactionMode(TransactionMode.TRANSACTIONAL)
.lockingMode(LockingMode.OPTIMISTIC)
.eviction().strategy(EvictionStrategy.NONE).build();
final Configuration indexDefaultConfig = new ConfigurationBuilder()
.transaction().transactionMode(TransactionMode.TRANSACTIONAL)
.eviction().strategy(EvictionStrategy.NONE)
.invocationBatching().enable()
.build();
cacheManager = new DefaultCacheManager(globalConfig );
cacheManager.defineConfiguration("Foos", entityDefaultConfig);
cacheManager.defineConfiguration("SubFoos", entityDefaultConfig);
cacheManager.defineConfiguration("Foo2SubFoos", indexDefaultConfig);
}
@Produces
public Cache<Long, Foo> createFooCache()
{
final Cache<Long, Foo> entityCache = cacheManager.getCache("Foos");
return entityCache;
}
@Produces
public Cache<Long, SubFoo> createSubFooCache()
{
final Cache<Long, SubFoo> entityCache = cacheManager.getCache("SubFoos");
return entityCache;
}
@Produces
public TreeCache<Long, Boolean> createFoo2SubFoos()
{
Cache<Long, Boolean> cache = cacheManager.getCache("Foo2SubFoos");
final TreeCacheFactory treeCacheFactory = new TreeCacheFactory();
final TreeCache<Long, Boolean> treeCache = treeCacheFactory.createTreeCache(cache);
return treeCache;
}
}
И я протянул FooService с поддержкой TreeCache: При добавлении subFoo идентификатор, удален или удален кэш foo2SubFoo обновляется тоже.
public class FooService
{
@Inject
EntityManger em;
@Inject
Cache<Long, Foo> fooCache;
@Inject
Cache<Long, SubFoo> subFooCache;
@Inject
TreeCache<Long, Boolean> foo2SubFoosCache;
public void save(Foo foo)
{
em.save(foo);
fooCache.put(foo.id, foo);
}
public void save(SubFoo subFoo)
{
em.save(subFoo);
subFooCache.put(subFoo.id, subFoo);
Fqn fqn = Fqn.fromElements(subFoo.fooId);
foo2SubFoosCache.put(fqn, subFoo,id, Boolean.TRUE);
}
public void remove ....
public void update ....
public Foo get(Long fooId)
{
return fooCache.get(fooId);
}
public SubFoo get(Long subFooId)
{
return subFooCache.get(subFooId);
}
public List<SubFoo> findSubFoo(Long fooId)
{
Fqn fqn = Fqn.fromElements(fooId);
return foo2SubFoosCache.getKeys(fooId).stream().map(subFooId -> subFooCache.get(subFooId)).collect(Collector.list());
}
}
Благодарим вас за ответ. Я попытался реализовать идею из решения 1, прежде чем решил использовать TreeCache. Но я не смог обеспечить согласованность транзакций. Предположим, что две параллельные транзакции Tx1 и Tx2 будут модифицировать List из Cache > для того же ключа. Tx1 добавит новое значение, а Tx2 удалит значение из списка. Конечным результатом будет List из последней транзакции (некоторое время будет Tx1, некоторое время будет Tx2). Чтобы этого избежать, необходимо использовать режим блокировки PESSIMICTIC. –
ostry
Я не вижу здесь решение TreeCache. В общем: если вы напрямую мутируете свои кеши, вам нужно сделать правильные транзакции/блокировку, если вы хотите обеспечить согласованность. Другой подход заключается в том, чтобы недействить кеши при мутации и всегда заполнять кеши, читая из базы данных. Кроме того, см. Мои замечания здесь: http://stackoverflow.com/questions/25969983/guava-cache-how-to-block-access-while-doing-removal/26011908#26011908 – cruftex
Я надеялся, что TreeCache был чем-то вроде обходного пути для этого случая. Конечно, проблема параллелизма по-прежнему оставалась. Особенно, когда один из Tx удаляет узел, а второй - добавляет элемент в список. Решением этой проблемы был пессимистический режим блокировки. Казалось, что все работает. К сожалению, большое количество запросов было моментом несогласованности. Я думаю, что одобрение изменений после Tx, каждый кеш выполнялся независимо. – ostry