Skip to content

usage: widen usage_volume unique key to include vm_id - #13909

Open
Alpha162 wants to merge 7 commits into
apache:mainfrom
Alpha162:patch-1
Open

usage: widen usage_volume unique key to include vm_id#13909
Alpha162 wants to merge 7 commits into
apache:mainfrom
Alpha162:patch-1

Conversation

@Alpha162

@Alpha162 Alpha162 commented Aug 18, 2026

Copy link
Copy Markdown

Description

createVolumeHelperEvent() writes two usage_volume rows for a single VOLUME.CREATE when the event carries vm_id, one cumulative and one per-VM, as intended by #11531. Both use event.getCreateDate(), but the unique key is still (volume_id, created), so the second insert can never succeed.

#11531 added vm_id to cloud_usage.usage_volume via IDEMPOTENT_ADD_COLUMN without widening the key. This adds the missing DDL.

Uses the existing cloud_usage.IDEMPOTENT_DROP_INDEX / IDEMPOTENT_ADD_UNIQUE_INDEX
procedures, which are loaded on every upgrade by DatabaseUpgradeChecker.executeProcedureScripts(). Both carry CONTINUE HANDLERs (1091 and 1061), so a re-run is a no-op. This mirrors schema-41600to41610.sql:72, which created this same key with IDEMPOTENT_ADD_UNIQUE_INDEX.

Partially addresses #13399. The remaining two defects from that investigation are tracked in #13905 and #13906.

Scope: this prevents the problem, it does not remediate a deployment that already has it

The DDL runs both on fresh installs and on upgrades from 4.22.1.0, so the key is widened
either way. On a deployment that has already hit this, however, widening the key only stops
further collisions; it does not undo the existing damage. The cloud_usage.usage_event rows
stuck at processed = 0 stay stuck, the duplicate cloud_usage rows remain, and because the
rewind in #13906 is unbounded the aggregation window stays pinned to the oldest of those
events. Operators in that position still need to force the stuck events processed and then
dedupe cloud_usage separately.

Note for anyone doing that: update the flag, never DELETE from cloud_usage.usage_event.
getMostRecentEventId() is ORDER BY id DESC LIMIT 1 over the whole table and returns 0 on
an empty one, which triggers COPY_ALL_EVENTS and re-copies every event with processed = 0.

Note on the widened key and NULLs

vm_id is nullable, and VOLUME.CREATE deliberately writes one row with it NULL (cumulative)
and one with it set (per-VM). MySQL permits multiple NULLs in a UNIQUE index, so
(volume_id, created, vm_id) no longer constrains the cumulative row the way
(volume_id, created) did. That is intentional and unavoidable here; keeping it strict would
require a generated column or NOT NULL DEFAULT 0, both of which change the meaning of the
column and need a data migration. The per-VM rows remain constrained, and duplicate cumulative
rows only arise through the re-aggregation in #13906, which seems the right place to address it.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

How Has This Been Tested?

Not tested end-to-end, and I'd rather say so plainly: I'm an operator rather than a dev, and the affected environment is being rebuilt to 4.22.0.1 today, so it's no longer available to me.

What I can attest to:

  • The narrow UNIQUE KEY id (volume_id, created) was confirmed via SHOW CREATE TABLE on a live 4.22.1.0 install, alongside the vm_id column added by Track volume usage data at a vm granularity as well #11531.
  • Widening a unique key is strictly less restrictive, so no existing row can violate the new constraint; the DDL cannot fail on data.
  • Both helpers are idempotent, so a re-run or resumed upgrade is safe.

One point worth noting: the key is named id but covers (volume_id, created).
That name is not incidental; schema-41600to41610.sql:72 sets it explicitly via
IDEMPOTENT_ADD_UNIQUE_INDEX('cloud_usage.usage_volume', 'id', '(volume_id ASC, created ASC)'),
so any install that has passed through 4.16.1.0 will have it. The drop targets that
same name.

Would appreciate someone running this against a real upgrade before merge.

How did you try to break this feature and the system with this change?

Reasoned through the failure modes rather than executing them, given no test environment:
re-running the migration (safe; the handlers swallow 1061 and 1091), running against a table
whose key name is not id (the drop no-ops and the add creates a second key instead of
replacing the first; unlikely, since schema-41600to41610.sql:72 sets that name explicitly),
and existing data violating the widened key (not possible; it is less restrictive than the key
it replaces).

Branch targeting

Targeted at main per @winterhazel's guidance: a separate backport PR to 4.22 after
this merges, rather than a cherry-pick, to avoid merge-forward issues. Per @abh1sar the
backport lands in 4.22.2.

createVolumeHelperEvent() writes two usage_volume rows for a single VOLUME.CREATE when the event carries vm_id, both using the same created timestamp. 

The unique key was left at (volume_id, created) when vm_id was added in 4.22.1, so the second insert always fails with a duplicate key error.

Uses the existing idempotent helpers per review guidance on apache#13399.

Refs apache#13399
Copy of cloud.IDEMPOTENT_ADD_UNIQUE_KEY with the schema changed, so cloud_usage DDL can go through a cloud_usage procedure like the rest of the usage schema changes.

