@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.mjs
CHANGED
|
@@ -32,11 +32,6 @@ const ONE_MINUTE = 60 * ONE_SECOND;
|
|
|
32
32
|
*/
|
|
33
33
|
const DEFAULT_TTL = 30 * ONE_MINUTE;
|
|
34
34
|
/**
|
|
35
|
-
* Default stale window in milliseconds after expiration.
|
|
36
|
-
* Allows serving slightly outdated data while fetching fresh data.
|
|
37
|
-
*/
|
|
38
|
-
const DEFAULT_STALE_WINDOW = 0;
|
|
39
|
-
/**
|
|
40
35
|
* Maximum number of entries the cache can hold.
|
|
41
36
|
* Beyond this limit, new entries are ignored.
|
|
42
37
|
*/
|
|
@@ -60,15 +55,9 @@ const DEFAULT_MAX_MEMORY_SIZE = Infinity;
|
|
|
60
55
|
const MAX_KEYS_PER_BATCH = 1e3;
|
|
61
56
|
/**
|
|
62
57
|
* Minimal expired ratio enforced during sweeps.
|
|
63
|
-
* Ensures control sweeps run above {@link EXPIRED_RATIO_MEMORY_THRESHOLD}.
|
|
64
58
|
*/
|
|
65
59
|
const MINIMAL_EXPIRED_RATIO = .05;
|
|
66
60
|
/**
|
|
67
|
-
* Memory usage threshold (normalized 0–1) triggering control sweeps.
|
|
68
|
-
* At or above this level, sweeping becomes more aggressive.
|
|
69
|
-
*/
|
|
70
|
-
const EXPIRED_RATIO_MEMORY_THRESHOLD = .8;
|
|
71
|
-
/**
|
|
72
61
|
* Maximum allowed expired ratio when memory usage is low.
|
|
73
62
|
* Upper bound for interpolation with MINIMAL_EXPIRED_RATIO.
|
|
74
63
|
* Recommended range: `0.3 – 0.5` .
|
|
@@ -86,21 +75,6 @@ const DEFAULT_MAX_EXPIRED_RATIO = .4;
|
|
|
86
75
|
*/
|
|
87
76
|
const OPTIMAL_SWEEP_INTERVAL = 2 * ONE_SECOND;
|
|
88
77
|
/**
|
|
89
|
-
* Worst-case interval in milliseconds between sweeps.
|
|
90
|
-
* Used when system load is high or metrics unavailable.
|
|
91
|
-
*/
|
|
92
|
-
const WORST_SWEEP_INTERVAL = 200;
|
|
93
|
-
/**
|
|
94
|
-
* Maximum time budget in milliseconds for sweep operations.
|
|
95
|
-
* Prevents sweeping from consuming excessive CPU during high load.
|
|
96
|
-
*/
|
|
97
|
-
const WORST_SWEEP_TIME_BUDGET = 40;
|
|
98
|
-
/**
|
|
99
|
-
* Optimal time budget in milliseconds for each sweep cycle.
|
|
100
|
-
* Used when performance metrics are not available or unreliable.
|
|
101
|
-
*/
|
|
102
|
-
const OPTIMAL_SWEEP_TIME_BUDGET_IF_NOTE_METRICS_AVAILABLE = 15;
|
|
103
|
-
/**
|
|
104
78
|
* ===================================================================
|
|
105
79
|
* Memory Management
|
|
106
80
|
* Process limits and memory-safe thresholds.
|
|
@@ -113,18 +87,6 @@ const OPTIMAL_SWEEP_TIME_BUDGET_IF_NOTE_METRICS_AVAILABLE = 15;
|
|
|
113
87
|
*/
|
|
114
88
|
const DEFAULT_MAX_PROCESS_MEMORY_MB = 1024;
|
|
115
89
|
/**
|
|
116
|
-
* ===================================================================
|
|
117
|
-
* System Utilization Weights
|
|
118
|
-
* Balance how memory, CPU, and event-loop pressure influence sweep behavior.
|
|
119
|
-
* Sum of all weights: 10 + 8.5 + 6.5 = 25
|
|
120
|
-
* ===================================================================
|
|
121
|
-
*/
|
|
122
|
-
/**
|
|
123
|
-
* Weight applied to memory utilization in sweep calculations.
|
|
124
|
-
* Higher weight = memory pressure has more influence on sweep aggressiveness.
|
|
125
|
-
*/
|
|
126
|
-
const DEFAULT_MEMORY_WEIGHT = 10;
|
|
127
|
-
/**
|
|
128
90
|
* Weight applied to CPU utilization in sweep calculations.
|
|
129
91
|
* Combined with event-loop weight to balance CPU-related pressure.
|
|
130
92
|
*/
|
|
@@ -135,22 +97,6 @@ const DEFAULT_CPU_WEIGHT = 8.5;
|
|
|
135
97
|
*/
|
|
136
98
|
const DEFAULT_LOOP_WEIGHT = 6.5;
|
|
137
99
|
/**
|
|
138
|
-
* Fallback behavior for stale purging on GET
|
|
139
|
-
* when no resource limits are defined.
|
|
140
|
-
*
|
|
141
|
-
* In this scenario, threshold-based purging is disabled,
|
|
142
|
-
* so GET operations do NOT purge stale entries.
|
|
143
|
-
*/
|
|
144
|
-
const DEFAULT_PURGE_STALE_ON_GET_NO_LIMITS = false;
|
|
145
|
-
/**
|
|
146
|
-
* Fallback behavior for stale purging on SWEEP
|
|
147
|
-
* when no resource limits are defined.
|
|
148
|
-
*
|
|
149
|
-
* In this scenario, threshold-based purging is disabled,
|
|
150
|
-
* so SWEEP operations DO purge stale entries to prevent buildup.
|
|
151
|
-
*/
|
|
152
|
-
const DEFAULT_PURGE_STALE_ON_SWEEP_NO_LIMITS = true;
|
|
153
|
-
/**
|
|
154
100
|
* Default threshold for purging stale entries on get operations (backend with limits).
|
|
155
101
|
* Stale entries are purged when resource usage exceeds 80%.
|
|
156
102
|
*
|
|
@@ -314,7 +260,7 @@ const resolvePurgeStaleOnGet = (config) => resolvePurgeMode(config.limits, {
|
|
|
314
260
|
operation: "purgeStaleOnGet"
|
|
315
261
|
}, {
|
|
316
262
|
withLimits: DEFAULT_PURGE_STALE_ON_GET_THRESHOLD,
|
|
317
|
-
withoutLimits:
|
|
263
|
+
withoutLimits: false
|
|
318
264
|
}, config.userValue);
|
|
319
265
|
|
|
320
266
|
//#endregion
|
|
@@ -563,7 +509,7 @@ function startMonitor() {
|
|
|
563
509
|
callback(metrics) {
|
|
564
510
|
_metrics = metrics;
|
|
565
511
|
},
|
|
566
|
-
interval:
|
|
512
|
+
interval: 200,
|
|
567
513
|
maxMemory: maxMemoryLimit
|
|
568
514
|
});
|
|
569
515
|
_monitorInstance.start();
|
|
@@ -620,10 +566,10 @@ function interpolate({ value, fromStart, fromEnd, toStart, toEnd }) {
|
|
|
620
566
|
* @returns Interpolated sweep interval, time budget, and the ratio used.
|
|
621
567
|
*/
|
|
622
568
|
const calculateOptimalSweepParams = (options) => {
|
|
623
|
-
const { metrics, weights = {}, optimalSweepIntervalMs = OPTIMAL_SWEEP_INTERVAL, worstSweepIntervalMs =
|
|
624
|
-
const memoryWeight = weights.memory ??
|
|
625
|
-
const cpuWeight = weights.cpu ??
|
|
626
|
-
const loopWeight = weights.loop ??
|
|
569
|
+
const { metrics, weights = {}, optimalSweepIntervalMs = OPTIMAL_SWEEP_INTERVAL, worstSweepIntervalMs = 200, worstSweepTimeBudgetMs = 40 } = options;
|
|
570
|
+
const memoryWeight = weights.memory ?? 10;
|
|
571
|
+
const cpuWeight = weights.cpu ?? 8.5;
|
|
572
|
+
const loopWeight = weights.loop ?? 6.5;
|
|
627
573
|
const memoryUtilization = metrics?.memory.utilization ?? 0;
|
|
628
574
|
const cpuUtilizationRaw = metrics?.cpu.utilization ?? 0;
|
|
629
575
|
const loopUtilizationRaw = metrics?.loop.utilization ?? 0;
|
|
@@ -671,38 +617,27 @@ const calculateOptimalSweepParams = (options) => {
|
|
|
671
617
|
* or `undefined` if the cache is empty.
|
|
672
618
|
*/
|
|
673
619
|
function _selectInstanceToSweep({ totalSweepWeight, batchSweep }) {
|
|
674
|
-
let instanceToSweep = _instancesCache[0];
|
|
675
620
|
if (totalSweepWeight <= 0) {
|
|
676
|
-
if (batchSweep > _instancesCache.length)
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
instanceToSweep = inst;
|
|
684
|
-
break;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
621
|
+
if (batchSweep > _instancesCache.length) return null;
|
|
622
|
+
return _instancesCache[batchSweep - 1];
|
|
623
|
+
}
|
|
624
|
+
let threshold = Math.random() * totalSweepWeight;
|
|
625
|
+
for (const inst of _instancesCache) {
|
|
626
|
+
threshold -= inst._sweepWeight;
|
|
627
|
+
if (threshold <= 0) return inst;
|
|
687
628
|
}
|
|
688
|
-
return
|
|
629
|
+
return _instancesCache[0];
|
|
689
630
|
}
|
|
690
631
|
|
|
691
632
|
//#endregion
|
|
692
633
|
//#region src/cache/delete.ts
|
|
693
|
-
let DELETE_REASON = /* @__PURE__ */ function(DELETE_REASON$1) {
|
|
694
|
-
DELETE_REASON$1["MANUAL"] = "manual";
|
|
695
|
-
DELETE_REASON$1["EXPIRED"] = "expired";
|
|
696
|
-
DELETE_REASON$1["STALE"] = "stale";
|
|
697
|
-
return DELETE_REASON$1;
|
|
698
|
-
}({});
|
|
699
634
|
/**
|
|
700
635
|
* Deletes a key from the cache.
|
|
701
636
|
* @param state - The cache state.
|
|
702
637
|
* @param key - The key.
|
|
703
638
|
* @returns A boolean indicating whether the key was successfully deleted.
|
|
704
639
|
*/
|
|
705
|
-
const deleteKey = (state, key, reason =
|
|
640
|
+
const deleteKey = (state, key, reason = "manual") => {
|
|
706
641
|
const onDelete = state.onDelete;
|
|
707
642
|
const onExpire = state.onExpire;
|
|
708
643
|
if (!onDelete && !onExpire) return state.store.delete(key);
|
|
@@ -710,7 +645,7 @@ const deleteKey = (state, key, reason = DELETE_REASON.MANUAL) => {
|
|
|
710
645
|
if (!entry) return false;
|
|
711
646
|
state.store.delete(key);
|
|
712
647
|
state.onDelete?.(key, entry[1], reason);
|
|
713
|
-
if (reason !==
|
|
648
|
+
if (reason !== "manual") state.onExpire?.(key, entry[1], reason);
|
|
714
649
|
return true;
|
|
715
650
|
};
|
|
716
651
|
|
|
@@ -719,14 +654,14 @@ const deleteKey = (state, key, reason = DELETE_REASON.MANUAL) => {
|
|
|
719
654
|
/**
|
|
720
655
|
* Entry status: fresh, stale, or expired.
|
|
721
656
|
*/
|
|
722
|
-
let ENTRY_STATUS = /* @__PURE__ */ function(ENTRY_STATUS
|
|
657
|
+
let ENTRY_STATUS = /* @__PURE__ */ function(ENTRY_STATUS) {
|
|
723
658
|
/** Valid and within TTL. */
|
|
724
|
-
ENTRY_STATUS
|
|
659
|
+
ENTRY_STATUS["FRESH"] = "fresh";
|
|
725
660
|
/** Expired but within stale window; still served. */
|
|
726
|
-
ENTRY_STATUS
|
|
661
|
+
ENTRY_STATUS["STALE"] = "stale";
|
|
727
662
|
/** Beyond stale window; not served. */
|
|
728
|
-
ENTRY_STATUS
|
|
729
|
-
return ENTRY_STATUS
|
|
663
|
+
ENTRY_STATUS["EXPIRED"] = "expired";
|
|
664
|
+
return ENTRY_STATUS;
|
|
730
665
|
}({});
|
|
731
666
|
|
|
732
667
|
//#endregion
|
|
@@ -752,22 +687,22 @@ let ENTRY_STATUS = /* @__PURE__ */ function(ENTRY_STATUS$1) {
|
|
|
752
687
|
function _statusFromTags(state, entry) {
|
|
753
688
|
const entryCreatedAt = entry[0][0];
|
|
754
689
|
let earliestTagStaleInvalidation = Infinity;
|
|
755
|
-
let status =
|
|
690
|
+
let status = "fresh";
|
|
756
691
|
const tags = entry[2];
|
|
757
692
|
if (tags) for (const tag of tags) {
|
|
758
693
|
const ts = state._tags.get(tag);
|
|
759
694
|
if (!ts) continue;
|
|
760
695
|
const [tagExpiredAt, tagStaleSinceAt] = ts;
|
|
761
696
|
if (tagExpiredAt >= entryCreatedAt) {
|
|
762
|
-
status =
|
|
697
|
+
status = "expired";
|
|
763
698
|
break;
|
|
764
699
|
}
|
|
765
700
|
if (tagStaleSinceAt >= entryCreatedAt) {
|
|
766
701
|
if (tagStaleSinceAt < earliestTagStaleInvalidation) earliestTagStaleInvalidation = tagStaleSinceAt;
|
|
767
|
-
status =
|
|
702
|
+
status = "stale";
|
|
768
703
|
}
|
|
769
704
|
}
|
|
770
|
-
return [status, status ===
|
|
705
|
+
return [status, status === "stale" ? earliestTagStaleInvalidation : 0];
|
|
771
706
|
}
|
|
772
707
|
|
|
773
708
|
//#endregion
|
|
@@ -790,12 +725,12 @@ function _statusFromTags(state, entry) {
|
|
|
790
725
|
function computeEntryStatus(state, entry, now) {
|
|
791
726
|
const [__createdAt, expiresAt, staleExpiresAt] = entry[0];
|
|
792
727
|
const [tagStatus, earliestTagStaleInvalidation] = _statusFromTags(state, entry);
|
|
793
|
-
if (tagStatus ===
|
|
728
|
+
if (tagStatus === "expired") return "expired";
|
|
794
729
|
const windowStale = staleExpiresAt - expiresAt;
|
|
795
|
-
if (tagStatus ===
|
|
796
|
-
if (now < expiresAt) return
|
|
797
|
-
if (staleExpiresAt > 0 && now < staleExpiresAt) return
|
|
798
|
-
return
|
|
730
|
+
if (tagStatus === "stale" && staleExpiresAt > 0 && now < earliestTagStaleInvalidation + windowStale && now <= staleExpiresAt) return "stale";
|
|
731
|
+
if (now < expiresAt) return "fresh";
|
|
732
|
+
if (staleExpiresAt > 0 && now < staleExpiresAt) return "stale";
|
|
733
|
+
return "expired";
|
|
799
734
|
}
|
|
800
735
|
/**
|
|
801
736
|
* Determines whether a cache entry is fresh.
|
|
@@ -813,8 +748,8 @@ function computeEntryStatus(state, entry, now) {
|
|
|
813
748
|
* @returns True if the entry is fresh.
|
|
814
749
|
*/
|
|
815
750
|
const isFresh = (state, entry, now) => {
|
|
816
|
-
if (typeof entry === "string") return entry ===
|
|
817
|
-
return computeEntryStatus(state, entry, now) ===
|
|
751
|
+
if (typeof entry === "string") return entry === "fresh";
|
|
752
|
+
return computeEntryStatus(state, entry, now) === "fresh";
|
|
818
753
|
};
|
|
819
754
|
/**
|
|
820
755
|
* Determines whether a cache entry is stale.
|
|
@@ -832,8 +767,8 @@ const isFresh = (state, entry, now) => {
|
|
|
832
767
|
* @returns True if the entry is stale.
|
|
833
768
|
*/
|
|
834
769
|
const isStale = (state, entry, now) => {
|
|
835
|
-
if (typeof entry === "string") return entry ===
|
|
836
|
-
return computeEntryStatus(state, entry, now) ===
|
|
770
|
+
if (typeof entry === "string") return entry === "stale";
|
|
771
|
+
return computeEntryStatus(state, entry, now) === "stale";
|
|
837
772
|
};
|
|
838
773
|
/**
|
|
839
774
|
* Determines whether a cache entry is expired.
|
|
@@ -851,8 +786,8 @@ const isStale = (state, entry, now) => {
|
|
|
851
786
|
* @returns True if the entry is expired.
|
|
852
787
|
*/
|
|
853
788
|
const isExpired = (state, entry, now) => {
|
|
854
|
-
if (typeof entry === "string") return entry ===
|
|
855
|
-
return computeEntryStatus(state, entry, now) ===
|
|
789
|
+
if (typeof entry === "string") return entry === "expired";
|
|
790
|
+
return computeEntryStatus(state, entry, now) === "expired";
|
|
856
791
|
};
|
|
857
792
|
|
|
858
793
|
//#endregion
|
|
@@ -924,7 +859,7 @@ const shouldPurge = (mode, state, purgeContext) => {
|
|
|
924
859
|
if (mode === false) return false;
|
|
925
860
|
if (mode === true) return true;
|
|
926
861
|
const userThreshold = Number(mode);
|
|
927
|
-
const defaultPurge = purgeContext === "sweep" ?
|
|
862
|
+
const defaultPurge = purgeContext === "sweep" ? true : false;
|
|
928
863
|
if (Number.isNaN(userThreshold)) return defaultPurge;
|
|
929
864
|
const usage = computeResourceUsage(state);
|
|
930
865
|
if (!usage) return defaultPurge;
|
|
@@ -942,9 +877,11 @@ const shouldPurge = (mode, state, purgeContext) => {
|
|
|
942
877
|
*/
|
|
943
878
|
function _sweepOnce(state, _maxKeysPerBatch = MAX_KEYS_PER_BATCH) {
|
|
944
879
|
if (!state._sweepIter) state._sweepIter = state.store.entries();
|
|
880
|
+
const now = Date.now();
|
|
945
881
|
let processed = 0;
|
|
946
882
|
let expiredCount = 0;
|
|
947
883
|
let staleCount = 0;
|
|
884
|
+
const shouldPurgeStale = shouldPurge(state.purgeStaleOnSweep, state, "sweep");
|
|
948
885
|
for (let i = 0; i < _maxKeysPerBatch; i++) {
|
|
949
886
|
const next = state._sweepIter.next();
|
|
950
887
|
if (next.done) {
|
|
@@ -953,22 +890,20 @@ function _sweepOnce(state, _maxKeysPerBatch = MAX_KEYS_PER_BATCH) {
|
|
|
953
890
|
}
|
|
954
891
|
processed += 1;
|
|
955
892
|
const [key, entry] = next.value;
|
|
956
|
-
const now = Date.now();
|
|
957
893
|
const status = computeEntryStatus(state, entry, now);
|
|
958
894
|
if (isExpired(state, status, now)) {
|
|
959
|
-
deleteKey(state, key,
|
|
895
|
+
deleteKey(state, key, "expired");
|
|
960
896
|
expiredCount += 1;
|
|
961
897
|
} else if (isStale(state, status, now)) {
|
|
962
898
|
staleCount += 1;
|
|
963
|
-
if (
|
|
899
|
+
if (shouldPurgeStale) deleteKey(state, key, "stale");
|
|
964
900
|
}
|
|
965
901
|
}
|
|
966
|
-
const expiredStaleCount = shouldPurge(state.purgeStaleOnSweep, state, "sweep") ? staleCount : 0;
|
|
967
902
|
return {
|
|
968
903
|
processed,
|
|
969
904
|
expiredCount,
|
|
970
905
|
staleCount,
|
|
971
|
-
ratio: processed > 0 ? (expiredCount +
|
|
906
|
+
ratio: processed > 0 ? (expiredCount + (shouldPurgeStale ? staleCount : 0)) / processed : 0
|
|
972
907
|
};
|
|
973
908
|
}
|
|
974
909
|
|
|
@@ -979,8 +914,8 @@ function _sweepOnce(state, _maxKeysPerBatch = MAX_KEYS_PER_BATCH) {
|
|
|
979
914
|
*
|
|
980
915
|
* This function interpolates between `maxAllowExpiredRatio` and `MINIMAL_EXPIRED_RATIO`
|
|
981
916
|
* depending on the memory usage reported by `_metrics`. At low memory usage (0%),
|
|
982
|
-
* the optimal ratio equals `maxAllowExpiredRatio`.
|
|
983
|
-
*
|
|
917
|
+
* the optimal ratio equals `maxAllowExpiredRatio`. At high memory usage
|
|
918
|
+
* the optimal ratio decreases toward `MINIMAL_EXPIRED_RATIO`.
|
|
984
919
|
*
|
|
985
920
|
* @param maxAllowExpiredRatio - The maximum allowed expired ratio at minimal memory usage.
|
|
986
921
|
* Defaults to `DEFAULT_MAX_EXPIRED_RATIO`.
|
|
@@ -990,7 +925,7 @@ function calculateOptimalMaxExpiredRatio(maxAllowExpiredRatio = DEFAULT_MAX_EXPI
|
|
|
990
925
|
const optimalExpiredRatio = interpolate({
|
|
991
926
|
value: _metrics?.memory.utilization ?? 0,
|
|
992
927
|
fromStart: 0,
|
|
993
|
-
fromEnd:
|
|
928
|
+
fromEnd: 1,
|
|
994
929
|
toStart: maxAllowExpiredRatio,
|
|
995
930
|
toEnd: MINIMAL_EXPIRED_RATIO
|
|
996
931
|
});
|
|
@@ -1009,8 +944,6 @@ function calculateOptimalMaxExpiredRatio(maxAllowExpiredRatio = DEFAULT_MAX_EXPI
|
|
|
1009
944
|
* This function complements (`_selectInstanceToSweep`), which is responsible
|
|
1010
945
|
* for selecting the correct instance based on the weights assigned here.
|
|
1011
946
|
*
|
|
1012
|
-
* ---
|
|
1013
|
-
*
|
|
1014
947
|
* ### Sweep systems:
|
|
1015
948
|
* 1. **Normal sweep**
|
|
1016
949
|
* - Runs whenever the percentage of expired keys exceeds the allowed threshold
|
|
@@ -1018,14 +951,7 @@ function calculateOptimalMaxExpiredRatio(maxAllowExpiredRatio = DEFAULT_MAX_EXPI
|
|
|
1018
951
|
* - It is the main cleanup mechanism and is applied proportionally to the
|
|
1019
952
|
* store size and the expired‑key ratio.
|
|
1020
953
|
*
|
|
1021
|
-
* 2. **
|
|
1022
|
-
* - Works exactly like the normal sweep, except it may run even when it
|
|
1023
|
-
* normally wouldn’t.
|
|
1024
|
-
* - Only activates under **high memory pressure**.
|
|
1025
|
-
* - Serves as an additional control mechanism to adjust weights, keep the
|
|
1026
|
-
* system updated, and help prevent memory overflows.
|
|
1027
|
-
*
|
|
1028
|
-
* 3. **Round‑robin sweep (minimal control)**
|
|
954
|
+
* 2. **Round‑robin sweep (minimal control)**
|
|
1029
955
|
* - Always runs, even if the expired ratio is low or memory usage does not
|
|
1030
956
|
* require it.
|
|
1031
957
|
* - Processes a very small number of keys per instance, much smaller than
|
|
@@ -1033,16 +959,6 @@ function calculateOptimalMaxExpiredRatio(maxAllowExpiredRatio = DEFAULT_MAX_EXPI
|
|
|
1033
959
|
* - Its main purpose is to ensure that all instances receive at least a
|
|
1034
960
|
* periodic weight update and minimal expired‑key control.
|
|
1035
961
|
*
|
|
1036
|
-
* ---
|
|
1037
|
-
* #### Important notes:
|
|
1038
|
-
* - A minimum `MINIMAL_EXPIRED_RATIO` (e.g., 5%) is assumed to ensure that
|
|
1039
|
-
* control sweeps can always run under high‑memory scenarios.
|
|
1040
|
-
* - Even with a minimum ratio, the normal sweep and the memory‑conditioned sweep
|
|
1041
|
-
* may **skip execution** if memory usage allows it and the expired ratio is
|
|
1042
|
-
* below the optimal maximum.
|
|
1043
|
-
* - The round‑robin sweep is never skipped: it always runs with a very small,
|
|
1044
|
-
* almost imperceptible cost.
|
|
1045
|
-
*
|
|
1046
962
|
* @returns The total accumulated sweep weight across all cache instances.
|
|
1047
963
|
*/
|
|
1048
964
|
function _updateWeightSweep() {
|
|
@@ -1052,14 +968,10 @@ function _updateWeightSweep() {
|
|
|
1052
968
|
instCache._sweepWeight = 0;
|
|
1053
969
|
continue;
|
|
1054
970
|
}
|
|
1055
|
-
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
if (expiredRatio <= optimalMaxExpiredRatio) {
|
|
1060
|
-
instCache._sweepWeight = 0;
|
|
1061
|
-
continue;
|
|
1062
|
-
}
|
|
971
|
+
const expiredRatio = instCache._expiredRatio;
|
|
972
|
+
if (expiredRatio <= calculateOptimalMaxExpiredRatio(instCache._maxAllowExpiredRatio)) {
|
|
973
|
+
instCache._sweepWeight = 0;
|
|
974
|
+
continue;
|
|
1063
975
|
}
|
|
1064
976
|
instCache._sweepWeight = instCache.store.size * expiredRatio;
|
|
1065
977
|
totalSweepWeight += instCache._sweepWeight;
|
|
@@ -1086,7 +998,7 @@ const sweep = async (state, utilities = {}) => {
|
|
|
1086
998
|
const { schedule = defaultSchedule, yieldFn = defaultYieldFn, now = Date.now(), runOnlyOne = false } = utilities;
|
|
1087
999
|
const startTime = now;
|
|
1088
1000
|
let sweepIntervalMs = OPTIMAL_SWEEP_INTERVAL;
|
|
1089
|
-
let sweepTimeBudgetMs =
|
|
1001
|
+
let sweepTimeBudgetMs = 15;
|
|
1090
1002
|
if (_metrics) ({sweepIntervalMs, sweepTimeBudgetMs} = calculateOptimalSweepParams({ metrics: _metrics }));
|
|
1091
1003
|
const totalSweepWeight = _updateWeightSweep();
|
|
1092
1004
|
const currentExpiredRatios = [];
|
|
@@ -1124,7 +1036,7 @@ const _instancesCache = [];
|
|
|
1124
1036
|
* @returns The initial cache state.
|
|
1125
1037
|
*/
|
|
1126
1038
|
const createCache = (options = {}) => {
|
|
1127
|
-
const { onExpire, onDelete, defaultTtl = DEFAULT_TTL, maxSize = DEFAULT_MAX_SIZE, maxMemorySize = DEFAULT_MAX_MEMORY_SIZE, _maxAllowExpiredRatio = DEFAULT_MAX_EXPIRED_RATIO, defaultStaleWindow =
|
|
1039
|
+
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;
|
|
1128
1040
|
_instanceCount++;
|
|
1129
1041
|
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`);
|
|
1130
1042
|
const resolvedPurgeResourceMetric = purgeResourceMetric ?? resolvePurgeResourceMetric({
|
|
@@ -1193,10 +1105,10 @@ const getWithStatus = (state, key, purgeMode, now = Date.now()) => {
|
|
|
1193
1105
|
const status = computeEntryStatus(state, entry, now);
|
|
1194
1106
|
if (isFresh(state, status, now)) return [status, entry];
|
|
1195
1107
|
if (isStale(state, status, now)) {
|
|
1196
|
-
if (shouldPurge(purgeMode ?? state.purgeStaleOnGet, state, "get")) deleteKey(state, key,
|
|
1108
|
+
if (shouldPurge(purgeMode ?? state.purgeStaleOnGet, state, "get")) deleteKey(state, key, "stale");
|
|
1197
1109
|
return [status, entry];
|
|
1198
1110
|
}
|
|
1199
|
-
deleteKey(state, key,
|
|
1111
|
+
deleteKey(state, key, "expired");
|
|
1200
1112
|
return [status, void 0];
|
|
1201
1113
|
};
|
|
1202
1114
|
/**
|