Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 6 additions & 8 deletions .github/scripts/build_playwright_shards.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,15 @@
# 19 min × 1.23 = 1402 s → 98 s of margin
# 19 min × 1.32 = 1500 s → break-even; tolerated tail is 1.32
# 21 min was a stop-gap (#30784) to fit the lane under the old 24-shard
# ceiling; the ceiling is now 28 (below), which is what actually makes a
# ceiling; increasing that ceiling is what keeps a
# 19-minute budget feasible again.
COMMON_SHARD_BUDGET_MS = 19 * 60 * 1000
EFFICIENCY = 0.85
# Raised 24 → 28 together with the budget revert above. Current chromium
# content (~71,700 predicted worker-seconds) needs 25 shards at a
# 19-minute budget — over the old cap, which is exactly why #30784 had to
# raise the budget instead. 28 leaves ~12% content-growth headroom before
# planning aborts; if the lane grows past that, split heavy suites (see
# AUDITED_PARALLEL_SUITES) before considering another cap raise.
COMMON_MAX_SHARDS = 28
# Full-run history now predicts ~84,600 worker-seconds, requiring at least
# 30 shards at three workers and 85% efficiency within the 19-minute budget.
# Splitting atomic suites cannot reduce that aggregate lower bound. Allow
# 32 shards to retain the execution margin and modest content-growth headroom.
COMMON_MAX_SHARDS = 32
# Weight assigned to a test that has no timing evidence in `timing-baseline.json`
# (or in any additional history payloads). Bumped from 20 s → 30 s alongside the
# all-zero-history fix in `load_history`: a suite re-enabled after being
Expand Down
20 changes: 19 additions & 1 deletion .github/scripts/tests/test_playwright_ci_planning.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ def test_full_common_shard_count_is_capped_at_the_common_max():
assert planner.shard_count(units, "chromium", "full") == cap


def test_full_common_lane_fits_1410_worker_minutes_without_relaxing_its_budget():
planner = load_script("build_playwright_shards")
units = [
planner.Unit("chromium", f"{index}.spec.ts", str(index), weight_ms=70_500)
for index in range(1_200)
]

shards = planner.assign_lane_within_budget(units, "chromium", "full")

assert 28 < len(shards) <= 32
assert all(
planner.predicted_execution_ms(shard, 3) <= 19 * 60 * 1000 for shard in shards
)
assert sorted(unit.key for shard in shards for unit in shard) == sorted(
unit.key for unit in units
)


def test_common_lane_carries_its_own_shard_budget():
# Chromium's budget is a minute UNDER the other lanes' TARGET_MS, derived
# from the predicted→actual execution tail: actuals run up to 1.23× the
Expand Down Expand Up @@ -146,7 +164,7 @@ def test_full_mode_chromium_reports_a_lane_the_ceiling_cannot_hold():
for index in range(120)
]

with pytest.raises(SystemExit, match=r"needs more than 28 shards"):
with pytest.raises(SystemExit, match=r"needs more than 32 shards"):
planner.assign_lane_within_budget(units, "chromium", "full")


Expand Down
10 changes: 7 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@

**Path A — an API request** (e.g. `POST /v1/tables`). Enters `openmetadata-service` at
`OpenMetadataApplication.java` (Jersey). A JAX-RS resource in `service/resources/**` (e.g.
`resources/databases/…`) delegates to a repository in `service/jdbi3/**` (extends `EntityRepository`),
which persists via `jdbi3/CollectionDAO` / `EntityDAO` (JDBI SQL-objects; 129 sub-DAOs) → the SQL DB.
`resources/databases/…`) invokes the native services of an `entity/EntityModule`. Entity policies
in `service/jdbi3/**` implement `entity/policy/EntityPolicy`; `EntityModuleFactory` composes their
query, command, metadata and deletion services once at startup. Service families implement
`entity/service/EntityServicePolicy`. Entity-specific mutations compose the final `EntityUpdater`;
column and service policies share its transaction and retry state. Persistence uses the retained
`jdbi3/CollectionDAO` / `EntityDAO` graph (JDBI SQL-objects; 129 sub-DAOs) → the SQL DB.
A non-GET response also fans out to `service/events/` (change events) and `service/search/` (index
update). **Exit:** JSON response + a persisted row + an async index write. *A "create/update returns the
wrong field" bug lives in `resources/` or `jdbi3/` — or in the schema that typed it.*
Expand Down Expand Up @@ -76,7 +80,7 @@
| 233 | `resources/` | JAX-RS entry points; largest domains `ai/` (34, flat, grew its own seed/service tier) and `services/` (32, sub-packaged) |
| 160 | `apps/` | pluggable applications / schedulers (incl. reindex) |
| 157 | `migration/` | migration runner (`MigrationWorkflow`) |
| 147 | `jdbi3/` | repositories (`EntityRepository`) + DAOs (`CollectionDAO`, 129 sub-DAOs) |
| 146 | `jdbi3/` | entity policies + DAOs (`CollectionDAO`, 129 sub-DAOs); shared application services live in `entity/` |
| 128 | `util/` · 98 `security/` · 93 `governance/` · 27 `events/` | shared utils · authN/Z · workflow engine · change events |

