Performance¶
Tests were conducted on machine with 12vCPU and 64GB RAM (Falcon DB's core microservice had -Xmx set to 3072m).
| Operation Type | Number of threads | Number of executions | Object/File size | Average time per request | Succeded executions | Failed executions |
|---|---|---|---|---|---|---|
| Save Object | 100 | 1 000 000 | 75 bytes | 2.1578 ms | 1 000 000 (100.00%) | 0 (0.00%) |
| Read Object | 100 | 1 000 000 | 75 bytes | 2.4778 ms | 1 000 000 (100.00%) | 0 (0.00%) |
| Save Object | 100 | 100 000 | 43,8 kB | 7.3327 ms | 100 000 (100.00%) | 0 (0.00%) |
| Read Object | 100 | 100 000 | 43,8 kB | 9.9318 ms | 100 000 (100.00%) | 0 (0.00%) |
| Save file as bytes stream | 10 | 10 | 512 MB | 64461.9923 ms | 10 (100.00%) | 0 (0.00%) |
| Save file as base64 chunks | 10 | 10 | 512 MB | 62480.5076 ms | 10 (100.00%) | 0 (0.00%) |
| Get file as bytes stream | 10 | 10 | 512 MB | 5425.7143 ms | 10 (100.00%) | 0 (0.00%) |
| Get file as base64 chunks | 10 | 10 | 512 MB | 39292.8876 ms | 10 (100.00%) | 0 (0.00%) |
| Save file as bytes stream | 50 | 100 | 512 MB | 303038.4085 ms | 50 (100.00%) | 0 (0.00%) |
| Save file as base64 chunks | 50 | 100 | 512 MB | 277968.7984 ms | 50 (100.00%) | 0 (0.00%) |
| Get file as bytes stream | 50 | 100 | 512 MB | 154063.1980 ms | 50 (100.00%) | 0 (0.00%) |
| Get file as base64 chunks | 50 | 100 | 512 MB | 245265.3650 ms | 50 (100.00%) | 0 (0.00%) |
Example tester Java class¶
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.entity.EntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import tech.onteon.falcondb.test.performance.dto.FileChunkOutputDTO;
import tech.onteon.falcondb.test.performance.dto.FileDTO;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Base64;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FalconDBTester {
private final String falconDbUri;
private final ObjectMapper objectMapper = new ObjectMapper();
private static PoolingHttpClientConnectionManager cm;
private static CloseableHttpClient httpClient;
public FalconDBTester(String falconDbUri) {
this.falconDbUri = falconDbUri;
cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(100);
httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
}
/**
* save object
*/
public void saveObject(
String tableName,
String key,
String jsonObject,
int numberOfThreads,
int numberOfExecutions) throws Exception {
executeOperation(numberOfThreads, numberOfExecutions, (index) -> {
String url = falconDbUri
+ "/v1/saveObject"
+ "?table-name=" + tableName
+ "&key=" + key + "-" + index;
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(EntityBuilder.create().setText(jsonObject).build());
try (CloseableHttpResponse httpResponse = httpClient.execute(httpPost)) {
EntityUtils.consumeQuietly(httpResponse.getEntity());
} catch (Throwable th) {
throw new Exception("error while saving object to FalconDB", th);
}
});
}
/**
* read object
*/
public void readObject(
String tableName,
String key,
int numberOfThreads,
int numberOfExecutions) throws Exception {
executeOperation(numberOfThreads, numberOfExecutions, (index) -> {
String url = falconDbUri
+ "/v1/readObject"
+ "?table-name=" + tableName
+ "&key=" + key + "-" + index
+ "&lock=false";
HttpGet httpGet = new HttpGet(url);
try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) {
HttpEntity httpEntity = httpResponse.getEntity();
Object output = null;
if (httpEntity != null) {
try (InputStream inputStream = httpEntity.getContent()) {
output = objectMapper.readValue(inputStream, Object.class);
}
}
EntityUtils.consumeQuietly(httpEntity);
} catch (Throwable th) {
throw new Exception("error while getting object from FalconDB", th);
}
});
}
/**
* save file as bytes stream
*/
public void saveFileAsStream(
String fileKey,
String fileName,
String inputFilePath,
int numberOfThreads,
int numberOfExecutions) throws Exception {
executeOperation(numberOfThreads, numberOfExecutions, (index) -> {
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = (HttpURLConnection) new URL(falconDbUri + "/v2/saveBinaryFile").openConnection();
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Content-Type", "application/octet-stream");
httpURLConnection.setRequestProperty("key", fileKey);
httpURLConnection.setRequestProperty("fileName", fileName + "-" + index);
try (
FileInputStream fileInputStream = new FileInputStream(inputFilePath);
OutputStream outputStream = httpURLConnection.getOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
try (BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()))) {
while (br.readLine() != null) {}
}
}
} catch (Throwable th) {
throw new Exception("error while saving file as stream to FalconDB", th);
} finally {
if (Objects.nonNull(httpURLConnection)) {
httpURLConnection.disconnect();
}
}
});
}
/**
* save file as base64 chunks
*/
public void saveFileAsChunks(
String fileKey,
String fileName,
int chunkSize,
String inputFilePath,
int numberOfThreads,
int numberOfExecutions) throws Exception {
executeOperation(numberOfThreads, numberOfExecutions, (index) -> {
String url = falconDbUri + "/v1/saveOrAppendFile";
HttpPut httpPut = new HttpPut(url);
FileDTO fileDTO = new FileDTO();
fileDTO.setFileName(fileName + "-" + index);
fileDTO.setKey(fileKey);
try (FileInputStream fis = new FileInputStream(inputFilePath)) {
byte[] chunk = new byte[chunkSize];
int bytesRead;
while ((bytesRead = fis.read(chunk)) != -1) {
String base64Encoded = Base64.getEncoder().encodeToString(
bytesRead == chunkSize ? chunk : Arrays.copyOf(chunk, bytesRead)
);
fileDTO.setBase64FileContent(base64Encoded);
String json = objectMapper.writeValueAsString(fileDTO);
httpPut.setEntity(EntityBuilder.create().setText(json).build());
try (CloseableHttpResponse response = httpClient.execute(httpPut)) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
} catch (Throwable th) {
throw new Exception("error while saving file as chunks to FalconDB", th);
}
});
}
/**
* read file as base64 chunks
*/
public void getFileAsChunks(
String fileKey,
String fileName,
int chunkSize,
String outputFilePath,
int numberOfThreads,
int numberOfExecutions) throws Exception {
executeOperation(numberOfThreads, numberOfExecutions, (index) -> {
String url = falconDbUri + "/v1/getFileChunk"
+ "?key=" + fileKey
+ "&file-name=" + fileName + "-" + index
+ "&chunk-size=" + chunkSize;
int chunkNumber = 0;
File outputFile = new File(outputFilePath + "-" + index);
boolean isEOF = false;
try (FileOutputStream fos = new FileOutputStream(outputFile)) {
while (!isEOF) {
HttpGet httpGet = new HttpGet(URI.create(url + "&chunk-number=" + chunkNumber));
try (CloseableHttpResponse response = httpClient.execute(httpGet);
InputStream inputStream = response.getEntity().getContent()) {
FileChunkOutputDTO dto = objectMapper.readValue(inputStream, FileChunkOutputDTO.class);
isEOF = dto.isEOF();
byte[] bytes = Base64.getDecoder().decode(dto.getBase64FileContent());
if (dto.getReadBytesSize() != chunkSize) {
bytes = Arrays.copyOf(bytes, Math.max(dto.getReadBytesSize(), 0));
}
fos.write(bytes);
}
chunkNumber++;
}
} catch (Throwable th) {
throw new Exception("error while getting file as chunks from FalconDB", th);
}
});
}
/**
* read file as bytes stream
*/
public void getFileAsStream(
String fileKey,
String fileName,
int chunkSize,
String outputFilePath,
int numberOfThreads,
int numberOfExecutions) throws Exception {
executeOperation(numberOfThreads, numberOfExecutions, (index) -> {
String url = falconDbUri + "/v1/getBinaryFile"
+ "?key=" + fileKey
+ "&file-name=" + fileName + "-" + index
+ "&chunk-size=" + chunkSize;
HttpGet httpGet = new HttpGet(url);
File outputFile = new File(outputFilePath + "-" + index);
try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
InputStream inputStream = httpResponse.getEntity().getContent();
FileOutputStream fos = new FileOutputStream(outputFile)) {
byte[] chunk = new byte[chunkSize];
int bytesRead;
while ((bytesRead = inputStream.read(chunk)) != -1) {
fos.write(chunk, 0, bytesRead);
}
EntityUtils.consumeQuietly(httpResponse.getEntity());
} catch (Throwable th) {
throw new Exception("error while getting file as stream from FalconDB", th);
}
});
}
private void executeOperation(
int numberOfThreads,
int numberOfExecutions,
IndexedRunnable runnable) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
CountDownLatch countDownLatch = new CountDownLatch(numberOfExecutions);
for (int i = 0; i < numberOfExecutions; i++) {
int finalI = i;
executorService.submit(() -> {
try {
runnable.run(finalI);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
countDownLatch.countDown();
}
});
}
try {
countDownLatch.await();
} catch (Throwable th) {
throw new Exception("error, while executing operation on FalconDB, caused by: ", th);
}
executorService.shutdown();
}
private interface IndexedRunnable {
void run(int index) throws Exception;
}
}