Files
mdm-ordinis/.gitlab-ci.yml
T
Zimin A.N. c11044c32e feat(bench): JMH microbenchmarks module + manual CI job
CEO plan v1 polish — закрывает gap "JMH performance benchmarks в CI для
baseline + Redis-enabled scenarios".

New module:
- ordinis-bench/ — JMH 1.37 microbenchmarks. Gated behind `bench` Maven
  profile в parent pom — НЕ строится при обычном `mvn package` (default
  CI остаётся быстрым).
- maven-shade-plugin под `bench` profile собирает uber-jar
  `target/benchmarks.jar` (86 MB, ~12 sec build).
- LineageBenchmark covers hot paths из dict-relationships-v2:
  * parseRef_simple / withSchemaButNoOnClose / withOnClose
  * onCloseAction_block / cascade / null
  * schemaTraversal_findRefs (40 props, 5 refs — realistic shape)

Baseline numbers (M3 MBP, JDK 25, single thread):
- onCloseAction_*: 0.6–12 ns/op
- parseRef_*: 20–93 ns/op
- schemaTraversal: 417 ns/op (~83 ns per ref на 40 props)
Cumulative budget for findSchemaDependents на 40 dicts × 40 props × 5 refs:
~17 µs. p99 endpoint SLO = 200 ms — запас 4 порядка. README documents
budget + 2x regression alarm.

CI integration:
- New job maven-bench (stage: test). Manual trigger only:
  * RUN_BENCH=true variable, OR
  * web pipeline source с manual click.
- Default CI (push/merge) НЕ запускает bench — slow + noisy в history.
- Output artifacts: bench-results.json + bench-results.txt (5 day expire).
- Quick smoke config: 1 fork, 2 warmups, 3 measurements × 2s = ~5 min total.

Side effect: ReferenceValidator.parseRef static methods переведены из
package-private в public. Они и так часть public ParsedRef contract —
visible to bench module без изменения design intent.

Verify:
- mvn -P bench -pl ordinis-bench -am package: BUILD SUCCESS (12s).
- java -jar benchmarks.jar -f 1 -wi 1 -i 2 Lineage: all 7 benchmarks
  exec'ятся, valid ns/op numbers.
- mvn -pl ordinis-rest-api -am test: 106/106 PASS unchanged.
- glab ci lint: clean.

README в ordinis-bench/README.md документирует:
- baseline numbers + 2x regression budget
- local run options (smoke / full / GC profile / JSON output)
- when CI bench runs (manual web trigger)
- difference vs k6/wrk (HTTP load tests are different layer, both needed)
- how to add new benchmark
2026-05-08 12:22:18 +03:00

