11package devut .buzzerbidder .domain .liveBid .service ;
22
3+ import devut .buzzerbidder .domain .liveBid .dto .BidAtomicResult ;
34import io .micrometer .core .annotation .Timed ;
45import java .util .Arrays ;
56import java .util .List ;
@@ -107,13 +108,13 @@ public long getRedisNowMs() {
107108 -- 추가: endTime 확인 (없으면 초기화 안 된 것)
108109 local endTimeStr = redis.call('HGET', liveKey, 'endTime')
109110 if (not endTimeStr) or (endTimeStr == false) then
110- return -3
111+ return {-3}
111112 end
112113 local endTime = tonumber(endTimeStr)
113114
114115 -- 추가: 이미 종료 시간이 지났으면 입찰 거절
115116 if nowMs >= endTime then
116- return -4
117+ return {-4}
117118 end
118119
119120 -- wallet keys
@@ -123,12 +124,12 @@ public long getRedisNowMs() {
123124
124125 -- 세션 없으면 실패
125126 if redis.call('EXISTS', sesKey) == 0 then
126- return -3
127+ return {-3}
127128 end
128129
129130 -- verKey 없으면 실패 (TTL 꼬임/상태 이상 방지)
130131 if redis.call('EXISTS', verKey) == 0 then
131- return -3
132+ return {-3}
132133 end
133134
134135 local maxPriceStr = redis.call('HGET', liveKey, 'maxBidPrice')
@@ -138,12 +139,12 @@ public long getRedisNowMs() {
138139
139140 -- 이미 본인이 최고입찰자면 실패
140141 if prevBidder == newBidderId then
141- return -1
142+ return {-1}
142143 end
143144
144145 -- deposits에 bidderId가 남아있으면(정상이라면 없어야 함) -1 반환
145146 if redis.call('HEXISTS', depositsKey, newBidderId) == 1 then
146- return -1
147+ return {-1}
147148 end
148149
149150 -- 현재 최고 입찰 금액이 최소 5% 이상, 5%가 100보다 작으면 100 이상
@@ -155,7 +156,7 @@ public long getRedisNowMs() {
155156 local minPrice = curMax + inc
156157
157158 if newPrice < minPrice then
158- return 0
159+ return {0}
159160 end
160161
161162 -- 환불에 필요한 값은 쓰기(차감/HSET) 전에 미리 읽고 검증(부분반영 방지)
@@ -173,7 +174,7 @@ public long getRedisNowMs() {
173174
174175 local prevBalStr = redis.call('GET', prevBalKey)
175176 if (not prevBalStr) or (redis.call('EXISTS', prevVerKey) == 0) then
176- return -3
177+ return {-3}
177178 end
178179 prevBal = tonumber(prevBalStr)
179180 end
@@ -182,15 +183,16 @@ public long getRedisNowMs() {
182183 -- deposit 차감: 잔액 체크 후 차감
183184 local balStr = redis.call('GET', balKey)
184185 if not balStr then
185- return -3
186+ return {-3}
186187 end
187188
188189 local bal = tonumber(balStr)
189190 if bal < deposit then
190- return -2
191+ return {-2, bal, bal}
191192 end
192193
193- redis.call('SET', balKey, bal - deposit)
194+ local afterBal = bal - deposit
195+ redis.call('SET', balKey, afterBal)
194196 redis.call('INCR', verKey)
195197
196198 -- deposits에 deposit 기록
@@ -245,15 +247,15 @@ public long getRedisNowMs() {
245247 redis.call('EXPIRE', liveKey, balanceTtl)
246248 end
247249
248- return 1
250+ return {1, bal, afterBal}
249251""" ;
250252
251253 @ Timed (
252254 value = "buzzerbidder.redis.livebid" ,
253255 extraTags = {"op" , "atomic_update" },
254256 histogram = true
255257 )
256- public Long updateMaxBidPriceAtomicWithDeposit (
258+ public BidAtomicResult updateMaxBidPriceAtomicWithDeposit (
257259 String redisKey ,
258260 Long liveItemId ,
259261 Long bidderId ,
@@ -262,30 +264,51 @@ public Long updateMaxBidPriceAtomicWithDeposit(
262264 Long sessionTtlSeconds ,
263265 Long balanceTtlSeconds
264266 ) {
265- DefaultRedisScript <Long > script = new DefaultRedisScript <>(LUA_BID_SCRIPT , Long .class );
267+ @ SuppressWarnings ({"rawtypes" , "unchecked" })
268+ DefaultRedisScript <List > script = new DefaultRedisScript <>(LUA_BID_SCRIPT , List .class );
266269
267270 try {
268- return redisTemplate .execute (
271+ List <?> raw = redisTemplate .execute (
269272 script ,
270273 List .of (
271274 redisKey , // KEYS[1] 기존 LiveItem 키
272275 ENDING_ZSET_KEY , // KEYS[2] ending zset
273276 "liveItems:currentPrice" , // KEYS[3] 가격 필터용 ZSET 키
274277 "liveItems:hasBid" // KEYS[4] 입찰 존재 SET 키
275278 ),
276- bidderId .toString (),
277- bidPrice .toString (),
278- depositAmount .toString (),
279- sessionTtlSeconds .toString (),
280- balanceTtlSeconds .toString (),
281- liveItemId .toString ()
279+ bidderId .toString (), // ARGV[1]
280+ bidPrice .toString (), // ARGV[2]
281+ depositAmount .toString (), // ARGV[3]
282+ sessionTtlSeconds .toString (), // ARGV[4]
283+ balanceTtlSeconds .toString (), // ARGV[5]
284+ liveItemId .toString () // ARGV[6]
282285 );
286+
287+ if (raw == null || raw .isEmpty ()) {
288+ throw new IllegalStateException ("Redis LUA 반환이 null/empty 입니다. redisKey=" + redisKey );
289+ }
290+
291+ long code = Long .parseLong (String .valueOf (raw .get (0 )));
292+
293+ Long before = null ;
294+ Long after = null ;
295+
296+ if (raw .size () >= 3 ) {
297+ before = Long .parseLong (String .valueOf (raw .get (1 )));
298+ after = Long .parseLong (String .valueOf (raw .get (2 )));
299+ }
300+
301+ return new BidAtomicResult (code , before , after );
302+
283303 } catch (DataAccessException e ) {
284- // 레디스 연결/타임아웃/스크립트 실행 실패 등은 여기로 옴
285304 throw new IllegalStateException ("Redis LUA 실행 실패. redisKey=" + redisKey + ", bidderId=" + bidderId , e );
305+ } catch (RuntimeException e ) {
306+ // 파싱 실패 등
307+ throw new IllegalStateException ("Redis LUA 반환 파싱 실패. redisKey=" + redisKey + ", bidderId=" + bidderId , e );
286308 }
287309 }
288310
311+
289312 /**
290313 * ending ZSET에서 (score <= nowMs) 인 liveItemId들을 limit 만큼 꺼냄
291314 * 여러 서버/스레드가 돌아도 같은 itemId를 중복 처리하지 않게 하려고 ZREM까지 같이 함
0 commit comments