ready for performance test

This commit is contained in:
Shautvast 2023-05-31 20:35:17 +02:00
parent dd6b9c2281
commit f61a1e672d
8 changed files with 226 additions and 37 deletions

View file

@ -25,6 +25,11 @@
<artifactId>contiguous-jdbc</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>nl.sanderhautvast</groupId>
<artifactId>contiguous-jackson</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View file

@ -1,6 +1,9 @@
package nl.sanderhautvast.contiguous.demo;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.extern.slf4j.Slf4j;
import nl.sanderhautvast.contiguous.ListSerializer;
import nl.sanderhautvast.contiguous.demo.repository.RandomStuffGenerator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
@ -40,7 +43,7 @@ public class DemoApplication {
jdbcTemplate.execute("drop table if exists customers");
jdbcTemplate.execute("create table customers (name varchar(100), email varchar(100), streetname varchar(100), housenumber integer, city varchar(100), country varchar(100))");
final RandomStuffGenerator generator = new RandomStuffGenerator();
for (int i = 0; i < 100_000; i++) {
for (int i = 0; i < 10_000; i++) {
jdbcTemplate.update("insert into customers (name, email, streetname, housenumber, city, country) values(?,?,?,?,?,?)",
ps -> {
String firstName = generator.generateFirstName();
@ -57,6 +60,14 @@ public class DemoApplication {
};
}
@Bean
public Module jacksonModule() {
final SimpleModule module = new SimpleModule("contiguous_module");
module.addSerializer(new ListSerializer());
return module;
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(datasource());

View file

@ -1,10 +1,14 @@
package nl.sanderhautvast.contiguous.demo.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
String name;
String email;

View file

@ -9,6 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@Slf4j
public class CustomerRepository {
@ -21,7 +23,21 @@ public class CustomerRepository {
}
public ContiguousList<Customer> getAllCustomers() {
return jdbcTemplate.query("select * from customers limit 5", rs -> {
return jdbcTemplate.query("select * from customers limit 10000", rs -> {
return JdbcResults.toList(rs, Customer.class);
});
}
public List<Customer> getAllCustomersTraditional() {
return jdbcTemplate.query("select * from customers", (rs, rowNum) -> new Customer(
rs.getString("name"),rs.getString("email"),
rs.getString("streetname"), rs.getInt("housenumber"),
rs.getString("city"), rs.getString("country")
));
}
public List<Customer> getAllCustomersHybrid() {
return jdbcTemplate.query("select * from customers", rs -> {
return JdbcResults.toList(rs, Customer.class);
});
}

View file

@ -22,14 +22,17 @@ public class DemoRestApi {
}
@GetMapping(value = "/api/customers", produces = "application/json")
public List<Customer> getCustomers() {
try {
ContiguousList<Customer> customers = customerRepository.getAllCustomers();
log.info("customers {}", customers.size());
return customers;
} catch (Exception e) {
log.error("Error", e);
throw new RuntimeException(e);
public ContiguousList<Customer> getCustomers() {
return customerRepository.getAllCustomers();
}
@GetMapping(value = "/api/customers/traditional", produces = "application/json")
public List<Customer> getCustomersTraditional() {
return customerRepository.getAllCustomersTraditional();
}
@GetMapping(value = "/api/customers/hybrid", produces = "application/json")
public List<Customer> getCustomersHybrid() {
return customerRepository.getAllCustomersHybrid();
}
}

View file

@ -1,4 +1,3 @@
logging.level.org.springframework=ERROR
spring.jpa.hibernate.ddl-auto=none
spring.datasource.driverClass=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres

163
jmeter-test.jmx Normal file
View file

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">100</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="contiguous" enabled="false">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">8080</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/api/customers</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="traditional" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">8080</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">api/customers/traditional</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="hybrid" enabled="false">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">8080</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">api/customers/hybrid</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="Aggregate Report" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="false">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</jmeterTestPlan>

View file

@ -56,11 +56,11 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
/*
* storage for dehydated objects
*/
private ByteBuffer data = ByteBuffer.allocate(32);
private ByteBuffer data = ByteBuffer.allocate(4096);//TODO create constructor with capacity
private int bufferPosition;
private int[] elementIndices = new int[10]; // avoids autoboxing. Could also use standard ArrayList though
private ArrayList<Integer> elementIndices = new ArrayList<>(); // avoids autoboxing. Could also use standard ArrayList though
// is there a standard lib IntList??
private int size;
@ -69,11 +69,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
public ContiguousList(Class<E> type) {
inspectType(type);
elementIndices[0] = 0; // index of first element
}
public Class<?> getElementType() {
return rootHandler.getType();
elementIndices.add(0); // index of first element
}
/*
@ -136,13 +132,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
return false;
}
storePropertyData(element, rootHandler);
size += 1;
// keep track of where the objects are stored
if (elementIndices.length < size + 1) {
this.elementIndices = Arrays.copyOf(this.elementIndices, this.elementIndices.length * 2);
}
elementIndices[size] = bufferPosition;
extend();
return true;
}
@ -189,7 +179,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("index <0 or >" + size);
}
data.position(elementIndices[index]);
data.position(elementIndices.get(index));
try {
if (rootHandler instanceof BuiltinTypeHandler<?>) {
Object read = ValueReader.read(data);
@ -278,14 +268,14 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("index <0 or >" + size);
}
data.position(elementIndices[index]);
data.position(elementIndices.get(index));
if (rootHandler instanceof BuiltinTypeHandler<?>) {
BuiltinTypeHandler<?> handler = (BuiltinTypeHandler<?>) rootHandler;
return getValue(handler);
}
// create a new instance of the list element type
StringBuilder s = new StringBuilder();
StringBuilder s = new StringBuilder(300);
s.append("{");
copyDataIntoStringBuilder(s, (CompoundTypeHandler) rootHandler);
s.append("}");
@ -303,7 +293,7 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
}
private static String quote(String out) {
StringBuilder s = new StringBuilder();
StringBuilder s = new StringBuilder(out.length() + 2);
out = s.append("\"").append(out).append("\"").toString();
return out;
}
@ -315,9 +305,9 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
compoundType.getProperties().forEach(property -> {
if (property instanceof BuiltinTypeHandler) {
BuiltinTypeHandler<?> typeHandler = (BuiltinTypeHandler<?>) property;
String name = typeHandler.getName();
String value = getValue(typeHandler);
s.append(quote(name)).append(": ").append(value);
s.append(quote(typeHandler.getName()))
.append(": ")
.append(getValue(typeHandler));
} else {
CompoundTypeHandler p = (CompoundTypeHandler) property;
s.append(p.getName()).append(":{");
@ -608,16 +598,14 @@ public class ContiguousList<E> extends NotImplementedList<E> implements List<E>
// used by SetterIterator
void extend() {
size += 1;
// keep track of index of element in data
elementIndices.add(bufferPosition);
}
byte[] getData() {
return Arrays.copyOfRange(data.array(), 0, bufferPosition);
}
int[] getElementIndices() {
return Arrays.copyOfRange(elementIndices, 0, size + 1);
}
private void ensureFree(int length) {
while (bufferPosition + length > data.capacity()) {
byte[] bytes = this.data.array();