Skip to content
Merged
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
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ ThisBuild / organization := "app.softnetwork"

name := "softclient4es"

ThisBuild / version := "0.20.2-SNAPSHOT"
ThisBuild / version := "0.20.2"

ThisBuild / scalaVersion := scala213

Expand Down
32 changes: 32 additions & 0 deletions documentation/client/repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,38 @@ INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob');
SELECT * FROM users;
```

#### Cold-start & batch invocation cost

Each `softclient4es -c "…"` invocation starts a fresh JVM and pays the full
initialization cost (class loading, client setup, extension discovery) before the
statement runs. For scripted or CI use, prefer batching statements into **one**
invocation — both forms below execute all `;`-separated statements in a single JVM,
so the startup cost is paid once:

```bash
# Recommended: a SQL file (N statements, one JVM)
softclient4es -f batch.sql

# Also works: several statements in one -c
softclient4es -c "CREATE TABLE t (id INT); INSERT INTO t (id) VALUES (1); SELECT * FROM t"
```

In practice the marginal cost of each extra statement is a few milliseconds, versus
1.5–2s of JVM startup for every separate invocation.

On bundle installs (the default self-contained `-all` jar) running on JDK 13+, the
launcher additionally uses [Class-Data Sharing (AppCDS)](https://docs.oracle.com/en/java/javase/17/vm/class-data-sharing.html)
to cut class-loading time (~20-25% faster start on JDK 17/21):

- **JDK 13-18** — the installer generates `cache/softclient4es.jsa` once at install
time. After a Java upgrade, re-run the installer to regenerate it.
- **JDK 19+** — the JVM creates and refreshes the archive by itself
(`-XX:+AutoCreateSharedArchive`); nothing to do.
- The archive is an internal cache: it is safe to delete `cache/` at any time (the
REPL falls back to normal class loading, and JDK 19+ recreates it on the next run).
- Plain and `--no-extensions` installs are unaffected — no CDS flags, no `cache/`
directory.

---

## Basic Usage
Expand Down
80 changes: 79 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,27 @@ if [[ -n "\$JAVA_MAJOR" ]] && [[ "\$JAVA_MAJOR" -ge 9 ]]; then
EXTRA_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true"
fi

# Class-Data Sharing (AppCDS) — cuts JVM class-load time on every start (#163 fix 3).
# BUNDLE-ONLY: CDS_BUNDLE below is baked in AT INSTALL TIME from REPL.4's USE_BUNDLE
# (intentionally NOT \$-escaped) — plain/--no-extensions installs get NO CDS behaviour
# at all (no flags, no cache/ dir), on every JDK. Without this gate the 19+ branch
# would fire on plain installs too and attempt runtime dumps over the multi-jar lib/*.
# - JDK 19+: the JVM creates/refreshes the archive itself (AutoCreateSharedArchive).
# - JDK 13-18: use the archive generated at install time, if present and not stale.
# - JDK 11/12 or no archive: no CDS flags — default behaviour, zero regression.
# -Xshare:auto (the default) silently ignores an invalid/mismatched archive.
CDS_BUNDLE=${USE_BUNDLE:-false}
CDS_ARCHIVE="\$BASE_DIR/cache/softclient4es.jsa"
CDS_OPTS=""
if [[ "\$CDS_BUNDLE" == true ]] && [[ -n "\$JAVA_MAJOR" ]]; then
if [[ "\$JAVA_MAJOR" -ge 19 ]]; then
mkdir -p "\$BASE_DIR/cache"
CDS_OPTS="-XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=\$CDS_ARCHIVE"
elif [[ "\$JAVA_MAJOR" -ge 13 ]] && [[ -f "\$CDS_ARCHIVE" ]] && [[ ! "\$JAR_FILE" -nt "\$CDS_ARCHIVE" ]]; then
CDS_OPTS="-XX:SharedArchiveFile=\$CDS_ARCHIVE"
fi
fi

# Logback configuration
LOGBACK_OPTS=""
if [[ -f "\$LOGBACK_FILE" ]]; then
Expand All @@ -1005,7 +1026,7 @@ fi
# The engine assembly comes first on the classpath so it wins any conflict;
# lib/* brings the extension jars, discovered via the ServiceLoader SPI.
# (java -jar would ignore the classpath entirely — do not switch back.)
exec java \$JAVA_OPTS \$EXTRA_OPTS \\
exec java \$JAVA_OPTS \$EXTRA_OPTS \$CDS_OPTS \\
-Dconfig.file="\$CONFIG_FILE" \\
-Dlog.dir="\$LOG_DIR" \\
\$LOGBACK_OPTS \\
Expand All @@ -1019,6 +1040,55 @@ LAUNCHER_EOF
success "Created $TARGET_DIR/bin/softclient4es"
}

# =============================================================================
# Generate AppCDS archive (REPL.5 / #163 fix 3) — bundle installs, JDK 13-18.
# JDK 19+ needs nothing here (the launcher passes -XX:+AutoCreateSharedArchive).
# =============================================================================

CDS_STATUS="disabled"

generate_cds_archive() {
[[ "$USE_BUNDLE" == true ]] || return 0 # single-jar classpath only (CDS forbids lib/*)
local jv
jv=$(get_java_version)
[[ -n "$jv" ]] || return 0
if [[ "$jv" -ge 19 ]]; then
# The launcher's -XX:+AutoCreateSharedArchive branch dumps/refreshes at runtime.
CDS_STATUS="enabled (runtime auto-create, JDK 19+)"
return 0
fi
[[ "$jv" -ge 13 ]] || return 0 # 11/12: no dynamic archive support

info "Generating AppCDS archive (one-off; speeds up every REPL/batch start)..."
# The installer runs under `set -e`: every may-fail step below is guarded so a
# CDS failure can NEVER fail the install (warn-and-continue only).
if ! mkdir -p "$TARGET_DIR/cache" 2>/dev/null; then
warn "AppCDS archive generation skipped — could not create cache/ (no runtime impact)"
CDS_STATUS="disabled (generation failed)"
return 0
fi
# Re-install into an existing dir: never dump over a stale archive.
rm -f "$TARGET_DIR/cache/softclient4es.jsa" 2>/dev/null || true
# Dump workload: a short-lived batch run. It needs NO Elasticsearch — whatever the
# statement resolves to (local help or a connection error printed by the executor),
# the process exits normally with code 0 (Repl.executeCommand always returns 0) and
# the dynamic archive is written at exit. -cp is the bare jar: wildcards are
# forbidden at dump time, and [jar] is a prefix of the runtime [jar, lib/*] path.
# The exit code alone is not proof the archive was written (an unwritable path
# still exits 0) — require the .jsa file to actually exist.
if java -XX:ArchiveClassesAtExit="$TARGET_DIR/cache/softclient4es.jsa" \
-cp "$TARGET_DIR/lib/$JAR_NAME" \
app.softnetwork.elastic.client.Cli -c "help" > /dev/null 2>&1 \
&& [[ -f "$TARGET_DIR/cache/softclient4es.jsa" ]]; then
success "AppCDS archive created: cache/softclient4es.jsa"
CDS_STATUS="enabled (cache/softclient4es.jsa)"
else
warn "AppCDS archive generation failed — continuing without it (no runtime impact)"
rm -f "$TARGET_DIR/cache/softclient4es.jsa" 2>/dev/null || true
CDS_STATUS="disabled (generation failed)"
fi
}

# =============================================================================
# Create Logback Configuration
# =============================================================================
Expand Down Expand Up @@ -1139,6 +1209,7 @@ Scala: $SCALA_VERSION
Java Required: $REQUIRED_JAVA_VERSION+
Artifact: $ARTIFACT_NAME
Extensions: $EXTENSIONS_INSTALLED
AppCDS: $CDS_STATUS
OS: $OS_TYPE
$(print_license_notice)
EOF
Expand All @@ -1162,6 +1233,12 @@ print_summary() {
echo " SoftClient4ES version: $SOFT_VERSION"
echo " Java required: $REQUIRED_JAVA_VERSION+"
echo " OS detected: $OS_TYPE"
if [[ "$CDS_STATUS" == enabled* ]]; then
echo " AppCDS: $CDS_STATUS"
if [[ "$CDS_STATUS" == *cache/* ]]; then
echo " (regenerate by re-running the installer after a Java upgrade)"
fi
fi
echo ""
echo " Directory structure:"
echo " $TARGET_DIR/"
Expand Down Expand Up @@ -1272,6 +1349,7 @@ main() {
create_config
create_logback_config
create_launcher
generate_cds_archive
create_uninstaller
create_version_info
print_summary
Expand Down
Loading