From 821b08a0faecfd88bc990efeec35c56edbebbf8c Mon Sep 17 00:00:00 2001 From: ORybak5 <12736698+ORybak5@users.noreply.github.com> Date: Fri, 8 May 2026 13:37:13 +0100 Subject: [PATCH] make Docker JVM self-tuned and testcontainer tests improvements --- .testcontainers.properties.template | 26 +++++++++++++++++++ CHANGELOG.md | 3 +++ docker/service/Dockerfile | 11 +++++++- nhs-england-developer-information.md | 22 ++++++++++++++++ .../testcontainers/ActiveMqContainer.java | 6 +++++ .../testcontainers/MongoDbContainer.java | 6 +++++ .../storage/S3StorageConnectorTest.java | 2 +- 7 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 .testcontainers.properties.template diff --git a/.testcontainers.properties.template b/.testcontainers.properties.template new file mode 100644 index 0000000000..ef205d737d --- /dev/null +++ b/.testcontainers.properties.template @@ -0,0 +1,26 @@ +# Testcontainers reuse configuration +# ------------------------------------ +# Place this file at: ~/.testcontainers.properties +# (i.e., in your home directory, NOT the project directory) +# +# With reuse enabled, Testcontainers will leave containers running between +# test runs rather than stopping and removing them when the JVM exits. +# On the next run it detects that a matching container already exists and +# connects to it instead of starting a new one. +# +# Effect on local dev: +# - Integration test startup drops from ~15-30 s (container start + Spring init) +# to ~2-3 s (Spring context only) after the first run. +# - MongoDB and ActiveMQ data persists between runs; run a clean start +# (`./gradlew integrationTest --rerun`) when you want a truly fresh state. +# +# This setting has NO effect in CI (containers always start fresh there because +# the host is ephemeral). To explicitly disable reuse in CI, set the env var: +# TESTCONTAINERS_REUSE_ENABLE=false +# +# IMPORTANT: This file must also be created on each developer's machine. +# Commit this template to the repo (e.g., as .testcontainers.properties.template) +# so new developers know to copy it to ~/ + +testcontainers.reuse.enable=true + diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a27de768..89484f2c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed * Updated logging configuration to route application logs through async appender settings for improved runtime logging performance. +* The service Docker image now uses JVM percentage-based heap sizing (`-XX:MaxRAMPercentage=75.0`, `-XX:InitialRAMPercentage=50.0`) +* instead of a fixed `-Xmx` value, so the heap adapts automatically when the container memory limit is changed without requiring an image rebuild. +* Enabled G1GC explicitly (`-XX:+UseG1GC`) in the service container entrypoint for consistent garbage collection behaviour across deployments. ## [3.0.0] - 2025-11-06 diff --git a/docker/service/Dockerfile b/docker/service/Dockerfile index 7c387be278..e8920ecf4b 100644 --- a/docker/service/Dockerfile +++ b/docker/service/Dockerfile @@ -20,5 +20,14 @@ COPY --from=package /home/gradle/service/build/libs/gp2gp-fhir-send-adaptor.jar USER 65534 -ENTRYPOINT ["java", "-XX:+PrintCommandLineFlags", "-cp", "/app/gp2gp-fhir-send-adaptor.jar", "-Dloader.main=uk.nhs.adaptors.gp2gp.Gp2gpApplication", "org.springframework.boot.loader.launch.PropertiesLauncher"] +ENTRYPOINT ["java", \ + "-XX:MaxRAMPercentage=75.0", \ + "-XX:InitialRAMPercentage=50.0", \ + "-XX:+UseG1GC", \ + "-XX:+PrintCommandLineFlags", \ + "-cp", "/app/gp2gp-fhir-send-adaptor.jar", \ + "-Dloader.main=uk.nhs.adaptors.gp2gp.Gp2gpApplication", \ + "org.springframework.boot.loader.launch.PropertiesLauncher"] + + diff --git a/nhs-england-developer-information.md b/nhs-england-developer-information.md index df15b05909..4b71c944cd 100644 --- a/nhs-england-developer-information.md +++ b/nhs-england-developer-information.md @@ -8,6 +8,28 @@ * JDK 21 — we develop the adaptor in Java with Spring Boot * Docker — we release the adaptor using Docker images on [Dockerhub](https://hub.docker.com/repository/docker/nhsdev/nia-gp2gp-adaptor) +## One-time local dev setup + +### Enable Testcontainers container reuse (saves ~15–30 s per integration test run) + +Copy the template to your home directory: + +**macOS / Linux:** +```bash +cp .testcontainers.properties.template ~/.testcontainers.properties +``` + +This keeps MongoDB and ActiveMQ containers alive between runs so subsequent `./gradlew integrationTest` +invocations skip the container start-up overhead. +The property has no effect in CI (ephemeral hosts never have the file). + +To verify the setup is correct: + +**macOS / Linux:** +```bash +grep 'testcontainers.reuse.enable' ~/.testcontainers.properties +``` + ## How to operate the adaptor The following sections describe how to run the adaptor for development and testing. diff --git a/service/src/intTest/java/uk/nhs/adaptors/gp2gp/testcontainers/ActiveMqContainer.java b/service/src/intTest/java/uk/nhs/adaptors/gp2gp/testcontainers/ActiveMqContainer.java index bff231a203..55d9f41d81 100644 --- a/service/src/intTest/java/uk/nhs/adaptors/gp2gp/testcontainers/ActiveMqContainer.java +++ b/service/src/intTest/java/uk/nhs/adaptors/gp2gp/testcontainers/ActiveMqContainer.java @@ -10,6 +10,12 @@ public final class ActiveMqContainer extends GenericContainer private ActiveMqContainer() { super("docker-activemq:latest"); addExposedPort(ACTIVEMQ_PORT); + // withReuse(true) keeps the container alive after the JVM exits so the next + // test run can reconnect to it instead of starting a new one (~15 s saving). + // Reuse is only activated when testcontainers.reuse.enable=true in + // ~/.testcontainers.properties (see .testcontainers.properties.template). + // In CI that property is absent so containers always start fresh. + withReuse(true); } public static ActiveMqContainer getInstance() { diff --git a/service/src/intTest/java/uk/nhs/adaptors/gp2gp/testcontainers/MongoDbContainer.java b/service/src/intTest/java/uk/nhs/adaptors/gp2gp/testcontainers/MongoDbContainer.java index 09af24ac34..c1962175e8 100644 --- a/service/src/intTest/java/uk/nhs/adaptors/gp2gp/testcontainers/MongoDbContainer.java +++ b/service/src/intTest/java/uk/nhs/adaptors/gp2gp/testcontainers/MongoDbContainer.java @@ -13,6 +13,12 @@ public final class MongoDbContainer extends GenericContainer { private MongoDbContainer() { super(DEFAULT_IMAGE_AND_TAG); addExposedPort(MONGODB_PORT); + // withReuse(true) keeps the container alive after the JVM exits so the next + // test run can reconnect to it instead of starting a new one (~15 s saving). + // Reuse is only activated when testcontainers.reuse.enable=true in + // ~/.testcontainers.properties (see .testcontainers.properties.template). + // In CI that property is absent so containers always start fresh. + withReuse(true); } public static MongoDbContainer getInstance() { diff --git a/service/src/test/java/uk/nhs/adaptors/gp2gp/common/storage/S3StorageConnectorTest.java b/service/src/test/java/uk/nhs/adaptors/gp2gp/common/storage/S3StorageConnectorTest.java index 6062dc9001..ec050da1e1 100644 --- a/service/src/test/java/uk/nhs/adaptors/gp2gp/common/storage/S3StorageConnectorTest.java +++ b/service/src/test/java/uk/nhs/adaptors/gp2gp/common/storage/S3StorageConnectorTest.java @@ -38,7 +38,7 @@ class S3StorageConnectorTest { private static S3StorageConnector s3StorageConnector; @Container - private static final S3MockContainer S3_MOCK = new S3MockContainer("4.7.0"); + private static final S3MockContainer S3_MOCK = new S3MockContainer("4.7.0").withReuse(true); private static S3Client s3Client;