Skip to content

Commit

Permalink
Optimize result deletion by defining explicit query
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Marcellin committed Nov 29, 2023
1 parent e6a950c commit 0f8b1e0
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 0 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<gridsuite-dependencies.version>28</gridsuite-dependencies.version>
<liquibase-hibernate-package>org.gridsuite.sensitivityanalysis.server</liquibase-hibernate-package>
<mockito-inline.version>3.11.1</mockito-inline.version>
<db-util.version>1.0.5</db-util.version>
</properties>

<build>
Expand Down Expand Up @@ -267,6 +268,12 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>db-util</artifactId>
<version>${db-util.version}</version>
<scope>test</scope>
</dependency>
<!-- Needed to mock SensitivityAnalysis.Runner (which happens to be a final class) in SensitivityAnalysisControllerTest -->
<!-- see "mockito-inline" in https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.mocking-beans -->
<!-- otherwise we get the following exception:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.gridsuite.sensitivityanalysis.server.entities.GlobalStatusEntity;
import org.gridsuite.sensitivityanalysis.server.entities.SensitivityEntity;
import org.gridsuite.sensitivityanalysis.server.entities.SensitivityFactorEmbeddable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand All @@ -36,6 +38,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand All @@ -46,6 +50,8 @@
@Repository
public class SensitivityAnalysisResultRepository {

private static final Logger LOGGER = LoggerFactory.getLogger(SensitivityAnalysisResultRepository.class);

private final GlobalStatusRepository globalStatusRepository;

private final AnalysisResultRepository analysisResultRepository;
Expand Down Expand Up @@ -124,8 +130,12 @@ public void insert(UUID resultUuid, SensitivityAnalysisResult result, String sta
@Transactional
public void delete(UUID resultUuid) {
Objects.requireNonNull(resultUuid);
AtomicReference<Long> startTime = new AtomicReference<>();
startTime.set(System.nanoTime());
sensitivityRepository.deleteSensitivityBySensitivityAnalysisResultUUid(resultUuid);
globalStatusRepository.deleteByResultUuid(resultUuid);
analysisResultRepository.deleteByResultUuid(resultUuid);
LOGGER.info("Sensitivity analysis result '{}' has been deleted in {}ms", resultUuid, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime.get()));
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.util.CollectionUtils;

Expand Down Expand Up @@ -57,6 +58,10 @@ public interface SensitivityRepository extends JpaRepository<SensitivityEntity,
"order by s.contingency.contingencyId")
List<String> getDistinctContingencyIds(UUID resultUuid, SensitivityFunctionType sensitivityFunctionType);

@Modifying
@Query(value = "DELETE FROM SensitivityEntity WHERE result.resultUuid = ?1")
void deleteSensitivityBySensitivityAnalysisResultUUid(UUID resultUuid);

static Specification<SensitivityEntity> getSpecification(AnalysisResultEntity sas,
SensitivityFunctionType functionType,
Collection<String> functionIds,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Copyright (c) 2023, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.sensitivityanalysis.server.repositories;

import com.powsybl.contingency.ContingencyContext;
import com.powsybl.contingency.ContingencyContextType;
import com.powsybl.sensitivity.*;
import com.vladmihalcea.sql.SQLStatementCountValidator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.gridsuite.sensitivityanalysis.server.util.TestUtils.assertRequestsCount;

/**
* @author Hugo Marcellin <hugo.marcelin at rte-france.com>
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class SensitivityAnalysisResultRepositoryTest {

private static final SensitivityFunctionType MW_FUNC_TYPE = SensitivityFunctionType.BRANCH_ACTIVE_POWER_1;
private static final SensitivityVariableType MW_VAR_TYPE = SensitivityVariableType.INJECTION_ACTIVE_POWER;
private static final UUID RESULT_UUID = UUID.fromString("0c8de370-3e6c-4d72-b292-d355a97e0d5d");

@Autowired
private SensitivityAnalysisResultRepository sensitivityAnalysisResultRepository;

@Before
public void setUp() {
sensitivityAnalysisResultRepository.deleteAll();
SQLStatementCountValidator.reset();
}

private static List<SensitivityFactor> makeMWFactors(String funcId, String varId, List<String> mayContingencies) {
List<SensitivityFactor> aleaFactors;
if (mayContingencies == null) {
aleaFactors = List.of(new SensitivityFactor(MW_FUNC_TYPE, funcId, MW_VAR_TYPE, varId, false, ContingencyContext.all()));
} else {
aleaFactors = mayContingencies.stream().map(aleaId ->
new SensitivityFactor(MW_FUNC_TYPE, funcId, MW_VAR_TYPE, varId, false,
ContingencyContext.create(aleaId, ContingencyContextType.SPECIFIC)))
.collect(Collectors.toList());
aleaFactors.add(0, new SensitivityFactor(MW_FUNC_TYPE, funcId, MW_VAR_TYPE, varId, false, ContingencyContext.none()));
}
return aleaFactors;
}

@Test
public void deleteResultTest() {
List<String> aleaIds = List.of("a1", "a2", "a3");
final List<SensitivityAnalysisResult.SensitivityContingencyStatus> contingenciesStatuses = aleaIds.stream()
.map(aleaId -> new SensitivityAnalysisResult.SensitivityContingencyStatus(aleaId, SensitivityAnalysisResult.Status.SUCCESS))
.collect(Collectors.toList());

final List<SensitivityFactor> sensitivityFactors = Stream.of(
makeMWFactors("l1", "GEN", null), // factor index 0 ; for all()
makeMWFactors("l2", "GEN", List.of()), // factor index 1 ; for none()
makeMWFactors("l3", "LOAD", aleaIds) // factor indices 2, 3, 4 & 5
).flatMap(Collection::stream).collect(Collectors.toList());

final List<SensitivityValue> sensitivityValues = new ArrayList<>(List.of(
// l1 x GEN, before + a1, a2, a3 (implicit)
new SensitivityValue(0, -1, 500.1, 2.9),
new SensitivityValue(0, 0, 500.3, 2.7),
new SensitivityValue(0, 1, 500.4, 2.6),
new SensitivityValue(0, 2, 500.5, 2.5),
// l2 x GEN, just before
new SensitivityValue(1, -1, 500.2, 2.8),
// l3 x LOAD, before + a1, a2, a3 (explicit)
new SensitivityValue(2, -1, 500.9, 2.1),
new SensitivityValue(3, 1, 500.6, 2.4),
new SensitivityValue(4, 0, 500.7, 2.3),
new SensitivityValue( 5, 2, 500.8, 2.2)
));
Collections.shuffle(sensitivityValues);

final SensitivityAnalysisResult result = new SensitivityAnalysisResult(sensitivityFactors,
contingenciesStatuses,
sensitivityValues);
sensitivityAnalysisResultRepository.insert(RESULT_UUID, result, "OK");
SQLStatementCountValidator.reset();

sensitivityAnalysisResultRepository.delete(RESULT_UUID);

// 3 deletes for one result :
// - its global status
// - its sensitivities
// - the result itself
assertRequestsCount(3, 0, 0, 3);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Copyright (c) 2023, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.sensitivityanalysis.server.util;

import net.ttddyy.dsproxy.listener.ChainListener;
import net.ttddyy.dsproxy.listener.DataSourceQueryCountListener;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSource;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.sql.DataSource;
import java.lang.reflect.Method;

@Component
/**
* The author of db-utils describes its library in https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/
* But the recommended method to select the datasource (using @bean public DataSource dataSource(DataSource originalDataSource) {...} )
* doesn't work when you use the spring default profile for tests
* It crashes with this exception : java.lang.IllegalStateException: Failed to load ApplicationContext : org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference
* Instead, the underlying datasource-proxy library author recommends to use a BeanPostProcessor:
* https://github.com/ttddyy/datasource-proxy-examples/blob/master/springboot-autoconfig-example/src/main/java/net/ttddyy/dsproxy/example/DatasourceProxyBeanPostProcessor.java
*/
public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof DataSource && !(bean instanceof ProxyDataSource)) {
// Instead of directly returning a less specific datasource bean
// (e.g.: HikariDataSource -> DataSource), return a proxy object.
// See following links for why:
// https://stackoverflow.com/questions/44237787/how-to-use-user-defined-database-proxy-in-datajpatest
// https://gitter.im/spring-projects/spring-boot?at=5983602d2723db8d5e70a904
// http://blog.arnoldgalovics.com/2017/06/26/configuring-a-datasource-proxy-in-spring-boot/
final ProxyFactory factory = new ProxyFactory(bean);
factory.setProxyTargetClass(true);
factory.addAdvice(new ProxyDataSourceInterceptor((DataSource) bean));
return factory.getProxy();
}
return bean;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}

private static class ProxyDataSourceInterceptor implements MethodInterceptor {
private final DataSource dataSource;

public ProxyDataSourceInterceptor(final DataSource dataSource) {
ChainListener listener = new ChainListener();
listener.addListener(new DataSourceQueryCountListener());
this.dataSource = ProxyDataSourceBuilder.create(dataSource)
.multiline()
.listener(listener)
.logQueryBySlf4j(SLF4JLogLevel.INFO)
.build();
}

@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
final Method proxyMethod = ReflectionUtils.findMethod(this.dataSource.getClass(),
invocation.getMethod().getName());
if (proxyMethod != null) {
return proxyMethod.invoke(this.dataSource, invocation.getArguments());
}
return invocation.proceed();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2023, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.sensitivityanalysis.server.util;


import static com.vladmihalcea.sql.SQLStatementCountValidator.*;

/**
* @author Hugo Marcellin <hugo.marcelin at rte-france.com>
*/
public final class TestUtils {
public static void assertRequestsCount(long select, long insert, long update, long delete) {
assertSelectCount(select);
assertInsertCount(insert);
assertUpdateCount(update);
assertDeleteCount(delete);
}
}

0 comments on commit 0f8b1e0

Please sign in to comment.