새로 추가한 시스템은 환율 시스템
게임중에 얻는 리소스 아이템중에 뭐 청나라에서 유입된 마재은이나 달러, 파운드화를 환전하는 시스템을 구현했음.

은전을 사용하면 사라지면서 폭죽과 함께 이쁘게 얼마 환전했는지 알려줌.
근데 웹서버가 닫혀있는 상태면 환전 자체가 되지 않음...

위 사진은 웹서버에서의 관리자 페이지. 실시간으로 환율을 통제하고 확인함ㅇㅇ
각 재화마다 다른 변동비를 가지고 있으며 정각을 기준으로 매일마다 크거나 작게 변동이 생김.

실시간으로 화폐 환율을 볼 수 있는 패널을 제공해서 보고 비싼 녀석 팔 수 있도록 의도했슴
컴맹이라 이쁜 이펙트랑 이런거 스샷 못찍어서 그냥 로그창 보여주는걸로 만족ㅠㅠㅠ
나중에는 은화를 불법으로 찍어내는 등 근대경제사에 있어서 흥미로운 조선의 혐성질에 대해서 이야기를 추가할 생각.
현재 생긴 오류 23-08-10
인벤토리.. 만들 생각 없다가 급하게 대충 만들었는데 역시 뇌가는대로 만들어서 그런지
아이템 교체시 하나의 itemSlot에 두개가 들어가는 오류가 종종 생김. 다 엎고 다시 만들어야할 삘
기존 구조에서는 유니티 클라이언트에서 웹서버로 친구리스트를 요청하면 DB에서 받아왔다. 근데 너무 느림;
그래서 공부한 내용이 NoSQL 캐싱 서버를 이용해서 빠르게 데이터를 받아오고자 했음.


해당 데이터가 캐싱 서버에 존재하는가? 존재한다면 가져오고, 없으면 DB에서 가져오는 캐싱 기법
로직을 구현하던 도중 든 오류인데.. redis 서버에 남아있는 데이터가 최신화되지 않은 이전 데이터면?
어? 어떡하지?
→ 일단 만료 기간을 하루로 설정해두고, 비즈니스 로직에서 DB에 저장할때 변경점이 있으면 redis에도 업데이트하도록 설계
친구를 CRUD 할때만 정보가 변하기 때문에 Redis Cache를 업데이트해줬다.
그런데 친구목록 json 안에는 친구에 대한 정보가 있는데 이 친구의 데이터가 업데이트되지 않는다.
한마디로, 친구 CRUD할때 시점의 사용자 정보로 저장되고 최신화되지 않는다.
예를 들어 Redis 서버에 캐시가 남아있는 상태에서 장비나 코스튬을 교체한다면 불러온 친구 리스트에서는 업데이트되지 않는다.


튜토리얼과 전투씬도 구현되었음.
성능 테스트를 위한 dummy client 생성 로직
블로그에 설명을 위해 대충 파이썬으로 짠 의사코드 내용이다.
dummy 사용자들을 만드는 create_user 함수
def create_users(num):
with app.app_context():
for i in range(1, num):
user = User.query.filter_by(user_id=i).first()
if user is None:
user = User(user_id=i, name=f"User{i}")
# user.gold 등의 데이터 초기화..
db.session.add(user)
db.session.commit()
친구목록에 추가하는 함수
def friend_test(num):
user_24 = User.query.filter_by(user_id=24).first() # 기본 id = 24
for i in range(1, num):
user = User.query.filter_by(user_id=i).first()
if user is not None and user.gold is None:
user_24.friends.append(user)
user.friends.append(user_24)
print("Completed")
📌 서버에서 받아오는 함께 데려갈 동료 리스트나, 친구 리스트는 Redis의 캐싱 기능을 통해 성능을 약 34% 향상
테스트를 위해 1000명의 친구목록을 불러오는데 걸린 시간 : 평균 1800ms → 평균 1200ms
Spring Data JPA의 N+1 오류 해결
Membership 서비스의 사용자 Entity
@ElementCollection
private List<Long> friends;
@ElementCollection
private List<Long> wantedFriends;
이 @ElementCollection는 컬렉션을 매핑하기 때문에 엔티티 조회시 N+1 문제가 생길 수 있다.
친구 수가 1,000명이라고 한다면 친구 리스트를 받아오는 요청 한번에 쿼리가 1,001번 요청된다는 소리다.
당연히 엄청난 성능 저하를 야기할 수 있다.
뭐 Paging 하는 녀석도 아니니.. 그냥 Fetch Join으로만 간단하게 해결했다.
Spring Data JPA에서 기본적으로 제공하는 findById를 사용했던 것을 Repository에서 커스텀 메소드를 추가해보겠다.
public interface MembershipRepository extends JpaRepository<MembershipJpaEntity,Long> {
@Query("SELECT m FROM MembershipJpaEntity m LEFT JOIN FETCH m.friends LEFT JOIN FETCH m.wantedFriends WHERE m.membershipId = :membershipId")
Optional<MembershipJpaEntity> findById(@Param("membershipId") Long membershipId);
@Query(value = "SELECT * FROM resistance.membership WHERE membership_id != ?1 ORDER BY RAND() LIMIT ?2", nativeQuery = true)
List<MembershipJpaEntity> getRandomAlly(String membershipId, int count);
}
네이티브 쿼리로 작성된 getRandomAlly 함수의 경우는 신경 쓸 필요가 없다.
비즈니스 상에서 friends 등의 컬렉션이나 다른 연관 엔티티를 불러오지 않으므로 JPA N+1 문제가 발생하지 않는다.
MultipleBagFetchException: cannot simultaneously fetch multiple bags 오류
2024-03-24T07:06:23.611Z ERROR 1 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.ns.membership.adapter.out.persistance.MembershipJpaEntity.friends, com.ns.membership.adapter.out.persistance.MembershipJpaEntity.wantedFriends]] with root cause
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.ns.membership.adapter.out.persistance.MembershipJpaEntity.friends, com.ns.membership.adapter.out.persistance.MembershipJpaEntity.wantedFriends]
여러 Collection을 2개 이상 가져오려고 해서 생긴 오류이다. 친구 목록와 친구 신청 목록을 List 형태로 받으니 생겼다.
Set 형태로 바꿔서 해결해버렸다.
'project > resistance' 카테고리의 다른 글
게임 서비스에 대한 취약점 모의해킹 이벤트 (1) | 2024.12.17 |
---|---|
Kubernetes 운영과 모니터링 및 오류 해결 (0) | 2024.12.17 |
Kubernetes 배포를 위한 클라우드 환경 구축 (0) | 2024.11.27 |
웹서버 마이그레이션, MSA 설계하기 (1) | 2024.11.27 |