From ea8a9ce0c91512e9bbb52be0e8a150abd4e23bcc Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Fri, 15 Aug 2025 14:15:17 -0400 Subject: [PATCH 1/9] Only update a feature's lastUsed time every ten seconds --- .../elasticsearch/license/XPackLicenseState.java | 14 +++++++++++++- .../license/XPackLicenseStateTests.java | 15 +++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java index 3c7b089b4cd63..f8fc080012901 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -33,6 +33,12 @@ */ public class XPackLicenseState { + /** + * For features that are used at high frequency (e.g. per-search or per-document, etc.), we don't want to bother updating + * the 'last used' timestamp more often than some minimum frequency. + */ + private static final long FEATURE_USAGE_MINIMUM_ELAPSED_TIME_MILLIS = TimeValue.timeValueSeconds(10).getMillis(); + /** Messages for each feature which are printed when the license expires. */ static final Map EXPIRATION_MESSAGES; static { @@ -447,7 +453,13 @@ public String statusDescription() { void featureUsed(LicensedFeature feature) { checkExpiry(); - usage.put(new FeatureUsage(feature, null), epochMillisProvider.getAsLong()); + // if at least the minimum elapsed time has passed, then update the most recently used time for this feature + final FeatureUsage feat = new FeatureUsage(feature, null); + final long now = epochMillisProvider.getAsLong(); + final Long mostRecent = usage.get(feat); + if (mostRecent == null || now - mostRecent >= FEATURE_USAGE_MINIMUM_ELAPSED_TIME_MILLIS) { + usage.put(feat, now); + } } void enableUsageTracking(LicensedFeature feature, String contextName) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java index d788a0b5abd37..91c67e2cffa84 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java @@ -239,11 +239,18 @@ public void testLastUsedMomentaryFeature() { assertThat(usage.contextName(), nullValue()); assertThat(lastUsed.get(usage), equalTo(100L)); - currentTime.set(200); + // updates to the last used timestamp only happen if at least ten seconds have passed since the last update (*for this feature*) + currentTime.set(10099); goldFeature.check(licenseState); lastUsed = licenseState.getLastUsed(); assertThat("feature.check updates usage", lastUsed.keySet(), containsInAnyOrder(usage)); - assertThat(lastUsed.get(usage), equalTo(200L)); + assertThat(lastUsed.get(usage), equalTo(100L)); + + currentTime.set(10100); + goldFeature.check(licenseState); + lastUsed = licenseState.getLastUsed(); + assertThat("feature.check updates usage", lastUsed.keySet(), containsInAnyOrder(usage)); + assertThat(lastUsed.get(usage), equalTo(10100L)); } public void testLastUsedMomentaryFeatureWithSameNameDifferentFamily() { @@ -270,7 +277,7 @@ public void testLastUsedMomentaryFeatureWithSameNameDifferentFamily() { ) ); - currentTime.set(200); + currentTime.set(10200); featureFamilyB.check(licenseState); lastUsed = licenseState.getLastUsed(); @@ -283,7 +290,7 @@ public void testLastUsedMomentaryFeatureWithSameNameDifferentFamily() { actualFeatures, containsInAnyOrder( new FeatureInfoWithTimestamp("familyA", "goldFeature", 100L), - new FeatureInfoWithTimestamp("familyB", "goldFeature", 200L) + new FeatureInfoWithTimestamp("familyB", "goldFeature", 10200L) ) ); } From 3657ad91267ddff3a673a68afa633ec005346495 Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Fri, 15 Aug 2025 14:53:04 -0400 Subject: [PATCH 2/9] Update docs/changelog/133004.yaml --- docs/changelog/133004.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/133004.yaml diff --git a/docs/changelog/133004.yaml b/docs/changelog/133004.yaml new file mode 100644 index 0000000000000..71079a1fb0bdc --- /dev/null +++ b/docs/changelog/133004.yaml @@ -0,0 +1,5 @@ +pr: 133004 +summary: Limit the frequency of licensed feature last-used time updates +area: License +type: enhancement +issues: [] From bfdeb0c2bb54ce17629224e96e5fef4c3eddebd9 Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Sun, 17 Aug 2025 09:18:18 -0400 Subject: [PATCH 3/9] Simplify the logic -- only update if it has increased --- .../org/elasticsearch/license/XPackLicenseState.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java index f8fc080012901..ae3d293c2590b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -33,12 +33,6 @@ */ public class XPackLicenseState { - /** - * For features that are used at high frequency (e.g. per-search or per-document, etc.), we don't want to bother updating - * the 'last used' timestamp more often than some minimum frequency. - */ - private static final long FEATURE_USAGE_MINIMUM_ELAPSED_TIME_MILLIS = TimeValue.timeValueSeconds(10).getMillis(); - /** Messages for each feature which are printed when the license expires. */ static final Map EXPIRATION_MESSAGES; static { @@ -453,11 +447,11 @@ public String statusDescription() { void featureUsed(LicensedFeature feature) { checkExpiry(); - // if at least the minimum elapsed time has passed, then update the most recently used time for this feature + // update the most recent usage time, but only if isn't already present or has increased final FeatureUsage feat = new FeatureUsage(feature, null); - final long now = epochMillisProvider.getAsLong(); final Long mostRecent = usage.get(feat); - if (mostRecent == null || now - mostRecent >= FEATURE_USAGE_MINIMUM_ELAPSED_TIME_MILLIS) { + final long now = epochMillisProvider.getAsLong(); + if (mostRecent == null || now > mostRecent) { usage.put(feat, now); } } From 8586fc1a36211abf202b4496a3a42760e20a1223 Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Sun, 17 Aug 2025 09:45:57 -0400 Subject: [PATCH 4/9] Rewrite this comment --- .../main/java/org/elasticsearch/license/XPackLicenseState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java index ae3d293c2590b..c07b1c71bb431 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -447,7 +447,7 @@ public String statusDescription() { void featureUsed(LicensedFeature feature) { checkExpiry(); - // update the most recent usage time, but only if isn't already present or has increased + // update the most recent usage time, but only if it isn't already present or if it has increased final FeatureUsage feat = new FeatureUsage(feature, null); final Long mostRecent = usage.get(feat); final long now = epochMillisProvider.getAsLong(); From 0c95040911653057a3f7d4d7ccfe43d0942b47a2 Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Sun, 17 Aug 2025 09:46:08 -0400 Subject: [PATCH 5/9] Rewrite the tests --- .../license/XPackLicenseStateTests.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java index 91c67e2cffa84..710db0287fa2b 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java @@ -239,18 +239,18 @@ public void testLastUsedMomentaryFeature() { assertThat(usage.contextName(), nullValue()); assertThat(lastUsed.get(usage), equalTo(100L)); - // updates to the last used timestamp only happen if at least ten seconds have passed since the last update (*for this feature*) - currentTime.set(10099); + currentTime.set(200); goldFeature.check(licenseState); lastUsed = licenseState.getLastUsed(); assertThat("feature.check updates usage", lastUsed.keySet(), containsInAnyOrder(usage)); - assertThat(lastUsed.get(usage), equalTo(100L)); + assertThat(lastUsed.get(usage), equalTo(200L)); - currentTime.set(10100); + // updates to the last used timestamp only happen if the time has increased + currentTime.set(199); goldFeature.check(licenseState); lastUsed = licenseState.getLastUsed(); assertThat("feature.check updates usage", lastUsed.keySet(), containsInAnyOrder(usage)); - assertThat(lastUsed.get(usage), equalTo(10100L)); + assertThat(lastUsed.get(usage), equalTo(200L)); } public void testLastUsedMomentaryFeatureWithSameNameDifferentFamily() { @@ -277,7 +277,7 @@ public void testLastUsedMomentaryFeatureWithSameNameDifferentFamily() { ) ); - currentTime.set(10200); + currentTime.set(200); featureFamilyB.check(licenseState); lastUsed = licenseState.getLastUsed(); @@ -290,7 +290,7 @@ public void testLastUsedMomentaryFeatureWithSameNameDifferentFamily() { actualFeatures, containsInAnyOrder( new FeatureInfoWithTimestamp("familyA", "goldFeature", 100L), - new FeatureInfoWithTimestamp("familyB", "goldFeature", 10200L) + new FeatureInfoWithTimestamp("familyB", "goldFeature", 200L) ) ); } From b511c500476650592ae1912eff6e11a041ae2891 Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Sun, 17 Aug 2025 09:46:17 -0400 Subject: [PATCH 6/9] Do the time check before doing the map get --- .../main/java/org/elasticsearch/license/XPackLicenseState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java index c07b1c71bb431..6c73be62d3b55 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -448,9 +448,9 @@ public String statusDescription() { void featureUsed(LicensedFeature feature) { checkExpiry(); // update the most recent usage time, but only if it isn't already present or if it has increased + final long now = epochMillisProvider.getAsLong(); final FeatureUsage feat = new FeatureUsage(feature, null); final Long mostRecent = usage.get(feat); - final long now = epochMillisProvider.getAsLong(); if (mostRecent == null || now > mostRecent) { usage.put(feat, now); } From 27de051530773a3467b12e80da44bb452461be3d Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Mon, 18 Aug 2025 11:02:16 -0400 Subject: [PATCH 7/9] Tweak the comment to explain the reason --- .../main/java/org/elasticsearch/license/XPackLicenseState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java index 6c73be62d3b55..6b9b8266ae8b9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -447,10 +447,10 @@ public String statusDescription() { void featureUsed(LicensedFeature feature) { checkExpiry(); - // update the most recent usage time, but only if it isn't already present or if it has increased final long now = epochMillisProvider.getAsLong(); final FeatureUsage feat = new FeatureUsage(feature, null); final Long mostRecent = usage.get(feat); + // only update if needed, to prevent ConcurrentHashMap lock-contention on writes if (mostRecent == null || now > mostRecent) { usage.put(feat, now); } From 262263b6af538205102ea5ed704b9dd58b1382b2 Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Mon, 18 Aug 2025 11:04:01 -0400 Subject: [PATCH 8/9] Update changelog to match PR title --- docs/changelog/133004.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog/133004.yaml b/docs/changelog/133004.yaml index 71079a1fb0bdc..e3e49fc499a28 100644 --- a/docs/changelog/133004.yaml +++ b/docs/changelog/133004.yaml @@ -1,5 +1,5 @@ pr: 133004 -summary: Limit the frequency of licensed feature last-used time updates +summary: Limit frequency of feature last-used time updates area: License type: enhancement issues: [] From 3502fa1f1e5675d647113f56f665ab2be09898d1 Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Tue, 19 Aug 2025 09:54:32 -0400 Subject: [PATCH 9/9] Update docs/changelog/133004.yaml --- docs/changelog/133004.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog/133004.yaml b/docs/changelog/133004.yaml index e3e49fc499a28..da87ab0803e55 100644 --- a/docs/changelog/133004.yaml +++ b/docs/changelog/133004.yaml @@ -1,5 +1,5 @@ pr: 133004 summary: Limit frequency of feature last-used time updates area: License -type: enhancement +type: bug issues: []