Feature adoption is not one click. A feature can be discovered, tried, repeated, embedded in a workflow, abandoned, replaced, and eventually removed. A lifecycle view helps a product team distinguish novelty from durable value and retire old behavior without surprising important customers.
GraphJSON stores the analytical facts and runs the SQL. Your application, feature-management system, and release process remain responsible for access, rollout, migration, and removal.
Define the feature contract#
Create one stable record outside the event stream:
feature_key: scheduled_exports
owner: Reporting
eligible entity: account
eligible population: paid accounts with reporting enabled
discovery event: feature_entry_viewed
first-value event: scheduled_export_delivered
repeat-value rule: 3 successful deliveries in 30 days
replacement feature: report_subscriptions
definition version: 2
Do not rename the analytical key when marketing copy changes. A stable
feature_key should survive UI labels, routes, and implementation rewrites.
Model exposure, use, and value separately#
Useful events:
{
"event": "feature_entry_viewed",
"event_id": "evt_01J...",
"account_id": "acct_42",
"user_id": "usr_91",
"feature_key": "scheduled_exports",
"entry_point": "reports_toolbar",
"eligibility_version": "paid-v4",
"release": "web-2026.07.26",
"schema_version": 1
}
{
"event": "scheduled_export_delivered",
"event_id": "evt_01K...",
"account_id": "acct_42",
"user_id": "usr_91",
"feature_key": "scheduled_exports",
"artifact_type": "csv",
"outcome": "succeeded",
"schema_version": 1
}
Opening a menu is discovery. Completing the useful workflow is value. Keep both, and make the adopted outcome a valid product fact even after the feature analysis ends.
Measure the lifecycle stages#
Define mutually understandable stages:
| Stage | Example definition |
|---|---|
| Eligible | Account could use the feature during the window |
| Discovered | Entry point viewed |
| Tried | Workflow started |
| First value | First successful outcome |
| Repeated | Value event repeated within a defined interval |
| Habitual | Repeated use across several periods |
| Dormant | Previously adopted, no recent use |
| Migrated | Replacement feature reached equivalent value |
These stages are calculations, not hidden user-profile state. Record the definition version and evaluation time whenever you publish membership.
Calculate eligible adoption#
The denominator should be eligible entities, not every account ever created:
WITH eligible AS (
SELECT DISTINCT
JSONExtractString(json, 'account_id') AS account_id
FROM account_state_events
WHERE
JSONExtractString(json, 'event') = 'feature_eligibility_effective'
AND JSONExtractString(json, 'feature_key') = 'scheduled_exports'
AND JSONExtractBool(json, 'eligible') = true
),
value AS (
SELECT DISTINCT
JSONExtractString(json, 'account_id') AS account_id
FROM product_events
WHERE
JSONExtractString(json, 'event') = 'scheduled_export_delivered'
AND JSONExtractString(json, 'outcome') = 'succeeded'
AND timestamp >= now() - INTERVAL 30 DAY
)
SELECT
count() AS eligible_accounts,
countIf(value.account_id != '') AS adopted_accounts,
round(
100 * adopted_accounts / nullIf(eligible_accounts, 0),
2
) AS adoption_pct
FROM eligible
LEFT JOIN value USING account_id
If eligibility lives in an authoritative database, publish a versioned eligibility snapshot or derive the denominator there. Do not infer eligibility from who happened to see the entry point.
Distinguish breadth, depth, and durability#
Report together:
- breadth: percentage of eligible accounts reaching value
- depth: value outcomes per adopted account
- frequency: active periods per account
- time to first value
- repeat rate after first value
- durable adoption after 30, 60, or 90 days
- multi-user participation for collaborative features
A widely tried feature with no repeat use has a different problem from a deeply valuable feature that few customers discover.
Build adoption cohorts#
Cohort accounts by first-value week, then measure later value activity:
WITH first_value AS (
SELECT
JSONExtractString(json, 'account_id') AS account_id,
toStartOfWeek(toDateTime(min(timestamp)), 1, 'UTC') AS cohort_week
FROM product_events
WHERE
JSONExtractString(json, 'event') = 'scheduled_export_delivered'
AND JSONExtractString(json, 'outcome') = 'succeeded'
GROUP BY account_id
),
activity AS (
SELECT
JSONExtractString(json, 'account_id') AS account_id,
toStartOfWeek(toDateTime(timestamp), 1, 'UTC') AS active_week
FROM product_events
WHERE
JSONExtractString(json, 'event') = 'scheduled_export_delivered'
AND JSONExtractString(json, 'outcome') = 'succeeded'
GROUP BY account_id, active_week
)
SELECT
cohort_week,
dateDiff('week', cohort_week, active_week) AS week_number,
uniqExact(activity.account_id) AS active_accounts
FROM first_value
INNER JOIN activity USING account_id
WHERE active_week >= cohort_week
GROUP BY cohort_week, week_number
ORDER BY cohort_week, week_number
Wait for cohorts to mature before comparing later weeks.
Detect decline correctly#
Falling event volume can mean:
- fewer eligible accounts
- fewer adopted accounts
- lower frequency among adopters
- seasonality
- tracking or delivery failure
- replacement-feature migration
- genuine abandonment
Decompose the movement before deciding to remove the feature. Use Explain why a metric changed for the full method.
Instrument deprecation communication#
When retirement becomes a product decision, emit:
{
"event": "feature_deprecation_notice_presented",
"event_id": "evt_notice_01J...",
"account_id": "acct_42",
"user_id": "usr_91",
"feature_key": "scheduled_exports",
"deprecation_version": "scheduled-exports-sunset-v1",
"surface": "feature_banner",
"replacement_feature_key": "report_subscriptions",
"schema_version": 1
}
Track:
- eligible affected accounts
- notice reach by channel
- acknowledgment where meaningful
- help or migration action
- replacement first value
- continued legacy use
- support contact
- blocked migration reason
Communication delivery is not comprehension, and replacement discovery is not migration.
Define migration completion#
Write an equivalent-value rule:
replacement first value occurred
AND no legacy value event in the following 30 complete days
For low-frequency workflows, use a longer observation window or an explicit customer confirmation. Do not call a monthly feature migrated after three quiet days.
Create an affected-account table:
SELECT
legacy.account_id,
legacy.last_legacy_at,
replacement.first_replacement_at,
if(
replacement.first_replacement_at > 0
AND legacy.last_legacy_at < replacement.first_replacement_at,
'candidate_migrated',
'action_required'
) AS migration_status
FROM legacy_usage AS legacy
LEFT JOIN replacement_usage AS replacement USING account_id
ORDER BY legacy.last_legacy_at DESC
Use the application or customer-success system for the authoritative migration workflow. The query is an analytical view.
Set removal gates#
A safe removal decision includes:
- no critical internal dependency
- affected accounts identified
- replacement covers the required outcome
- high-value and contractual customers reviewed
- notice period completed
- settled legacy-use window
- support and exception plan
- rollback or restoration plan where practical
- dashboards, alerts, docs, and event catalog updated
An adoption percentage alone is not a removal gate.
Retire the analytics assets#
After removal:
- stop emitting deprecated events deliberately
- keep the event definition in the catalog as retired
- preserve the deprecation decision and migration query
- update dashboards and alerts
- remove dead feature-flag and instrumentation code
- monitor replacement outcomes and support demand
- shorten retention only after reproduction needs are satisfied
Follow Analytics asset change management before renaming or removing shared events and queries.
Lifecycle checklist#
- The feature has a stable analytical key and owner.
- Eligibility, discovery, use, and value are separate concepts.
- Adoption uses an eligible entity denominator.
- Breadth, depth, frequency, and durability are reported together.
- Cohorts are mature before long-term comparisons.
- Decline is decomposed before being labeled abandonment.
- Deprecation notices and replacement outcomes are instrumented.
- Migration requires equivalent value and a settled legacy-free window.
- Customer tier, contracts, support, and internal dependencies are reviewed.
- Product systems remain authoritative for access and migration state.
- Retired events remain documented in the catalog.
- Removal includes post-cutover monitoring and an owner.