Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a7e3d06
postgres migration
battermann Jan 15, 2026
18a396c
wip: postgres interpreter
battermann Jan 15, 2026
c1f5ce3
format
battermann Jan 16, 2026
d72c69d
added PostgresMarshall tests
battermann Jan 16, 2026
03710b6
more marshall tests and impl
battermann Jan 16, 2026
c11d587
implement pending todos
battermann Jan 16, 2026
399f5e9
fix lookup
battermann Jan 16, 2026
5d0f798
wiring for postgresql tests
battermann Jan 16, 2026
2f01460
dual write interpreter for migration phase
battermann Jan 16, 2026
9e5156f
migration implementation for conversation codes
battermann Jan 16, 2026
fd5b2e2
background worker wiring
battermann Jan 19, 2026
5f26cfb
rename
battermann Jan 19, 2026
f561671
changelog
battermann Jan 19, 2026
9af7712
hlint
battermann Jan 19, 2026
fbd7400
restructure migration test modules
battermann Jan 19, 2026
aa471b3
refactoring of tests, and new module for conv codes
battermann Jan 19, 2026
913c3dc
wip
battermann Jan 19, 2026
1bf7079
migration tests for conversation codes
battermann Jan 19, 2026
6665114
extended integration test
battermann Jan 20, 2026
e708fb1
test code expiry
battermann Jan 20, 2026
89019d4
updated docs
battermann Jan 20, 2026
9e95ec4
wip: extract common migration patterns
battermann Jan 20, 2026
b9ce6c8
extract migration loop
battermann Jan 20, 2026
a20919d
refactor bg worker code
battermann Jan 20, 2026
b88c288
simplify helm template code
battermann Jan 20, 2026
68a79cb
fixed bg worker config map
battermann Jan 20, 2026
0e3a980
removed focus
battermann Jan 20, 2026
09e7fda
regenerated psql schema
battermann Jan 20, 2026
aa1d558
corrected copyright comments
battermann Jan 20, 2026
3d461eb
reworked the migration docs
battermann Jan 20, 2026
90892da
release notes changelog
battermann Jan 20, 2026
545b5f1
clean up
battermann Jan 21, 2026
b2abafa
moved PostgresMarshall instances for Password
battermann Jan 21, 2026
a941b2c
made comment a haddock comment
battermann Jan 21, 2026
cbae89b
removed Scope
battermann Jan 21, 2026
4f14d6f
change page size to 1k
battermann Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog.d/0-release-notes/WPB-22811
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Conversation codes can now be migrated to PostgreSQL. For existing installations:
- Set `postgresMigration.conversationCodes: migration-to-postgresql` in both `galley` and `background-worker`.
- Run the backfill with `migrateConversationCodes: true`.
- Wait for `wire_conv_codes_migration_finished` to reach `1.0`.
- Switch to `postgresMigration.conversationCodes: postgresql` and disable `migrateConversationCodes`.
2 changes: 1 addition & 1 deletion changelog.d/5-internal/WPB-22811
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Moved CodeStore from galley to subsystems
Migration of conversation codes from cassandra to postgres (#4959, #4961)
1 change: 1 addition & 0 deletions charts/background-worker/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ data:
{{- end }}

migrateConversations: {{ .migrateConversations }}
migrateConversationCodes: {{ .migrateConversationCodes }}
migrateConversationsOptions:
{{toYaml .migrateConversationsOptions | indent 6 }}

Expand Down
5 changes: 5 additions & 0 deletions charts/background-worker/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ config:
migrateConversationsOptions:
pageSize: 10000
parallelism: 2
# This will start the migration of conversation codes.
# It's important to set `settings.postgresMigration.conversationCodes` to `migration-to-postgresql`
# before starting the migration.
migrateConversationCodes: false

backendNotificationPusher:
pushBackoffMinWait: 10000 # in microseconds, so 10ms
Expand All @@ -87,6 +91,7 @@ config:
# Controls where conversation data is stored/accessed
postgresMigration:
conversation: cassandra
conversationCodes: cassandra

secrets:
{}
Expand Down
1 change: 1 addition & 0 deletions charts/galley/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ config:

postgresMigration:
conversation: cassandra
conversationCodes: cassandra
settings:
httpPoolSize: 128
maxTeamSize: 10000
Expand Down
61 changes: 38 additions & 23 deletions docs/src/developer/reference/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1811,79 +1811,94 @@ galley:
config:
postgresMigration:
conversation: postgresql
conversationCodes: postgresql
background-worker:
config:
postgresMigration:
conversation: postgresql
conversationCodes: postgresql
migrateConversations: false
```

#### Migration for existing installations

Existing installations should migrate the conversation data to PostgreSQL from
Existing installations should migrate conversation data to PostgreSQL from
Cassandra. This is necessary for channel search and management of channels from
the team-management UI. It is highly recommended to take a backup of the Galley
Cassandra before triggering the migration.

The migration needs to happen in 3 steps:
Migrations are independent and can be run separately, in batches, or all at
once. This is expected, because migrations will be released over time. The
pattern below applies per store. Use it for `conversation` and
`conversationCodes` now, and for future stores as they are added.

1. Prepare wire-server for migration.
**Migration pattern per store(s)**

This step make sure that wire-server keep working as expected during the
migration. To do this deploy wire-server with this config change:

Configure both `galley` and `background-worker` so that newly created
conversations are written to PostgreSQL while existing data still reads from
Cassandra:
1. Prepare the selected store(s) for migration by setting
`postgresMigration.<store>` to `migration-to-postgresql`. This enables the
migration interpreter for that store, which ensures data is written to
PostgreSQL (store-specific details are handled internally).
The configuration must be consistent across `galley` and
`background-worker`.

```yaml
galley:
config:
postgresMigration:
conversation: migration-to-postgresql
conversationCodes: migration-to-postgresql
background-worker:
config:
postgresMigration:
conversation: migration-to-postgresql
conversationCodes: migration-to-postgresql
migrateConversations: false
migrateConversationCodes: false
```

This change should restart all the galley pods, any new conversations will
now be written to PostgreSQL.

2. Trigger the migration and wait.
This change should restart all the galley pods, and new writes will follow
the migration interpreter.

This step will actually carry out the migration. To do this deploy
wire-server with this config change:
2. Run the backfill for the selected store(s) via background-worker.

```yaml
background-worker:
config:
migrateConversations: true
migrateConversationCodes: true
```

This change should restart the background-worker pods. It is recommended to
watch the logs and wait for both of these two metrics to report `1.0`:
`wire_local_convs_migration_finished` and `wire_user_remote_convs_migration_finished`.
This can take a long time depending on number of conversations in the DB.

3. Configure wire-server to only use PostgreSQL for conversations.
Wait for the store-specific migration metrics to reach `1.0`. For
conversations: `wire_local_convs_migration_finished` and
`wire_user_remote_convs_migration_finished`. For conversation codes:
`wire_conv_codes_migration_finished`.

This will be the configuration which must be used from now on for every new
release.
3. Cut over reads and writes to PostgreSQL for the selected store(s). This
configuration must be used from now on for every new release.

```yaml
galley:
config:
postgresMigration:
conversation: postgresql
conversationCodes: postgresql
background-worker:
config:
postgresMigration:
conversation: postgresql
conversationCodes: postgresql
migrateConversations: false
migrateConversationCodes: false
```

**How to run migrations independently or in batches**

- To migrate a single store, set only that store’s `postgresMigration.<store>`
and `migrate<Store>` flags; leave others unchanged.
- To migrate a batch, set multiple stores to `migration-to-postgresql` and
enable only the matching `migrate<Store>` flags together.
- To reduce load, run large stores alone and group small stores together.

## Configure Cells

If Cells integration is enabled, gundeck must be configured with the name of
Expand Down
8 changes: 3 additions & 5 deletions hack/helm_vars/common.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ dynBackendDomain1: dynamic-backend-1.{{ requiredEnv "NAMESPACE_1" }}.svc.cluster
dynBackendDomain2: dynamic-backend-2.{{ requiredEnv "NAMESPACE_1" }}.svc.cluster.local
dynBackendDomain3: dynamic-backend-3.{{ requiredEnv "NAMESPACE_1" }}.svc.cluster.local

{{- if (eq (env "PREFERRED_STORE") "") }}
conversationStore: cassandra
{{- else }}
conversationStore: {{ env "PREFERRED_STORE" }}
{{- end }}
{{- $preferredStore := default "cassandra" (env "PREFERRED_STORE") }}
conversationStore: {{ $preferredStore }}
conversationCodesStore: {{ $preferredStore }}

{{- if (eq (env "UPLOAD_XML_S3_BASE_URL") "") }}
uploadXml: {}
Expand Down
2 changes: 2 additions & 0 deletions hack/helm_vars/wire-server/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ galley:
enableFederation: true # keep in sync with brig.config.enableFederation, cargohold.config.enableFederation and tags.federator!
postgresMigration:
conversation: {{ .Values.conversationStore }}
conversationCodes: {{ .Values.conversationCodesStore }}
settings:
maxConvAndTeamSize: 16
maxTeamSize: 32
Expand Down Expand Up @@ -673,6 +674,7 @@ background-worker:
federationDomain: integration.example.com
postgresMigration:
conversation: {{ .Values.conversationStore }}
conversationCodes: {{ .Values.conversationCodesStore }}
rabbitmq:
port: 5671
adminPort: 15671
Expand Down
4 changes: 3 additions & 1 deletion integration/integration.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ library
Test.Client
Test.Connection
Test.Conversation
Test.Conversation.Migration
Test.Demo
Test.DNSMock
Test.DomainVerification
Expand Down Expand Up @@ -174,6 +173,9 @@ library
Test.LegalHold
Test.Login
Test.MessageTimer
Test.Migration.Conversation
Test.Migration.ConversationCodes
Test.Migration.Util
Test.MLS
Test.MLS.Clients
Test.MLS.History
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,22 @@
-- The tests are from the perspective of mel, a user on the dynamic backend,
-- called backendM (migrating backend). There are also users called mark and mia
-- on this backend.
module Test.Conversation.Migration where
module Test.Migration.Conversation where

import API.Galley
import Control.Applicative
import Control.Concurrent (threadDelay)
import Control.Monad.Codensity
import Control.Monad.Reader
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import qualified Data.IntSet as IntSet
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import GHC.Stack
import MLS.Util
import Notifications
import SetupHelpers hiding (deleteUser)
import Test.Migration.Util
import Testlib.Prelude
import Testlib.ResourcePool
import Text.Regex.TDFA ((=~))
import UnliftIO

-- | Our test setup cannot process updates to many MLS convs concurrently, so we
Expand Down Expand Up @@ -84,7 +80,9 @@ testMigrationToPostgresMLS = do

actualConvs `shouldMatchSet` ((convIdToQidObject <$> expectedConvs) <> otherMelConvs)

when (phase == 3) $ waitForMigration domainM
when (phase == 3) $ do
waitForMigration domainM convMigrationFinishedCounterName
waitForMigration domainM userMigrationFinishedCounterName
runPhase 1
runPhase 2
runPhase 3
Expand Down Expand Up @@ -191,7 +189,9 @@ testMigrationToPostgresProteus = do

actualConvs `shouldMatchSet` ((convIdToQidObject <$> expectedConvs) <> otherMelConvs)

when (phase == 3) $ waitForMigration domainM
when (phase == 3) $ do
waitForMigration domainM convMigrationFinishedCounterName
waitForMigration domainM userMigrationFinishedCounterName
runPhase 1
runPhase 2
runPhase 3
Expand Down Expand Up @@ -292,17 +292,11 @@ instance Semigroup TestConvList where
addMelConvs = IntMap.unionWith (<>) l1.addMelConvs l2.addMelConvs
}

waitForMigration :: (HasCallStack) => String -> App ()
waitForMigration domainM = do
metrics <-
getMetrics domainM BackgroundWorker `bindResponse` \resp -> do
resp.status `shouldMatchInt` 200
pure $ Text.decodeUtf8 resp.body
let (_, _, _, convFinishedMatches) :: (Text, Text, Text, [Text]) = (metrics =~ Text.pack "^wire_local_convs_migration_finished\\ ([0-9]+\\.[0-9]+)$")
let (_, _, _, userFinishedMatches) :: (Text, Text, Text, [Text]) = (metrics =~ Text.pack "^wire_user_remote_convs_migration_finished\\ ([0-9]+\\.[0-9]+)$")
when (convFinishedMatches /= [Text.pack "1.0"] || userFinishedMatches /= [Text.pack "1.0"]) $ do
liftIO $ threadDelay 100_000
waitForMigration domainM
convMigrationFinishedCounterName :: String
convMigrationFinishedCounterName = "^wire_local_convs_migration_finished"

userMigrationFinishedCounterName :: String
userMigrationFinishedCounterName = "^wire_user_remote_convs_migration_finished"

phase1Overrides, phase2Overrides, phase3Overrides, phase4Overrides, phase5Overrides :: ServiceOverrides
phase1Overrides =
Expand Down
Loading