Files
42ec4f12a6 [release-11.7] Tighten thread membership cleanup on team membership changes (#37079)
* Tighten thread membership cleanup on team membership changes (#36764)

* Tighten thread membership cleanup on team membership changes

Ensure ThreadMembership rows are cleaned up when a user is removed
from or leaves a team, add a defense-in-depth filter on the thread
retrieval path so memberships referencing channels the user is no
longer a member of are excluded, and add a one-time migration to
clean up stale records on existing deployments.

https://mattermost.atlassian.net/browse/MM-69008

* Centralize per-channel membership removal and tighten tests

Extract the combined channel-member and thread-membership removal into
a shared helper used by both removeUserFromChannel and LeaveTeam, so
future code paths cannot revoke channel access without dropping the
dependent thread state. The channel-leave event is now logged after
the combined removal completes.

Also drop verbose test header comments and rename a test to a
behavior-focused name.

* Backfill channel members in thread storetest setups

The new ChannelMembers predicate on thread read queries filters out
ThreadMembership rows whose user has no ChannelMembers row for the
thread's channel. Several existing storetest setups bypassed the
normal write path and inserted threads/memberships without channel
members. Add the missing channel-member rows so the test data matches
the real-world invariant.

* Retrigger enterprise CI

Pick up enterprise merge e6953d4 (master into
MM-69008-thread-membership-team-leave) in the combined Enterprise
CI/tests lane, which pins the enterprise SHA at mattermost-side
dispatch time.

Co-authored-by: Maria A Nunez <[email protected]>

---------

Co-authored-by: Mattermost Build <[email protected]>

* Retrigger CI

---------

Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-19 10:50:37 -04:00
..
2026-04-01 13:03:36 +00:00

Database Migration Rules

Enforce these rules to prevent table locks and performance degradation on large production databases (100M+ posts).

General Rules

  • Each migration should do one thing. Don't combine unrelated schema changes.
  • Prefer additive changes (new columns, new tables) over destructive ones.
  • Schema migrations must ALWAYS be backwards compatible until the last ESR.
  • Test migrations against a database with realistic data volume, not just a dev-sized dataset. An EXPLAIN ANALYZE that looks fine on 12M posts may behave very differently at 100M posts.
  • If a shipped migration is broken, do not modify it. Add a new migration to correct.

File Naming and Structure

Migration files follow the convention {sequence_number}_{description}.{up|down}.sql and live in db/migrations/{driver_name}/. After creating migration files, run make migrations-extract to update db/migrations/migrations.list. Merge upstream before submitting a PR to avoid sequence number collisions.

Table Locking

Never write a migration that locks an entire table. Common operations that acquire table-level locks:

  • ALTER COLUMN (type change) — rewrites the table
  • CREATE INDEX without CONCURRENTLY
  • ADD FOREIGN KEY without NOT VALID
  • LOCK TABLE

Column Type Changes

As noted above, ALTER COLUMN (type change) rewrites the entire table and blocks concurrent DML: strongly avoid. A real-world example took 8+ hours on a large customer database. When unavoidable, use a multi-release phased approach:

  1. Release N: add the new column.
  2. Release N+1: backfill data via background jobs; use triggers for ongoing updates.
  3. Release N+2 (ESR): start using the new column in code.
  4. Release N+3 (ESR): drop the old column.

Batch Updates

Never do a full-table UPDATE in a migration. Process data in batches in a job at runtime:

UPDATE foo SET col = value
WHERE id IN (
  SELECT id FROM foo WHERE id > :offset ORDER BY id ASC LIMIT 100
);

Store the offset and resume across job runs.

Adding Unique Constraints

Do not add a unique constraint directly. Create the index concurrently first, then attach it:

CREATE UNIQUE INDEX CONCURRENTLY constraint_name ON foo (bar);
ALTER TABLE foo ADD UNIQUE USING INDEX constraint_name;

Note that this cannot be done inside a transaction block, so it must be in a separate migration file. Use the -- morph:nontransactional comment to disable transactions for that migration.

Foreign Key Constraints

Avoid foreign key constraints. Adding a foreign key takes a SHARE ROW EXCLUSIVE lock, limiting concurrent activity to SELECT queries only. If truly needed, consider adding the constraint with NOT VALID to avoid the full-table scan under lock.