Skip to content

Commit 13fd47b

Browse files
authored
Merge pull request #257 from FunD-StockProject/refactor/shortview-performance-improvement2
Refactor: ShortView 성능 개선 시도
2 parents 5009cee + 49f7dc3 commit 13fd47b

2 files changed

Lines changed: 108 additions & 21 deletions

File tree

src/main/java/com/fund/stockProject/shortview/controller/ShortViewController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,10 @@ private boolean isValidPriceInfo(StockInfoResponse stockInfo) {
161161
if (price == null || price <= 0 || !Double.isFinite(price)) {
162162
return false;
163163
}
164-
if (priceDiff == null || !Double.isFinite(priceDiff)) {
164+
if (priceDiff != null && !Double.isFinite(priceDiff)) {
165165
return false;
166166
}
167-
if (priceDiffPerCent == null || !Double.isFinite(priceDiffPerCent)) {
167+
if (priceDiffPerCent != null && !Double.isFinite(priceDiffPerCent)) {
168168
return false;
169169
}
170170
return true;

src/main/java/com/fund/stockProject/stock/service/SecurityService.java

Lines changed: 106 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.Collections;
1212
import java.util.List;
1313
import java.util.Map;
14+
import java.util.concurrent.ConcurrentHashMap;
1415

1516
import org.slf4j.Logger;
1617
import org.slf4j.LoggerFactory;
@@ -29,7 +30,6 @@
2930
import lombok.RequiredArgsConstructor;
3031
import org.springframework.cache.Cache;
3132
import org.springframework.cache.CacheManager;
32-
import org.springframework.cache.annotation.Cacheable;
3333
import reactor.core.publisher.Mono;
3434

3535
@Service
@@ -43,6 +43,7 @@ public class SecurityService {
4343
private final ObjectMapper objectMapper;
4444
private final CacheManager cacheManager;
4545
private static final String STOCK_PRICE_CACHE = "stockPrice";
46+
private final Map<String, Mono<StockInfoResponse>> inFlightPriceRequests = new ConcurrentHashMap<>();
4647

4748
/**
4849
* 국내, 해외 주식 정보 조회
@@ -152,9 +153,7 @@ private Mono<StockInfoResponse> parseFStockInfoKorea2(String response, Integer i
152153

153154
/**
154155
* 국내, 해외 주식 정보 조회
155-
* Redis 캐시: 30초간 동일한 결과 반환 (실시간 가격 변동 고려)
156156
*/
157-
@Cacheable(value = "stockPrice", key = "#symbol + '_' + #exchangenum.name()", unless = "#result == null")
158157
public Mono<StockInfoResponse> getSecurityStockInfoKorea(Integer id, String symbolName, String securityName, String symbol, EXCHANGENUM exchangenum, COUNTRY country) {
159158
if (country == COUNTRY.KOREA) {
160159
return webClient.get()
@@ -251,9 +250,14 @@ private Mono<StockInfoResponse> parseFStockInfoKorea(String response, Integer id
251250
return Mono.error(new UnsupportedOperationException("주가 정보가 없습니다 (output node missing, symbol: " + symbol + ")"));
252251
}
253252

254-
log.debug("Successfully parsed StockInfo (inquire-price) - symbol: {}, price: {}, yesterdayPrice: {}",
255-
symbol, stockInfoResponse.getPrice(), stockInfoResponse.getYesterdayPrice());
256-
return Mono.just(stockInfoResponse);
253+
StockInfoResponse normalized = normalizePriceInfo(stockInfoResponse);
254+
if (normalized == null) {
255+
return Mono.error(new UnsupportedOperationException("주가 정보가 없습니다 (symbol: " + symbol + ")"));
256+
}
257+
258+
log.debug("Successfully parsed StockInfo (inquire-price) - symbol: {}, price: {}, yesterdayPrice: {}",
259+
symbol, normalized.getPrice(), normalized.getYesterdayPrice());
260+
return Mono.just(normalized);
257261
} catch (Exception e) {
258262
log.error("Failed to parse StockInfo response - symbol: {}, response: {}, error: {}",
259263
symbol, response, e.getMessage(), e);
@@ -290,6 +294,10 @@ private Mono<StockInfoResponse> parseFStockInfoOversea(String response, Integer
290294
if (rate != null && diff != null) {
291295
// 해외 diff는 절대값으로 오는 경우가 많아 부호를 rate 기준으로 정규화
292296
stockInfoResponse.setPriceDiff(rate < 0 ? -Math.abs(diff) : Math.abs(diff));
297+
} else if (diff != null) {
298+
stockInfoResponse.setPriceDiff(diff);
299+
}
300+
if (rate != null) {
293301
stockInfoResponse.setPriceDiffPerCent(rate);
294302
}
295303

@@ -302,7 +310,12 @@ private Mono<StockInfoResponse> parseFStockInfoOversea(String response, Integer
302310
return Mono.error(new UnsupportedOperationException("해외 종목 정보가 없습니다 (output node missing, symbol: " + symbol + ")"));
303311
}
304312

305-
return Mono.just(stockInfoResponse);
313+
StockInfoResponse normalized = normalizePriceInfo(stockInfoResponse);
314+
if (normalized == null) {
315+
return Mono.error(new UnsupportedOperationException("해외 종목 주가 정보가 없습니다 (symbol: " + symbol + ")"));
316+
}
317+
318+
return Mono.just(normalized);
306319
} catch (Exception e) {
307320
return Mono.error(new UnsupportedOperationException("해외 종목 정보가 없습니다"));
308321
}
@@ -336,6 +349,60 @@ private Double parseFiniteDouble(JsonNode node, String symbol, String fieldName)
336349
}
337350
}
338351

352+
private StockInfoResponse normalizePriceInfo(StockInfoResponse response) {
353+
if (response == null) {
354+
return null;
355+
}
356+
357+
Double price = toPositiveFiniteOrNull(response.getPrice());
358+
Double yesterdayPrice = toPositiveFiniteOrNull(response.getYesterdayPrice());
359+
Double priceDiff = toFiniteOrNull(response.getPriceDiff());
360+
Double priceDiffPercent = toFiniteOrNull(response.getPriceDiffPerCent());
361+
362+
priceDiff = derivePriceDiff(priceDiff, price, yesterdayPrice);
363+
priceDiffPercent = derivePriceDiffPercent(priceDiffPercent, priceDiff, yesterdayPrice);
364+
365+
response.setPrice(price);
366+
response.setYesterdayPrice(yesterdayPrice);
367+
response.setPriceDiff(priceDiff);
368+
response.setPriceDiffPerCent(priceDiffPercent);
369+
370+
if (price == null && yesterdayPrice == null) {
371+
return null;
372+
}
373+
return response;
374+
}
375+
376+
private Double derivePriceDiff(Double currentDiff, Double price, Double yesterdayPrice) {
377+
if (currentDiff != null) {
378+
return currentDiff;
379+
}
380+
if (!isPositiveFinite(price) || !isPositiveFinite(yesterdayPrice)) {
381+
return null;
382+
}
383+
double derived = price - yesterdayPrice;
384+
return Double.isFinite(derived) ? derived : null;
385+
}
386+
387+
private Double derivePriceDiffPercent(Double currentDiffPercent, Double priceDiff, Double yesterdayPrice) {
388+
if (currentDiffPercent != null) {
389+
return currentDiffPercent;
390+
}
391+
if (!isPositiveFinite(yesterdayPrice) || priceDiff == null) {
392+
return null;
393+
}
394+
double derived = (priceDiff / yesterdayPrice) * 100.0;
395+
return Double.isFinite(derived) ? derived : null;
396+
}
397+
398+
private Double toFiniteOrNull(Double value) {
399+
return value != null && Double.isFinite(value) ? value : null;
400+
}
401+
402+
private Double toPositiveFiniteOrNull(Double value) {
403+
return isPositiveFinite(value) ? value : null;
404+
}
405+
339406
private boolean isPositiveFinite(Double value) {
340407
return value != null && Double.isFinite(value) && value > 0;
341408
}
@@ -1048,19 +1115,25 @@ private Mono<List<PriceInfo>> parseFStockChartPriceOverseas(String response) {
10481115
}
10491116

10501117
public Mono<StockInfoResponse> getRealTimeStockPrice(Stock stock) {
1118+
if (stock == null || stock.getSymbol() == null || stock.getExchangeNum() == null) {
1119+
return Mono.error(new IllegalArgumentException("유효하지 않은 종목 정보입니다."));
1120+
}
1121+
10511122
StockInfoResponse cached = getCachedRealTimeStockPrice(stock);
10521123
if (cached != null) {
10531124
return Mono.just(cached);
10541125
}
10551126

1056-
return getSecurityStockInfoKorea(
1057-
stock.getId(),
1058-
stock.getSymbolName(),
1059-
stock.getSecurityName(),
1060-
stock.getSymbol(),
1061-
stock.getExchangeNum(),
1062-
getCountryFromExchangeNum(stock.getExchangeNum())
1063-
).doOnNext(response -> putStockPriceCache(stock, response));
1127+
String cacheKey = buildStockPriceCacheKey(stock);
1128+
if (cacheKey == null) {
1129+
return requestAndCacheRealTimeStockPrice(stock);
1130+
}
1131+
1132+
return inFlightPriceRequests.computeIfAbsent(cacheKey, key ->
1133+
requestAndCacheRealTimeStockPrice(stock)
1134+
.doFinally(signalType -> inFlightPriceRequests.remove(key))
1135+
.cache()
1136+
);
10641137
}
10651138

10661139
public StockInfoResponse getCachedRealTimeStockPrice(Stock stock) {
@@ -1081,16 +1154,30 @@ public StockInfoResponse getCachedRealTimeStockPrice(Stock stock) {
10811154

10821155
Object value = wrapper.get();
10831156
if (value instanceof StockInfoResponse) {
1084-
return (StockInfoResponse) value;
1157+
return normalizePriceInfo((StockInfoResponse) value);
10851158
}
10861159
if (value instanceof Map) {
1087-
return objectMapper.convertValue(value, StockInfoResponse.class);
1160+
StockInfoResponse converted = objectMapper.convertValue(value, StockInfoResponse.class);
1161+
return normalizePriceInfo(converted);
10881162
}
10891163
return null;
10901164
}
10911165

1166+
private Mono<StockInfoResponse> requestAndCacheRealTimeStockPrice(Stock stock) {
1167+
return getSecurityStockInfoKorea(
1168+
stock.getId(),
1169+
stock.getSymbolName(),
1170+
stock.getSecurityName(),
1171+
stock.getSymbol(),
1172+
stock.getExchangeNum(),
1173+
getCountryFromExchangeNum(stock.getExchangeNum())
1174+
).map(this::normalizePriceInfo)
1175+
.doOnNext(response -> putStockPriceCache(stock, response));
1176+
}
1177+
10921178
private void putStockPriceCache(Stock stock, StockInfoResponse response) {
1093-
if (response == null || response.getPrice() == null || response.getPrice() <= 0) {
1179+
StockInfoResponse normalized = normalizePriceInfo(response);
1180+
if (normalized == null || !isPositiveFinite(normalized.getPrice())) {
10941181
return;
10951182
}
10961183

@@ -1101,7 +1188,7 @@ private void putStockPriceCache(Stock stock, StockInfoResponse response) {
11011188

11021189
String cacheKey = buildStockPriceCacheKey(stock);
11031190
if (cacheKey != null) {
1104-
cache.put(cacheKey, response);
1191+
cache.put(cacheKey, normalized);
11051192
}
11061193
}
11071194

0 commit comments

Comments
 (0)