Skip to content

Commit fa7e4dc

Browse files
reduce batch size
1 parent 0f1c5ce commit fa7e4dc

File tree

1 file changed

+94
-168
lines changed
  • apps/web/client/src/server/api/routers/publish

1 file changed

+94
-168
lines changed

apps/web/client/src/server/api/routers/publish/manager.ts

Lines changed: 94 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -186,203 +186,52 @@ export class PublishManager {
186186
}
187187

188188
/**
189-
* Serialize files from a directory into a record of file paths to their content
190-
* Memory-optimized version using chunking + batching approach
189+
* Serializes all files in a directory for deployment using parallel processing
191190
* @param currentDir - The directory path to serialize
192191
* @returns Record of file paths to their content (base64 for binary, utf-8 for text)
193192
*/
194193
private async serializeFiles(currentDir: string): Promise<Record<string, FreestyleFile>> {
195194
const timer = new LogTimer('File Serialization');
196-
const startTime = Date.now();
197195

198196
try {
199197
const allFilePaths = await this.getAllFilePathsFlat(currentDir);
200198
timer.log(`File discovery completed - ${allFilePaths.length} files found`);
201199

202200
const filteredPaths = allFilePaths.filter(filePath => !this.shouldSkipFile(filePath));
203-
timer.log(`Filtered to ${filteredPaths.length} files after exclusions`);
204201

205202
const { binaryFiles, textFiles } = this.categorizeFiles(filteredPaths);
206-
timer.log(`Categorized: ${textFiles.length} text files, ${binaryFiles.length} binary files`);
207203

204+
const BATCH_SIZE = 10;
208205
const files: Record<string, FreestyleFile> = {};
209-
206+
210207
if (textFiles.length > 0) {
211-
timer.log(`Processing ${textFiles.length} text files using chunking + batching`);
212-
const textResults = await this.processFilesWithChunkingAndBatching(textFiles, currentDir, false);
213-
Object.assign(files, textResults);
208+
timer.log(`Processing ${textFiles.length} text files in batches of ${BATCH_SIZE}`);
209+
for (let i = 0; i < textFiles.length; i += BATCH_SIZE) {
210+
const batch = textFiles.slice(i, i + BATCH_SIZE);
211+
const batchFiles = await this.processTextFilesBatch(batch, currentDir);
212+
Object.assign(files, batchFiles);
213+
}
214214
timer.log('Text files processing completed');
215215
}
216216

217217
if (binaryFiles.length > 0) {
218-
timer.log(`Processing ${binaryFiles.length} binary files using chunking + batching`);
219-
const binaryResults = await this.processFilesWithChunkingAndBatching(binaryFiles, currentDir, true);
220-
Object.assign(files, binaryResults);
218+
timer.log(`Processing ${binaryFiles.length} binary files in batches of ${BATCH_SIZE}`);
219+
for (let i = 0; i < binaryFiles.length; i += BATCH_SIZE) {
220+
const batch = binaryFiles.slice(i, i + BATCH_SIZE);
221+
const batchFiles = await this.processBinaryFilesBatch(batch, currentDir);
222+
Object.assign(files, batchFiles);
223+
}
221224
timer.log('Binary files processing completed');
222225
}
223226

224-
const endTime = Date.now();
225-
const totalTime = endTime - startTime;
226-
timer.log(`Serialization completed - ${Object.keys(files).length} files processed in ${totalTime}ms`);
227+
timer.log(`Serialization completed - ${Object.keys(files).length} files processed`);
227228
return files;
228229
} catch (error) {
229-
const endTime = Date.now();
230-
const totalTime = endTime - startTime;
231-
console.error(`[serializeFiles] Error during serialization after ${totalTime}ms:`, error);
230+
console.error(`[serializeFiles] Error during serialization:`, error);
232231
throw error;
233232
}
234233
}
235234

