Feature/improve portfolio snapshot caching (#3717)
* Improve portfolio snapshot caching * Update changelog
This commit is contained in:
parent
c6f804f68c
commit
71a568264d
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Reworked the portfolio calculator
|
- Reworked the portfolio calculator
|
||||||
|
- Improved the caching of the portfolio snapshot in the portfolio calculator by returning cached data and recalculating in the background when it expires
|
||||||
- Exposed the log levels as an environment variable (`LOG_LEVELS`)
|
- Exposed the log levels as an environment variable (`LOG_LEVELS`)
|
||||||
- Exposed the maximum of chart data items as an environment variable (`MAX_CHART_ITEMS`)
|
- Exposed the maximum of chart data items as an environment variable (`MAX_CHART_ITEMS`)
|
||||||
- Changed the data format of the environment variable `CACHE_QUOTES_TTL` from seconds to milliseconds
|
- Changed the data format of the environment variable `CACHE_QUOTES_TTL` from seconds to milliseconds
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
||||||
|
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
|
||||||
import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface';
|
import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface';
|
||||||
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
||||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
@ -32,6 +33,7 @@ import { Logger } from '@nestjs/common';
|
|||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
|
addMilliseconds,
|
||||||
differenceInDays,
|
differenceInDays,
|
||||||
eachDayOfInterval,
|
eachDayOfInterval,
|
||||||
endOfDay,
|
endOfDay,
|
||||||
@ -863,6 +865,29 @@ export abstract class PortfolioCalculator {
|
|||||||
return chartDateMap;
|
return chartDateMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async computeAndCacheSnapshot() {
|
||||||
|
const snapshot = await this.computeSnapshot();
|
||||||
|
|
||||||
|
const expiration = addMilliseconds(
|
||||||
|
new Date(),
|
||||||
|
this.configurationService.get('CACHE_QUOTES_TTL')
|
||||||
|
);
|
||||||
|
|
||||||
|
this.redisCacheService.set(
|
||||||
|
this.redisCacheService.getPortfolioSnapshotKey({
|
||||||
|
filters: this.filters,
|
||||||
|
userId: this.userId
|
||||||
|
}),
|
||||||
|
JSON.stringify(<PortfolioSnapshotValue>(<unknown>{
|
||||||
|
expiration: expiration.getTime(),
|
||||||
|
portfolioSnapshot: snapshot
|
||||||
|
})),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
@LogPerformance
|
@LogPerformance
|
||||||
private computeTransactionPoints() {
|
private computeTransactionPoints() {
|
||||||
this.transactionPoints = [];
|
this.transactionPoints = [];
|
||||||
@ -1006,19 +1031,33 @@ export abstract class PortfolioCalculator {
|
|||||||
private async initialize() {
|
private async initialize() {
|
||||||
const startTimeTotal = performance.now();
|
const startTimeTotal = performance.now();
|
||||||
|
|
||||||
const cachedSnapshot = await this.redisCacheService.get(
|
let cachedPortfolioSnapshot: PortfolioSnapshot;
|
||||||
this.redisCacheService.getPortfolioSnapshotKey({
|
let isCachedPortfolioSnapshotExpired = false;
|
||||||
filters: this.filters,
|
|
||||||
userId: this.userId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cachedSnapshot) {
|
try {
|
||||||
this.snapshot = plainToClass(
|
const cachedPortfolioSnapshotValue = await this.redisCacheService.get(
|
||||||
PortfolioSnapshot,
|
this.redisCacheService.getPortfolioSnapshotKey({
|
||||||
JSON.parse(cachedSnapshot)
|
filters: this.filters,
|
||||||
|
userId: this.userId
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { expiration, portfolioSnapshot }: PortfolioSnapshotValue =
|
||||||
|
JSON.parse(cachedPortfolioSnapshotValue);
|
||||||
|
|
||||||
|
cachedPortfolioSnapshot = plainToClass(
|
||||||
|
PortfolioSnapshot,
|
||||||
|
portfolioSnapshot
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isAfter(new Date(), new Date(expiration))) {
|
||||||
|
isCachedPortfolioSnapshotExpired = true;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (cachedPortfolioSnapshot) {
|
||||||
|
this.snapshot = cachedPortfolioSnapshot;
|
||||||
|
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
`Fetched portfolio snapshot from cache in ${(
|
`Fetched portfolio snapshot from cache in ${(
|
||||||
(performance.now() - startTimeTotal) /
|
(performance.now() - startTimeTotal) /
|
||||||
@ -1026,17 +1065,14 @@ export abstract class PortfolioCalculator {
|
|||||||
).toFixed(3)} seconds`,
|
).toFixed(3)} seconds`,
|
||||||
'PortfolioCalculator'
|
'PortfolioCalculator'
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
this.snapshot = await this.computeSnapshot();
|
|
||||||
|
|
||||||
this.redisCacheService.set(
|
if (isCachedPortfolioSnapshotExpired) {
|
||||||
this.redisCacheService.getPortfolioSnapshotKey({
|
// Compute in the background
|
||||||
filters: this.filters,
|
this.computeAndCacheSnapshot();
|
||||||
userId: this.userId
|
}
|
||||||
}),
|
} else {
|
||||||
JSON.stringify(this.snapshot),
|
// Wait for computation
|
||||||
this.configurationService.get('CACHE_QUOTES_TTL')
|
this.snapshot = await this.computeAndCacheSnapshot();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
export interface PortfolioSnapshotValue {
|
||||||
|
expiration: number;
|
||||||
|
portfolioSnapshot: string;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user