mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
logical rep setup
This commit is contained in:
@@ -17,6 +17,7 @@ services:
|
||||
depends_on:
|
||||
- db
|
||||
- notification-db
|
||||
- logical-db
|
||||
- redis
|
||||
- redis-sys
|
||||
environment:
|
||||
@@ -26,6 +27,7 @@ services:
|
||||
DARKPIT_SHADOW_URL: 'postgresql://modelshare:postgres@db:5432/postgres'
|
||||
NOTIFICATION_DB_URL: 'postgresql://postgres:postgres@notification-db:5432/postgres'
|
||||
NOTIFICATION_DB_REPLICA_URL: 'postgresql://postgres:postgres@notification-db:5432/postgres'
|
||||
LOGICAL_REPLICA_DB_URL: 'postgresql://postgres:postgres@logical-db:5432/postgres'
|
||||
REDIS_URL: 'redis://:redis@redis:6379'
|
||||
REDIS_SYS_URL: 'redis://:redis@redis-sys:6379'
|
||||
CLICKHOUSE_HOST: 'http://clickhouse:8123'
|
||||
|
||||
@@ -13,6 +13,8 @@ DATABASE_REPLICA_URL=postgresql://postgres:postgres@localhost:15432/civitai
|
||||
NOTIFICATION_DB_URL=postgresql://postgres:postgres@localhost:15434/postgres
|
||||
NOTIFICATION_DB_REPLICA_URL=postgresql://postgres:postgres@localhost:15434/postgres
|
||||
|
||||
LOGICAL_REPLICA_DB_URL=postgresql://postgres:postgres@localhost:15435/postgres
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://:redis@localhost:6379
|
||||
REDIS_SYS_URL=redis://:redis@localhost:6378
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM postgres:15-bookworm
|
||||
COPY ./docker-init/*.sql /docker-entrypoint-initdb.d/
|
||||
RUN chmod a+r /docker-entrypoint-initdb.d/*
|
||||
@@ -0,0 +1,3 @@
|
||||
CREATE TABLE public."Image" (
|
||||
id integer NOT NULL
|
||||
);
|
||||
@@ -35,6 +35,18 @@ services:
|
||||
volumes:
|
||||
- notification-db:/var/lib/postgresql/data
|
||||
|
||||
logical-db:
|
||||
build:
|
||||
context: containers/logical-db
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "15435:5432"
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
volumes:
|
||||
- logical-db:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7-bookworm
|
||||
command: redis-server --requirepass "redis"
|
||||
@@ -127,6 +139,7 @@ volumes:
|
||||
prisma-pit:
|
||||
db:
|
||||
notification-db:
|
||||
logical-db:
|
||||
redis:
|
||||
redis-sys:
|
||||
minio:
|
||||
|
||||
@@ -23,5 +23,5 @@ JOIN "ModelVersion" mv ON mv.id = irn."modelVersionId"
|
||||
JOIN "RestrictedBaseModels" rbm ON rbm."baseModel" = mv."baseModel"
|
||||
WITH DATA;
|
||||
|
||||
CREATE UNIQUE INDEX CONCURRENTLY idx_restricted_images_by_base_model_imageid
|
||||
CREATE UNIQUE INDEX idx_restricted_images_by_base_model_imageid
|
||||
ON "RestrictedImagesByBaseModel" ("imageId");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import dayjs from 'dayjs';
|
||||
import { capitalize, pull, range, without } from 'lodash-es';
|
||||
import type { DatabaseError } from 'pg';
|
||||
import format from 'pg-format';
|
||||
// import type { DatabaseError } from 'pg-protocol/src/messages';
|
||||
import { clickhouse } from '~/server/clickhouse/client';
|
||||
@@ -12,6 +13,7 @@ import { pgDbWrite } from '~/server/db/pgDb';
|
||||
import { notificationProcessors } from '~/server/notifications/utils.notifications';
|
||||
import { REDIS_SYS_KEYS, sysRedis } from '~/server/redis/client';
|
||||
import { getChatHash, getUsersFromHash } from '~/server/utils/chat';
|
||||
import { baseModels } from '~/shared/constants/base-model.constants';
|
||||
import { IMAGE_MIME_TYPE, VIDEO_MIME_TYPE } from '~/shared/constants/mime-types';
|
||||
import {
|
||||
ArticleEngagementType,
|
||||
@@ -51,8 +53,6 @@ import {
|
||||
insertRows,
|
||||
randPrependBad,
|
||||
} from './utils';
|
||||
import type { DatabaseError } from 'pg';
|
||||
import { baseModels } from '~/shared/constants/base-model.constants';
|
||||
// import { fetchBlob } from '~/utils/file-utils';
|
||||
|
||||
// Usage: npx tsx ./scripts/local-dev/gen_seed.ts --rows=1000
|
||||
@@ -76,6 +76,8 @@ const fbool = faker.datatype.boolean;
|
||||
|
||||
// TODO fix tables ownership from doadmin to civitai
|
||||
|
||||
// TODO seed logicalDb
|
||||
|
||||
const setSerialNotif = async (table: string) => {
|
||||
// language=text
|
||||
const query = `SELECT setval(pg_get_serial_sequence('"${table}"', 'id'), coalesce(max(id)+1, 1), false) FROM %I`;
|
||||
@@ -439,7 +441,7 @@ const genUsers = (num: number, includeCiv = false) => {
|
||||
for (let step = extraUsers.length + (includeCiv ? 0 : 1); step <= num; step++) {
|
||||
const created = faker.date.past({ years: 3 }).toISOString();
|
||||
const isMuted = fbool(0.01);
|
||||
let username = randPrependBad(faker.internet.userName(), '.');
|
||||
let username = randPrependBad(faker.internet.username(), '.');
|
||||
if (seenUserNames.includes(username)) username = `${username}_${faker.number.int(1_000)}`;
|
||||
seenUserNames.push(username);
|
||||
|
||||
|
||||
Vendored
+1
@@ -19,6 +19,7 @@ export const serverSchema = z.object({
|
||||
DATABASE_SSL: zc.booleanString.default(true),
|
||||
NOTIFICATION_DB_URL: z.url(),
|
||||
NOTIFICATION_DB_REPLICA_URL: z.url(),
|
||||
LOGICAL_REPLICA_DB_URL: z.url(),
|
||||
DATABASE_CONNECTION_TIMEOUT: z.coerce.number().default(0),
|
||||
DATABASE_POOL_MAX: z.coerce.number().default(20),
|
||||
DATABASE_POOL_IDLE_TIMEOUT: z.coerce.number().default(30000),
|
||||
|
||||
@@ -12,6 +12,10 @@ import { dbRead, dbWrite } from '~/server/db/client';
|
||||
import { reportAcceptedReward } from '~/server/rewards';
|
||||
import type { GetByIdInput } from '~/server/schema/base.schema';
|
||||
import { getUserCollectionPermissionsById } from '~/server/services/collection.service';
|
||||
import {
|
||||
isImageInQueue,
|
||||
updatePendingImageRatings,
|
||||
} from '~/server/services/games/new-order.service';
|
||||
import {
|
||||
addBlockedImage,
|
||||
bulkRemoveBlockedImages,
|
||||
@@ -63,10 +67,6 @@ import {
|
||||
getTagNamesForImages,
|
||||
moderateImages,
|
||||
} from './../services/image.service';
|
||||
import {
|
||||
isImageInQueue,
|
||||
updatePendingImageRatings,
|
||||
} from '~/server/services/games/new-order.service';
|
||||
|
||||
export const moderateImageHandler = async ({
|
||||
input,
|
||||
@@ -257,6 +257,7 @@ export const getInfiniteImagesHandler = async ({
|
||||
useCombinedNsfwLevel: !features.canViewNsfw,
|
||||
headers: { src: 'getInfiniteImagesHandler' },
|
||||
include: [...input.include, 'tagIds'],
|
||||
useLogicalReplica: features.logicalReplica,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof TRPCError) throw error;
|
||||
@@ -317,6 +318,7 @@ export const getImagesAsPostsInfiniteHandler = async ({
|
||||
user,
|
||||
headers: { src: 'getImagesAsPostsInfiniteHandler' },
|
||||
include: [...input.include, 'tagIds', 'profilePictures'],
|
||||
useLogicalReplica: features.logicalReplica,
|
||||
});
|
||||
|
||||
for (const image of pinnedPostsImages) {
|
||||
@@ -338,6 +340,7 @@ export const getImagesAsPostsInfiniteHandler = async ({
|
||||
user,
|
||||
headers: { src: 'getImagesAsPostsInfiniteHandler' },
|
||||
include: [...input.include, 'tagIds', 'profilePictures'],
|
||||
useLogicalReplica: features.logicalReplica,
|
||||
});
|
||||
|
||||
// Merge images by postId
|
||||
|
||||
@@ -24,13 +24,15 @@ type ClientInstanceType =
|
||||
| 'primaryRead'
|
||||
| 'primaryReadLong'
|
||||
| 'notification'
|
||||
| 'notificationRead';
|
||||
| 'notificationRead'
|
||||
| 'logicalReplica';
|
||||
const instanceUrlMap: Record<ClientInstanceType, string> = {
|
||||
notification: env.NOTIFICATION_DB_URL,
|
||||
notificationRead: env.NOTIFICATION_DB_REPLICA_URL ?? env.NOTIFICATION_DB_URL,
|
||||
primary: env.DATABASE_URL,
|
||||
primaryRead: env.DATABASE_REPLICA_URL ?? env.DATABASE_URL,
|
||||
primaryReadLong: env.DATABASE_REPLICA_LONG_URL ?? env.DATABASE_URL,
|
||||
logicalReplica: env.LOGICAL_REPLICA_DB_URL ?? env.DATABASE_URL,
|
||||
};
|
||||
|
||||
export function getClient(
|
||||
@@ -46,7 +48,11 @@ export function getClient(
|
||||
const connectionString = connectionStringUrl.toString();
|
||||
|
||||
const isNotification = instance === 'notification' || instance === 'notificationRead';
|
||||
const appBaseName = isNotification ? 'notif-pg' : 'node-pg';
|
||||
const appBaseName = isNotification
|
||||
? 'notif-pg'
|
||||
: instance === 'logicalReplica'
|
||||
? 'logical-pg'
|
||||
: 'node-pg';
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { types } from 'pg';
|
||||
import { isProd } from '~/env/other';
|
||||
import type { AugmentedPool } from '~/server/db/db-helpers';
|
||||
import { getClient } from '~/server/db/db-helpers';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var, vars-on-top
|
||||
var globalLogicalDb: AugmentedPool | undefined;
|
||||
}
|
||||
|
||||
// Fix Dates
|
||||
types.setTypeParser(types.builtins.TIMESTAMP, function (stringValue) {
|
||||
return new Date(stringValue.replace(' ', 'T') + 'Z');
|
||||
});
|
||||
|
||||
export let logicalDb: AugmentedPool;
|
||||
if (isProd) {
|
||||
logicalDb = getClient({ instance: 'logicalReplica' });
|
||||
} else {
|
||||
if (!global.globalLogicalDb) global.globalLogicalDb = getClient({ instance: 'logicalReplica' });
|
||||
logicalDb = global.globalLogicalDb;
|
||||
}
|
||||
@@ -128,6 +128,7 @@ const featureFlags = createFeatureFlags({
|
||||
zkp2pPayments: ['mod'],
|
||||
thirtyDayEarlyAccess: ['granted'],
|
||||
kontextAds: ['mod', 'granted'],
|
||||
logicalReplica: ['public'],
|
||||
});
|
||||
|
||||
export const featureFlagKeys = Object.keys(featureFlags) as FeatureFlagKey[];
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import { getImageGenerationProcess } from '~/server/common/model-helpers';
|
||||
import { dbRead, dbWrite } from '~/server/db/client';
|
||||
import { getDbWithoutLag, preventReplicationLag } from '~/server/db/db-lag-helpers';
|
||||
import { logicalDb } from '~/server/db/logicalDb';
|
||||
import { pgDbRead } from '~/server/db/pgDb';
|
||||
import { poolCounters } from '~/server/games/new-order/utils';
|
||||
import { logToAxiom } from '~/server/logging/client';
|
||||
@@ -767,6 +768,7 @@ type GetAllImagesInput = GetInfiniteImagesOutput & {
|
||||
useCombinedNsfwLevel?: boolean;
|
||||
user?: SessionUser;
|
||||
headers?: Record<string, string>; // TODO needed?
|
||||
useLogicalReplica: boolean;
|
||||
};
|
||||
export type ImagesInfiniteModel = AsyncReturnType<typeof getAllImages>['items'][0];
|
||||
export const getAllImages = async (
|
||||
@@ -1738,6 +1740,7 @@ export async function getImagesFromSearch(input: ImageSearchInput) {
|
||||
poiOnly,
|
||||
minorOnly,
|
||||
blockedFor,
|
||||
useLogicalReplica,
|
||||
// TODO check the unused stuff in here
|
||||
} = input;
|
||||
let { browsingLevel, userId } = input;
|
||||
@@ -2063,11 +2066,23 @@ export async function getImagesFromSearch(input: ImageSearchInput) {
|
||||
});
|
||||
|
||||
const filteredHitIds = filteredHits.map((fh) => fh.id);
|
||||
// we could pull in nsfwLevel/needsReview here too and overwrite the search index attributes (move above the hits filter)
|
||||
const dbIdResp = await dbRead.image.findMany({
|
||||
where: { id: { in: filteredHitIds } },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
let dbIdResp: { id: number }[];
|
||||
|
||||
if (!useLogicalReplica) {
|
||||
// we could pull in nsfwLevel/needsReview here too and overwrite the search index attributes (move above the hits filter)
|
||||
dbIdResp = await dbRead.image.findMany({
|
||||
where: { id: { in: filteredHitIds } },
|
||||
select: { id: true },
|
||||
});
|
||||
} else {
|
||||
const dbIdData = await logicalDb.query<{ id: number }>(
|
||||
`select id from "Image" where "id" in ($1)`,
|
||||
[filteredHitIds]
|
||||
);
|
||||
dbIdResp = dbIdData.rows;
|
||||
}
|
||||
|
||||
const dbIds = dbIdResp.map((dbi) => dbi.id);
|
||||
const filtered = filteredHits.filter((fh) => dbIds.includes(fh.id));
|
||||
|
||||
|
||||
+10
-1
@@ -7,6 +7,11 @@ test.describe('bidding', () => {
|
||||
test('bid first', async ({ page }) => {
|
||||
await page.goto('/auctions');
|
||||
|
||||
const skipTour = page.getByRole('button', { name: 'Skip' });
|
||||
if (await skipTour.isVisible()) {
|
||||
await skipTour.click();
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Select model' }).click();
|
||||
|
||||
const confirmBtn = page.getByRole('button', { name: 'Close' });
|
||||
@@ -15,7 +20,11 @@ test.describe('bidding', () => {
|
||||
}
|
||||
|
||||
await page.getByText('ALL', { exact: true }).click();
|
||||
await page.locator('div').getByRole('button', { name: 'Select' }).first().click();
|
||||
await page
|
||||
.getByTestId('resource-select-items')
|
||||
.getByRole('button', { name: 'Select' })
|
||||
.first()
|
||||
.click();
|
||||
// TODO get name here...
|
||||
|
||||
await page.getByRole('button', { name: '1st' }).click();
|
||||
|
||||
Reference in New Issue
Block a user