236-
/**
237-
* Process files using chunking + batching approach
238-
* 1. Split files into chunks
239-
* 2. Process each chunk in batches
240-
* 3. Clean up memory after each chunk
241-
*/
242-
private async processFilesWithChunkingAndBatching(
243-
filePaths: string[],
244-
baseDir: string,
245-
isBinary: boolean,
246-
chunkSize = 100,
247-
batchSize = 10
248-
): Promise<Record<string, FreestyleFile>> {
249-
const files: Record<string, FreestyleFile> = {};
250-
const totalFiles = filePaths.length;
251-
let processedCount = 0;
252-
const chunkStartTime = Date.now();
253-
254-
for (let i = 0; i < filePaths.length; i += chunkSize) {
255-
const chunk = filePaths.slice(i, i + chunkSize);
256-
const chunkNumber = Math.floor(i / chunkSize) + 1;
257-
const totalChunks = Math.ceil(filePaths.length / chunkSize);
258-
console.log(`Processing chunk ${chunkNumber}/${totalChunks} (${chunk.length} files)`);
259-
260-
const chunkResults = await this.processChunkInBatches(chunk, baseDir, isBinary, batchSize);
261-
Object.assign(files, chunkResults);
262-
263-
processedCount += chunk.length;
264-
const chunkTime = Date.now() - chunkStartTime;
265-
console.log(`Completed chunk ${chunkNumber}/${totalChunks}. Total processed: ${processedCount}/${totalFiles} (${chunkTime}ms elapsed)`);
266-
267-
if (global.gc) {
268-
global.gc();
269-
}
270-
}
271-
272-
const totalChunkTime = Date.now() - chunkStartTime;
273-
console.log(`Completed all chunks in ${totalChunkTime}ms`);
274-
return files;
275-
}
276-
277-
private async processChunkInBatches(
278-
chunk: string[],
279-
baseDir: string,
280-
isBinary: boolean,
281-
batchSize = 10
282-
): Promise<Record<string, FreestyleFile>> {
283-
const files: Record<string, FreestyleFile> = {};
284-
const batchStartTime = Date.now();
285-
286-
for (let i = 0; i < chunk.length; i += batchSize) {
287-
const batch = chunk.slice(i, i + batchSize);
288-
const batchNumber = Math.floor(i / batchSize) + 1;
289-
const totalBatches = Math.ceil(chunk.length / batchSize);
290-
291-
const batchPromises = batch.map(filePath =>
292-
isBinary
293-
? this.processBinaryFile(filePath, baseDir)
294-
: this.processTextFile(filePath, baseDir)
295-
);
296-
297-
const batchResults = await Promise.all(batchPromises);
298-
299-
// Add successful results to files
300-
for (const result of batchResults) {
301-
if (result) {
302-
files[result.path] = result.file;
303-
}
304-
}
305-
306-
const batchTime = Date.now() - batchStartTime;
307-
console.log(` Batch ${batchNumber}/${totalBatches} completed (${batch.length} files) in ${batchTime}ms`);
308-
}
309-
310-
const totalBatchTime = Date.now() - batchStartTime;
311-
console.log(` All batches completed in ${totalBatchTime}ms`);
312-
return files;
313-
}
314-
315-
private async processTextFile(fullPath: string, baseDir: string): Promise<{ path: string; file: FreestyleFile } | null> {
316-
const relativePath = fullPath.replace(baseDir + '/', '');
317-
318-
try {
319-
const textContent = await this.session.fs.readTextFile(fullPath);
320-
321-
if (textContent !== null) {
322-
// Skip very large text files to prevent memory issues
323-
const MAX_TEXT_SIZE = 5 * 1024 * 1024; // 5MB limit for text files
324-
if (textContent.length > MAX_TEXT_SIZE) {
325-
console.warn(`[processTextFile] Skipping large text file ${relativePath} (${textContent.length} bytes)`);
326-
return null;
327-
}
328-
329-
return {
330-
path: relativePath,
331-
file: {
332-
content: textContent,
333-
encoding: 'utf-8' as const,
334-
}
335-
};
336-
} else {
337-
console.warn(`[processTextFile] Failed to read text content for ${relativePath}`);
338-
return null;
339-
}
340-
} catch (error) {
341-
console.warn(`[processTextFile] Error processing ${relativePath}:`, error);
342-
return null;
343-
}
344-
}
345-
346-
347-
private async processBinaryFile(fullPath: string, baseDir: string): Promise<{ path: string; file: FreestyleFile } | null> {
348-
const relativePath = fullPath.replace(baseDir + '/', '');
349-
350-
try {
351-
const binaryContent = await this.session.fs.readFile(fullPath);
352-
353-
if (binaryContent) {
354-
// For very large binary files, consider skipping or compressing ??
355-
const MAX_BINARY_SIZE = 10 * 1024 * 1024; // 10MB limit
356-
if (binaryContent.length > MAX_BINARY_SIZE) {
357-
console.warn(`[processBinaryFile] Skipping large binary file ${relativePath} (${binaryContent.length} bytes)`);
358-
return null;
359-
}
360-
361-
const base64String = convertToBase64(binaryContent);
362-
363-
return {
364-
path: relativePath,
365-
file: {
366-
content: base64String,
367-
encoding: 'base64' as const,
368-
}
369-
};
370-
} else {
371-
console.warn(`[processBinaryFile] Failed to read binary content for ${relativePath}`);
372-
return null;
373-
}
374-
} catch (error) {
375-
console.warn(`[processBinaryFile] Error processing ${relativePath}:`, error);
376-
return null;
377-
}
378-
}
379-
380-
381-
382-
/**
383-
* Get all file paths in a directory tree using memory-efficient streaming
384-
* Instead of accumulating all paths in memory, we yield them one by one
385-
*/
386235
private async getAllFilePathsFlat(rootDir: string): Promise<string[]> {
387236
const allPaths: string[] = [];
388237
const dirsToProcess = [rootDir];
@@ -439,4 +288,81 @@ export class PublishManager {
439288

440289
return { binaryFiles, textFiles };
441290
}
291+
292+
293+
private async processTextFilesBatch(filePaths: string[], baseDir: string): Promise<Record<string, FreestyleFile>> {
294+
const promises = filePaths.map(async (fullPath) => {
295+
const relativePath = fullPath.replace(baseDir + '/', '');
296+
297+
try {
298+
const textContent = await this.session.fs.readTextFile(fullPath);
299+
300+
if (textContent !== null) {
301+
return {
302+
path: relativePath,
303+
file: {
304+
content: textContent,
305+
encoding: 'utf-8' as const,
306+
}
307+
};
308+
} else {
309+
console.warn(`[processTextFilesBatch] Failed to read text content for ${relativePath}`);
310+
return null;
311+
}
312+
} catch (error) {
313+
console.warn(`[processTextFilesBatch] Error processing ${relativePath}:`, error);
314+
return null;
315+
}
316+
});
317+
318+
const results = await Promise.all(promises);
319+
const files: Record<string, FreestyleFile> = {};
320+
321+
for (const result of results) {
322+
if (result) {
323+
files[result.path] = result.file;
324+
}
325+
}
326+
327+
return files;
328+
}
329+
330+
private async processBinaryFilesBatch(filePaths: string[], baseDir: string): Promise<Record<string, FreestyleFile>> {
331+
const promises = filePaths.map(async (fullPath) => {
332+
const relativePath = fullPath.replace(baseDir + '/', '');
333+
334+
try {
335+
const binaryContent = await this.session.fs.readFile(fullPath);
336+
337+
if (binaryContent) {
338+
const base64String = convertToBase64(binaryContent);
339+
340+
return {
341+
path: relativePath,
342+
file: {
343+
content: base64String,
344+
encoding: 'base64' as const,
345+
}
346+
};
347+
} else {
348+
console.warn(`[processBinaryFilesBatch] Failed to read binary content for ${relativePath}`);
349+
return null;
350+
}
351+
} catch (error) {
352+
console.warn(`[processBinaryFilesBatch] Error processing ${relativePath}:`, error);
353+
return null;
354+
}
355+
});
356+
357+
const results = await Promise.all(promises);
358+
const files: Record<string, FreestyleFile> = {};
359+
360+
for (const result of results) {
361+
if (result) {
362+
files[result.path] = result.file;
363+
}
364+
}
365+
366+
return files;
367+
}
442368
}

0 commit comments

Comments
 (0)