### `openmetadata-ui` internals (`SRC = …/ui/src`; 4,725 ts/tsx, 23 dirs; layering is convention-only — no path aliases)
Expand Down Expand Up @@ -126,7 +130,7 @@

**I3 — Generated code is a pure sink; source imports it only as types.** No generated artifact imports
application code — frontend `generated/` imports **0** from `components|pages|rest|utils|hooks|context`;
Python has **0** runtime `import metadata.generated…`; Java POJOs live in `openmetadata-spec/target/` and

Check warning on line 133 in ARCHITECTURE.md

View workflow job for this annotation

GitHub Actions / harness-integrity

[dead-reference] path does not resolve: `openmetadata-spec/target/`
depend only on the spec (08a Pass 3+4, OBSERVED). The inbound corollary — *source imports generated only
as types* — holds at **1,736/1,738 = 99.9%** in Python (one exception: `spline/utils.py` uses generated
ANTLR parsers at runtime). *Enforces:* **partially** — the agent harness blocks *edits* to generated trees
Expand Down
46 changes: 27 additions & 19 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Entity.FIELD_FULLY_QUALIFIED_NAME
Entity.SEPARATOR // "."

// Access repositories by entity type
EntityRepository<?> repo = Entity.getRepository(entityType);
EntityModule<?> module = Entity.getEntityModule(entityType);

