75aa5d9b2d
* feat(multiregion): replace user replication * chore(multiregion): optimise replication * maybe it's this * postgres is fun * once more * chore(multiregion): only replicate test user creation during multiregion tests * feat: improved replicate_query logic * fix: minor * fix: starting issue * feat: included user create and delete specs to multiregion * feat: removed console logs * fix: user defaults * fix: multiregion test helper * fix: update scenarios for users * refactor(multiregion): swap replicateQuery concept to asMultiregionOperation (#5301) feat(multiregion): introduced asMultregionOperator, refactor test to user builder classes * chore: renamings * fix: remove comments * feat: remove user replication * refactor: simplified spec usages * chore: comments * chore: branches and favs * chore: more tests * chore: more tests * fix linting * fix tests * feat: dropping replication * refactor: moved project delete to service * fix: comment * feat: updateStreamFactory and updateProjectFacotry * deleteProjectFactory + replicateFactory * deleteWorkspaceFactory * fix: selector * fix: tests * fix tests, finished createStreamFactory * feat: simplify changes * fix: remove comment * fix: minor strucutres * fix: moveProjectToRegion * fix: moved branch creation outside of multiregion scope * fix: branch creation * fix: tests * fix: ci tests * fix: removed log form test * fix: on specs, no random regionKeys * feat: simplify ci for postgres * try: fix health check * feat: fixed tests in ci * try: entrypoint * try: entrypoint * try: entrypoint * try: POSTGRES_INITDB_ARGS * feat: apply POSTGRES_INITDB_ARGS to all server tests * fix: broken test * fix: reinstate max health attempts * fix: after merge * fix: after merge --------- Co-authored-by: Charles Driesler <chuck@speckle.systems>
188 lines
5.8 KiB
TypeScript
188 lines
5.8 KiB
TypeScript
import { getDb } from '@/modules/multiregion/utils/dbSelector'
|
|
import { Scopes } from '@/modules/core/dbSchema'
|
|
import { expect } from 'chai'
|
|
import type { Knex } from 'knex'
|
|
import { isMultiRegionTestMode } from '@/test/speckle-helpers/regions'
|
|
import { db } from '@/db/knex'
|
|
import { sleep } from '@/test/helpers'
|
|
import { asMultiregionalOperation } from '@/modules/shared/command'
|
|
import { logger } from '@/observability/logging'
|
|
|
|
isMultiRegionTestMode()
|
|
? describe('Prepared transaction utils (2PC) @multiregion', async () => {
|
|
let main: Knex
|
|
let region1: Knex
|
|
let region2: Knex
|
|
let ALL_DBS: [Knex, ...Knex[]] = [db]
|
|
|
|
const testOperationFactory =
|
|
({ db }: { db: Knex }) =>
|
|
async (payload: {
|
|
name: string
|
|
description: string
|
|
public: boolean
|
|
}): Promise<void> => {
|
|
await db(Scopes.name).insert(payload)
|
|
}
|
|
|
|
before(async () => {
|
|
main = db
|
|
region1 = await getDb({ regionKey: 'region1' })
|
|
region2 = await getDb({ regionKey: 'region2' })
|
|
ALL_DBS = [main, region1, region2]
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
await db('users').del()
|
|
})
|
|
|
|
it('successfully replicates operation across all specified db instances', async () => {
|
|
const testOperationParams = {
|
|
name: 'test:scope:a',
|
|
description: 'for test purposes only',
|
|
public: false
|
|
}
|
|
|
|
await asMultiregionalOperation(
|
|
({ allDbs }) =>
|
|
Promise.all(
|
|
allDbs.map((db) => testOperationFactory({ db })(testOperationParams))
|
|
),
|
|
{
|
|
dbs: ALL_DBS,
|
|
name: 'testing regional success',
|
|
logger
|
|
}
|
|
)
|
|
|
|
const scopeMain = await main
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
const scopeRegion1 = await region1
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
const scopeRegion2 = await region2
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
|
|
expect(scopeMain).to.deep.eq(testOperationParams)
|
|
expect(scopeMain).to.deep.equal(scopeRegion1)
|
|
expect(scopeMain).to.deep.equal(scopeRegion2)
|
|
})
|
|
|
|
it('rolls back when one node fails on write', async () => {
|
|
// Create scope before replicated query
|
|
const testOperationParams = {
|
|
name: 'test:scope:b',
|
|
description: 'for test purposes only',
|
|
public: false
|
|
}
|
|
|
|
await testOperationFactory({ db: region2 })(testOperationParams)
|
|
|
|
const promise = asMultiregionalOperation(
|
|
({ allDbs }) =>
|
|
Promise.all(
|
|
allDbs.map((db) => testOperationFactory({ db })(testOperationParams))
|
|
),
|
|
{
|
|
dbs: ALL_DBS,
|
|
name: 'testing regional failure',
|
|
logger
|
|
}
|
|
)
|
|
await expect(promise).eventually.to.be.rejected
|
|
|
|
const scopeMain = await main
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
const scopeRegion1 = await region1
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
const scopeRegion2 = await region2
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
|
|
expect(scopeMain).to.be.undefined
|
|
expect(scopeRegion1).to.be.undefined
|
|
expect(scopeRegion2).to.exist
|
|
})
|
|
|
|
it('rolls back all commits in case of one node failure on transaction', async () => {
|
|
const testOperationParams = {
|
|
name: 'test:scope:c',
|
|
description: 'for test purposes only',
|
|
public: false
|
|
}
|
|
|
|
const dbThatFails = {
|
|
transaction: async () => Promise.reject(new Error('Transaction failed'))
|
|
} as unknown as Knex
|
|
|
|
const promise = asMultiregionalOperation(
|
|
({ allDbs }) =>
|
|
Promise.all(
|
|
allDbs.map((db) => testOperationFactory({ db })(testOperationParams))
|
|
),
|
|
{
|
|
dbs: [...ALL_DBS, dbThatFails],
|
|
name: 'testing regional success',
|
|
logger
|
|
}
|
|
)
|
|
|
|
await expect(promise).to.eventually.be.rejected
|
|
|
|
const scopeMain = await main
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
const scopeRegion1 = await region1
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
const scopeRegion2 = await region2
|
|
.table(Scopes.name)
|
|
.where({ name: testOperationParams.name })
|
|
.first()
|
|
|
|
expect(scopeMain).to.be.undefined
|
|
expect(scopeRegion1).to.be.undefined
|
|
expect(scopeRegion2).to.be.undefined
|
|
})
|
|
|
|
it('does not has visibile perfomance issues using 2PC', async () => {
|
|
const connectionsUsedBefore = main.client.pool.numUsed()
|
|
|
|
const oneKnexInstanceCall = async () => {
|
|
const { buildBasicTestUser, createTestUser } = await import(
|
|
'@/test/authHelper'
|
|
)
|
|
|
|
const user = buildBasicTestUser()
|
|
await createTestUser(user) // This uses the asMultireagionOperation helper }
|
|
}
|
|
|
|
const manyParallelCreates = async () => {
|
|
await Promise.allSettled(Array.from({ length: 500 }, oneKnexInstanceCall))
|
|
}
|
|
|
|
await manyParallelCreates()
|
|
|
|
const [{ count }] = await db('users').count()
|
|
expect(count).to.eql('500')
|
|
|
|
await sleep(50)
|
|
|
|
const connectionsUsedAfter = main.client.pool.numUsed()
|
|
expect(connectionsUsedAfter).to.be.lte(connectionsUsedBefore)
|
|
})
|
|
})
|
|
: null
|