mirror of
https://github.com/strapi/strapi.git
synced 2025-11-11 15:49:50 +00:00
Batching using the amount of bytes
Co-authored-by: Jean-Sébastien Herbaux <Convly@users.noreply.github.com>
This commit is contained in:
parent
db1c010f5b
commit
2bea33bf67
@ -691,7 +691,7 @@ class TransferEngine<
|
|||||||
const transform = this.#createStageTransformStream(stage);
|
const transform = this.#createStageTransformStream(stage);
|
||||||
const tracker = this.#progressTracker(stage, {
|
const tracker = this.#progressTracker(stage, {
|
||||||
size: (value: IAsset) => value.stats.size,
|
size: (value: IAsset) => value.stats.size,
|
||||||
key: (value: IAsset) => extname(value.filename) ?? 'NA',
|
key: (value: IAsset) => extname(value.filename) || 'No extension',
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.#transferStage({ stage, source, destination, transform, tracker });
|
await this.#transferStage({ stage, source, destination, transform, tracker });
|
||||||
|
|||||||
@ -171,9 +171,14 @@ class LocalStrapiDestinationProvider implements IDestinationProvider {
|
|||||||
const entryPath = path.join(assetsDirectory, chunk.filename);
|
const entryPath = path.join(assetsDirectory, chunk.filename);
|
||||||
const writableStream = fse.createWriteStream(entryPath);
|
const writableStream = fse.createWriteStream(entryPath);
|
||||||
|
|
||||||
|
console.log('hello from the other side?');
|
||||||
|
|
||||||
chunk.stream
|
chunk.stream
|
||||||
.pipe(writableStream)
|
.pipe(writableStream)
|
||||||
.on('close', callback)
|
.on('close', () => {
|
||||||
|
console.log('close/end substream');
|
||||||
|
callback(null);
|
||||||
|
})
|
||||||
.on('error', async (error: NodeJS.ErrnoException) => {
|
.on('error', async (error: NodeJS.ErrnoException) => {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
error.code === 'ENOSPC'
|
error.code === 'ENOSPC'
|
||||||
|
|||||||
@ -5,15 +5,7 @@ import { once } from 'lodash/fp';
|
|||||||
|
|
||||||
import { createDispatcher } from './utils';
|
import { createDispatcher } from './utils';
|
||||||
|
|
||||||
import type {
|
import type { IDestinationProvider, IMetadata, ProviderType, IAsset } from '../../../../types';
|
||||||
IDestinationProvider,
|
|
||||||
IEntity,
|
|
||||||
ILink,
|
|
||||||
IMetadata,
|
|
||||||
ProviderType,
|
|
||||||
IConfiguration,
|
|
||||||
IAsset,
|
|
||||||
} from '../../../../types';
|
|
||||||
import type { client, server } from '../../../../types/remote/protocol';
|
import type { client, server } from '../../../../types/remote/protocol';
|
||||||
import type { ILocalStrapiDestinationProviderOptions } from '../local-destination';
|
import type { ILocalStrapiDestinationProviderOptions } from '../local-destination';
|
||||||
import { TRANSFER_PATH } from '../../remote/constants';
|
import { TRANSFER_PATH } from '../../remote/constants';
|
||||||
@ -30,6 +22,8 @@ export interface IRemoteStrapiDestinationProviderOptions
|
|||||||
auth?: ITransferTokenAuth;
|
auth?: ITransferTokenAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const jsonLength = (obj: object) => Buffer.byteLength(JSON.stringify(obj));
|
||||||
|
|
||||||
class RemoteStrapiDestinationProvider implements IDestinationProvider {
|
class RemoteStrapiDestinationProvider implements IDestinationProvider {
|
||||||
name = 'destination::remote-strapi';
|
name = 'destination::remote-strapi';
|
||||||
|
|
||||||
@ -134,6 +128,59 @@ class RemoteStrapiDestinationProvider implements IDestinationProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#writeStream(step: Exclude<client.TransferPushStep, 'assets'>): Writable {
|
||||||
|
type Step = typeof step;
|
||||||
|
|
||||||
|
const batchSize = 1024 * 1024; // 1MB;
|
||||||
|
const startTransferOnce = this.#startStepOnce(step);
|
||||||
|
|
||||||
|
let batch = [] as client.GetTransferPushStreamData<Step>;
|
||||||
|
|
||||||
|
const batchLength = () => jsonLength(batch);
|
||||||
|
|
||||||
|
return new Writable({
|
||||||
|
objectMode: true,
|
||||||
|
|
||||||
|
final: async (callback) => {
|
||||||
|
if (batch.length > 0) {
|
||||||
|
const streamError = await this.#streamStep(step, batch);
|
||||||
|
|
||||||
|
batch = [];
|
||||||
|
|
||||||
|
if (streamError) {
|
||||||
|
return callback(streamError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const e = await this.#endStep(step);
|
||||||
|
|
||||||
|
callback(e);
|
||||||
|
},
|
||||||
|
|
||||||
|
write: async (chunk, _encoding, callback) => {
|
||||||
|
const startError = await startTransferOnce();
|
||||||
|
if (startError) {
|
||||||
|
return callback(startError);
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.push(chunk);
|
||||||
|
|
||||||
|
if (batchLength() >= batchSize) {
|
||||||
|
console.log('flushing', batchLength(), '>', batchSize);
|
||||||
|
console.log(batch.length, 'items at once');
|
||||||
|
const streamError = await this.#streamStep(step, batch);
|
||||||
|
|
||||||
|
if (streamError) {
|
||||||
|
return callback(streamError);
|
||||||
|
}
|
||||||
|
|
||||||
|
batch = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async bootstrap(): Promise<void> {
|
async bootstrap(): Promise<void> {
|
||||||
const { url, auth } = this.options;
|
const { url, auth } = this.options;
|
||||||
const validProtocols = ['https:', 'http:'];
|
const validProtocols = ['https:', 'http:'];
|
||||||
@ -214,135 +261,45 @@ class RemoteStrapiDestinationProvider implements IDestinationProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createEntitiesWriteStream(): Writable {
|
createEntitiesWriteStream(): Writable {
|
||||||
const chunkSize = 100;
|
return this.#writeStream('entities');
|
||||||
let entities: IEntity[] = [];
|
|
||||||
const startEntitiesTransferOnce = this.#startStepOnce('entities');
|
|
||||||
|
|
||||||
return new Writable({
|
|
||||||
objectMode: true,
|
|
||||||
final: async (callback) => {
|
|
||||||
if (entities.length > 0) {
|
|
||||||
const streamError = await this.#streamStep('entities', entities);
|
|
||||||
entities = [];
|
|
||||||
if (streamError) {
|
|
||||||
return callback(streamError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const e = await this.#endStep('entities');
|
|
||||||
|
|
||||||
callback(e);
|
|
||||||
},
|
|
||||||
write: async (entity: IEntity, _encoding, callback) => {
|
|
||||||
const startError = await startEntitiesTransferOnce();
|
|
||||||
if (startError) {
|
|
||||||
return callback(startError);
|
|
||||||
}
|
|
||||||
entities.push(entity);
|
|
||||||
if (entities.length === chunkSize) {
|
|
||||||
const streamError = await this.#streamStep('entities', entities);
|
|
||||||
if (streamError) {
|
|
||||||
return callback(streamError);
|
|
||||||
}
|
|
||||||
entities = [];
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createLinksWriteStream(): Writable {
|
createLinksWriteStream(): Writable {
|
||||||
const chunkSize = 100;
|
return this.#writeStream('links');
|
||||||
let links: ILink[] = [];
|
|
||||||
const startLinksTransferOnce = this.#startStepOnce('links');
|
|
||||||
|
|
||||||
return new Writable({
|
|
||||||
objectMode: true,
|
|
||||||
final: async (callback) => {
|
|
||||||
if (links.length > 0) {
|
|
||||||
const streamError = await this.#streamStep('links', links);
|
|
||||||
|
|
||||||
if (streamError) {
|
|
||||||
return callback(streamError);
|
|
||||||
}
|
|
||||||
links = [];
|
|
||||||
}
|
|
||||||
const e = await this.#endStep('links');
|
|
||||||
|
|
||||||
callback(e);
|
|
||||||
},
|
|
||||||
write: async (link: ILink, _encoding, callback) => {
|
|
||||||
const startError = await startLinksTransferOnce();
|
|
||||||
|
|
||||||
if (startError) {
|
|
||||||
return callback(startError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (links.length === chunkSize) {
|
|
||||||
const streamError = await this.#streamStep('links', links);
|
|
||||||
|
|
||||||
if (streamError) {
|
|
||||||
return callback(streamError);
|
|
||||||
}
|
|
||||||
links = [];
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createConfigurationWriteStream(): Writable {
|
createConfigurationWriteStream(): Writable {
|
||||||
const chunkSize = 100;
|
return this.#writeStream('configuration');
|
||||||
let configurations: IConfiguration[] = [];
|
|
||||||
const startConfigurationTransferOnce = this.#startStepOnce('configuration');
|
|
||||||
|
|
||||||
return new Writable({
|
|
||||||
objectMode: true,
|
|
||||||
final: async (callback) => {
|
|
||||||
if (configurations.length > 0) {
|
|
||||||
const streamError = await this.#streamStep('configuration', configurations);
|
|
||||||
|
|
||||||
if (streamError) {
|
|
||||||
return callback(streamError);
|
|
||||||
}
|
|
||||||
configurations = [];
|
|
||||||
}
|
|
||||||
const e = await this.#endStep('configuration');
|
|
||||||
|
|
||||||
callback(e);
|
|
||||||
},
|
|
||||||
write: async (configuration: IConfiguration, _encoding, callback) => {
|
|
||||||
const startError = await startConfigurationTransferOnce();
|
|
||||||
|
|
||||||
if (startError) {
|
|
||||||
return callback(startError);
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations.push(configuration);
|
|
||||||
if (configurations.length === chunkSize) {
|
|
||||||
const streamError = await this.#streamStep('configuration', configurations);
|
|
||||||
|
|
||||||
if (streamError) {
|
|
||||||
return callback(streamError);
|
|
||||||
}
|
|
||||||
configurations = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createAssetsWriteStream(): Writable | Promise<Writable> {
|
createAssetsWriteStream(): Writable | Promise<Writable> {
|
||||||
let batch: client.TransferAssetFlow[] = [];
|
let batch: client.TransferAssetFlow[] = [];
|
||||||
const batchSize = 100;
|
const batchSize = 1024 * 1024; // 1MB;
|
||||||
|
const batchLength = () => {
|
||||||
|
return batch.reduce(
|
||||||
|
(acc, chunk) => (chunk.action === 'stream' ? acc + chunk.data.byteLength : acc),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
};
|
||||||
const startAssetsTransferOnce = this.#startStepOnce('assets');
|
const startAssetsTransferOnce = this.#startStepOnce('assets');
|
||||||
|
|
||||||
|
const flush = async () => {
|
||||||
|
await this.#streamStep('assets', batch);
|
||||||
|
batch = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const safePush = async (chunk: client.TransferAssetFlow) => {
|
||||||
|
batch.push(chunk);
|
||||||
|
if (batchLength() >= batchSize) {
|
||||||
|
await flush();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return new Writable({
|
return new Writable({
|
||||||
objectMode: true,
|
objectMode: true,
|
||||||
final: async (callback) => {
|
final: async (callback) => {
|
||||||
if (batch.length > 0) {
|
if (batch.length > 0) {
|
||||||
await this.#streamStep('assets', batch);
|
await flush();
|
||||||
batch = [];
|
|
||||||
}
|
}
|
||||||
// TODO: replace this stream call by an end call
|
// TODO: replace this stream call by an end call
|
||||||
const endError = await this.#streamStep('assets', null);
|
const endError = await this.#streamStep('assets', null);
|
||||||
@ -360,41 +317,23 @@ class RemoteStrapiDestinationProvider implements IDestinationProvider {
|
|||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
write: async (asset: IAsset, _encoding, callback) => {
|
async write(asset: IAsset, _encoding, callback) {
|
||||||
const startError = await startAssetsTransferOnce();
|
const startError = await startAssetsTransferOnce();
|
||||||
|
|
||||||
if (startError) {
|
if (startError) {
|
||||||
return callback(startError);
|
return callback(startError);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { filename, filepath, stats, stream } = asset;
|
|
||||||
const assetID = v4();
|
const assetID = v4();
|
||||||
batch.push({
|
const { filename, filepath, stats, stream } = asset;
|
||||||
action: 'start',
|
|
||||||
assetID,
|
await safePush({ action: 'start', assetID, data: { filename, filepath, stats } });
|
||||||
data: { filename, filepath, stats },
|
|
||||||
});
|
|
||||||
if (batch.length === batchSize) {
|
|
||||||
await this.#streamStep('assets', batch);
|
|
||||||
batch = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
batch.push({ action: 'stream', assetID, data: chunk });
|
await safePush({ action: 'stream', assetID, data: chunk });
|
||||||
if (batch.length === batchSize) {
|
|
||||||
await this.#streamStep('assets', batch);
|
|
||||||
batch = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.push({
|
await safePush({ action: 'end', assetID });
|
||||||
action: 'end',
|
|
||||||
assetID,
|
|
||||||
});
|
|
||||||
if (batch.length === batchSize) {
|
|
||||||
await this.#streamStep('assets', batch);
|
|
||||||
batch = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -109,11 +109,16 @@ const createPushController = (options: ILocalStrapiDestinationProviderOptions):
|
|||||||
streams.assets?.end();
|
streams.assets?.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!streams.assets) {
|
if (!streams.assets) {
|
||||||
streams.assets = await provider.createAssetsWriteStream();
|
streams.assets = await provider.createAssetsWriteStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
payloads.forEach(async (payload) => {
|
for (const payload of payloads) {
|
||||||
|
if (streams.assets.closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { action, assetID } = payload;
|
const { action, assetID } = payload;
|
||||||
|
|
||||||
if (action === 'start' && streams.assets) {
|
if (action === 'start' && streams.assets) {
|
||||||
@ -139,11 +144,15 @@ const createPushController = (options: ILocalStrapiDestinationProviderOptions):
|
|||||||
delete assets[assetID];
|
delete assets[assetID];
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.on('error', reject)
|
.on('error', (e) => {
|
||||||
|
reject(e);
|
||||||
|
})
|
||||||
.end();
|
.end();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
console.log('[assets] done');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { ProviderTransferError, ProviderInitializationError } from '../../errors
|
|||||||
import { TRANSFER_METHODS } from './constants';
|
import { TRANSFER_METHODS } from './constants';
|
||||||
import { createFlow, DEFAULT_TRANSFER_FLOW } from './flows';
|
import { createFlow, DEFAULT_TRANSFER_FLOW } from './flows';
|
||||||
|
|
||||||
type TransferMethod = typeof TRANSFER_METHODS[number];
|
type TransferMethod = (typeof TRANSFER_METHODS)[number];
|
||||||
|
|
||||||
interface ITransferState {
|
interface ITransferState {
|
||||||
transfer?: {
|
transfer?: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user