Better cache

This commit is contained in:
James Cole 2024-07-06 15:42:50 +02:00
parent 7219c90957
commit cba1213dd1
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80

View File

@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
/** /**
@ -48,10 +49,10 @@ class ExchangeRateConverter
*/ */
public function convert(TransactionCurrency $from, TransactionCurrency $to, Carbon $date, string $amount): string public function convert(TransactionCurrency $from, TransactionCurrency $to, Carbon $date, string $amount): string
{ {
if(false === config('cer.enabled')) { if (false === config('cer.enabled')) {
Log::debug('ExchangeRateConverter: disabled, return amount as is.');
return $amount; return $amount;
} }
Log::debug('convert()');
$rate = $this->getCurrencyRate($from, $to, $date); $rate = $this->getCurrencyRate($from, $to, $date);
return bcmul($amount, $rate); return bcmul($amount, $rate);
@ -62,10 +63,10 @@ class ExchangeRateConverter
*/ */
public function getCurrencyRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string public function getCurrencyRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{ {
if(false === config('cer.enabled')) { if (false === config('cer.enabled')) {
Log::debug('ExchangeRateConverter: disabled, return "1".');
return '1'; return '1';
} }
Log::debug('getCurrencyRate()');
$rate = $this->getRate($from, $to, $date); $rate = $this->getRate($from, $to, $date);
return '0' === $rate ? '1' : $rate; return '0' === $rate ? '1' : $rate;
@ -76,25 +77,33 @@ class ExchangeRateConverter
*/ */
private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{ {
Log::debug('getRate()'); $key = $this->getCacheKey($from, $to, $date);
if ($this->isPrepared && $this->noPreparedRates) { $res = Cache::get($key, null);
$fallback = $this->fallback[$from->id][$to->id] ?? '0';
Log::debug(sprintf('Return fallback rate from #%d to #%d on %s: %s', $from->id, $to->id, $date->format('Y-m-d'), $fallback));
return $fallback; // find in cache
if (null !== $res) {
Log::debug(sprintf('ExchangeRateConverter: Return cached rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
return $res;
} }
// first attempt:
// find in database
$rate = $this->getFromDB($from->id, $to->id, $date->format('Y-m-d')); $rate = $this->getFromDB($from->id, $to->id, $date->format('Y-m-d'));
if (null !== $rate) { if (null !== $rate) {
Cache::forever($key, $rate);
Log::debug(sprintf('ExchangeRateConverter: Return DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
return $rate; return $rate;
} }
// no result. perhaps the other way around?
// find reverse in database
$rate = $this->getFromDB($to->id, $from->id, $date->format('Y-m-d')); $rate = $this->getFromDB($to->id, $from->id, $date->format('Y-m-d'));
if (null !== $rate) { if (null !== $rate) {
return bcdiv('1', $rate); $rate = bcdiv('1', $rate);
Cache::forever($key, $rate);
Log::debug(sprintf('ExchangeRateConverter: Return DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
return $rate;
} }
// if nothing in place, fall back on the rate for $from to EUR // fallback scenario.
$first = $this->getEuroRate($from, $date); $first = $this->getEuroRate($from, $date);
$second = $this->getEuroRate($to, $date); $second = $this->getEuroRate($to, $date);
@ -102,17 +111,18 @@ class ExchangeRateConverter
if (0 === bccomp('0', $first) || 0 === bccomp('0', $second)) { if (0 === bccomp('0', $first) || 0 === bccomp('0', $second)) {
Log::warning(sprintf('$first is "%s" and $second is "%s"', $first, $second)); Log::warning(sprintf('$first is "%s" and $second is "%s"', $first, $second));
return '0'; return '1';
} }
$second = bcdiv('1', $second); $second = bcdiv('1', $second);
$rate = bcmul($first, $second);
return bcmul($first, $second); Log::debug(sprintf('ExchangeRateConverter: Return DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
Cache::forever($key, $rate);
return $rate;
} }
private function getFromDB(int $from, int $to, string $date): ?string private function getFromDB(int $from, int $to, string $date): ?string
{ {
Log::debug('getFromDB()');
if ($from === $to) { if ($from === $to) {
return '1'; return '1';
} }
@ -121,7 +131,7 @@ class ExchangeRateConverter
// perhaps the rate has been cached during this particular run // perhaps the rate has been cached during this particular run
$preparedRate = $this->prepared[$date][$from][$to] ?? null; $preparedRate = $this->prepared[$date][$from][$to] ?? null;
if (null !== $preparedRate && 0 !== bccomp('0', $preparedRate)) { if (null !== $preparedRate && 0 !== bccomp('0', $preparedRate)) {
Log::debug(sprintf('Found prepared rate from #%d to #%d on %s.', $from, $to, $date)); Log::debug(sprintf('ExchangeRateConverter: Found prepared rate from #%d to #%d on %s.', $from, $to, $date));
return $preparedRate; return $preparedRate;
} }
@ -133,7 +143,7 @@ class ExchangeRateConverter
if ('' === $rate) { if ('' === $rate) {
return null; return null;
} }
Log::debug(sprintf('Found cached rate from #%d to #%d on %s.', $from, $to, $date)); Log::debug(sprintf('ExchangeRateConverter: Found !cached! rate from #%d to #%d on %s.', $from, $to, $date));
return $rate; return $rate;
} }
@ -145,22 +155,21 @@ class ExchangeRateConverter
->where('to_currency_id', $to) ->where('to_currency_id', $to)
->where('date', '<=', $date) ->where('date', '<=', $date)
->orderBy('date', 'DESC') ->orderBy('date', 'DESC')
->first() ->first();
;
++$this->queryCount; ++$this->queryCount;
$rate = (string)$result?->rate; $rate = (string) $result?->rate;
if ('' === $rate) { if ('' === $rate) {
app('log')->debug(sprintf('Found no rate for #%d->#%d (%s) in the DB.', $from, $to, $date)); app('log')->debug(sprintf('ExchangeRateConverter: Found no rate for #%d->#%d (%s) in the DB.', $from, $to, $date));
return null; return null;
} }
if (0 === bccomp('0', $rate)) { if (0 === bccomp('0', $rate)) {
app('log')->debug(sprintf('Found rate for #%d->#%d (%s) in the DB, but it\'s zero.', $from, $to, $date)); app('log')->debug(sprintf('ExchangeRateConverter: Found rate for #%d->#%d (%s) in the DB, but it\'s zero.', $from, $to, $date));
return null; return null;
} }
app('log')->debug(sprintf('Found rate for #%d->#%d (%s) in the DB: %s.', $from, $to, $date, $rate)); app('log')->debug(sprintf('ExchangeRateConverter: Found rate for #%d->#%d (%s) in the DB: %s.', $from, $to, $date, $rate));
$cache->store($rate); $cache->store($rate);
// if the rate has not been cached during this particular run, save it // if the rate has not been cached during this particular run, save it
@ -184,7 +193,6 @@ class ExchangeRateConverter
*/ */
private function getEuroRate(TransactionCurrency $currency, Carbon $date): string private function getEuroRate(TransactionCurrency $currency, Carbon $date): string
{ {
Log::debug('getEuroRate()');
$euroId = $this->getEuroId(); $euroId = $this->getEuroId();
if ($euroId === $currency->id) { if ($euroId === $currency->id) {
return '1'; return '1';
@ -204,7 +212,7 @@ class ExchangeRateConverter
// grab backup values from config file: // grab backup values from config file:
$backup = config(sprintf('cer.rates.%s', $currency->code)); $backup = config(sprintf('cer.rates.%s', $currency->code));
if (null !== $backup) { if (null !== $backup) {
return bcdiv('1', (string)$backup); return bcdiv('1', (string) $backup);
// app('log')->debug(sprintf('Backup rate for %s to EUR is %s.', $currency->code, $backup)); // app('log')->debug(sprintf('Backup rate for %s to EUR is %s.', $currency->code, $backup));
// return $backup; // return $backup;
} }
@ -222,7 +230,7 @@ class ExchangeRateConverter
$cache = new CacheProperties(); $cache = new CacheProperties();
$cache->addProperty('cer-euro-id'); $cache->addProperty('cer-euro-id');
if ($cache->has()) { if ($cache->has()) {
return (int)$cache->get(); return (int) $cache->get();
} }
$euro = TransactionCurrency::whereCode('EUR')->first(); $euro = TransactionCurrency::whereCode('EUR')->first();
++$this->queryCount; ++$this->queryCount;
@ -239,7 +247,7 @@ class ExchangeRateConverter
*/ */
public function prepare(TransactionCurrency $from, TransactionCurrency $to, Carbon $start, Carbon $end): void public function prepare(TransactionCurrency $from, TransactionCurrency $to, Carbon $start, Carbon $end): void
{ {
if(false === config('cer.enabled')) { if (false === config('cer.enabled')) {
return; return;
} }
Log::debug('prepare()'); Log::debug('prepare()');
@ -252,8 +260,7 @@ class ExchangeRateConverter
->where('to_currency_id', $to->id) ->where('to_currency_id', $to->id)
->where('date', '<=', $end->format('Y-m-d')) ->where('date', '<=', $end->format('Y-m-d'))
->where('date', '>=', $start->format('Y-m-d')) ->where('date', '>=', $start->format('Y-m-d'))
->orderBy('date', 'DESC')->get() ->orderBy('date', 'DESC')->get();
;
++$this->queryCount; ++$this->queryCount;
if (0 === $set->count()) { if (0 === $set->count()) {
Log::debug('No prepared rates found in this period, use the fallback'); Log::debug('No prepared rates found in this period, use the fallback');
@ -314,9 +321,14 @@ class ExchangeRateConverter
public function summarize(): void public function summarize(): void
{ {
if(false === config('cer.enabled')) { if (false === config('cer.enabled')) {
return; return;
} }
Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount)); Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount));
} }
private function getCacheKey(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{
return sprintf('cer-%d-%d-%s', $from->id, $to->id, $date->format('Y-m-d'));
}
} }