Skip to content

Conversation

@cyprain-okeke
Copy link
Contributor

@cyprain-okeke cyprain-okeke commented Nov 14, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-27605

📔 Objective

This PR implements the transition migration for PM-27605: Populate MaxStorageGbIncreased for storage increase from 1GB to 5GB.

This migration populates the MaxStorageGbIncreased column (added in PM-27603) by setting it to MaxStorageGb + 4 for all User and Organization records that have a storage quota defined.

Changes

  • Added: util/Migrator/DbScripts/2025-11-14_00_PopulateMaxStorageGbIncreased.sql
    • Updates all User records where MaxStorageGb IS NOT NULL
    • Updates all Organization records where MaxStorageGb IS NOT NULL
    • Sets MaxStorageGbIncreased = MaxStorageGb + 4 to represent the 4GB increase

Dependencies

  • Requires PM-27603 to be deployed first (creates the MaxStorageGbIncreased column)
  • This migration will fail if the column doesn't exist

Database Impact

According to the ticket, as of October 28th, 2025:

  • ~148,000 User records will be updated
  • ~35,000 Organization records will be updated

📸 Screenshots

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

This migration populates the MaxStorageGbIncreased column for Users and
Organizations by setting it to MaxStorageGb + 4, representing the storage
increase from 1GB to 5GB.

This migration depends on PM-27603 being deployed first to create the
MaxStorageGbIncreased column.

Target release: January 6th, 2026

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@cyprain-okeke cyprain-okeke requested a review from a team as a code owner November 14, 2025 11:23
@claude
Copy link
Contributor

claude bot commented Nov 14, 2025

Claude Code is working…

I'll analyze this and get back to you.

View job run

@@ -0,0 +1,13 @@
-- Populate MaxStorageGbIncreased for Users
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing column existence check

This migration will fail with a cryptic error if the MaxStorageGbIncreased column doesn't exist (PM-27603 not deployed).

Recommendation: Add a check at the start of the migration:

-- Verify MaxStorageGbIncreased column exists before proceeding
IF NOT EXISTS (
    SELECT * FROM sys.columns 
    WHERE object_id = OBJECT_ID('[dbo].[User]') 
    AND name = 'MaxStorageGbIncreased'
)
BEGIN
    RAISERROR('MaxStorageGbIncreased column does not exist in User table. PM-27603 must be deployed first.', 16, 1);
    RETURN;
END;

IF NOT EXISTS (
    SELECT * FROM sys.columns 
    WHERE object_id = OBJECT_ID('[dbo].[Organization]') 
    AND name = 'MaxStorageGbIncreased'
)
BEGIN
    RAISERROR('MaxStorageGbIncreased column does not exist in Organization table. PM-27603 must be deployed first.', 16, 1);
    RETURN;
END;
GO

This provides a clear deployment dependency error rather than "invalid column name" errors.

UPDATE [dbo].[User]
SET [MaxStorageGbIncreased] = [MaxStorageGb] + 4
WHERE [MaxStorageGb] IS NOT NULL;
GO
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 Transaction safety consideration

The GO statement here commits the User table update as a separate transaction from the Organization update. This means:

  • Pro: Reduces lock duration, better for production with 148k User records
  • ⚠️ Con: If Organization update fails, User table will have been updated but Organization table won't (partial completion)

Question: Is this intentional to minimize lock contention, or should both updates be wrapped in a single transaction for atomicity?

If atomicity is needed:

BEGIN TRANSACTION;
-- User update
-- Organization update  
COMMIT TRANSACTION;
GO

Please clarify the intended behavior for this migration.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 14, 2025

Logo
Checkmarx One – Scan Summary & Details99df16c1-7d7d-4d9b-ace8-3148f899eae7

New Issues (3)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
MEDIUM CSRF /src/Api/Auth/Controllers/AccountsController.cs: 427
detailsMethod at line 427 of /src/Api/Auth/Controllers/AccountsController.cs gets a parameter from a user request from model. This parameter value flow...
ID: VU8sgdorwW4N9vJXTUl%2BCpjmMcY%3D
Attack Vector
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 1534
detailsMethod at line 1534 of /src/Api/Vault/Controllers/CiphersController.cs gets a parameter from a user request from id. This parameter value flows ...
ID: epGI%2BCkZ7DdCYvC5nrKcJrkPGsQ%3D
Attack Vector
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 1410
detailsMethod at line 1410 of /src/Api/Vault/Controllers/CiphersController.cs gets a parameter from a user request from id. This parameter value flows ...
ID: JfZKzrs1wXpqwvFqHp5f%2BGliPoo%3D
Attack Vector
Fixed Issues (1)

Great job! The following issues were fixed in this Pull Request

Severity Issue Source File / Package
MEDIUM CSRF /src/Api/Vault/Controllers/CiphersController.cs: 300

@codecov
Copy link

