From 2b844611880d4abc98f3abd07d36474ab9b424d8 Mon Sep 17 00:00:00 2001 From: mkuchenbecker Date: Wed, 27 May 2026 12:10:41 -0700 Subject: [PATCH 1/2] Add Claude.md to Optimizer --- services/optimizer/CLAUDE.md | 263 +++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 services/optimizer/CLAUDE.md diff --git a/services/optimizer/CLAUDE.md b/services/optimizer/CLAUDE.md new file mode 100644 index 000000000..1238ec6e6 --- /dev/null +++ b/services/optimizer/CLAUDE.md @@ -0,0 +1,263 @@ +# Optimizer subsystem + +Continuous table optimizer for OpenHouse Iceberg tables. The analyzer issues PENDING +operation rows per table on a cadence; the scheduler claims those rows, packs them into +bins, and submits one Spark job per bin to the Jobs Service. Analysis and scheduling +state lives in the optimizer DB (`table_operations`, `table_operations_history`, +`table_stats`, `table_stats_history`). + +## Layout + +Libraries (logic): + +- `services/optimizer/` — REST service: `api/spec/` DTOs, controllers, `model/` DTOs, + `db/` JPA entities, `repository/`, `service/`. Owns the schema in + `src/main/resources/db/optimizer-schema.sql`. +- `services/optimizer/analyzer/` — analyzer library: `AnalyzerRunner`, the + `OperationAnalyzer` strategy interface, `CadencePolicy`, per-operation analyzers + (e.g. `CadenceBasedOrphanFilesDeletionAnalyzer`). +- `services/optimizer/scheduler/` — scheduler library: `SchedulerRunner`, `BinPacker`, + `Bin`, `SchedulingCandidate`, `JobsServiceClient`. + +Deployable Spring Boot wrappers (entry points only): + +- `apps/optimizer/analyzerapp/` — `AnalyzerApplication` + `application.properties`. +- `apps/optimizer/schedulerapp/` — `SchedulerApplication` + `application.properties`. + +The REST service (`services/optimizer/`) is itself a runnable Spring Boot app +(`OptimizerServiceApplication`); it does not have an `apps/` wrapper. + +## No internal-LinkedIn references + +This is an OSS subsystem. Source and docs under `services/optimizer/` and +`apps/optimizer/` must not contain: internal ticket IDs (`BDP-XXXX`, etc.), +`linkedin.atlassian.net` URLs, internal team names, internal usernames, internal +Confluence or wiki links, internal Slack channels, internal email addresses, or +LinkedIn-prod-specific table or service identifiers. Generic references to the public +`linkedin/openhouse` GitHub repo and the published docs at `linkedin.github.io/openhouse` +are fine. + +## Library / deployable split + +Library modules disable `bootJar` and publish a plain `jar`; deployable apps depend on +the library via `implementation project(':services:optimizer:')`. + +Library `build.gradle` shape: + +```groovy +bootJar { enabled = false } +jar { enabled = true; archiveClassifier = '' } + +dependencies { + api project(':services:optimizer') + // ... +} +``` + +App `build.gradle` shape: + +```groovy +dependencies { + implementation project(':services:optimizer:analyzer') // or :scheduler + // ... +} +``` + +The `@SpringBootApplication` class lives only in the app wrapper. Library modules carry +no `main` method. + +## Package convention + +Everything is under `com.linkedin.openhouse.optimizer.*`. The library and its deployable +share the same root package, which is what lets Spring component-scan from the app's +main class pick up the library's `@Component` / `@Service` beans without extra +`@ComponentScan` configuration. Do not introduce a deeper sub-package just because the +directory nests — `apps/optimizer/analyzerapp/.../analyzer/AnalyzerApplication.java` +sits in the same package as `services/optimizer/analyzer/.../analyzer/AnalyzerRunner.java`. + +JPA wiring is centralized in the app classes: + +```java +@SpringBootApplication +@EntityScan(basePackages = "com.linkedin.openhouse.optimizer.db") +@EnableJpaRepositories(basePackages = "com.linkedin.openhouse.optimizer.repository") +``` + +## `table_operations` concurrent-instance contract + +Multiple `PENDING` (or `SCHEDULING` / `SCHEDULED`) rows for the same +`(tableUuid, operationType)` are intentional and allowed. The only primary key on +`table_operations` is `id`. There is no uniqueness constraint on +`(tableUuid, operationType)`, by design. + +Two analyzer instances can race and both insert a PENDING row for the same table; that +is fine. Dedup is performed per scheduling cycle inside +`SchedulerRunner.cancelDuplicates`: for each `tableUuid` with more than one PENDING row, +the oldest survives (lex-tiebreak on `id`) and the rest are cancelled via +`TableOperationsRepository.cancel`. Cancellation is gated on `status = PENDING` so a row +another instance has already claimed is never dropped. + +Do not add a unique index on `(tableUuid, operationType)`. Discuss before changing this +contract. + +## Pagination caveat (analyzer + scheduler) + +Aligned per-page pagination across the analyzer's three pre-load reads — current ops, +latest history, tables — is incorrect. Page N of one query has no relationship to page N +of the others, so keyed lookups by `tableUuid` mostly miss, the maps look empty, and the +analyzer issues duplicate PENDING rows. The codebase uses `Pageable.unpaged()` for these +reads (`AnalyzerRunner.analyzeDatabase`) and for the scheduler's PENDING load +(`SchedulerRunner.schedule`). The working set is bounded by tables-per-database +(analyzer) or count of PENDING rows for the operation type (scheduler). + +If memory ever needs to be bounded here: + +- Paginate the primary list (tables for the analyzer, PENDING ops for the scheduler). +- Then either (a) load the joined maps unbounded, or (b) batch-lookup the maps by + `tableUuid IN `. +- Do not reintroduce the broken aligned-page pattern. + +## Property naming + +`kebab-case` keys, module-prefixed where applicable. One default per key, set via +`${ENV_VAR:fallback}` if it should be overridable at runtime. + +Examples: + +``` +ofd.success-retry-hours=16 +ofd.failure-retry-hours=1 +scheduler.ofd.max-files-per-bin=${SCHEDULER_OFD_MAX_FILES_PER_BIN:1000000} +scheduler.results-endpoint=${SCHEDULER_RESULTS_ENDPOINT:http://openhouse-optimizer:8080/v1/optimizer/operations} +scheduler.cluster-id=${SCHEDULER_CLUSTER_ID:LocalHadoopCluster} +jobs.base-uri=${JOBS_BASE_URI:http://localhost:8002} +``` + +Per-operation property convention: prefix with the operation short-name. OFD uses +`ofd.*` (analyzer) and `scheduler.ofd.*` (scheduler). New operations follow the same +shape (`.*` and `scheduler..*`). + +Table-level opt-in is a table property, not a server config: +`maintenance.optimizer..enabled=true` (e.g. `maintenance.optimizer.ofd.enabled`). +This piggybacks on the existing `maintenance.*` prefix and lives on the table itself. + +## REST error contract + +Controllers throw `org.springframework.web.server.ResponseStatusException(status, +reason)`. `application.properties` sets `server.error.include-message=always` so the +reason reaches the wire (Spring Boot 2.7 omits the `message` field by default). + +Example: + +```java +.orElseThrow(() -> new ResponseStatusException( + HttpStatus.NOT_FOUND, String.format("no operation with id %s", id))); +``` + +Do not introduce a custom global `@ControllerAdvice` exception handler or `ApiError` +DTO unless there is a concrete reason the codebase can't grow into. That was tried; it +duplicated Spring's defaults and was removed. + +`MockMvc` does not trigger Spring's error-dispatch path, so the response body of a +`ResponseStatusException` is empty in tests even though it's populated in production. +Assert on status code, not body, in controller tests. + +## `@ApiResponses` convention + +Annotate each endpoint with the codes it actually returns. Use the form +`"Resource ACTION: STATUS"` for descriptions: + +```java +@ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Operation UPDATE: CREATED"), + @ApiResponse(responseCode = "400", description = "Operation UPDATE: BAD_REQUEST"), + @ApiResponse(responseCode = "404", description = "Operation UPDATE: NOT_FOUND") +}) +``` + +Do not list `500`. Do not list codes the endpoint cannot return. + +## `Optional` over nullable + +All optional parameters in repository and service signatures are `Optional`, never +bare nullable. This includes filter parameters on `find(...)` methods. Example +(`TableOperationsRepository.find`): every filter — `operationType`, `status`, +`tableUuid`, `databaseName`, `tableName`, `scheduledAt`, `ids` — is `Optional`. + +Spring Data's `@Query` doesn't unwrap `Optional`. Bridge with a `default`-method facade +that takes `Optional` and dispatches to a nullable internal `@Query`-annotated +method: + +```java +default List find(Optional operationType, ...) { + return findInternal(operationType.orElse(null), ...); +} + +@Query("SELECT r FROM ... WHERE (:operationType IS NULL OR r.operationType = :operationType) ...") +List findInternal(@Param("operationType") OperationType operationType, ...); +``` + +Same shape applies for `updateBatch` / `updateBatchInternal`. `List` parameters can't +share an `:ids IS NULL OR r.id IN :ids` pattern (Hibernate expands the list inline and +the `IS NULL` check turns ungrammatical) — branch in the `default` method on +`ids.isPresent()` and dispatch to two separate `@Query` methods. + +## Tests run on H2 in MySQL mode; production is MySQL + +Test resources set `jdbc:h2:mem:...;MODE=MySQL`. Production is MySQL 8 (see +`spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver` and the MySQL8 dialect in +`services/optimizer/src/main/resources/application.properties`). Schema is shared: +`db/optimizer-schema.sql`, executed via `spring.sql.init.schema-locations` on both. + +Watch for behavior that differs across the two: + +- `@Modifying` queries use `flushAutomatically = true, clearAutomatically = true` so + pending writes hit the DB before the modifying query runs and the persistence context + is cleared after. Required for the SchedulerRunner's + `updateBatch` + re-`find` claim pattern to see its own writes. +- Timestamp precision: MySQL `TIMESTAMP(6)` stores microseconds; H2 and `Instant.now()` + on macOS may carry nanoseconds. Round-tripping reads back at lower precision and + breaks equality. Use `Instant.now().truncatedTo(ChronoUnit.MICROS)` when constructing + test fixtures that are later compared to repository-loaded rows. The pattern is in + `TableOperationsRepositoryTest`. + +Any new `@Query` should be sanity-checked against MySQL 8 — particularly anything using +`IN`, `IS NULL`, `COALESCE`, or window functions. + +## Entry-point pattern (analyzer / scheduler) + +Both deployable apps follow the same shape: a `CommandLineRunner` bean iterates the +registered strategies once per process invocation. The process is expected to be driven +externally (cron, scheduler, etc.) — there is no internal scheduling loop. + +Analyzer: + +```java +@Bean +public CommandLineRunner run(AnalyzerRunner runner, List analyzers) { + return args -> analyzers.forEach(a -> runner.analyze(a.getOperationType())); +} +``` + +Scheduler: + +```java +@Bean +public CommandLineRunner run(SchedulerRunner runner, Map binPackers) { + return args -> binPackers.keySet().forEach(runner::schedule); +} +``` + +Adding a new operation type: + +1. Add a value to `OperationTypeDto` / `OperationType` / api-spec `OperationType` and + wire their `toDb` / `fromDb` / `toModel` conversions. +2. Implement an `OperationAnalyzer` in `services/optimizer/analyzer/` and register it as + a `@Component`. The analyzer app picks it up via the `List` + injection. +3. Implement a `BinPacker` in `services/optimizer/scheduler/` and register it as a + `@Component` keyed by its `OperationTypeDto` (the scheduler app injects the + `Map`). +4. Add any operation-specific table-properties opt-in flag under + `maintenance.optimizer..*` and any tuning knobs under `.*` (analyzer) / + `scheduler..*` (scheduler). From 0b4d422ea1ec6b4c5e82203540d1526c71febd84 Mon Sep 17 00:00:00 2001 From: mkuchenbecker Date: Wed, 27 May 2026 12:12:31 -0700 Subject: [PATCH 2/2] docs(optimizer): add architecture doc + PlantUML diagrams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit services/optimizer/ARCHITECTURE.md — externally-facing description of the optimizer subsystem: what it is, components, one cycle end-to-end, operation states, REST endpoints, how to enable on a table, how to add a new operation type. services/optimizer/diagrams/ — diagram sources + rendered PNGs: components.puml + .png — analyzer / scheduler / optimizer service / optimizer DB; boundaries to tables service, jobs service, and the Spark job sequence.puml + .png — five phases: stats commit, analyzer cycle, scheduler cycle, job submission, callback + history append state.puml + .png — PENDING → SCHEDULING → SCHEDULED → history; CANCELED exit from PENDING render.sh — PlantUML rendering instructions + one-shot regeneration script Distilled to the data-flow principles; retry, dedup specifics, and internal implementation details are out of scope. Co-Authored-By: Claude Opus 4.7 --- services/optimizer/ARCHITECTURE.md | 88 ++++++++++++++++++++ services/optimizer/diagrams/components.png | Bin 0 -> 17760 bytes services/optimizer/diagrams/components.puml | 35 ++++++++ services/optimizer/diagrams/render.sh | 17 ++++ services/optimizer/diagrams/sequence.png | Bin 0 -> 23446 bytes services/optimizer/diagrams/sequence.puml | 35 ++++++++ services/optimizer/diagrams/state.png | Bin 0 -> 12517 bytes services/optimizer/diagrams/state.puml | 18 ++++ 8 files changed, 193 insertions(+) create mode 100644 services/optimizer/ARCHITECTURE.md create mode 100644 services/optimizer/diagrams/components.png create mode 100644 services/optimizer/diagrams/components.puml create mode 100755 services/optimizer/diagrams/render.sh create mode 100644 services/optimizer/diagrams/sequence.png create mode 100644 services/optimizer/diagrams/sequence.puml create mode 100644 services/optimizer/diagrams/state.png create mode 100644 services/optimizer/diagrams/state.puml diff --git a/services/optimizer/ARCHITECTURE.md b/services/optimizer/ARCHITECTURE.md new file mode 100644 index 000000000..8735add32 --- /dev/null +++ b/services/optimizer/ARCHITECTURE.md @@ -0,0 +1,88 @@ +# Optimizer architecture + +The Optimizer is a continuous maintenance loop for OpenHouse Iceberg tables. It decides which tables need work, batches that work into one Spark job per bin, and records the outcome. It replaces the previous fixed-cron, one-job-per-`(table, operation)` model. + +## Components + +![Components](diagrams/components.png) + +| Component | Role | Shape | +|---|---|---| +| **Analyzer** | Decides which tables are due for an operation; writes a `PENDING` row per table. | Short-lived job (cron). | +| **Scheduler** | Reads `PENDING` rows, bin-packs them, claims a bin, submits one Spark job. | Short-lived job (cron). | +| **Optimizer service** | REST API and the persistence layer for all operation state. | Long-running service. | +| **Optimizer DB** | Stores operations, history, table stats, and stats history. | MySQL. | + +The Optimizer interacts with three OpenHouse components at its boundary: + +- **Tables service** — pushes per-commit table stats into the Optimizer service. +- **Jobs service** — accepts job submissions from the Scheduler and launches Spark. +- **Maintenance Spark job** — runs the work for a bin and reports per-table results back to the Optimizer service. + +## One cycle, end to end + +![Sequence](diagrams/sequence.png) + +Five things happen, in order: + +1. **Tables service** writes the latest table stats to the Optimizer service on every Iceberg commit. This is the freshness signal the Analyzer uses to decide what's due. +2. **Analyzer** reads stats and recent history, then writes one `PENDING` operation per table that should be processed this cycle. +3. **Scheduler** reads `PENDING` operations, bin-packs them, and claims a bin by walking each operation through `PENDING → SCHEDULING → SCHEDULED`. +4. **Scheduler** submits one Spark job per bin to the Jobs service. +5. **Spark job** runs and POSTs per-operation results back to the Optimizer service, which appends a history row for each. + +The Analyzer and Scheduler do not talk to each other. The contract between them is the operations table — the Analyzer writes `PENDING`, the Scheduler consumes `PENDING`. Anything more sophisticated (cadence, retry, bin sizing) is internal to one side or the other. + +## Operation states + +![States](diagrams/state.png) + +A given `(table, operation type)` may have multiple operations in flight by design; the Scheduler reconciles duplicates per cycle by cancelling all but the oldest. Once an operation transitions to `SCHEDULED`, its outcome is captured as a history row rather than another state change on the operation itself. + +## REST endpoints (Optimizer service) + +| Endpoint | Purpose | +|---|---| +| `POST /v1/optimizer/operations/{id}/update` | Reports the outcome of one operation (called by the Spark job). | +| `GET /v1/optimizer/operations/{id}` | Fetches a single operation by id. | +| `GET /v1/optimizer/operations` | Lists operations with optional filters. | +| `GET /v1/optimizer/operations-history/{tableUuid}` | Recent history for a table. | +| `PUT /v1/optimizer/stats/{tableUuid}` | Upserts table stats (called by the Tables service on commit). | +| `GET /v1/optimizer/stats/{tableUuid}` | Fetches current stats for a table. | +| `GET /v1/optimizer/stats` | Lists stats with optional filters. | +| `GET /v1/optimizer/stats/{tableUuid}/history` | Per-commit stats history for a table. | + +Payload shapes and parameter details are in the OpenAPI spec generated from the controllers. + +## Enabling optimization on a table + +A table opts into a given operation type via a table property of the form `maintenance.optimizer..enabled=true`. The Analyzer ignores any table without the relevant flag. + +Example — enable orphan-file deletion: + +```sql +ALTER TABLE db.tbl + SET TBLPROPERTIES ('maintenance.optimizer.ofd.enabled'='true'); +``` + +## Adding a new operation type + +Four steps: + +1. **Declare the type.** Add the new value to the `OperationType` enum and any wire/model variants, with the corresponding conversion methods. +2. **Implement an analyzer strategy.** Provide an `OperationAnalyzer` implementation that decides when a table is due. Register it as a Spring component; the Analyzer process picks it up via the registered list. +3. **Implement a bin packer.** Provide a `BinPacker` keyed by the new `OperationType`. The Scheduler process picks it up via the registered map. +4. **Wire up configuration.** Tuning knobs go under `.*` (analyzer) and `scheduler..*` (scheduler) keys; the table-level opt-in flag is `maintenance.optimizer..enabled`. + +The Optimizer service itself does not change when a new operation type is added — it is operation-type agnostic. + +## Diagrams + +Sources are in [`diagrams/`](diagrams/) as PlantUML (`.puml`) plus the rendered PNGs that GitHub displays above. To regenerate after editing a source: + +```sh +cd diagrams +./render.sh +``` + +Requirements and full instructions are at the top of [`diagrams/render.sh`](diagrams/render.sh). diff --git a/services/optimizer/diagrams/components.png b/services/optimizer/diagrams/components.png new file mode 100644 index 0000000000000000000000000000000000000000..2d886f4a16b33e4a52c42854bf967dc16c50fb5f GIT binary patch literal 17760 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 literal 0 HcmV?d00001 diff --git a/services/optimizer/diagrams/components.puml b/services/optimizer/diagrams/components.puml new file mode 100644 index 000000000..867604901 --- /dev/null +++ b/services/optimizer/diagrams/components.puml @@ -0,0 +1,35 @@ +@startuml components +title Optimizer — Components + +skinparam componentStyle rectangle +skinparam shadowing false +skinparam defaultFontName Helvetica + +actor "External cron" as Cron + +package "Optimizer" { + component "Analyzer" as Analyzer + component "Scheduler" as Scheduler + component "Optimizer service\n(REST)" as Service + database "Optimizer DB" as DB +} + +package "OpenHouse" { + component "Tables service" as Tables + component "Jobs service" as Jobs + component "Maintenance Spark job" as Spark +} + +Cron --> Analyzer : trigger +Cron --> Scheduler : trigger + +Tables --> Service : commit stats +Analyzer --> Service : read stats / history,\nwrite PENDING +Scheduler --> Service : read PENDING,\nclaim, mark SCHEDULED +Scheduler --> Jobs : submit batched job +Jobs --> Spark : launch +Spark --> Service : per-table result + +Service --> DB + +@enduml diff --git a/services/optimizer/diagrams/render.sh b/services/optimizer/diagrams/render.sh new file mode 100755 index 000000000..20db20fdf --- /dev/null +++ b/services/optimizer/diagrams/render.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# Render every PlantUML diagram in this directory to PNG. +# GitHub renders the committed PNGs; the .puml sources live here so the +# diagrams can be regenerated. +# +# Install PlantUML: +# macOS: brew install plantuml +# Debian/Ubuntu: apt-get install plantuml +# +# Then, from this directory: +# ./render.sh +# +# Commit both the .puml source and the resulting .png. + +set -e +cd "$(dirname "$0")" +plantuml -tpng *.puml diff --git a/services/optimizer/diagrams/sequence.png b/services/optimizer/diagrams/sequence.png new file mode 100644 index 0000000000000000000000000000000000000000..208389d06a8d190a286c4f7fd6cc66a1acb03c7e GIT binary patch literal 23446 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 Service : PUT table stats + +== Analyzer cycle == +Analyzer -> Service : read table stats + recent history +Analyzer -> Service : write PENDING operation\nfor each table due + +== Scheduler cycle == +Scheduler -> Service : read PENDING operations +Scheduler -> Scheduler : bin-pack +Scheduler -> Service : claim bin\n(PENDING → SCHEDULING → SCHEDULED) +Scheduler -> Jobs : submit one job per bin + +== Job execution == +Jobs -> Spark : launch +loop one POST per operation in the bin + Spark -> Service : POST result\n(SUCCESS or FAILED) +end +Service -> Service : append history row + +@enduml diff --git a/services/optimizer/diagrams/state.png b/services/optimizer/diagrams/state.png new file mode 100644 index 0000000000000000000000000000000000000000..ff4770a61bfaedbcfde0177073268c83edea2ea6 GIT binary patch literal 12517 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

uFi-%u;&O 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 H PENDING : analyzer\ndecides table is due + +PENDING --> SCHEDULING : scheduler\nclaims for a bin +SCHEDULING --> SCHEDULED : job submitted\nto Jobs service + +SCHEDULED --> [*] : history row written\n(SUCCESS or FAILED) + +PENDING --> CANCELED : superseded\nby another PENDING + +CANCELED --> [*] + +@enduml