Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions persistence-modules/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
<module>spring-data-jpa-repo</module>
<module>spring-data-jpa-repo-2</module>
<module>spring-data-jpa-repo-4</module>
<module>spring-data-jpa-repo-5</module>
<module>spring-data-jdbc</module>
<module>spring-data-jpa-simple</module>
<module>spring-data-keyvalue</module>
Expand Down
27 changes: 27 additions & 0 deletions persistence-modules/spring-data-jpa-repo-5/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baeldung</groupId>
<artifactId>spring-data-jpa-repo-5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
<module>spring-data-jpa-aot</module>
<module>spring-data-jpa-aot-repository</module>
<module>spring-data-jpa-not-aot</module>
</modules>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.13</version>
</parent>

<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
178 changes: 178 additions & 0 deletions persistence-modules/spring-data-jpa-repo-5/scripts/load-test-linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#!/usr/bin/env bash
set -euo pipefail

# =========================
# CONFIGURATION
# =========================
APP_COMMAND=""
URL="http://localhost:8080/hello"
URL_LOAD="http://localhost:8080/get-user"
INTERVAL=0.01
DURATION_SEC=30
TPS=300
MAX_CONCURRENCY=300

MEM_SAMPLE_INTERVAL_NS=1000000000 # sample max memory every 1 second
PEAK_RSS=0

# =========================
# SELECT MODE
# =========================
case "${1:-}" in
aot)
APP_COMMAND="java -Dspring.aot.enabled=true \
-Dspring.aot.repositories.enabled=false \
-jar spring-data-jpa-aot/target/spring-data-jpa-aot-0.0.1-SNAPSHOT.jar"
;;
aot-repo)
APP_COMMAND="java -Dspring.aot.enabled=true \
-Dspring.aot.repositories.enabled=true \
-jar spring-data-jpa-aot-repository/target/spring-data-jpa-aot-repository-0.0.1-SNAPSHOT.jar"
;;
non-aot)
APP_COMMAND="java -jar spring-data-jpa-not-aot/target/spring-data-jpa-not-aot-0.0.1-SNAPSHOT.jar"
;;
*)
echo "Usage: $0 {aot|aot-repo|non-aot}"
exit 1
;;
esac

# =========================
# START APPLICATION
# =========================
echo "Starting application..."
start_ns=$(date +%s%N)

bash -c "$APP_COMMAND" &
APP_PID=$!

echo "PID: $APP_PID"
echo "Waiting for service at $URL ..."

# Wait until HTTP 200
while [[ "$(curl -s -o /dev/null -L -w "%{http_code}" "$URL")" != "200" ]]; do
sleep "$INTERVAL"
done

end_ns=$(date +%s%N)
elapsed_ms=$(((end_ns - start_ns) / 1000000))

startup_mem=$(ps -o rss=,time= -p "$APP_PID")

echo ""
echo "==== STARTUP RESULTS ===="
echo "Startup time: ${elapsed_ms} ms"
echo "Memory/CPU (RSS KB / TIME): $startup_mem"

# =========================
# LOAD TEST SETUP
# =========================
echo ""
echo "==== LOAD TEST ===="
echo "URL: $URL_LOAD"
echo "TPS: $TPS"
echo "Duration: ${DURATION_SEC}s"

START_NS=$(date +%s%N)
END_NS=$((START_NS + DURATION_SEC * 1000000000))
INTERVAL_NS=$((1000000000 / TPS))
next_time=$START_NS

METRICS_FILE=$(mktemp)
MEMORY_BEFORE=$(ps -o rss=,time= -p "$APP_PID")

# =========================
# REQUEST FUNCTION
# =========================
get() {
local url="$1"
local start end duration_ms result code time_total

start=$(date +%s%3N)

result=$(curl --max-time 2 -s -o /dev/null -w "%{http_code} %{time_total}" "$url")
code=$(awk '{print $1}' <<< "$result")
time_total=$(awk '{print $2}' <<< "$result")

end=$(date +%s%3N)
duration_ms=$((end - start))

echo "$code $time_total $duration_ms" >> "$METRICS_FILE"
}

# =========================
# LOAD GENERATION (TPS + CONCURRENCY CONTROL)
# =========================
last_mem_sample=0
while [[ "$(date +%s%N)" -lt "$END_NS" ]]; do
now=$(date +%s%N)
if (( now - last_mem_sample >= MEM_SAMPLE_INTERVAL_NS )); then
current_rss=$(ps -o rss= -p "$APP_PID")

if [[ "$current_rss" -gt "$PEAK_RSS" ]]; then
PEAK_RSS=$current_rss
fi

last_mem_sample=$now
fi

while [[ "$now" -ge "$next_time" ]]; do

# ---- concurrency cap (THIS is the new part) ----
while [[ "$(jobs -rp | wc -l)" -ge "$MAX_CONCURRENCY" ]]; do
sleep 0.001
done

get "$URL_LOAD" &

next_time=$((next_time + INTERVAL_NS))
done

sleep 0.001
done

# Allow in-flight requests to finish (bounded)
sleep 1

# =========================
# SHUTDOWN
# =========================
MEMORY_AFTER=$(ps -o rss=,time= -p "$APP_PID")

echo "Stopping application..."
kill "$APP_PID" 2>/dev/null || true
sleep 2

