안녕하세요. 인기 게시판 목록 기능을 Redis의 Sorted Set을 활용해 좋아요 순으로 정렬하는 방식으로 구현하다 고민이 생겨 질문하게 되었습니다. 먼저 잦은 데이터 변경을 고려해서 Sorted Set의 member에는 게시판 ID만 저장하고, score에는 좋아요 수를 저장하도록 했습니다. 하지만 목록을 생성하기 위해서는 제목, 생성일 같은 부가 정보가 필요한데 이 데이터를 얻는 효율적인 방식에 대해 고민이 생겨 질문을 드리게 되었습니다. AI에게 도움을 받아 2가지 방법을 생각해보았는데 현실성이 있는지 판단 부탁 드립니다. Redis + RDB 조합 Redis ZSet에서 상위 10개의 ID 목록을 먼저 조회합니다. 해당 ID 목록을 가지고 RDB에 WHERE id IN (...) 쿼리를 날려 상세 데이터를 가져옵니다. 데이터베이스의 IN 쿼리는 순서를 보장하지 않기 때문에 애플리케이션에서 Redis가 준 ID 순서대로 리스트를 다시 재정렬하여 반환합니다. 제가 생각하기에는 인기 게시판 목록을 조회할 때마다 RDB의 ORDER BY 부하를 줄이려다 오히려 애플리케이션 단에서 정렬과 DB 조회가 일어나다 보니 얻는 것보다는 잃는 게 많은 구조가 아닌지 고민이 됩니다. Redis Sorted Set + Hash 조합 정렬 기준(ID, 좋아요 수)은 기존대로 Sorted Set에 관리합니다. 게시판의 상세 부가 정보는 Redis의 Hash 자료구조에 따로 캐싱합니다. 목록 조회 시 Sorted Set에서 ID를 뽑은 후 DB를 거치지 않고 Hash에서 상세 데이터를 조회합니다. 이 방식은 DB에 부하를 줄일 수 있지만 데이터가 자주 변하게 된다면 레디스에서 데이터를 자주 변경해야 되어 메모리에 큰 부하를 줄 수 있을 것 같아 고민이 되는 것 같습니다.
안녕하세요 강사님 강의 잘 듣고 있습니다. 섹션 9에서 강의대로 docker에 rabbitmq를 올린 뒤 configservice를 실행했는데 오류가 발생합니다. org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[springCloudBus.anonymous.PswTKgDeT12hTiCFa33hLw] at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:772) ~[spring-rabbit-3.2.5.jar:3.2.5] at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.passiveDeclarations(BlockingQueueConsumer.java:649) ~[spring-rabbit-3.2.5.jar:3.2.5] at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:636) ~[spring-rabbit-3.2.5.jar:3.2.5] at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.initialize(SimpleMessageListenerContainer.java:1482) ~[spring-rabbit-3.2.5.jar:3.2.5] at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$ AsyncMessageProcessingConsumer.run (SimpleMessageListenerContainer.java:1322) ~[spring-rabbit-3.2.5.jar:3.2.5] at java.base/ java.lang.Thread.run (Thread.java:1447) ~[na:na] Caused by: java.io .IOException: null at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:140) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:136) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:158) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.ChannelN.queueDeclarePassive(ChannelN.java:1033) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.ChannelN.queueDeclarePassive(ChannelN.java:47) ~[amqp-client-5.25.0.jar:5.25.0] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[na:na] at org.springframework.amqp.rabbit.connection.CachingConnectionFactory$CachedChannelInvocationHandler.invoke(CachingConnectionFactory.java:1201) ~[spring-rabbit-3.2.5.jar:3.2.5] at jdk.proxy2/jdk.proxy2.$Proxy140.queueDeclarePassive(Unknown Source) ~[na:na] at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:750) ~[spring-rabbit-3.2.5.jar:3.2.5] ... 5 common frames omitted Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'springCloudBus.anonymous.PswTKgDeT12hTiCFa33hLw' in vhost '/', class-id=50, method-id=10) at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:66) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:36) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:552) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:316) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:152) ~[amqp-client-5.25.0.jar:5.25.0] ... 12 common frames omitted Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'springCloudBus.anonymous.PswTKgDeT12hTiCFa33hLw' in vhost '/', class-id=50, method-id=10) at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:528) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:349) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:193) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:125) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQConnection.readFrame(AMQConnection.java:761) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQConnection.access$400(AMQConnection.java:48) ~[amqp-client-5.25.0.jar:5.25.0] at com.rabbitmq.client.impl.AMQConnection$ MainLoop.run (AMQConnection.java:688) ~[amqp-client-5.25.0.jar:5.25.0] ... 1 common frames omitted 깃허브의 프로젝트를 그대로 가져오고 어떻게 해도 해결이 안되네요ㅠㅠ 도움 부탁드립니다..
좋은 강의 감사합니다. 덕분에 전반적인 개념 잡는 것에 큰 도움 되고 있습니다. 33. AWS EC2 인스턴스 생성 12분 부분 쯤에 보면, 아웃바운드 규칙에서 이미 모든 트래픽의 모든 아이피가 허용상태인데 SSH와 HTTP를 내 아이피만 허용하는 것이 맞는건가요? 인바운드에서만 추가하는 것이 맞지 않나요?
안녕하세요. 최근 수백만 동시 접속을 처리하는 예매 시스템 아키텍처 관련 강의와 유튜브 영상을 참고하면서, 작은 프로젝트로 선착순 쿠폰 발급 시스템을 직접 구현해보며 학습하고 있습니다. 현재는 Redis를 활용해 선착순 쿠폰 발급을 구현하는 구조를 고민하고 있는데, 몇 가지 아키텍처 설계에 대해 궁금한 점이 있어 질문드립니다. 1. Redis + DB 원자성 관련 Redis에서 쿠폰 발급 가능 수량을 감소시키고, 동시에 DB에도 반드시 반영(쿠폰 발급 및 사용자 할당 기록)해야 하는 상황을 가정하고 있습니다. 이 경우 Redis의 재고 감소와 DB 반영을 하나의 트랜잭션처럼 원자적으로 묶어야 할 것 같은데, 현실적으로는 분산 환경 특성상 이를 직접적으로 묶는 것이 어렵다고 이해하고 있습니다. 그래서 일반적으로는 Lua Script로 Redis 내부에서 쿠폰 재고 감소 중복 발급 방지 처리 Redis Stream(또는 Queue)에 발급 이벤트 기록 을 원자적으로 수행하고, 이후 별도의 스케줄러(또는 Worker)가 해당 Stream을 읽어서 DB에 반영하는 방식 으로 설계하는 것이 맞는지 궁금합니다. 2. 메시지 큐(Kafka 등) 사용 시 구조 또한 쿠폰 발급 결과를 Kafka 같은 메시지 큐로 전달하는 방식도 있을 것 같은데, 이 경우에도 동일하게 Lua Script로 Redis 내부에서 쿠폰 재고 감소 중복 발급 방지 처리 Redis Stream(또는 Queue)에 발급 이벤트 기록 이후 스케줄러가 stream읽어서 Kafka로 전달 Consumer가 DB에 반영 과 같은 구조로 가져가는 것이 일반적인지 궁금합니다.
Lock 해제 코드에서 다른 사용자가 생성한 락을 삭제하지 않기 위해 본인의 identifier이랑 락의 value를 비교해서 삭제하는 로직 부분에서 궁금한 내용이 있습니다. 멀티스레드, 프로세스 환경에서 충분히 일어날 일 이라고 하셨는데 내가 락을 얻으면 다른 사용자는 락을 얻지 못해 value는 항상 나의 identifier이 들어있어야 하는게 아닌가? 라는 생각이 드는데 rd.set(lock_name, identifier, nx=True, px=lock_timeout_ms) 이 코드 방식으로 락을 만들면 여러 사용자가 특정 환경에 락을 얻을 수 있는건가요?
안녕하세요 해여님. 강의 잘 수강하고 있습니다. 남은 강의는 언제 업로드 되는지 일정 공유 부탁드립니다. 수업자료 공유(notion/pdf)는 가능한지 문의드립니다. 2-1. 가능하다면 어떤 형태로 언제 업로드 되는지 공유 부탁드립니다. 감사합니다. 오늘도 행복한 하루 되시길 바랍니다.
학습 중 궁금한 점이 있으시면 편하게 질문 주세요. 참고로 질문이 구체적일 수록 더 정확한 답변을 드릴 수 있습니다. 😊 안녕하세요. 강의를 듣고 있는 개발자입니다. 이전에 유튜브에서 선착순 예매 관련 영상을 봤었는데 해당 영상에서 레디스를 이용해서 대기큐를 구현하는 것을 설명해주셨는데 그거에 대해서 질문이 있습니다. 레디스의 소티드 셋에 넣고 폴링을 통해서 상태를 확인한다. 타 스케줄러가 소티드셋에서 꺼내서 액티브로 바꾼다 라는 설명이 있었는데, 정확히 어떤식으로 구현하는 건지가 궁금합니다. 스케줄러가 꺼내고 액티브 유저 set으로 집어 넣는건가요? 그리고 소티드셋에서 꺼내서 엑티브 유저로 만드는 작업은 원자적으로 처리되게 하는건가요?
안녕하세요. 항상 좋은 강의 감사합니다. API LIMIT을 보면 ABUSING이랑 비슷한 개념이라고 느꼈습니다. 예를 들어, 어떤 유튜브 라이브에서 좋아요 리액션을 너무 많이 눌렸을 때, API LIMIT 알고리즘으로 고정 윈도우 방식이 적절하다고 생각했습니다. INCR 이 빈번한 작업이기 때문이죠. 근데 악성 BOT(봇)으로 좋아요를 악의적으로 누를 수도 있는데 이때는 ABUSING(어뷰징) 처리를 해야한다면 API LIMIT과 동일하게 가져가는게 맞을지 설명주신 API LIMIT도 제한 구간 10초당 몇개 를 구간과 임계치를 설정하는데, 보통 어떤 기준으로 실무에서 설정하는지 궁금합니다. 운영하면서도 모니터링하면서 조절할거 같은데 유튜브 라이브의 임계치 구간을 설명한다고 가정하면 인간의 마우스나 손으로 눌렸을 때 1초당 10번 이하? 이렇게 추상적인 실험 데이터도 정하는게 맞는지도 궁금합니다. 감사합니다.
항상 좋은 강의 감사합니다. Redis와 Kafca의 Pub/Sub 차이가 궁금합니당. 예를 들어, 인스타 라이브에서 사용자의 리액션 좋아요를 Redis에 두고 post로 좋아요를 업데이트 칠 때, MYSQL 좋아요 수도 같이 증가하기 위해 Redis와 Kafca의 Pub/Sub 의 차이점이 있을까요? 좋아요 api가 redis의 좋아요 수 증가 > kafca 메시지 발행 > mysql 구조라 가정했을 때, 대규모트래픽에서 kafca와 redis의 Pub/Sub의 차이랑 뭐가 더 적절한 선택인지 궁금합니다. 감사합니다.
안녕하세요. 웹/백엔드 JAVA 개발자입니다. 현재 SP를 실제로 사용할까요? (DB Procedure로 이해) (레거시 시스템 운영 외 현대의 서비스에서) 일반적으로 SP 방식의 경우 강의 내용에서 나온 버전 관리의 문제, 벤더 락인, 부분적 로직 재사용 등의 한계가 있어보여, 저는 줄곧 ORM에 적합한 환경이 아니더라도 SP 보다는 Application 레벨에서 Query를 작성&관리&실행하는 형태로 줄곧 서비스를 운영해왔고, 강의에서 언급된 ORM의 단점도 Application 에서 쿼리를 관리하면 사라지는 단점으로 보여져서, 사실상 SP는 지양해야 할 레거시 방식으로 생각하고 있었습니다. 아직 다른 도메인 영역에서는 SP를 고수하는 분야가 있는걸까요?
안녕하세요. 강사님 캐시전략 - Write-behind 전략을 설명해주셨는데, 인스타라이브나 유튜브라이브에서 좋아요를 한 사용자가 여러번 누를 수 있는데, 이때가 아마 Write-behind 전략을 적용할 수 있을 거 같습니다. 1. 좋아요를 레디스 캐시에 카운트 증가 2. 좋아요 누른 개수를 몇 초마다 flush로 카프카 큐에 발행 3. 카프카 consumer에서 db저장 이런 방식으로 설계가 가능할 거 같습니다. 강의에서는 Write-behind DB에 나중에 저장한다고 말씀하셨는데 그럼 이런 라이브 상황에서 DB에 좋아요를 언제 저장하는 것이 바람직할까요? 그 기준을 어떤 식으로 잡으면 좋을지도 선생님의 고견이 듣고 싶습니다. 좋은 강의 감사합니다.