328 lines
12 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================
# Ordinis — build & push images.
# Path-aware CI: backend и frontend jobs запускаются только когда меняются
# соответствующие пути. Trigger downstream передаёт BACKEND_IMAGE_TAG и/или
# FRONTEND_IMAGE_TAG; infra-pipeline применяет helm upgrade с per-service
# overrides — сервисы которые не пересобирались не катятся.
# =============================================================
stages:
- test
- build
- publish
- trigger-deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn"
MAVEN_CLI_OPTS: "-B -ntp --batch-mode"
IMAGE_TAG: $CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
REGISTRY: repo.nstart.cloud
IMAGE_REPO: terravault
DOCKER_BUILDKIT: "1"
cache:
key:
files: ["pom.xml"]
paths:
- .m2/repository
default:
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
- script_failure
# Backend changes триггерят maven + docker для всех Java-сервисов + миграции.
.backend-changes: &backend-changes
- "ordinis-app/**/*"
- "ordinis-read-api/**/*"
- "ordinis-projection-writer/**/*"
- "ordinis-rest-api/**/*"
- "ordinis-domain/**/*"
- "ordinis-validation/**/*"
- "ordinis-events-api/**/*"
- "ordinis-outbox/**/*"
- "ordinis-auth/**/*"
- "ordinis-cuod-bundle/**/*"
- "ordinis-migrations/**/*"
- "pom.xml"
- ".gitlab-ci.yml"
# Frontend changes — только admin-ui.
.frontend-changes: &frontend-changes
- "ordinis-admin-ui/**/*"
# Documentation changes — Diplodoc-built integrator guide.
# docs-internal/ исключено явно: оно НЕ build'ится Diplodoc'ом и не должно
# триггерить docs job.
.docs-changes: &docs-changes
- "docs/**/*"
# ─── TEST ──────────────────────────────────────
maven-test:
stage: test
image: repo.nstart.cloud/library/maven:3.9.9-eclipse-temurin-21
script:
- mvn $MAVEN_CLI_OPTS verify
artifacts:
when: always
reports:
junit: '**/target/surefire-reports/TEST-*.xml'
expire_in: 1 week
rules:
- changes: *backend-changes
# Е2Е тесты через Testcontainers (Postgres+Kafka). Профиль `e2e` в
# ordinis-app/pom.xml включает их (default surefire excludes e2e директорию).
# Требует docker:dind service для testcontainers — обычный maven-test runner
# не имеет Docker.
maven-e2e:
stage: test
image: repo.nstart.cloud/library/maven:3.9.9-eclipse-temurin-21
services:
- name: repo.nstart.cloud/library/docker:29.1.2-dind
alias: docker
command: ["--tls=false"]
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
TESTCONTAINERS_HOST_OVERRIDE: "docker"
TESTCONTAINERS_RYUK_DISABLED: "true"
script:
- for i in $(seq 1 30); do docker -H tcp://docker:2375 info >/dev/null 2>&1 && break; sleep 1; done
- mvn $MAVEN_CLI_OPTS -pl ordinis-app -am -P e2e test
artifacts:
when: always
reports:
junit: '**/target/surefire-reports/TEST-*.xml'
expire_in: 1 week
rules:
- changes: *backend-changes
# Можно сделать allow_failure: true если e2e будут флакать — пока нет.
# ─── BENCH (manual JMH) ────────────────────────
# Manual trigger only — slow (~5-10 min) + noisy в CI history. Trigger через
# pipeline run UI с variable RUN_BENCH=true. Catches алгоритмическую регрессию
# на hot paths (parseRef, OnCloseAction, schema traversal).
maven-bench:
stage: test
image: repo.nstart.cloud/library/maven:3.9.9-eclipse-temurin-21
script:
- mvn $MAVEN_CLI_OPTS -P bench -pl ordinis-bench -am package -DskipTests
# Quick smoke: 1 fork, 2 warmups, 3 measurements × 2s. Total ~5 min.
# Если нужно надёжнее — bump до -f 2 -wi 5 -i 5 -r 5 (15 min).
- java -jar ordinis-bench/target/benchmarks.jar -f 1 -wi 2 -i 3 -w 1 -r 2
-rf json -rff bench-results.json | tee bench-results.txt
artifacts:
paths:
- bench-results.json
- bench-results.txt
expire_in: 5 days
rules:
- if: '$RUN_BENCH == "true"'
when: on_success
- if: '$CI_PIPELINE_SOURCE == "web"'
when: manual
allow_failure: true
# ─── BUILD (Maven jars) ────────────────────────
# maven-package всегда запускается: docker backend jobs нуждаются в jar даже
# при frontend-only PR (иначе backend образы не пересобрать → ImagePullBackOff
# в кластере при rolling update). BuildKit layer cache делает повторную сборку
# идентичных jar'ов ~30 sec.
maven-package:
stage: build
image: repo.nstart.cloud/library/maven:3.9.9-eclipse-temurin-21
script:
- mvn $MAVEN_CLI_OPTS package -DskipTests
artifacts:
paths:
- "**/target/*.jar"
- ordinis-migrations/src/main/resources/db/changelog/**
expire_in: 1 day
# ─── BUILD: Integrator Guide (sanity check) ────
# Sanity-check build для PRs и non-main branches. Полный image build
# идёт в publish stage (docker-docs). Здесь только verify что yfm
# не выдаёт errors на изменения docs/**.
build-docs:
stage: build
image: repo.nstart.cloud/library/node:20-alpine
before_script:
# npm install pnpm — corepack может hang на CI runner integrity checks.
- npm install -g --silent pnpm@9.15.4
script:
- cd docs
- pnpm install --frozen-lockfile
- pnpm build
- echo "Built $(find _dist -name '*.html' | wc -l) HTML pages, total size:"
- du -sh _dist
artifacts:
name: "ordinis-docs-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA"
paths:
- docs/_dist
expire_in: 1 week
rules:
- if: '$CI_COMMIT_BRANCH != "main"'
changes: *docs-changes
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes: *docs-changes
# ─── PUBLISH (Docker images) ───────────────────
.docker_base:
stage: publish
image: repo.nstart.cloud/nstart/docker-cli-buildx:1.0.0
services:
- name: repo.nstart.cloud/library/docker:29.1.2-dind
alias: docker
command: ["--tls=false"]
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
DOCKER_BUILDKIT: "1"
.publish_script: &publish_script |
for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break; sleep 1; done
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
IMG="$REGISTRY/$IMAGE_REPO/$SVC"
docker build -f "$DOCKERFILE" \
-t "$IMG:$IMAGE_TAG" -t "$IMG:latest" -t "$IMG:$CI_COMMIT_REF_SLUG" \
--build-arg BUILDKIT_INLINE_CACHE=1 \
"$BUILD_CONTEXT"
docker push "$IMG:$IMAGE_TAG"
docker push "$IMG:latest"
docker push "$IMG:$CI_COMMIT_REF_SLUG"
docker-app:
extends: .docker_base
needs: [maven-package]
variables:
SVC: ordinis-app
DOCKERFILE: ordinis-app/Dockerfile
BUILD_CONTEXT: .
script:
- *publish_script
docker-read-api:
extends: .docker_base
needs: [maven-package]
variables:
SVC: ordinis-read-api
DOCKERFILE: ordinis-read-api/Dockerfile
BUILD_CONTEXT: .
script:
- *publish_script
docker-projection-writer:
extends: .docker_base
needs: [maven-package]
variables:
SVC: ordinis-projection-writer
DOCKERFILE: ordinis-projection-writer/Dockerfile
BUILD_CONTEXT: .
script:
- *publish_script
docker-migrations:
extends: .docker_base
needs: [maven-package]
variables:
SVC: ordinis-migrations
DOCKERFILE: ordinis-migrations/Dockerfile
BUILD_CONTEXT: ordinis-migrations/
script:
- *publish_script
docker-admin-ui:
extends: .docker_base
needs: []
variables:
SVC: ordinis-admin-ui
DOCKERFILE: ordinis-admin-ui/Dockerfile
BUILD_CONTEXT: ordinis-admin-ui/
script:
- *publish_script
# Раньше был path-filter, но это создавало дыру: если backend менялся, а
# frontend нет — резолвили общий тег и helm пытался катить admin-ui:<commit>
# которого не существовало. Layer-cache делает повторную сборку ~30 sec.
# Integrator Guide (Diplodoc) — multi-stage Dockerfile (yfm build → nginx serving).
# Path-filtered: rebuild только при изменении docs/. Если docs не менялся,
# helm берёт previous tag через --reset-then-reuse-values.
docker-docs:
extends: .docker_base
needs: []
variables:
SVC: ordinis-docs
DOCKERFILE: docs/Dockerfile
BUILD_CONTEXT: docs/
script:
- *publish_script
rules:
# На main + tag собираем БЕЗ path-filter. Иначе resolve-tag emit'ит
# docs=main-<sha>, helm пытается pull этого тега, image отсутствует
# (docker-docs не запускался) → atomic rollback с context deadline
# exceeded. Layer cache при отсутствии docs/* changes делает rebuild
# ~30 sec — приемлемая цена за гарантию что image для tag существует.
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
- if: '$CI_PIPELINE_SOURCE == "web"'
when: manual
# ─── TRIGGER DEPLOY ───────────────────────────
# Single resolve-tag (always runs) → один UPSTREAM_IMAGE_TAG. Path-filter
# работает на уровне docker-* jobs (backend skip если бэк не менялся,
# layers cache hit делает rebuild ~30 sec). Полное per-service splitting
# через dual dotenv не работает с GitLab trigger var interpolation.
resolve-tag:
stage: trigger-deploy
image: repo.nstart.cloud/library/alpine:3.20
script:
- echo "RESOLVED_IMAGE_TAG=$IMAGE_TAG" > resolved.env
# DOCS_IMAGE_TAG может быть пустым если docker-docs не пересобирался
# (path-filter no-match). Helm в infra оставит старый tag через
# --reset-then-reuse-values если переменная пустая.
- echo "RESOLVED_DOCS_TAG=$IMAGE_TAG" >> resolved.env
- cat resolved.env
artifacts:
reports:
dotenv: resolved.env
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
# На tag-пайплайне $CI_COMMIT_BRANCH пустой — но trigger-deploy-prod
# (тоже tag-only) needs: resolve-tag. Без этого правила prod deploy
# не запускается с ошибкой "resolve-tag does not exist in the pipeline".
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
trigger-deploy-staging:
stage: trigger-deploy
needs: [resolve-tag]
trigger:
project: 2-6/2-6-4/terravault/ordinis-infra
branch: main
strategy: depend
variables:
UPSTREAM_IMAGE_TAG: $RESOLVED_IMAGE_TAG
DOCS_IMAGE_TAG: $RESOLVED_DOCS_TAG
DEPLOY_ENV: staging
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
trigger-deploy-prod:
stage: trigger-deploy
needs: [resolve-tag]
trigger:
project: 2-6/2-6-4/terravault/ordinis-infra
branch: main
strategy: depend
variables:
UPSTREAM_IMAGE_TAG: $RESOLVED_IMAGE_TAG
DOCS_IMAGE_TAG: $RESOLVED_DOCS_TAG
DEPLOY_ENV: prod
rules:
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
when: manual