codecov bot commented Nov 14, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.83%. Comparing base (c0700a6) to head (79a824e).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6579   +/-   ##
=======================================
  Coverage   56.83%   56.83%           
=======================================
  Files        1909     1909           
  Lines       84810    84810           
  Branches     7633     7633           
=======================================
  Hits        48199    48199           
  Misses      34785    34785           
  Partials     1826     1826           

☔ View full report in Codecov by Sentry.
📢 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.

@amorask-bitwarden amorask-bitwarden added the hold Hold this PR or item until later; DO NOT MERGE label Nov 14, 2025
@amorask-bitwarden amorask-bitwarden self-requested a review November 14, 2025 15:04
Copy link
Contributor

@amorask-bitwarden amorask-bitwarden left a comment

Choose a reason for hiding this comment

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

Change looks fine to me - I'm going to lean on @bitwarden/dept-dbops for the PopulateMaxStorageGbIncreased.sql file. Also, looks like you have some CI failures.

Copy link
Contributor

@mkincaid-bw mkincaid-bw left a comment

Choose a reason for hiding this comment

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

How is the logic going to correlate with the actual enablement of the additional 4GB? From what I can tell, once this PR deploys, then any new Users/Orgs that get created, or Users/Orgs that get updated, will have the MaxStorageGbIncreased set to the same as MaxStorageGb. Is that logic correct? It seems like if we deploy this PR but don't enable the actual increase until later, then new Users/Orgs will get set to the old default, and then won't get updated since the migration will have already run. Am I missing something?


WHILE @RowsAffected > 0
BEGIN
UPDATE TOP (@BatchSize) [dbo].[User]
Copy link
Contributor

Choose a reason for hiding this comment

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

This migration is going to take quite a while. There's no index on the MaxStorageGb column so the script as-is will force hundreds (or thousands) of table scans - one for each update statement in the batch.

I tested this alternative against a backup (creating an index first) and it ran in 3-4 minutes:

IF NOT EXISTS (
    SELECT 1
    FROM sys.indexes 
    WHERE object_id = OBJECT_ID('dbo.User') 
    AND name = 'IX_TEMP_User_MaxStorageGb_MaxStorageGbIncreased'
)
BEGIN
    CREATE INDEX IX_TEMP_User_MaxStorageGb_MaxStorageGbIncreased
    ON [User]([MaxStorageGb], [MaxStorageGbIncreased]);
END
GO

DECLARE @BatchSize INT = 5000;
DECLARE @RowsAffected INT = 1;
DECLARE @TotalUpdated INT = 0;

PRINT 'Starting User table update...';

WHILE @RowsAffected > 0
BEGIN
    UPDATE TOP (@BatchSize) [dbo].[User]
    SET [MaxStorageGbIncreased] = [MaxStorageGb] + 4
    WHERE [MaxStorageGb] IS NOT NULL
      AND [MaxStorageGbIncreased] IS NULL; -- Only update rows not yet processed

    SET @RowsAffected = @@ROWCOUNT;
    SET @TotalUpdated = @TotalUpdated + @RowsAffected;

    PRINT 'Users updated: ' + CAST(@TotalUpdated AS VARCHAR(10));

    WAITFOR DELAY '00:00:00.100'; -- 100ms delay to reduce contention
END

PRINT 'User table update complete. Total rows updated: ' + CAST(@TotalUpdated AS VARCHAR(10));
GO

DROP INDEX IF EXISTS [dbo].[User].[IX_TEMP_User_MaxStorageGb_MaxStorageGbIncreased];
GO

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The logic works correctly when both changes deploy together or in the correct sequence:

  1. This migration script (2025-11-14_00_PopulateMaxStorageGbIncreased.sql):
    - Runs once during deployment
    - Updates existing records: MaxStorageGbIncreased = MaxStorageGb + 4
    - Migrates old 1GB quota → new 5GB quota
  2. Stored procedures (e.g., User_Create, Organization_Create,User_Update,Organization_Update):
    - For new records created after this PR
    - Set MaxStorageGbIncreased = @MaxStorageGb (same value, not +4)
    - The @MaxStorageGb parameter comes from the API/Pricing Service
  3. Pricing Service (separate deployment):
    - Updates plan definitions: 1GB → 5GB
    - API calls stored procedures with @MaxStorageGb = 5

Copy link
Contributor

@mkincaid-bw mkincaid-bw left a comment

Choose a reason for hiding this comment

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

LGTM

@amorask-bitwarden
Copy link
Contributor

@cyprain-okeke This looks good - however, it has to be merged for a specific release so leave the hold label on the PR. I'm going to change the subtask to a task and move it to On Hold with a note about the release and move the parent ticket to done.

@amorask-bitwarden amorask-bitwarden changed the title [PM-27605]Populate MaxStorageGbIncreased for storage increase from 1GB to 5GB. [DNM] [PM-27605] Populate MaxStorageGbIncreased for storage increase from 1GB to 5GB. Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hold Hold this PR or item until later; DO NOT MERGE

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants