@neezco/cache 0.5.0 → 0.7.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/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.js +43 -105
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +58 -145
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +2 -2
- package/dist/node/index.d.mts +2 -2
- package/dist/node/index.mjs +54 -142
- package/dist/node/index.mjs.map +1 -1
- package/package.json +16 -26
- package/CHANGELOG.md +0 -63
- package/docs/.gitkeep +0 -0
package/dist/node/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -26,9 +27,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
27
|
|
|
27
28
|
//#endregion
|
|
28
29
|
let fs = require("fs");
|
|
29
|
-
fs = __toESM(fs);
|
|
30
|
+
fs = __toESM(fs, 1);
|
|
30
31
|
let v8 = require("v8");
|
|
31
|
-
v8 = __toESM(v8);
|
|
32
|
+
v8 = __toESM(v8, 1);
|
|
32
33
|
let perf_hooks = require("perf_hooks");
|
|
33
34
|
|
|
34
35
|
//#region src/cache/clear.ts
|
|
@@ -61,11 +62,6 @@ const ONE_MINUTE = 60 * ONE_SECOND;
|
|
|
61
62
|
*/
|
|
62
63
|
const DEFAULT_TTL = 30 * ONE_MINUTE;
|
|
63
64
|
/**
|
|
64
|
-
* Default stale window in milliseconds after expiration.
|
|
65
|
-
* Allows serving slightly outdated data while fetching fresh data.
|
|
66
|
-
*/
|
|
67
|
-
const DEFAULT_STALE_WINDOW = 0;
|
|
68
|
-
/**
|
|
69
65
|
* Maximum number of entries the cache can hold.
|
|
70
66
|
* Beyond this limit, new entries are ignored.
|
|
71
67
|
*/
|
|
@@ -89,15 +85,9 @@ const DEFAULT_MAX_MEMORY_SIZE = Infinity;
|
|
|
89
85
|
const MAX_KEYS_PER_BATCH = 1e3;
|
|
90
86
|
/**
|
|
91
87
|
* Minimal expired ratio enforced during sweeps.
|
|
92
|
-
* Ensures control sweeps run above {@link EXPIRED_RATIO_MEMORY_THRESHOLD}.
|
|
93
88
|
*/
|
|
94
89
|
const MINIMAL_EXPIRED_RATIO = .05;
|
|
95
90
|
/**
|
|
96
|
-
* Memory usage threshold (normalized 0–1) triggering control sweeps.
|
|
97
|
-
* At or above this level, sweeping becomes more aggressive.
|
|
98
|
-
*/
|
|
99
|
-
const EXPIRED_RATIO_MEMORY_THRESHOLD = .8;
|
|
100
|
-
/**
|
|
101
91
|
* Maximum allowed expired ratio when memory usage is low.
|
|
102
92
|
* Upper bound for interpolation with MINIMAL_EXPIRED_RATIO.
|
|
103
93
|
* Recommended range: `0.3 – 0.5` .
|
|
@@ -115,21 +105,6 @@ const DEFAULT_MAX_EXPIRED_RATIO = .4;
|
|
|
115
105
|
*/
|
|
116
106
|
const OPTIMAL_SWEEP_INTERVAL = 2 * ONE_SECOND;
|
|
117
107
|
/**
|
|
118
|
-
* Worst-case interval in milliseconds between sweeps.
|
|
119
|
-
* Used when system load is high or metrics unavailable.
|
|
120
|
-
*/
|
|
121
|
-
const WORST_SWEEP_INTERVAL = 200;
|
|
122
|
-
/**
|
|
123
|
-
* Maximum time budget in milliseconds for sweep operations.
|
|
124
|
-
* Prevents sweeping from consuming excessive CPU during high load.
|
|
125
|
-
*/
|
|
126
|
-
const WORST_SWEEP_TIME_BUDGET = 40;
|
|
127
|
-
/**
|
|
128
|
-
* Optimal time budget in milliseconds for each sweep cycle.
|
|
129
|
-
* Used when performance metrics are not available or unreliable.
|
|
130
|
-
*/
|
|
131
|
-
const OPTIMAL_SWEEP_TIME_BUDGET_IF_NOTE_METRICS_AVAILABLE = 15;
|
|
132
|
-
/**
|
|
133
108
|
* ===================================================================
|
|
134
109
|
* Memory Management
|
|
135
110
|
* Process limits and memory-safe thresholds.
|
|
@@ -142,18 +117,6 @@ const OPTIMAL_SWEEP_TIME_BUDGET_IF_NOTE_METRICS_AVAILABLE = 15;
|
|
|
142
117
|
*/
|
|
143
118
|
const DEFAULT_MAX_PROCESS_MEMORY_MB = 1024;
|
|
144
119
|
/**
|
|
145
|
-
* ===================================================================
|
|
146
|
-
* System Utilization Weights
|
|
147
|
-
* Balance how memory, CPU, and event-loop pressure influence sweep behavior.
|
|
148
|
-
* Sum of all weights: 10 + 8.5 + 6.5 = 25
|
|
149
|
-
* ===================================================================
|
|
150
|
-
*/
|
|
151
|
-
/**
|
|
152
|
-
* Weight applied to memory utilization in sweep calculations.
|
|
153
|
-
* Higher weight = memory pressure has more influence on sweep aggressiveness.
|
|
154
|
-
*/
|
|
155
|
-
const DEFAULT_MEMORY_WEIGHT = 10;
|
|
156
|
-
/**
|
|
157
120
|
* Weight applied to CPU utilization in sweep calculations.
|
|
158
121
|
* Combined with event-loop weight to balance CPU-related pressure.
|
|
159
122
|
*/
|
|
@@ -164,22 +127,6 @@ const DEFAULT_CPU_WEIGHT = 8.5;
|
|
|
164
127
|
*/
|
|
165
128
|
const DEFAULT_LOOP_WEIGHT = 6.5;
|
|
166
129
|
/**
|
|
167
|
-
* Fallback behavior for stale purging on GET
|
|
168
|
-
* when no resource limits are defined.
|
|
169
|
-
*
|
|
170
|
-
* In this scenario, threshold-based purging is disabled,
|
|
171
|
-
* so GET operations do NOT purge stale entries.
|
|
172
|
-
*/
|
|
173
|
-
const DEFAULT_PURGE_STALE_ON_GET_NO_LIMITS = false;
|
|
174
|
-
/**
|
|
175
|
-
* Fallback behavior for stale purging on SWEEP
|
|
176
|
-
* when no resource limits are defined.
|
|
177
|
-
*
|
|
178
|
-
* In this scenario, threshold-based purging is disabled,
|
|
179
|
-
* so SWEEP operations DO purge stale entries to prevent buildup.
|
|
180
|
-
*/
|
|
181
|
-
const DEFAULT_PURGE_STALE_ON_SWEEP_NO_LIMITS = true;
|
|
182
|
-
/**
|
|
183
130
|
* Default threshold for purging stale entries on get operations (backend with limits).
|
|
184
131
|
* Stale entries are purged when resource usage exceeds 80%.
|
|
185
132
|
*
|
|
@@ -343,7 +290,7 @@ const resolvePurgeStaleOnGet = (config) => resolvePurgeMode(config.limits, {
|
|
|
343
290
|
operation: "purgeStaleOnGet"
|
|
344
291
|
}, {
|
|
345
292
|
withLimits: DEFAULT_PURGE_STALE_ON_GET_THRESHOLD,
|
|
346
|
-
withoutLimits:
|
|
293
|
+
withoutLimits: false
|
|
347
294
|
}, config.userValue);
|
|
348
295
|
|
|
349
296
|
//#endregion
|
|
@@ -592,7 +539,7 @@ function startMonitor() {
|
|
|
592
539
|
callback(metrics) {
|
|
593
540
|
_metrics = metrics;
|
|
594
541
|
},
|
|
595
|
-
interval:
|
|
542
|
+
interval: 200,
|
|
596
543
|
maxMemory: maxMemoryLimit
|
|
597
544
|
});
|
|
598
545
|
_monitorInstance.start();
|
|
@@ -649,10 +596,10 @@ function interpolate({ value, fromStart, fromEnd, toStart, toEnd }) {
|
|
|
649
596
|
* @returns Interpolated sweep interval, time budget, and the ratio used.
|
|
650
597
|
*/
|
|
651
598
|
const calculateOptimalSweepParams = (options) => {
|
|
652
|
-
const { metrics, weights = {}, optimalSweepIntervalMs = OPTIMAL_SWEEP_INTERVAL, worstSweepIntervalMs =
|
|
653
|
-
const memoryWeight = weights.memory ??
|
|
654
|
-
const cpuWeight = weights.cpu ??
|
|
655
|
-
const loopWeight = weights.loop ??
|
|
599
|
+
const { metrics, weights = {}, optimalSweepIntervalMs = OPTIMAL_SWEEP_INTERVAL, worstSweepIntervalMs = 200, worstSweepTimeBudgetMs = 40 } = options;
|
|
600
|
+
const memoryWeight = weights.memory ?? 10;
|
|
601
|
+
const cpuWeight = weights.cpu ?? 8.5;
|
|
602
|
+
const loopWeight = weights.loop ?? 6.5;
|
|
656
603
|
const memoryUtilization = metrics?.memory.utilization ?? 0;
|
|
657
604
|
const cpuUtilizationRaw = metrics?.cpu.utilization ?? 0;
|
|
658
605
|
const loopUtilizationRaw = metrics?.loop.utilization ?? 0;
|
|
@@ -700,38 +647,27 @@ const calculateOptimalSweepParams = (options) => {
|
|
|
700
647
|
* or `undefined` if the cache is empty.
|
|
701
648
|
*/
|
|
702
649
|
function _selectInstanceToSweep({ totalSweepWeight, batchSweep }) {
|
|
703
|
-
let instanceToSweep = _instancesCache[0];
|
|
704
650
|
if (totalSweepWeight <= 0) {
|
|
705
|
-
if (batchSweep > _instancesCache.length)
|
|
706
|
-
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
instanceToSweep = inst;
|
|
713
|
-
break;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
651
|
+
if (batchSweep > _instancesCache.length) return null;
|
|
652
|
+
return _instancesCache[batchSweep - 1];
|
|
653
|
+
}
|
|
654
|
+
let threshold = Math.random() * totalSweepWeight;
|
|
655
|
+
for (const inst of _instancesCache) {
|
|
656
|
+
threshold -= inst._sweepWeight;
|
|
657
|
+
if (threshold <= 0) return inst;
|
|
716
658
|
}
|
|
717
|
-
return
|
|
659
|
+
return _instancesCache[0];
|
|
718
660
|
}
|
|
719
661
|
|
|
720
662
|
//#endregion
|
|
721
663
|
//#region src/cache/delete.ts
|
|
722
|
-
let DELETE_REASON = /* @__PURE__ */ function(DELETE_REASON$1) {
|
|
723
|
-
DELETE_REASON$1["MANUAL"] = "manual";
|
|
724
|
-
DELETE_REASON$1["EXPIRED"] = "expired";
|
|
725
|
-
DELETE_REASON$1["STALE"] = "stale";
|
|
726
|
-
return DELETE_REASON$1;
|
|
727
|
-
}({});
|
|
728
664
|
/**
|
|
729
665
|
* Deletes a key from the cache.
|
|
730
666
|
* @param state - The cache state.
|
|
731
667
|
* @param key - The key.
|
|
732
668
|
* @returns A boolean indicating whether the key was successfully deleted.
|
|
733
669
|
*/
|
|
734
|
-
const deleteKey = (state, key, reason =
|
|
670
|
+
const deleteKey = (state, key, reason = "manual") => {
|
|
735
671
|
const onDelete = state.onDelete;
|
|
736
672
|
const onExpire = state.onExpire;
|
|
737
673
|
if (!onDelete && !onExpire) return state.store.delete(key);
|
|
@@ -739,7 +675,7 @@ const deleteKey = (state, key, reason = DELETE_REASON.MANUAL) => {
|
|
|
739
675
|
if (!entry) return false;
|
|
740
676
|
state.store.delete(key);
|
|
741
677
|
state.onDelete?.(key, entry[1], reason);
|
|
742
|
-
if (reason !==
|
|
678
|
+
if (reason !== "manual") state.onExpire?.(key, entry[1], reason);
|
|
743
679
|
return true;
|
|
744
680
|
};
|
|
745
681
|
|
|
@@ -748,14 +684,14 @@ const deleteKey = (state, key, reason = DELETE_REASON.MANUAL) => {
|
|
|
748
684
|
/**
|
|
749
685
|
* Entry status: fresh, stale, or expired.
|
|
750
686
|
*/
|
|
751
|
-
let ENTRY_STATUS = /* @__PURE__ */ function(ENTRY_STATUS
|
|
687
|
+
let ENTRY_STATUS = /* @__PURE__ */ function(ENTRY_STATUS) {
|
|
752
688
|
/** Valid and within TTL. */
|
|
753
|
-
ENTRY_STATUS
|
|
689
|
+
ENTRY_STATUS["FRESH"] = "fresh";
|
|
754
690
|
/** Expired but within stale window; still served. */
|
|
755
|
-
ENTRY_STATUS
|
|
691
|
+
ENTRY_STATUS["STALE"] = "stale";
|
|
756
692
|
/** Beyond stale window; not served. */
|
|
757
|
-
ENTRY_STATUS
|
|
758
|
-
return ENTRY_STATUS
|
|
693
|
+
ENTRY_STATUS["EXPIRED"] = "expired";
|
|
694
|
+
return ENTRY_STATUS;
|
|
759
695
|
}({});
|
|
760
696
|
|
|
761
697
|
//#endregion
|
|
@@ -781,22 +717,22 @@ let ENTRY_STATUS = /* @__PURE__ */ function(ENTRY_STATUS$1) {
|
|
|
781
717
|
function _statusFromTags(state, entry) {
|
|
782
718
|
const entryCreatedAt = entry[0][0];
|
|
783
719
|
let earliestTagStaleInvalidation = Infinity;
|
|
784
|
-
let status =
|
|
720
|
+
let status = "fresh";
|
|
785
721
|
const tags = entry[2];
|
|
786
722
|
if (tags) for (const tag of tags) {
|
|
787
723
|
const ts = state._tags.get(tag);
|
|
788
724
|
if (!ts) continue;
|
|
789
725
|
const [tagExpiredAt, tagStaleSinceAt] = ts;
|
|
790
726
|
if (tagExpiredAt >= entryCreatedAt) {
|
|
791
|
-
status =
|
|
727
|
+
status = "expired";
|
|
792
728
|
break;
|
|
793
729
|
}
|
|
794
730
|
if (tagStaleSinceAt >= entryCreatedAt) {
|
|
795
731
|
if (tagStaleSinceAt < earliestTagStaleInvalidation) earliestTagStaleInvalidation = tagStaleSinceAt;
|
|
796
|
-
status =
|
|
732
|
+
status = "stale";
|
|
797
733
|
}
|
|
798
734
|
}
|
|
799
|
-
return [status, status ===
|
|
735
|
+
return [status, status === "stale" ? earliestTagStaleInvalidation : 0];
|
|
800
736
|
}
|
|
801
737
|
|
|
802
738
|
//#endregion
|
|
@@ -819,12 +755,12 @@ function _statusFromTags(state, entry) {
|
|
|
819
755
|
function computeEntryStatus(state, entry, now) {
|
|
820
756
|
const [__createdAt, expiresAt, staleExpiresAt] = entry[0];
|
|
821
757
|
const [tagStatus, earliestTagStaleInvalidation] = _statusFromTags(state, entry);
|
|
822
|
-
if (tagStatus ===
|
|
758
|
+
if (tagStatus === "expired") return "expired";
|
|
823
759
|
const windowStale = staleExpiresAt - expiresAt;
|
|
824
|
-
if (tagStatus ===
|
|
825
|
-
if (now < expiresAt) return
|
|
826
|
-
if (staleExpiresAt > 0 && now < staleExpiresAt) return
|
|
827
|
-
return
|
|
760
|
+
if (tagStatus === "stale" && staleExpiresAt > 0 && now < earliestTagStaleInvalidation + windowStale && now <= staleExpiresAt) return "stale";
|
|
761
|
+
if (now < expiresAt) return "fresh";
|
|
762
|
+
if (staleExpiresAt > 0 && now < staleExpiresAt) return "stale";
|
|
763
|
+
return "expired";
|
|
828
764
|
}
|
|
829
765
|
/**
|
|
830
766
|
* Determines whether a cache entry is fresh.
|
|
@@ -842,8 +778,8 @@ function computeEntryStatus(state, entry, now) {
|
|
|
842
778
|
* @returns True if the entry is fresh.
|
|
843
779
|
*/
|
|
844
780
|
const isFresh = (state, entry, now) => {
|
|
845
|
-
if (typeof entry === "string") return entry ===
|
|
846
|
-
return computeEntryStatus(state, entry, now) ===
|
|
781
|
+
if (typeof entry === "string") return entry === "fresh";
|
|
782
|
+
return computeEntryStatus(state, entry, now) === "fresh";
|
|
847
783
|
};
|
|
848
784
|
/**
|
|
849
785
|
* Determines whether a cache entry is stale.
|
|
@@ -861,8 +797,8 @@ const isFresh = (state, entry, now) => {
|
|
|
861
797
|
* @returns True if the entry is stale.
|
|
862
798
|
*/
|
|
863
799
|
const isStale = (state, entry, now) => {
|
|
864
|
-
if (typeof entry === "string") return entry ===
|
|
865
|
-
return computeEntryStatus(state, entry, now) ===
|
|
800
|
+
if (typeof entry === "string") return entry === "stale";
|
|
801
|
+
return computeEntryStatus(state, entry, now) === "stale";
|
|
866
802
|
};
|
|
867
803
|
/**
|
|
868
804
|
* Determines whether a cache entry is expired.
|
|
@@ -880,8 +816,8 @@ const isStale = (state, entry, now) => {
|
|
|
880
816
|
* @returns True if the entry is expired.
|
|
881
817
|
*/
|
|
882
818
|
const isExpired = (state, entry, now) => {
|
|
883
|
-
if (typeof entry === "string") return entry ===
|
|
884
|
-
return computeEntryStatus(state, entry, now) ===
|
|
819
|
+
if (typeof entry === "string") return entry === "expired";
|
|
820
|
+
return computeEntryStatus(state, entry, now) === "expired";
|
|
885
821
|
};
|
|
886
822
|
|
|
887
823
|
//#endregion
|
|
@@ -953,7 +889,7 @@ const shouldPurge = (mode, state, purgeContext) => {
|
|
|
953
889
|
if (mode === false) return false;
|
|
954
890
|
if (mode === true) return true;
|
|
955
891
|
const userThreshold = Number(mode);
|
|
956
|
-
const defaultPurge = purgeContext === "sweep" ?
|
|
892
|
+
const defaultPurge = purgeContext === "sweep" ? true : false;
|
|
957
893
|
if (Number.isNaN(userThreshold)) return defaultPurge;
|
|
958
894
|
const usage = computeResourceUsage(state);
|
|
959
895
|
if (!usage) return defaultPurge;
|
|
@@ -971,9 +907,11 @@ const shouldPurge = (mode, state, purgeContext) => {
|
|
|
971
907
|
*/
|
|
972
908
|
function _sweepOnce(state, _maxKeysPerBatch = MAX_KEYS_PER_BATCH) {
|
|
973
909
|
if (!state._sweepIter) state._sweepIter = state.store.entries();
|
|
910
|
+
const now = Date.now();
|
|
974
911
|
let processed = 0;
|
|
975
912
|
let expiredCount = 0;
|
|
976
913
|
let staleCount = 0;
|
|
914
|
+
const shouldPurgeStale = shouldPurge(state.purgeStaleOnSweep, state, "sweep");
|
|
977
915
|
for (let i = 0; i < _maxKeysPerBatch; i++) {
|
|
978
916
|
const next = state._sweepIter.next();
|
|
979
917
|
if (next.done) {
|
|
@@ -982,22 +920,20 @@ function _sweepOnce(state, _maxKeysPerBatch = MAX_KEYS_PER_BATCH) {
|
|
|
982
920
|
}
|
|
983
921
|
processed += 1;
|
|
984
922
|
const [key, entry] = next.value;
|
|
985
|
-
const now = Date.now();
|
|
986
923
|
const status = computeEntryStatus(state, entry, now);
|
|
987
924
|
if (isExpired(state, status, now)) {
|
|
988
|
-
deleteKey(state, key,
|
|
925
|
+
deleteKey(state, key, "expired");
|
|
989
926
|
expiredCount += 1;
|
|
990
927
|
} else if (isStale(state, status, now)) {
|
|
991
928
|
staleCount += 1;
|
|
992
|
-
if (
|
|
929
|
+
if (shouldPurgeStale) deleteKey(state, key, "stale");
|
|
993
930
|
}
|
|
994
931
|
}
|
|
995
|
-
const expiredStaleCount = shouldPurge(state.purgeStaleOnSweep, state, "sweep") ? staleCount : 0;
|
|
996
932
|
return {
|
|
997
933
|
processed,
|
|
998
934
|
expiredCount,
|
|
999
935
|
staleCount,
|
|
1000
|
-
ratio: processed > 0 ? (expiredCount +
|
|
936
|
+
ratio: processed > 0 ? (expiredCount + (shouldPurgeStale ? staleCount : 0)) / processed : 0
|
|
1001
937
|
};
|
|
1002
938
|
}
|
|
1003
939
|
|
|
@@ -1008,8 +944,8 @@ function _sweepOnce(state, _maxKeysPerBatch = MAX_KEYS_PER_BATCH) {
|
|
|
1008
944
|
*
|
|
1009
945
|
* This function interpolates between `maxAllowExpiredRatio` and `MINIMAL_EXPIRED_RATIO`
|
|
1010
946
|
* depending on the memory usage reported by `_metrics`. At low memory usage (0%),
|
|
1011
|
-
* the optimal ratio equals `maxAllowExpiredRatio`.
|
|
1012
|
-
*
|
|
947
|
+
* the optimal ratio equals `maxAllowExpiredRatio`. At high memory usage
|
|
948
|
+
* the optimal ratio decreases toward `MINIMAL_EXPIRED_RATIO`.
|
|
1013
949
|
*
|
|
1014
950
|
* @param maxAllowExpiredRatio - The maximum allowed expired ratio at minimal memory usage.
|
|
1015
951
|
* Defaults to `DEFAULT_MAX_EXPIRED_RATIO`.
|
|
@@ -1019,7 +955,7 @@ function calculateOptimalMaxExpiredRatio(maxAllowExpiredRatio = DEFAULT_MAX_EXPI
|
|
|
1019
955
|
const optimalExpiredRatio = interpolate({
|
|
1020
956
|
value: _metrics?.memory.utilization ?? 0,
|
|
1021
957
|
fromStart: 0,
|
|
1022
|
-
fromEnd:
|
|
958
|
+
fromEnd: 1,
|
|
1023
959
|
toStart: maxAllowExpiredRatio,
|
|
1024
960
|
toEnd: MINIMAL_EXPIRED_RATIO
|
|
1025
961
|
});
|
|
@@ -1038,8 +974,6 @@ function calculateOptimalMaxExpiredRatio(maxAllowExpiredRatio = DEFAULT_MAX_EXPI
|
|
|
1038
974
|
* This function complements (`_selectInstanceToSweep`), which is responsible
|
|
1039
975
|
* for selecting the correct instance based on the weights assigned here.
|
|
1040
976
|
*
|
|
1041
|
-
* ---
|
|
1042
|
-
*
|
|
1043
977
|
* ### Sweep systems:
|
|
1044
978
|
* 1. **Normal sweep**
|
|
1045
979
|
* - Runs whenever the percentage of expired keys exceeds the allowed threshold
|
|
@@ -1047,14 +981,7 @@ function calculateOptimalMaxExpiredRatio(maxAllowExpiredRatio = DEFAULT_MAX_EXPI
|
|
|
1047
981
|
* - It is the main cleanup mechanism and is applied proportionally to the
|
|
1048
982
|
* store size and the expired‑key ratio.
|
|
1049
983
|
*
|
|
1050
|
-
* 2. **
|
|
1051
|
-
* - Works exactly like the normal sweep, except it may run even when it
|
|
1052
|
-
* normally wouldn’t.
|
|
1053
|
-
* - Only activates under **high memory pressure**.
|
|
1054
|
-
* - Serves as an additional control mechanism to adjust weights, keep the
|
|
1055
|
-
* system updated, and help prevent memory overflows.
|
|
1056
|
-
*
|
|
1057
|
-
* 3. **Round‑robin sweep (minimal control)**
|
|
984
|
+
* 2. **Round‑robin sweep (minimal control)**
|
|
1058
985
|
* - Always runs, even if the expired ratio is low or memory usage does not
|
|
1059
986
|
* require it.
|
|
1060
987
|
* - Processes a very small number of keys per instance, much smaller than
|
|
@@ -1062,16 +989,6 @@ function calculateOptimalMaxExpiredRatio(maxAllowExpiredRatio = DEFAULT_MAX_EXPI
|
|
|
1062
989
|
* - Its main purpose is to ensure that all instances receive at least a
|
|
1063
990
|
* periodic weight update and minimal expired‑key control.
|
|
1064
991
|
*
|
|
1065
|
-
* ---
|
|
1066
|
-
* #### Important notes:
|
|
1067
|
-
* - A minimum `MINIMAL_EXPIRED_RATIO` (e.g., 5%) is assumed to ensure that
|
|
1068
|
-
* control sweeps can always run under high‑memory scenarios.
|
|
1069
|
-
* - Even with a minimum ratio, the normal sweep and the memory‑conditioned sweep
|
|
1070
|
-
* may **skip execution** if memory usage allows it and the expired ratio is
|
|
1071
|
-
* below the optimal maximum.
|
|
1072
|
-
* - The round‑robin sweep is never skipped: it always runs with a very small,
|
|
1073
|
-
* almost imperceptible cost.
|
|
1074
|
-
*
|
|
1075
992
|
* @returns The total accumulated sweep weight across all cache instances.
|
|
1076
993
|
*/
|
|
1077
994
|
function _updateWeightSweep() {
|
|
@@ -1081,14 +998,10 @@ function _updateWeightSweep() {
|
|
|
1081
998
|
instCache._sweepWeight = 0;
|
|
1082
999
|
continue;
|
|
1083
1000
|
}
|
|
1084
|
-
|
|
1085
|
-
if (
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (expiredRatio <= optimalMaxExpiredRatio) {
|
|
1089
|
-
instCache._sweepWeight = 0;
|
|
1090
|
-
continue;
|
|
1091
|
-
}
|
|
1001
|
+
const expiredRatio = instCache._expiredRatio;
|
|
1002
|
+
if (expiredRatio <= calculateOptimalMaxExpiredRatio(instCache._maxAllowExpiredRatio)) {
|
|
1003
|
+
instCache._sweepWeight = 0;
|
|
1004
|
+
continue;
|
|
1092
1005
|
}
|
|
1093
1006
|
instCache._sweepWeight = instCache.store.size * expiredRatio;
|
|
1094
1007
|
totalSweepWeight += instCache._sweepWeight;
|
|
@@ -1115,7 +1028,7 @@ const sweep = async (state, utilities = {}) => {
|
|
|
1115
1028
|
const { schedule = defaultSchedule, yieldFn = defaultYieldFn, now = Date.now(), runOnlyOne = false } = utilities;
|
|
1116
1029
|
const startTime = now;
|
|
1117
1030
|
let sweepIntervalMs = OPTIMAL_SWEEP_INTERVAL;
|
|
1118
|
-
let sweepTimeBudgetMs =
|
|
1031
|
+
let sweepTimeBudgetMs = 15;
|
|
1119
1032
|
if (_metrics) ({sweepIntervalMs, sweepTimeBudgetMs} = calculateOptimalSweepParams({ metrics: _metrics }));
|
|
1120
1033
|
const totalSweepWeight = _updateWeightSweep();
|
|
1121
1034
|
const currentExpiredRatios = [];
|
|
@@ -1153,7 +1066,7 @@ const _instancesCache = [];
|
|
|
1153
1066
|
* @returns The initial cache state.
|
|
1154
1067
|
*/
|
|
1155
1068
|
const createCache = (options = {}) => {
|
|
1156
|
-
const { onExpire, onDelete, defaultTtl = DEFAULT_TTL, maxSize = DEFAULT_MAX_SIZE, maxMemorySize = DEFAULT_MAX_MEMORY_SIZE, _maxAllowExpiredRatio = DEFAULT_MAX_EXPIRED_RATIO, defaultStaleWindow =
|
|
1069
|
+
const { onExpire, onDelete, defaultTtl = DEFAULT_TTL, maxSize = DEFAULT_MAX_SIZE, maxMemorySize = DEFAULT_MAX_MEMORY_SIZE, _maxAllowExpiredRatio = DEFAULT_MAX_EXPIRED_RATIO, defaultStaleWindow = 0, purgeStaleOnGet, purgeStaleOnSweep, purgeResourceMetric, _autoStartSweep = true } = options;
|
|
1157
1070
|
_instanceCount++;
|
|
1158
1071
|
if (_instanceCount > INSTANCE_WARNING_THRESHOLD) console.warn(`Too many instances detected (${_instanceCount}). This may indicate a configuration issue; consider minimizing instance creation or grouping keys by expected expiration ranges. See the documentation: https://github.com/neezco/cache/docs/getting-started.md`);
|
|
1159
1072
|
const resolvedPurgeResourceMetric = purgeResourceMetric ?? resolvePurgeResourceMetric({
|
|
@@ -1222,10 +1135,10 @@ const getWithStatus = (state, key, purgeMode, now = Date.now()) => {
|
|
|
1222
1135
|
const status = computeEntryStatus(state, entry, now);
|
|
1223
1136
|
if (isFresh(state, status, now)) return [status, entry];
|
|
1224
1137
|
if (isStale(state, status, now)) {
|
|
1225
|
-
if (shouldPurge(purgeMode ?? state.purgeStaleOnGet, state, "get")) deleteKey(state, key,
|
|
1138
|
+
if (shouldPurge(purgeMode ?? state.purgeStaleOnGet, state, "get")) deleteKey(state, key, "stale");
|
|
1226
1139
|
return [status, entry];
|
|
1227
1140
|
}
|
|
1228
|
-
deleteKey(state, key,
|
|
1141
|
+
deleteKey(state, key, "expired");
|
|
1229
1142
|
return [status, void 0];
|
|
1230
1143
|
};
|
|
1231
1144
|
/**
|