qvdjs 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -4
- package/dist/index.cjs +50 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +49 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import crypto from 'crypto';
|
|
|
4
4
|
import xml2 from 'xml2js';
|
|
5
5
|
import assert from 'assert';
|
|
6
6
|
import os from 'os';
|
|
7
|
+
import v8 from 'v8';
|
|
7
8
|
|
|
8
9
|
var __defProp = Object.defineProperty;
|
|
9
10
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -647,6 +648,9 @@ var init_bitUtils = __esm({
|
|
|
647
648
|
"src/util/bitUtils.js"() {
|
|
648
649
|
}
|
|
649
650
|
});
|
|
651
|
+
function getHeapLimit() {
|
|
652
|
+
return v8.getHeapStatistics().heap_size_limit;
|
|
653
|
+
}
|
|
650
654
|
function estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
|
|
651
655
|
const FULL_PARSE_OVERHEAD = 6;
|
|
652
656
|
const MINIMAL_OVERHEAD = 0.01;
|
|
@@ -659,11 +663,15 @@ function estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
|
|
|
659
663
|
const skippedSymbolsMemory = symbolTableSize * (1 - symbolPercentage) * MINIMAL_OVERHEAD;
|
|
660
664
|
return keptSymbolsMemory + skippedSymbolsMemory;
|
|
661
665
|
}
|
|
662
|
-
function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePath) {
|
|
666
|
+
function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePath, safetyFactor = 0.3) {
|
|
667
|
+
if (typeof safetyFactor !== "number" || safetyFactor < 0 || safetyFactor > 1) {
|
|
668
|
+
throw new QvdValidationError("safetyFactor must be a number between 0.0 and 1.0", { safetyFactor });
|
|
669
|
+
}
|
|
663
670
|
const availableMemory = os.freemem();
|
|
671
|
+
const heapLimit = getHeapLimit();
|
|
664
672
|
const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
|
|
665
|
-
const
|
|
666
|
-
const maxAllowedMemory =
|
|
673
|
+
const effectiveLimit = Math.min(availableMemory, heapLimit);
|
|
674
|
+
const maxAllowedMemory = effectiveLimit * safetyFactor;
|
|
667
675
|
if (estimatedMemory > maxAllowedMemory) {
|
|
668
676
|
const safeSymbolPercentage = maxAllowedMemory / (symbolTableSize * 6);
|
|
669
677
|
const safeRowPercentage = Math.pow(safeSymbolPercentage, 2);
|
|
@@ -671,14 +679,20 @@ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePat
|
|
|
671
679
|
const sizeMB = Math.round(symbolTableSize / 1024 / 1024);
|
|
672
680
|
const estimatedMB = Math.round(estimatedMemory / 1024 / 1024);
|
|
673
681
|
const availableMB = Math.round(maxAllowedMemory / 1024 / 1024);
|
|
682
|
+
const heapLimitMB = Math.round(heapLimit / 1024 / 1024);
|
|
683
|
+
const availableRamMB = Math.round(availableMemory / 1024 / 1024);
|
|
684
|
+
const limitingFactor = heapLimit < availableMemory ? "V8 heap limit" : "available RAM";
|
|
674
685
|
throw new QvdValidationError(
|
|
675
|
-
`Insufficient memory to load file safely. Symbol table: ${sizeMB}MB, Estimated memory needed: ${estimatedMB}MB, Available: ${availableMB}MB. Try loading fewer rows using the maxRows parameter (recommended: ${recommendedMaxRows.toLocaleString()} rows or less).`,
|
|
686
|
+
`Insufficient memory to load file safely. Symbol table: ${sizeMB}MB, Estimated memory needed: ${estimatedMB}MB, Available: ${availableMB}MB (limited by ${limitingFactor}: ${heapLimitMB}MB heap / ${availableRamMB}MB RAM). Try loading fewer rows using the maxRows parameter (recommended: ${recommendedMaxRows.toLocaleString()} rows or less).`,
|
|
676
687
|
{
|
|
677
688
|
file: filePath,
|
|
678
689
|
symbolTableSize,
|
|
679
690
|
symbolTableSizeMB: sizeMB,
|
|
680
691
|
estimatedMemoryMB: estimatedMB,
|
|
681
692
|
availableMemoryMB: availableMB,
|
|
693
|
+
heapLimitMB,
|
|
694
|
+
availableRamMB,
|
|
695
|
+
limitingFactor,
|
|
682
696
|
totalRows,
|
|
683
697
|
maxRows,
|
|
684
698
|
recommendedMaxRows
|
|
@@ -687,13 +701,15 @@ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePat
|
|
|
687
701
|
}
|
|
688
702
|
}
|
|
689
703
|
function warnLargeSymbolTable(symbolTableSize, maxRows, totalRows) {
|
|
690
|
-
const
|
|
704
|
+
const heapLimit = getHeapLimit();
|
|
705
|
+
const LARGE_SYMBOL_TABLE_WARNING = heapLimit * 0.125;
|
|
691
706
|
if (symbolTableSize > LARGE_SYMBOL_TABLE_WARNING && (maxRows === null || maxRows >= totalRows)) {
|
|
692
707
|
const sizeMB = Math.round(symbolTableSize / 1024 / 1024);
|
|
693
708
|
const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
|
|
694
709
|
const estimatedMB = Math.round(estimatedMemory / 1024 / 1024);
|
|
710
|
+
const warnMB = Math.round(LARGE_SYMBOL_TABLE_WARNING / 1024 / 1024);
|
|
695
711
|
console.warn(
|
|
696
|
-
`\u26A0\uFE0F Large symbol table detected (${sizeMB}MB). Loading all ${totalRows.toLocaleString()} rows will use ~${estimatedMB}MB RAM. Consider using the maxRows parameter for better performance and lower memory usage.`
|
|
712
|
+
`\u26A0\uFE0F Large symbol table detected (${sizeMB}MB > ${warnMB}MB threshold). Loading all ${totalRows.toLocaleString()} rows will use ~${estimatedMB}MB RAM. Consider using the maxRows parameter for better performance and lower memory usage.`
|
|
697
713
|
);
|
|
698
714
|
}
|
|
699
715
|
}
|
|
@@ -705,35 +721,43 @@ var init_memoryUtils = __esm({
|
|
|
705
721
|
|
|
706
722
|
// src/util/validationUtils.js
|
|
707
723
|
function validateSymbolTableSizeEarly(symbolTableLength, filePath) {
|
|
708
|
-
const
|
|
724
|
+
const heapLimit = getHeapLimit();
|
|
725
|
+
const MAX_SYMBOL_TABLE_SIZE = heapLimit * 0.125;
|
|
709
726
|
if (symbolTableLength > MAX_SYMBOL_TABLE_SIZE) {
|
|
710
727
|
const sizeMB = Math.round(symbolTableLength / 1024 / 1024);
|
|
711
728
|
const maxMB = Math.round(MAX_SYMBOL_TABLE_SIZE / 1024 / 1024);
|
|
729
|
+
const heapMB = Math.round(heapLimit / 1024 / 1024);
|
|
712
730
|
throw new QvdValidationError(
|
|
713
|
-
`Symbol table too large (${sizeMB}MB exceeds ${maxMB}MB limit). This QVD file contains extremely high-cardinality fields.
|
|
731
|
+
`Symbol table too large (${sizeMB}MB exceeds ${maxMB}MB limit for lazy loading). This QVD file contains extremely high-cardinality fields. Limit scales with heap size (current: ${heapMB}MB, limit: 12.5% = ${maxMB}MB). Consider: (1) loading the full file without maxRows, (2) increasing heap size with --max-old-space-size, or (3) aggregating high-cardinality fields.`,
|
|
714
732
|
{
|
|
715
733
|
file: filePath,
|
|
716
734
|
symbolTableSize: symbolTableLength,
|
|
717
735
|
symbolTableSizeMB: sizeMB,
|
|
718
736
|
maxAllowed: MAX_SYMBOL_TABLE_SIZE,
|
|
719
|
-
maxAllowedMB: maxMB
|
|
737
|
+
maxAllowedMB: maxMB,
|
|
738
|
+
heapLimitMB: heapMB,
|
|
739
|
+
limitPercentage: 12.5
|
|
720
740
|
}
|
|
721
741
|
);
|
|
722
742
|
}
|
|
723
743
|
}
|
|
724
744
|
function validateSymbolTableSize(symbolTableLength, filePath, totalRows) {
|
|
725
|
-
const
|
|
745
|
+
const heapLimit = getHeapLimit();
|
|
746
|
+
const ABSOLUTE_MAX_SYMBOL_TABLE = heapLimit * 0.5;
|
|
726
747
|
if (symbolTableLength > ABSOLUTE_MAX_SYMBOL_TABLE) {
|
|
727
748
|
const sizeMB = Math.round(symbolTableLength / 1024 / 1024);
|
|
728
749
|
const maxMB = Math.round(ABSOLUTE_MAX_SYMBOL_TABLE / 1024 / 1024);
|
|
750
|
+
const heapMB = Math.round(heapLimit / 1024 / 1024);
|
|
729
751
|
throw new QvdValidationError(
|
|
730
|
-
`Symbol table exceeds absolute maximum size (${sizeMB}MB > ${maxMB}MB). This QVD file has pathological cardinality (likely a data modeling issue).
|
|
752
|
+
`Symbol table exceeds absolute maximum size (${sizeMB}MB > ${maxMB}MB). This QVD file has pathological cardinality (likely a data modeling issue). Limit scales with heap size (current: ${heapMB}MB, limit: 50% = ${maxMB}MB). Consider: (1) increasing heap size with --max-old-space-size, (2) aggregating high-cardinality fields, (3) splitting the data, or (4) using a different format.`,
|
|
731
753
|
{
|
|
732
754
|
file: filePath,
|
|
733
755
|
symbolTableSize: symbolTableLength,
|
|
734
756
|
symbolTableSizeMB: sizeMB,
|
|
735
757
|
maxAllowed: ABSOLUTE_MAX_SYMBOL_TABLE,
|
|
736
758
|
maxAllowedMB: maxMB,
|
|
759
|
+
heapLimitMB: heapMB,
|
|
760
|
+
limitPercentage: 50,
|
|
737
761
|
totalRows
|
|
738
762
|
}
|
|
739
763
|
);
|
|
@@ -870,6 +894,7 @@ function validateFieldBitMetadata(field, recordSize, filePath) {
|
|
|
870
894
|
var init_validationUtils = __esm({
|
|
871
895
|
"src/util/validationUtils.js"() {
|
|
872
896
|
init_QvdErrors();
|
|
897
|
+
init_memoryUtils();
|
|
873
898
|
}
|
|
874
899
|
});
|
|
875
900
|
|
|
@@ -1152,10 +1177,14 @@ var init_QvdFileReader = __esm({
|
|
|
1152
1177
|
* @param {Object} [options={}] Options for the reader.
|
|
1153
1178
|
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file
|
|
1154
1179
|
* path must be within this directory. Defaults to current working directory.
|
|
1180
|
+
* @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines
|
|
1181
|
+
* what percentage of available memory (or V8 heap limit, whichever is smaller) can be used
|
|
1182
|
+
* for loading QVD files. Default is 0.3 (30%). Increase for larger heap configurations.
|
|
1155
1183
|
*/
|
|
1156
1184
|
constructor(filePath, options = {}) {
|
|
1157
|
-
const { allowedDir } = options;
|
|
1185
|
+
const { allowedDir, memorySafetyFactor = 0.3 } = options;
|
|
1158
1186
|
this._path = validatePath(filePath, allowedDir);
|
|
1187
|
+
this._memorySafetyFactor = memorySafetyFactor;
|
|
1159
1188
|
this._buffer = null;
|
|
1160
1189
|
this._headerOffset = null;
|
|
1161
1190
|
this._symbolTableOffset = null;
|
|
@@ -1379,7 +1408,7 @@ var init_QvdFileReader = __esm({
|
|
|
1379
1408
|
const symbolTableSize = symbolBuffer.length;
|
|
1380
1409
|
const totalRows = parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10);
|
|
1381
1410
|
validateSymbolTableSize(symbolTableSize, this._path, totalRows);
|
|
1382
|
-
validateMemoryAvailability(symbolTableSize, maxRows, totalRows, this._path);
|
|
1411
|
+
validateMemoryAvailability(symbolTableSize, maxRows, totalRows, this._path, this._memorySafetyFactor);
|
|
1383
1412
|
warnLargeSymbolTable(symbolTableSize, maxRows, totalRows);
|
|
1384
1413
|
if (!Array.isArray(fields)) {
|
|
1385
1414
|
fields = [fields];
|
|
@@ -1960,11 +1989,17 @@ var init_QvdDataFrame = __esm({
|
|
|
1960
1989
|
* @param {Object} [options] Optional loading options.
|
|
1961
1990
|
* @param {number|null} [options.maxRows] The maximum number of rows to load. If not specified, all rows are loaded.
|
|
1962
1991
|
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file path must be within this directory.
|
|
1992
|
+
* @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines what percentage
|
|
1993
|
+
* of available memory (or V8 heap limit, whichever is smaller) can be used. Default is 0.3 (30%).
|
|
1994
|
+
* Increase this (e.g., to 0.5 or 0.7) when running with larger heap sizes via --max-old-space-size.
|
|
1963
1995
|
* @return {Promise<QvdDataFrame>} The data frame of the QVD file.
|
|
1964
1996
|
*/
|
|
1965
1997
|
static async fromQvd(path3, options = {}) {
|
|
1966
1998
|
const { QvdFileReader: QvdFileReader2 } = await Promise.resolve().then(() => (init_QvdFileReader(), QvdFileReader_exports));
|
|
1967
|
-
const readerOptions = {
|
|
1999
|
+
const readerOptions = {
|
|
2000
|
+
allowedDir: options.allowedDir,
|
|
2001
|
+
memorySafetyFactor: options.memorySafetyFactor
|
|
2002
|
+
};
|
|
1968
2003
|
return await new QvdFileReader2(path3, readerOptions).load(options.maxRows !== void 0 ? options.maxRows : null);
|
|
1969
2004
|
}
|
|
1970
2005
|
/**
|