// Build href for entity references
Entity.withHref(uriInfo, entityReference);
Expand All @@ -209,11 +209,11 @@ dashboardService.dashboard
pipelineService.pipeline
```

Each `EntityRepository` must implement `setFullyQualifiedName()` to build the FQN from parent FQN + entity name.
Each hierarchical entity policy overrides `setFullyQualifiedName()` to build the FQN from parent FQN + entity name.

### REST Resource Pattern

All entity REST resources extend `EntityResource<E, R extends EntityRepository<E>>`.
Entity REST resources extend `EntityResource<E, R extends EntityPolicy<E>>` and invoke the module's native services.

**Creating a new resource:**

Expand Down Expand Up @@ -255,27 +255,31 @@ public class MyEntityResource extends EntityResource<MyEntity, MyEntityRepositor

### JDBI3 Data Access Layer

OpenMetadata uses JDBI3 (not JPA/Hibernate) for database access. All repositories extend `EntityRepository<E>`.
OpenMetadata uses JDBI3 for database access. Entity families implement `EntityPolicy<E>`;
`EntityModuleFactory` constructs the shared services using their policy and retained dependencies.

**Creating a new repository:**

```java
@Slf4j
public class MyEntityRepository extends EntityRepository<MyEntity> {
@Repository
public class MyEntityRepository implements EntityPolicy<MyEntity> {
private final EntityPolicyContext<MyEntity> context;

public MyEntityRepository() {
super(
MyEntityResource.COLLECTION_PATH,
Entity.MY_ENTITY,
MyEntity.class,
Entity.getCollectionDAO().myEntityDAO(), // DAO interface
"", // patch fields
"" // put fields
);
supportsSearch = true; // enable ES indexing
context = new EntityPolicyContext<>(
new EntityPolicyContext.Schema<>(MyEntityResource.COLLECTION_PATH,
Entity.MY_ENTITY, MyEntity.class, Entity.getCollectionDAO().myEntityDAO()),
new EntityPolicyContext.WriteFields("", "", Set.of()),
EntityModuleDependencies.standard());
EntityModuleFactory.initialize(this, true);
context.options().setSupportsSearch(true);
}

// Required overrides:
@Override
public EntityPolicyContext<MyEntity> context() {
return context;
}

@Override
public void setFullyQualifiedName(MyEntity entity) {
Expand All @@ -292,7 +296,7 @@ public class MyEntityRepository extends EntityRepository<MyEntity> {

@Override
public void storeEntity(MyEntity entity, boolean update) {
store(entity, update);
persistence().store(entity, update);
}

@Override
Expand All @@ -302,9 +306,13 @@ public class MyEntityRepository extends EntityRepository<MyEntity> {
}
```

Also implement `setFields` and `clearFields` for the entity's own projections. The shared query
services hydrate common metadata and apply the field policy in the established order.

**Key patterns:**
- `@Transaction` annotation for multi-step writes
- `Entity.getCollectionDAO()` provides type-safe DAO access
- Normal commands own their flush through `EntityUnitOfWork`; hooks participate in that flush
- Use `persistence().execute(...)` for operations spanning module DAOs, preserving the retained transaction
- `context().dependencies().daos()` supplies the same DAO graph to every component
- Override `getFieldsStrippedFromStorageJson()` to exclude computed fields from JSON storage
- Bulk operations: override `storeEntities()`, `clearEntitySpecificRelationshipsForMany()`, `storeEntitySpecificRelationshipsForMany()`

Expand Down Expand Up @@ -558,7 +566,7 @@ Always import from `generated/` for API response types. Never hand-write interfa
3. **Generate code**: `mvn clean install -pl openmetadata-spec` + `make generate`
4. **Entity constant**: Add `Entity.MY_ENTITY = "myEntity"` in `Entity.java`
5. **DAO**: Add `myEntityDAO()` method to `CollectionDAO`
6. **Repository**: Create `MyEntityRepository extends EntityRepository<MyEntity>`
6. **Entity policy**: Create `MyEntityRepository implements EntityPolicy<MyEntity>`, annotate it with `@Repository`, and initialize its module once
7. **Mapper**: Create `MyEntityMapper`
8. **Resource**: Create `MyEntityResource extends EntityResource<MyEntity, MyEntityRepository>`
9. **Migration**: Create `bootstrap/sql/migrations/native/{version}/mysql/schemaChanges.sql` + postgres variant
Expand Down
116 changes: 116 additions & 0 deletions docs/assets/entity-repository-acceptance-summary.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"recorded_utc": "2026-09-14",
"accepted": false,
"baseline_service_sha256": "306263df42c131d28daa4a5c189591e2f5563937501dddd105c8221ee1cdfdf5",
"candidate_service_sha256": "854c3d28e74c6284f768cda27801c78b164249500577a2969b495b3e32ca34ae",
"coverage": {
"native": {
"minimum_percent": 90,
"changed_sources": 512,
"executable_classes": 1115,
"below_threshold": 349,
"missing_sources": [],
"failed_executions": [],
"passed": false,
"report_sha256": "ba7f6b63d07844cf425350b811d60194440194465e090373a32ce0c9b8990ade",
"input_manifest_sha256": "e6c9c8145c7c1957145d3326d1ba31731388ef8ea50e70f61d8c4f14c45605e6"
},
"collate": {
"minimum_percent": 90,
"changed_sources": 105,
"executable_classes": 177,
"below_threshold": 118,
"missing_sources": [],
"failed_executions": [],
"passed": false,
"report_sha256": "b451e07a2ffccc323c7f6516e7a7350c27d8bf3867025984386c23be0503f6e0",
"input_manifest_sha256": "f73e57e7203a13a39f360c41ee73c69d6c84fb30fac74d620dea1831fcbb3357"
}
},
"native_core_components": {
"executable_classes": 455,
"below_threshold": 0
},
"native_canonical_mysql": {
"found": 16129,
"successful": 14731,
"failed": 0,
"skipped": 101,
"aborted": 1247,
"duration_ms": 1164457,
"runtime": "Microsoft ARM Java 21.0.11",
"database": "MySQL 8.3",
"search": "Elasticsearch 9.3",
"cache": "Redis"
},
"linux_baseline_calibration": {
"accepted": false,
"purpose": "Identical baseline artifacts; not a candidate comparison",
"runtime": "Temurin 21.0.12+8-LTS, Linux aarch64",
"java_image": "sha256:8a26c2cf90dbc972c645c7a87d227fedc689f3e0cc0f9cd1ca775c14623c92e7",
"database": "durable MySQL 8.3",
"search": "OpenSearch 3.4",
"cache": "Redis 7, warm",
"pairs": 5,
"samples_per_workload_per_side_per_pair": 5000,
"warmups_per_workload_per_side_per_pair": 1000,
"conditioning_per_workload_per_side": 20000,
"measured_requests": 100000,
"errors": 0,
"heap": "1 GiB, G1",
"sut_cpus": "2-5",
"client_cpus": "6,7",
"backend_cpus": "0,1,8,9",
"summaries": [
{
"workload": "get.columns.100",
"metric": "p50_ms",
"median_ratio": 1.0869773150684932,
"spread": 1.2576499987066783,
"directional_bias": 1.0869773150684932,
"repeatable": false
},
{
"workload": "get.columns.100",
"metric": "p95_ms",
"median_ratio": 1.0193173902644828,
"spread": 1.2541372936138562,
"directional_bias": 1.0193173902644828,
"repeatable": false
},
{
"workload": "get.columns.100",
"metric": "p99_ms",
"median_ratio": 1.0825141175543416,
"spread": 1.270976017104482,
"directional_bias": 1.0825141175543416,
"repeatable": false
},
{
"workload": "get.relationships.100",
"metric": "p50_ms",
"median_ratio": 1.0506470737765556,
"spread": 1.1215679124442346,
"directional_bias": 1.0506470737765556,
"repeatable": false
},
{
"workload": "get.relationships.100",
"metric": "p95_ms",
"median_ratio": 1.0532753516664821,
"spread": 1.132316193503386,
"directional_bias": 1.0532753516664821,
"repeatable": false
},
{
"workload": "get.relationships.100",
"metric": "p99_ms",
"median_ratio": 1.004523425271636,
"spread": 1.2128611503685127,
"directional_bias": 1.004523425271636,
"repeatable": false
}
],
"notes": "The legacy client v11 traces failed their predeclared repeatability bounds. They are diagnostic inputs to the new analyzer because per-round cache-state files were not collected."
}
}
Loading
Loading