Refs apache#13399
Copy of cloud.IDEMPOTENT_DROP_UNIQUE_KEY with the schema changed, so cloud_usage DDL can go through a cloud_usage procedure like the rest of the usage schema changes.

Refs apache#13399
Per review on apache#13399, cloud_usage DDL should use cloud_usage-schema procedures rather than reaching across to the cloud schema.

Refs apache#13399
@boring-cyborg

boring-cyborg Bot commented Aug 18, 2026

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache CloudStack community! If you have any issues or are unsure about any anything please check our Contribution Guide (https://github.com/apache/cloudstack/blob/main/CONTRIBUTING.md)
Here are some useful points:

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 3.41%. Comparing base (121d931) to head (79290bd).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@          Coverage Diff           @@
##            main   #13909   +/-   ##
======================================
  Coverage   3.41%    3.41%           
======================================
  Files        487      487           
  Lines      41867    41867           
  Branches    7912     7912           
======================================
  Hits        1429     1429           
  Misses     40238    40238           
  Partials     200      200           
Flag Coverage Δ
uitests 3.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@weizhouapache

Copy link
Copy Markdown
Member

@winterhazel @DaanHoogland
shall we add this to 4.23.0 milestone ?

@winterhazel

Copy link
Copy Markdown
Member

@winterhazel @DaanHoogland shall we add this to 4.23.0 milestone ?

@weizhouapache yes.

This patch also applies to 4.22. However, to avoid having to cherry-pick it from 4.22 and resulting in merge-forward issues, I think it is better to open a separate backport PR after this one is merged.

@Alpha162

Copy link
Copy Markdown
Author

@winterhazel @weizhouapache thank you both, that settles the branch question I raised in the description, a separate backport after merge sounds cleaner than a cherry-pick.

Worth flagging for whenever that happens: 4.22's newest upgrade path is Upgrade42200to42210.java, so there's no 4.22.1 → 4.22.2 schema file for the two CALLs to live in yet.

On the red checks? build failed after 14s on Java setup process failed due to: Could not find satisfied version for SemVer '17', and the Sonar job on Resource not accessible by integration posting a comment, which looks like the standard fork-PR token restriction. Neither appears related to the change.

@weizhouapache

Copy link
Copy Markdown
Member

@blueorangutan package

@blueorangutan

Copy link
Copy Markdown

@weizhouapache a [SL] Jenkins job has been kicked to build packages. It will be bundled with no SystemVM templates. I'll keep you posted as I make progress.

@blueorangutan

Copy link
Copy Markdown

Packaging result [SF]: ✔️ el8 ✔️ el9 ✔️ el10 ✔️ debian ✔️ suse15. SL-JID 18905

@weizhouapache

Copy link
Copy Markdown
Member

@blueorangutan test

@blueorangutan

Copy link
Copy Markdown

@weizhouapache a [SL] Trillian-Jenkins test job (ol8 mgmt + kvm-ol8) has been kicked to run smoke tests

@abh1sar abh1sar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @Alpha162.
One review comment.

cc @DaanHoogland @vladimirpetrov this will need a backport to 4.22.2

Comment thread engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql Outdated
cloud_usage.IDEMPOTENT_ADD_UNIQUE_INDEX is already defined in procedures/cloud.idempotent_add_unique_index.sql (the cloud.* filename notwithstanding) and is loaded on every upgrade by DatabaseUpgradeChecker.executeProcedureScripts(), so a new procedure is unnecessary. Raised by @abh1sar in review.
cloud_usage.IDEMPOTENT_DROP_INDEX is already defined in
procedures/usage.idempotent_drop_index.sql. DROP INDEX <name> ON <table> is equivalent to ALTER TABLE <table> DROP KEY <name>, so the existing procedure covers this case. Raised by @abh1sar in review.
Use the pre-existing cloud_usage procedures instead of the two added in this PR. schema-41600to41610.sql:72 created this same key with IDEMPOTENT_ADD_UNIQUE_INDEX:

  CALL `cloud_usage`.`IDEMPOTENT_ADD_UNIQUE_INDEX`(       'cloud_usage.usage_volume', 'id', '(volume_id ASC, created ASC)');

so widening it the same way keeps the table's schema history consistent.

Note the two procedures take their arguments in opposite orders:
IDEMPOTENT_DROP_INDEX is (index_name, table_name), while
IDEMPOTENT_ADD_UNIQUE_INDEX is (table_name, index_name, definition).

@winterhazel winterhazel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Alpha162 the code looks good. This should already fix the Usage reprocessing. I will do some manual testing tomorrow.

On a related note, the Quota plugin probably needs to be updated to consider that a single volume can have more than two usage records for the same period now; otherwise, it will assign the incorrect monetary value. I will properly investigate this and, if required, open a PR addressing it tomorrow as well.

@blueorangutan

Copy link
Copy Markdown

[SF] Trillian test result (tid-16778)
Environment: kvm-ol8 (x2), zone: Advanced Networking with Mgmt server ol8
Total time taken: 55984 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr13909-t16778-kvm-ol8.zip
Smoke tests completed. 156 look OK, 0 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test Result Time (s) Test File

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants