From 85b2b18945b19eadc78cb211bf3d645fa152b172 Mon Sep 17 00:00:00 2001 From: jjoonleo Date: Sun, 28 Jun 2026 13:10:18 +0900 Subject: [PATCH 1/2] Add Apple login benchmark harness --- docs/performance/apple-login-323.md | 54 ++++ .../global/oauth/apple/AppleLoginService.java | 8 +- .../resources/application-bench.properties | 53 ++++ scripts/benchmarks/apple-login/README.md | 96 +++++++ .../benchmarks/apple-login/apple-login.k6.js | 74 ++++++ scripts/benchmarks/apple-login/apple-stub.mjs | 147 +++++++++++ .../benchmarks/apple-login/results/.gitkeep | 1 + scripts/benchmarks/apple-login/run.sh | 238 ++++++++++++++++++ .../apple-login/seed-returning-user.sql | 53 ++++ scripts/benchmarks/apple-login/summarize.py | 93 +++++++ 10 files changed, 815 insertions(+), 2 deletions(-) create mode 100644 docs/performance/apple-login-323.md create mode 100644 ontime-back/src/main/resources/application-bench.properties create mode 100644 scripts/benchmarks/apple-login/README.md create mode 100644 scripts/benchmarks/apple-login/apple-login.k6.js create mode 100644 scripts/benchmarks/apple-login/apple-stub.mjs create mode 100644 scripts/benchmarks/apple-login/results/.gitkeep create mode 100755 scripts/benchmarks/apple-login/run.sh create mode 100644 scripts/benchmarks/apple-login/seed-returning-user.sql create mode 100755 scripts/benchmarks/apple-login/summarize.py diff --git a/docs/performance/apple-login-323.md b/docs/performance/apple-login-323.md new file mode 100644 index 00000000..966cc681 --- /dev/null +++ b/docs/performance/apple-login-323.md @@ -0,0 +1,54 @@ +# Apple Login Performance Evidence for Issue #323 + +## Measurement Contract + +This document records before/after evidence for reducing Apple login latency by removing repeated Apple provider round trips from the returning Apple User login path. + +Primary benchmark environment: + +- Backend: local Spring Boot process with `bench` profile +- Database: isolated Docker MySQL database `ontime_bench` +- Apple provider: local stub +- Returning Apple User: `social_type=APPLE`, `social_id=bench-apple-user` +- Stub delay: + - `GET /auth/keys`: 80 ms + - `POST /auth/token`: 300 ms +- Warmup: 2 minutes +- Measurement: 5 minutes +- Full run count: 10 per scenario + +The benchmark harness lives in `scripts/benchmarks/apple-login/`. + +## Acceptance Gate + +Primary scenario: returning Apple User, warm cache, concurrency 1. + +- JWKS network calls/request: `before 1.0 -> after 0.0` +- Apple token exchange calls/request: `before 1.0 -> after 0.0` +- Error rate: `0%` +- p95 latency: `after <= before * 0.70` + +Secondary scenarios: returning Apple User, concurrency 10 and 20. + +- Error rate: `0%` +- Apple token exchange calls/request: `after 0.0` +- p95 latency: `after <= before` + +## Results + +Populate this table from `scripts/benchmarks/apple-login/results/*/summary.csv` after running both before and after measurements. + +| scenario | version | runs | requests | error_rate | p50_ms | p95_ms | p99_ms | jwks_calls/request | token_exchange_calls/request | +| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| c1 returning warm-cache | before | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | +| c1 returning warm-cache | after | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | +| c10 returning warm-cache | before | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | +| c10 returning warm-cache | after | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | +| c20 returning warm-cache | before | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | +| c20 returning warm-cache | after | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | + +## Notes + +- Real Apple network calls are intentionally excluded from the primary benchmark because provider and network variability would obscure backend request-path changes. +- The script labels results as `before` or `after` but does not change git refs. Select the checkout explicitly before running. +- Quick mode is for development feedback. Full mode is the acceptance evidence. diff --git a/ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java b/ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java index 1cd59a86..e327e5bc 100644 --- a/ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java +++ b/ontime-back/src/main/java/devkor/ontime_back/global/oauth/apple/AppleLoginService.java @@ -54,6 +54,10 @@ public class AppleLoginService { private static final String APPLE_KEYS_URL = "https://appleid.apple.com/auth/keys"; private static final String APPLE_TOKEN_URL = "https://appleid.apple.com/auth/token"; private String issuer = "https://appleid.apple.com"; + @Value("${apple.keys-url:" + APPLE_KEYS_URL + "}") + private String appleKeysUrl = APPLE_KEYS_URL; + @Value("${apple.token-url:" + APPLE_TOKEN_URL + "}") + private String appleTokenUrl = APPLE_TOKEN_URL; @Value("${apple.client.id}") private String clientId; @Value("${apple.team.id}") @@ -167,7 +171,7 @@ public Claims verifyIdentityToken(String identityToken) throws log.info("Verify Apple identity credential"); Map headers = jwtUtils.parseHeaders(identityToken); // apple publickey - ApplePublicKeyResponse applePublicKeyResponse = restTemplate.getForObject(APPLE_KEYS_URL, ApplePublicKeyResponse.class); + ApplePublicKeyResponse applePublicKeyResponse = restTemplate.getForObject(appleKeysUrl, ApplePublicKeyResponse.class); PublicKey publicKey = applePublicKeyGenerator.generatePublicKey(headers, applePublicKeyResponse); // claim Claims tokenClaims = jwtUtils.getTokenClaims(identityToken, publicKey); @@ -201,7 +205,7 @@ public AppleTokenResponseDto getAppleAccessTokenAndRefreshToken(String authCode) HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); ResponseEntity responseEntity = restTemplate.exchange( - APPLE_TOKEN_URL, HttpMethod.POST, requestEntity, JsonNode.class); + appleTokenUrl, HttpMethod.POST, requestEntity, JsonNode.class); JsonNode response = responseEntity.getBody(); diff --git a/ontime-back/src/main/resources/application-bench.properties b/ontime-back/src/main/resources/application-bench.properties new file mode 100644 index 00000000..4eb48855 --- /dev/null +++ b/ontime-back/src/main/resources/application-bench.properties @@ -0,0 +1,53 @@ +# Database Configuration +spring.datasource.url=${SPRING_DATASOURCE_URL} +spring.datasource.username=${SPRING_DATASOURCE_USERNAME} +spring.datasource.password=${SPRING_DATASOURCE_PASSWORD} +spring.datasource.driver-class-name=${SPRING_DATASOURCE_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver} + +# JPA / Hibernate +spring.jpa.database=mysql +spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=false + +# Flyway +spring.flyway.enabled=true +spring.flyway.baseline-on-migrate=false + +# JWT Configuration +jwt.secret.key=${JWT_SECRET_KEY} +jwt.access.expiration=${JWT_ACCESS_EXPIRATION:3600000} +jwt.refresh.expiration=${JWT_REFRESH_EXPIRATION:1209600000} +jwt.access.header=${JWT_ACCESS_HEADER:Authorization} +jwt.refresh.header=${JWT_REFRESH_HEADER:Authorization-refresh} + +# OAuth +google.web.client-id=${GOOGLE_WEB_CLIENT_ID:bench-google-web-client-id} +google.app.client-id=${GOOGLE_APP_CLIENT_ID:bench-google-app-client-id} +apple.client.id=${APPLE_CLIENT_ID} +apple.team.id=${APPLE_TEAM_ID} +apple.login.key=${APPLE_LOGIN_KEY} +apple.client.secret=${APPLE_CLIENT_SECRET:} +apple.private-key.base64=${APPLE_PRIVATE_KEY_BASE64} +apple.keys-url=${APPLE_KEYS_URL:https://appleid.apple.com/auth/keys} +apple.token-url=${APPLE_TOKEN_URL:https://appleid.apple.com/auth/token} + +# Firebase +firebase.credentials.base64=${FIREBASE_CREDENTIALS_BASE64:} + +# Logging +logging.level.root=WARN +logging.level.devkor.ontime_back=INFO + +# Feature flags +feature.apple-login.enabled=true +analytics.preference.default-enabled=${ANALYTICS_PREFERENCE_DEFAULT_ENABLED:false} + +# Actuator +management.endpoint.health.probes.enabled=true +management.endpoints.web.exposure.include=health +management.health.readinessstate.enabled=true +management.health.livenessstate.enabled=true +server.shutdown=graceful +spring.lifecycle.timeout-per-shutdown-phase=30s diff --git a/scripts/benchmarks/apple-login/README.md b/scripts/benchmarks/apple-login/README.md new file mode 100644 index 00000000..c23e710c --- /dev/null +++ b/scripts/benchmarks/apple-login/README.md @@ -0,0 +1,96 @@ +# Apple Login Benchmark + +This benchmark measures issue #323: repeated Apple provider round trips on the returning Apple User login path. + +## Scenario + +- Endpoint: `POST /oauth2/apple/login` +- Primary path: returning Apple User, warm Apple key cache +- Seeded Apple subject: `bench-apple-user` +- Apple provider: local stub, not the real Apple network +- Stub delay: + - `GET /auth/keys`: 80 ms + - `POST /auth/token`: 300 ms + +## Why the Apple provider is stubbed + +The primary comparison should isolate backend request-path behavior. Real Apple calls include DNS, TLS, internet routing, and provider-side variability, so they are useful as smoke checks but not as the main before/after evidence. + +## Run + +Install `k6` first if it is not already available: + +```bash +brew install k6 +``` + +Run from the repository root: + +```bash +scripts/benchmarks/apple-login/run.sh before quick +scripts/benchmarks/apple-login/run.sh after quick +``` + +Full evidence runs: + +```bash +scripts/benchmarks/apple-login/run.sh before full +scripts/benchmarks/apple-login/run.sh after full +``` + +The script starts an isolated MySQL container, starts the Apple stub, starts the backend with the `bench` Spring profile, seeds the returning Apple User, runs k6, and writes results. + +The script does not run `git checkout`. Measure the checkout you have selected, and use `before` or `after` only as the result label. + +## Defaults + +- Database container: `ontime-bench-mysql` +- Database: `ontime_bench` +- MySQL port: `127.0.0.1:3307` +- Backend port: `127.0.0.1:18081` +- Apple stub port: `127.0.0.1:18080` +- Quick mode: 1 run per concurrency +- Full mode: 10 runs per concurrency +- Warmup: 2 minutes +- Measurement: 5 minutes +- Concurrency: 1, 10, 20 + +Override example: + +```bash +WARMUP_DURATION=10s MEASUREMENT_DURATION=30s SCENARIOS="1" RUNS=1 \ + scripts/benchmarks/apple-login/run.sh before quick +``` + +## Results + +Results are written under: + +```text +scripts/benchmarks/apple-login/results/-