# =========================
# RESULTS
# =========================
echo ""
echo "==== RESULTS ===="

total=$(wc -l < "$METRICS_FILE")
success=$(awk '$1 ~ /^2/ {c++} END {print c+0}' "$METRICS_FILE")
fail=$((total - success))

avg_time=$(awk '{sum+=$2} END {if (NR>0) print sum/NR}' "$METRICS_FILE")
avg_duration=$(awk '{sum+=$3} END {if (NR>0) print sum/NR}' "$METRICS_FILE")

p95=$(awk '{print $2}' "$METRICS_FILE" | sort -n | awk '{a[NR]=$1} END {print a[int(NR*0.95)]}')
max_time=$(awk '{print $2}' "$METRICS_FILE" | sort -n | tail -1)

echo "Total requests: $total"
echo "Success (2xx): $success"
echo "Failed: $fail"

echo "Avg time (curl): ${avg_time}s"
echo "Avg duration (measured): ${avg_duration}ms"
echo "P95: ${p95}s"
echo "Max: ${max_time}s"

echo ""
echo "Max memory utilised: $PEAK_RSS"
echo "Memory/CPU (RSS KB / TIME):"
echo "Before: $MEMORY_BEFORE"
echo "After : $MEMORY_AFTER"

rm -f "$METRICS_FILE"
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail

APP_COMMAND=""
URL="http://localhost:8080/get-user"
INTERVAL=0.01 # seconds between checks

case "$1" in
aot)
APP_COMMAND="java -Dspring.aot.enabled=true \
-Dspring.aot.repositories.enabled=false \
-jar spring-data-jpa-aot/target/spring-data-jpa-aot-0.0.1-SNAPSHOT.jar"
;;
aot-repo)
APP_COMMAND="java -Dspring.aot.enabled=true \
-Dspring.aot.repositories.enabled=true \
-Dspring.data.repositories.aot.enabled=true \
-jar spring-data-jpa-aot-repository/target/spring-data-jpa-aot-repository-0.0.1-SNAPSHOT.jar"
;;
non-aot)
APP_COMMAND="java -jar spring-data-jpa-not-aot/target/spring-data-jpa-not-aot-0.0.1-SNAPSHOT.jar"
;;
*)
echo "Error: Unknown mode '$1'. Use 'aot', 'aot-repo' or 'non-aot'."
exit 1
;;
esac

# --- 1. Start your script in the background ---
# Record start time in milliseconds
start_ns=$(date +%s%N)

# --- 2. Start the application ---
$APP_COMMAND &
APP_PID=$!
echo "Started APP with PID: $APP_PID"

# --- 3. Poll the endpoint until it returns HTTP 200 ---
echo "Waiting for service at http://localhost:8080/get-user ..."
while [ "$(curl -s -o /dev/null -L -w ''%{http_code}'' $URL)" != 200 ]
do sleep $INTERVAL;
done

# Capture elapsed time (SECONDS has fractional part) and the memory info
end_ns=$(date +%s%N)
elapsed_ms=$(((end_ns - start_ns) / 1000000))
MEMINFO=$(ps -o rss=,time= -p "$APP_PID")

# --- 5. Clean up ---
echo "Stopping startup process..."
kill "$APP_PID" 2>/dev/null || true

# Give app a moment to shutdown
sleep 3

echo "Done."
echo "==== RESULTS ===="
echo "time elapsed $elapsed_ms millis"
echo "Process Specific Memory/CPU (RSS KB / CPU Time): $MEMINFO"
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Compile AOT, including AOT Repositories

```shell
mvn clean install -Paot-repo
```

Compilation time: `Total time: 23.390 s`

1) run using maven and the spring-boot plugin:

```shell
mvn spring-boot:run -Dspring.aot.enabled=true -Dspring.aot.repositories.enabled=true
```

Startup times:
`Root WebApplicationContext: initialization completed in 1242 ms`
`Started Application in 4.758 seconds (process running for 5.136)`

2) run using the jar (same as 1 mostly)

```shell
java -Dspring.aot.enabled=true \
-Dspring.aot.repositories.enabled=true \
-jar target/spring-data-jpa-aot-repository-0.0.1-SNAPSHOT.jar
```

Startup times:
`Started Application in 6.001 seconds (process running for 6.923)`

## Performance

### Time startup

from root `sudo ./scripts/startup-linux.sh aot-repo`:

```shell
==== RESULTS ====
time elapsed 8745 millis
Process Specific Memory/CPU (RSS KB / CPU Time): 292864 00:00:23
```

### Time startup

from root `sudo ./scripts/load-test-linux.sh aot-repo`:

```shell
==== RESULTS ====
Total requests: 6673
Success (2xx): 6673
Failed: 0
Avg time (curl): 0.0109863s
Avg duration (measured): 49.0766ms
P95: 0.018761s
Max: 0.839634s

Max memory utilised: 337948
Memory/CPU (RSS KB / TIME):
Before: 285532 00:00:23
After : 377504 00:01:07
```

**NOTE**:
AOT is a mandatory step to transform a Spring application to a native executable, so it is automatically enabled when
running within a native image. However, it is also possible to use AOT optimizations on the JVM by setting the
spring.aot.enabled System
property to true.
Loading