-
Notifications
You must be signed in to change notification settings - Fork 36
Add incremental backup support #459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0ab941e
1ff1593
e15b399
f493925
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,3 +45,19 @@ export function validateBackupId(backupId?: string) { | |
| } | ||
| return []; | ||
| } | ||
|
|
||
| export function validateIncrementalBaseBackupId(incrementalBaseBackupId?: string, backupId?: string) { | ||
| if (incrementalBaseBackupId === undefined || incrementalBaseBackupId === null) { | ||
| return []; | ||
| } | ||
| if (!isValidStringProperty(incrementalBaseBackupId)) { | ||
| return [ | ||
| 'string incrementalBaseBackupId must be a non-empty string - set with .withIncrementalBaseBackupId(backupId)', | ||
| ]; | ||
| } | ||
| // Weaviate treats backup IDs as case-insensitive, so 'B1' and 'b1' name the same backup. | ||
| if (incrementalBaseBackupId.toLowerCase() === backupId?.toLowerCase()) { | ||
| return ['incrementalBaseBackupId must be different from the ID of the backup being created']; | ||
| } | ||
| return []; | ||
|
Comment on lines
+58
to
+62
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: This validation should not be in the client, it is entirely up to the server to resolve backup IDs and allow/forbid them. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,11 @@ import { | |
| BackupRestoreStatusGetter, | ||
| BackupRestorer, | ||
| } from '../../backup/index.js'; | ||
| import { validateBackend, validateBackupId } from '../../backup/validation.js'; | ||
| import { | ||
| validateBackend, | ||
| validateBackupId, | ||
| validateIncrementalBaseBackupId, | ||
| } from '../../backup/validation.js'; | ||
| import Connection from '../../connection/index.js'; | ||
| import { | ||
| WeaviateBackupCanceled, | ||
|
|
@@ -14,24 +18,27 @@ import { | |
| WeaviateInvalidInputError, | ||
| WeaviateUnexpectedResponseError, | ||
| WeaviateUnexpectedStatusCodeError, | ||
| WeaviateUnsupportedFeatureError, | ||
| } from '../../errors.js'; | ||
| import { | ||
| BackupCreateResponse, | ||
| BackupCreateStatusResponse, | ||
| BackupListResponse, | ||
| BackupRestoreResponse, | ||
| } from '../../openapi/types.js'; | ||
| import { DbVersionSupport } from '../../utils/dbVersion.js'; | ||
| import { | ||
| BackupArgs, | ||
| BackupCancelArgs, | ||
| BackupConfigCreate, | ||
| BackupConfigRestore, | ||
| BackupCreateArgs, | ||
| BackupReturn, | ||
| BackupStatusArgs, | ||
| BackupStatusReturn, | ||
| ListBackupOptions, | ||
| } from './types.js'; | ||
|
|
||
| export const backup = (connection: Connection): Backup => { | ||
| export const backup = (connection: Connection, dbVersionSupport: DbVersionSupport): Backup => { | ||
| const parseStatus = (res: BackupCreateStatusResponse | BackupRestoreResponse): BackupStatusReturn => { | ||
| if (res.id === undefined) { | ||
| throw new WeaviateUnexpectedResponseError('Backup ID is undefined in response'); | ||
|
|
@@ -47,6 +54,11 @@ export const backup = (connection: Connection): Backup => { | |
| error: res.error, | ||
| path: res.path, | ||
| status: res.status, | ||
| // Restore responses carry neither of these fields; see BackupStatusReturn for when Weaviate | ||
| // omits `incremental_base_backup_id` from a create status. | ||
| size: 'size' in res ? res.size : undefined, | ||
| incrementalBaseBackupId: | ||
| 'incremental_base_backup_id' in res ? res.incremental_base_backup_id || undefined : undefined, | ||
| }; | ||
|
Comment on lines
+57
to
62
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: these checks are entirely unnecessary. If a key is not present in the See playground example. |
||
| }; | ||
| const parseResponse = (res: BackupCreateResponse | BackupRestoreResponse): BackupReturn => { | ||
|
|
@@ -109,10 +121,22 @@ export const backup = (connection: Connection): Backup => { | |
|
|
||
| return true; | ||
| }, | ||
| create: async (args: BackupArgs<BackupConfigCreate>): Promise<BackupReturn> => { | ||
| create: async (args: BackupCreateArgs): Promise<BackupReturn> => { | ||
| let builder = new BackupCreator(connection, new BackupCreateStatusGetter(connection)) | ||
| .withBackupId(args.backupId) | ||
| .withBackend(args.backend); | ||
| if (args.incrementalBaseBackupId !== undefined) { | ||
| const errors = validateIncrementalBaseBackupId(args.incrementalBaseBackupId, args.backupId); | ||
| if (errors.length > 0) { | ||
| throw new WeaviateInvalidInputError(errors.join(', ')); | ||
| } | ||
| const check = await dbVersionSupport.supportsIncrementalBackups(); | ||
| if (!check.supports) { | ||
| throw new WeaviateUnsupportedFeatureError(check.message); | ||
| } | ||
| // The builder lowercases the ID to match how Weaviate stores it. | ||
| builder = builder.withIncrementalBaseBackupId(args.incrementalBaseBackupId); | ||
| } | ||
| if (args.includeCollections) { | ||
| builder = builder.withIncludeClassNames(...args.includeCollections); | ||
| } | ||
|
|
@@ -213,7 +237,12 @@ export const backup = (connection: Connection): Backup => { | |
| if (opts?.startedAtAsc) { | ||
| url += '?order=asc'; | ||
| } | ||
| return connection.get<BackupReturn[]>(url); | ||
| return connection.get<BackupListResponse>(url).then((res) => | ||
| res.map(({ incremental_base_backup_id: baseBackupId, ...rest }) => ({ | ||
| ...rest, | ||
| incrementalBaseBackupId: baseBackupId || undefined, | ||
| })) | ||
| ) as Promise<BackupReturn[]>; | ||
| }, | ||
| }; | ||
| }; | ||
|
|
@@ -231,13 +260,16 @@ export interface Backup { | |
| /** | ||
| * Create a backup of the database. | ||
| * | ||
| * @param {BackupArgs} args The arguments for the request. | ||
| * Set `incrementalBaseBackupId` for a file-based incremental backup (Weaviate `v1.37.0` or higher). | ||
| * | ||
| * @param {BackupCreateArgs} args The arguments for the request. | ||
| * @returns {Promise<BackupReturn>} The response from Weaviate. | ||
| * @throws {WeaviateInvalidInputError} If the input is invalid. | ||
| * @throws {WeaviateUnsupportedFeatureError} If `incrementalBaseBackupId` is used with Weaviate <1.37.0. | ||
| * @throws {WeaviateBackupFailed} If the backup creation fails. | ||
| * @throws {WeaviateBackupCanceled} If the backup creation is canceled. | ||
| */ | ||
| create(args: BackupArgs<BackupConfigCreate>): Promise<BackupReturn>; | ||
| create(args: BackupCreateArgs): Promise<BackupReturn>; | ||
| /** | ||
| * Get the status of a backup creation. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,9 @@ | ||
| export type { Backup } from './client.js'; | ||
| export type { BackupCollection, BackupCollectionArgs } from './collection.js'; | ||
| export type { BackupArgs, BackupConfigCreate, BackupConfigRestore, BackupStatusArgs } from './types.js'; | ||
| export type { BackupCollection, BackupCollectionArgs, BackupCollectionCreateArgs } from './collection.js'; | ||
| export type { | ||
| BackupArgs, | ||
| BackupConfigCreate, | ||
| BackupConfigRestore, | ||
| BackupCreateArgs, | ||
| BackupStatusArgs, | ||
| } from './types.js'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,14 @@ export type BackupStatusReturn = { | |
| status: BackupStatus; | ||
| /** Size of the backup in Gibs */ | ||
| size?: number; | ||
| /** | ||
| * The ID of the base backup an incremental backup was built on. | ||
| * | ||
| * Undefined when the backup is not incremental. Also undefined for an incremental backup when the | ||
| * caller is not a root user, when the server is older than `v1.37.6`, and on the return of | ||
| * `create()` without `waitForCompletion` — the create response carries no such field. | ||
| */ | ||
| incrementalBaseBackupId?: string; | ||
| }; | ||
|
|
||
| /** The return type of a backup creation or restoration operation */ | ||
|
|
@@ -64,6 +72,12 @@ export type BackupArgs<C extends BackupConfigCreate | BackupConfigRestore> = { | |
| config?: C; | ||
| }; | ||
|
|
||
| /** The arguments required to create a backup. */ | ||
| export type BackupCreateArgs = BackupArgs<BackupConfigCreate> & { | ||
| /** The ID of an existing backup to build a file-based incremental backup on. Files identical to the base are not copied and are restored from the base instead, so deleting a base backup breaks every incremental built on it. Requires Weaviate `v1.37.0` or higher. */ | ||
| incrementalBaseBackupId?: string; | ||
| }; | ||
|
|
||
|
Comment on lines
+75
to
+80
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: why is it necessary to declare a new type? |
||
| /** The arguments required to get the status of a backup. */ | ||
| export type BackupStatusArgs = { | ||
| /** The ID of the backup. */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not an acceptance test suite for the Weaviate server and is not aimed at testing the server's behaviour. Please revert this change and the associated
describeblock intest/collections/backup/integration.test.ts