* Redesign of metric labels - secondary queries (lazy|query) now just use parent + relativePath + type
# Query metric/plan label change — comparison
Improves the metric/plan name generated for **secondary** (`_lazy` / `_query`) loads so
they relate to their parent/root query, and unifies separators on `.`.
The secondary lazy name is now **always** `orm.<parent's full name>.<path>.<loadMode>`,
so it always prefixes the real parent metric.
## Root query name
| Root query source | Original | New |
|---|---|---|
| `setLabel("custMain")` | `orm.Customer_custMain` | `orm.Customer.custMain` |
| ProfileLocation `CustomerFinder.byName` | `orm.CustomerFinder.byName` | `orm.CustomerFinder.byName` *(same)* |
| ProfileLocation `DataLoader.loadAll` (Customer query) | `orm.Customer_DataLoader.loadAll` | `orm.Customer.DataLoader.loadAll` |
| Unlabeled, no location | `orm.Customer.findList` | `orm.Customer.findList` *(same)* |
## Secondary lazy (`contacts`) name
Original prefixes the **loaded** type (`Contact`) + the call-site location and uses `__`
between path and load mode. New prefixes the **parent's full name**.
| Root query source | Original lazy name | New lazy name |
|---|---|---|
| `setLabel("custMain")` *(profile location also present)* | `orm.Contact_CustomerFinder.findActive_contacts__lazy` — explicit label **lost** | `orm.Customer.custMain.contacts.lazy` |
| `setLabel("custMain")` *(no profile location)* | `orm.Contact_custMain_contacts__lazy` | `orm.Customer.custMain.contacts.lazy` |
| ProfileLocation `CustomerFinder.byName` | `orm.Contact_CustomerFinder.byName_contacts__lazy` | `orm.CustomerFinder.byName.contacts.lazy` |
| ProfileLocation `DataLoader.loadAll` | `orm.Contact_DataLoader.loadAll_contacts__lazy` | `orm.Customer.DataLoader.loadAll.contacts.lazy` |
| Unlabeled, no location | `orm.Contact.findList` *(own name; no parent path)* | `orm.Contact.findList` *(same)* |
## Problems fixed
- **(A) Secondary load didn't relate to its parent** — original prefixed the *loaded* type
(`Contact`) + the *call-site* location, never the parent query's name. New name literally
starts with the parent's full name.
- Explicit `setLabel` was **silently dropped** for secondary queries when a profile location
existed.
- `__` path/loadMode separator and mixed `_`/`.` replaced by uniform `.`.
## Nested and `.query` secondary loads
Each secondary query name is `<immediate parent's full name>.<immediate path>.<loadMode>`,
so every hop literally prefixes its parent metric. Example with root `setLabel("custMain")`
on `Customer`, chain `Customer -> orders -> details`:
Nested lazy:
```
orm.Customer.custMain
orm.Customer.custMain.orders.lazy
orm.Customer.custMain.orders.lazy.details.lazy
```
Secondary eager `.query` fetch:
```
orm.Customer.custMain
orm.Customer.custMain.orders.query
orm.Customer.custMain.orders.query.details.query
```
The intermediate load mode (`.lazy.` / `.query.`) is retained so each name is an exact
extension of its immediate parent's name.
* Use ProfileLocation as leading metric name without <type> prefix
* docs: Add guide for ebean-query-metrics.md
* docs: Add guide for query plan capture
Caused by: java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
at jdk.internal.util.Preconditions.outOfBounds(Unknown Source)
at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Unknown Source)
at jdk.internal.util.Preconditions.checkIndex(Unknown Source)
at java.util.Objects.checkIndex(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at io.ebeaninternal.server.persist.BatchedPstmt.profile(BatchedPstmt.java:132)
So the desire here is to better support sending metrics to Prometheus style metrics collectors that prefer CUMULATIVE metrics rather than DELTA based metrics.
To do this, expose additional MetaInfoManager.collectMetrics(reset) method.
In supporting this, the MAX value really does need to reset even with CUMULATIVE metrics and act more like a gauge as otherwise it becomes almost useless as the max value over the lifetime. So we need to adjust MAX to always reset and act like a gauge to be useful in this CUMULATIVE metrics reporting mode.
- ebean.version 17.2.0 -> 17.5.0 (minimum for TestEntityBuilder)
- avaje-inject 11.5 -> 12.5
- Add io.avaje:junit:1.8 bundle to test dependencies
- Remove .skipDataSourceCheck(true) from database config example
- Add note about Configuration DI wiring in Step 3
* Deprecate DatabaseConfig and DatabaseFactory, prefer Database.builder()
Deprecate the DatabaseConfig way of creating Database instance.
Migrate to use Database.builder().
* Deprecate DatabaseConfig and DatabaseFactory, migrate test code
* Use @Deprecated(forRemoval = true) on DatabaseConfig and DatabaseFactory
* Tidy up Database javadoc for deprecation
* Change [implicit] Lazy loading to be by TYPE rather than by PATH
We have some graph models where a common type (in the tests it is Label)
is used in many different *paths* of the graph. When we are loading via
*path* then all the loading of the Labels isn't in a single batch / load context
but instead split into different load contexts PER PATH.
This change, means that lazy loading operates by TYPE instead of by PATH.
In the tests, all the Labels are read via a single lazy loading query rather
that one lazy loading query per PATH, and this is more efficient.
* ImmutableBeanCache part 1
* ImmutableBeanCache part 2
* ImmutableBeanCache part 3 - add ImmutableBeanCaches
* ImmutableBeanCache - add propagation of caches to secondary queries
* ImmutableBeanCache - use and improve BeanDescriptor.merge() to positional
Using positional avoids the lookup by property name.
* ImmutableBeanCache - add unit testing with BeanDescriptorMergeTest
* ImmutableBeanCache - add testing for the further lazy loading on mutable beans
Also add doc / guides
* Add ImmutableCacheBuilder with underlying DefaultServerCache implementation
This provides a cache with maxSize, maxIdleSeconds, maxSecondsToLive
* Improve javadoc and doc / guides for ImmutableBeanCache
* Add cache invalidation for ImmutableBeanCache
* Use immutable cache direct hits for unmodifiable assoc-one refs
- add ImmutableBeanCache.getIfPresent() as a non-loading probe
- use immutable cache hits in AssocOneHelp / AssocOneHelpRefInherit
- avoid creating ref beans and merge/copy for unmodifiable cache hits
- keep existing mutable-query and miss/backfill behavior unchanged
* Enhance ProfileStream to support Open Telemetry
* Add ebean-opentelemetry module
* Add the sql query text as span attribute.
Note: This is a breaking API change here but I'm pretty confident
that no one is using the ProfileStream API - hmmm.
* Otel: Use the query label and transaction label in span name if available
* SpiProfileHandler: Change such that if service loaded, doesn't need ProfileConfig
---------
Co-authored-by: Rob Bygrave <robin.bygrave@gmail.com>