묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결백엔드 애플리케이션 성능 개선하기 - 기초편
질문있습니다!!
스노우 플레이크를 통해 id값을 만들어 내는 이유가 인덱싱(타임 스탬프 순으로의 정렬)과 절대 중복될 수 없는 key 값을 만들어내서, 기존 생성API의 메커니즘인 Select(중복 여부 조건 검사) -> Insert의 방식에서 스노우 플레이크로 인해 중복이 될 수 없으니 Select 쿼리에 대한 비용을 줄이는 것으로 성능을 개선하는 방식이다. 그리고 여기에 추가로 비동기로 URL 생성을 진행했하고 클라이언트는 응답을 미리 받고, 실질적으로 Insert 쿼리를 날려 DB에 저장하는 부분은 백그라운드로 진행함으로써 Latency를 앞당기는 것으로 성능을 개선했다고 이해하면 될까요??
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [실습]
질문있습니다
RedisConfig 클래스에서 @Bean을 이용해서 @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> redis = new RedisTemplate<>(); redis.setConnectionFactory(factory); redis.setKeySerializer(new StringRedisSerializer()); redis.setHashKeySerializer(new StringRedisSerializer()); redis.setHashValueSerializer(new StringRedisSerializer()); redis.setValueSerializer(new StringRedisSerializer()); return redis; }해당 코드를 통해 RedisTemplate를 빈으로 등록한걸 확인했는데요.근데 왜 RedisCommon 클래스 쪽에선 해당 Bean을 사용하지 않고 RedisTemplate<String,String> 을 사용하나요?Bean으로 등록한건 RedisTemplate<String,Object>인데 그럼 스프링에서는 만들어 놓은 RedisTemplate<String,Object>을 사용하지 않고 새로운 RedisTemplate<String,String>을 만들어서 주입하는 걸로 알고있습니다.저 Bean으로 등록해놓은 RedisTemplate<String,Object>의 직렬화 및 역직렬화에 사용될 객체를 집어넣어주는 코드는 Redis 전체 설정인가요?? 저렇게 하면 RedisTemplate<String,String>에도 적용이 되는건가요??- 강사님의 이론편을 안봐서 제가 이해가 잘 안가는 것 일 수도 있습니다 ㅠㅠ실제로 RedisConfig 클래스에서 정의한 RedisTemplate<String, Object>와 RedisString에서 정의한 RedisTemplate<String,String>의 해시코드를 비교해봤는데 서로 다른 값이 나왔습니다.@Service @RequiredArgsConstructor public class RedisString { private final RedisTemplate<String, String> template; private final RedisTemplate<String, Object> template2; // RedisTemplate@544e3679RedisConfig에서 스프링의 빈 초기화시 로그 // RedisTemplate@544e3679RedisString에서 찍은 template2 로그 // StringRedisTemplate@6d5bb599 RedisString에서 찍은 template 로그
-
해결됨실전! Redis 활용
Stale Cahe Invalidation 방법에 대한 질문 요청 드립니다
강사님 안녕하세요. 해당 강의 부분 듣고 궁금한 점이 하나 생겨서 질문 요청 드립니다.제가 특정 rdb의 데이터를 레디스에 캐싱 후 데이터 조회마다 사용하고 데이터의 변경이 있을 경우에 레디스에 반영해주는 방법을 사용하곤 했는데, 개발자가 인지 못한 db 데이터의 수정이나 오염이 있을 경우 강의에서 말씀해주신 것 처럼 정합성이 깨지고 문제가 발생할 것 같은데 이에 대한 방법으로 어떻게 처리하시는 걸 선호하시나요? 주기적인 배치를 통한 데이터 정합성 검사? db에서 데이터의 변경이 있었는지 version 같은 컬럼을 두고 확인?해당 문제에 대한 처리 방법이 좋을지 강사님의 의견이 궁금합니다!
-
해결됨실전! Redis 활용
pipeline 다이어그램 m/s 오타 제보
안녕하세요 동현님!다이어그램을 설명하는 과정에서 밀리세컨드라고 말씀하셨는데, m/s 라고 표기 되어 있어요!m/s 는 meter per second 를 의미하는 것으로 알고 있습니다! 강의를 수정하기 어려우면 ppt 자료에라두 ms 로 정정하는게 좋을 것 같습니다! 명절 연휴에 강의 너무너무 잘 듣고 있습니다!!감사합니다.
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [이론편]
Redis의 클러스터 동작 방식 ( Hash Slot과 관련 )
강의를 들으면서 제가 아는 해시 테이블의 개념과 조금 헷갈려서 질문을 남깁니다. 일반적으로 해시 테이블은 특정한 값이 들어오면 어떤 연산을 통해 해싱을 합니다. 그리고 해싱된 값을 통해 이 데이터가 어떠한 버킷의 어떤 슬롯에 담기는지를 정하게 됩니다.그리고 여기에서 논리적으로 해시충돌이 일어나면 이를 해결할 수 있습니다. 여기에서는 데이터가 실제로 담기기 때문에 이러한 충돌이 발생할 수 있고 해결 또한 해야하죠. 근데 Redis의 클러스터에서는 어떤 데이터가 들어온다면 모듈러 연산을 통해 얻은 해시 값이 특정 노드에 속하는지를 판단한다고 강의를 들었습니다. 예) 노드 A : 0~ 4819 노드 B: 4820~16383 이 때 해싱을 통해 얻은 값이 4819라면 해당 데이터는 노드A에 속하게 되고 노드A에 연결된 DB에 데이터가 저장이 되겠죠 ?( DB의 Cluster 구동 방식을 검색해보니 각각의 노드마다 DB를 별도로 샤용한다고 했습니다. )이 때 해쉬 슬롯은 단순히 너는 노드A로 가라. 너는 노드B로 가라 라고 연결다리 역할만 하는건지 궁금합니다.실제로 해쉬 슬롯은 노드를 나누기 위한 논리적인 숫자에 불과하며 실제 슬롯에는 그 어떠한 값도 저장하고 있지 않는 것인지가 너무 헷갈립니다.. 제가 해쉬 테이블의 개념만 너무 생각해서 그런건지 잘 모르겠습니다 ..
-
해결됨실전! Redis 활용
Streams 삭제 관련 질문
안녕하세요! 동현님강의 잘 듣구 있습니다~!! Streams 자료구조를 설명할 때 append-only log 에 consumer groups 과 같은 기능을 더한 자료구조라고 설명해주셨는데요.append-only log 는 데이터를 오직 추가(append)만 할 수 있는 자료구조라고 추가로 설명해주셨습니다! 그런데 xdel 같은 명령어로 이벤트를 삭제할 수 있다고 설명해주셨는데요.오직 추가만 할 수 있는 자료구조 라고 인식했는데, 갑자기 삭제 명령어가 있어서 헷갈립니다! 감사합니다.
-
해결됨실전! Redis 활용
ZSet 다이어그램 질문
안녕하세요~!zadd를 통해서 TeamC 의 score는 50으로 저장한 것 같은데, 다이어그램은 100으로 되어 있어서 질문 남깁니다!
-
해결됨실전! Redis 활용
안녕하세요 Pub/sub과 분산 락에 대해 질문이 있습니다.
안녕하세요 강사님 우선 아래 질문한 내용에 대해 구글에서 이것저것 찾아보고 GPT도 다 돌려봤는데 도저히 이해가 안가서 강사님께 여쭤봅니다.강의에서 분산 락과 Pub/Sub 개념을 복습한 후, 이전에 Redisson을 사용해 구현했던 분산 락 로직을 정리하고 있습니다. 그 과정에서 RedissonLock 클래스의 tryLock 메서드를 살펴보다가 PublishSubscribe 클래스의 subscribe 메서드까지 코드를 따라가게 되었습니다.이와 관련하여 다음 몇 가지 질문이 생겼습니다:PublishSubscribe 클래스의 subscribe 메서드를 보면 세마포어를 사용하는데, 여기서 세마포어의 역할이 무엇인지 잘 이해가 되지 않습니다. 왜 이 시점에서 세마포어를 사용하는 건가요?CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId); 코드에서 threadId로 무언가를 구독합니다. 정확히 무엇을 구독하는지 잘 모르겠습니다. 구독 대상과 구독의 목적이 무엇인지 궁금합니다.tryLock 메서드의 아래쪽 코드를 보면 while (true)로 락을 반복적으로 시도하는 모습이 마치 스핀락처럼 보입니다. 저는 Redisson의 분산 락이 Lettuce의 스핀락과는 다르다고 생각했는데, 실제 구현을 보니 스핀락과 비슷한 방식으로 동작한다고 봐도 될까요?RedissonLock 클래스의 tryLock 메서드 @Override public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long time = unit.toMillis(waitTime); long current = System.currentTimeMillis(); long threadId = Thread.currentThread().getId(); Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId); // lock acquired if (ttl == null) { return true; } time -= System.currentTimeMillis() - current; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } current = System.currentTimeMillis(); CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId); try { subscribeFuture.get(time, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { if (!subscribeFuture.cancel(false)) { subscribeFuture.whenComplete((res, ex) -> { if (ex == null) { unsubscribe(res, threadId); } }); } acquireFailed(waitTime, unit, threadId); return false; } catch (ExecutionException e) { acquireFailed(waitTime, unit, threadId); return false; } try { time -= System.currentTimeMillis() - current; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } while (true) { long currentTime = System.currentTimeMillis(); ttl = tryAcquire(waitTime, leaseTime, unit, threadId); // lock acquired if (ttl == null) { return true; } time -= System.currentTimeMillis() - currentTime; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } // waiting for message currentTime = System.currentTimeMillis(); if (ttl >= 0 && ttl < time) { commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS); } time -= System.currentTimeMillis() - currentTime; if (time <= 0) { acquireFailed(waitTime, unit, threadId); return false; } } } finally { unsubscribe(commandExecutor.getNow(subscribeFuture), threadId); } // return get(tryLockAsync(waitTime, leaseTime, unit)); }PublishSubscribe 클래스/** * Copyright (c) 2013-2022 Nikita Koksharov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.redisson.pubsub; import org.redisson.PubSubEntry; import org.redisson.client.BaseRedisPubSubListener; import org.redisson.client.ChannelName; import org.redisson.client.RedisPubSubListener; import org.redisson.client.codec.LongCodec; import org.redisson.client.protocol.pubsub.PubSubType; import org.redisson.misc.AsyncSemaphore; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * * @author Nikita Koksharov * */ abstract class PublishSubscribe<E extends PubSubEntry<E>> { private final ConcurrentMap<String, E> entries = new ConcurrentHashMap<>(); private final PublishSubscribeService service; PublishSubscribe(PublishSubscribeService service) { super(); this.service = service; } public void unsubscribe(E entry, String entryName, String channelName) { AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName)); semaphore.acquire().thenAccept(c -> { if (entry.release() == 0) { entries.remove(entryName); service.unsubscribe(PubSubType.UNSUBSCRIBE, new ChannelName(channelName)) .whenComplete((r, e) -> { semaphore.release(); }); } else { semaphore.release(); } }); } public void timeout(CompletableFuture<?> promise) { service.timeout(promise); } public void timeout(CompletableFuture<?> promise, long timeout) { service.timeout(promise, timeout); } public CompletableFuture<E> subscribe(String entryName, String channelName) { AsyncSemaphore semaphore = service.getSemaphore(new ChannelName(channelName)); CompletableFuture<E> newPromise = new CompletableFuture<>(); semaphore.acquire().thenAccept(c -> { if (newPromise.isDone()) { semaphore.release(); return; } E entry = entries.get(entryName); if (entry != null) { entry.acquire(); semaphore.release(); entry.getPromise().whenComplete((r, e) -> { if (e != null) { newPromise.completeExceptionally(e); return; } newPromise.complete(r); }); return; } E value = createEntry(newPromise); value.acquire(); E oldValue = entries.putIfAbsent(entryName, value); if (oldValue != null) { oldValue.acquire(); semaphore.release(); oldValue.getPromise().whenComplete((r, e) -> { if (e != null) { newPromise.completeExceptionally(e); return; } newPromise.complete(r); }); return; } RedisPubSubListener<Object> listener = createListener(channelName, value); CompletableFuture<PubSubConnectionEntry> s = service.subscribeNoTimeout(LongCodec.INSTANCE, channelName, semaphore, listener); newPromise.whenComplete((r, e) -> { if (e != null) { s.completeExceptionally(e); } }); s.whenComplete((r, e) -> { if (e != null) { value.getPromise().completeExceptionally(e); return; } value.getPromise().complete(value); }); }); return newPromise; } protected abstract E createEntry(CompletableFuture<E> newPromise); protected abstract void onMessage(E value, Long message); private RedisPubSubListener<Object> createListener(String channelName, E value) { RedisPubSubListener<Object> listener = new BaseRedisPubSubListener() { @Override public void onMessage(CharSequence channel, Object message) { if (!channelName.equals(channel.toString())) { return; } PublishSubscribe.this.onMessage(value, (Long) message); } }; return listener; } } 감사합니다.
-
해결됨실전! Redis 활용
분산 락에 대해 질문 있습니다.
이전에 분산 락을 구현해본 경험이 있습니다.그때는 간단하게 구현을 하다보니 분산 락에 대해 꼬리 질문이 들어 온다면 어떤 질문들이 들어올지 궁금합니다.강사님께서 생각하시기에 분산 락 관련하여 질문을 한다면 혹시 어떤 식으로 질문을 하실지 궁금합니다.
-
미해결백엔드 개발자 성능 개선 초석 다지기
섹션4: Ngrinder를 통한 성능 개선 확인 에서 사용하는 예제 코드
Ngrinder를 통한 성능 개선 확인하는 부분에 NoticeReadMapper.xml 파일 내용이 깃허브의 예제 코드랑 다른데 이건 예제 코드에 없는건가요?
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [실습]
value 에 gson.toJson 사용 관련
value 에 대해서 모두 gson.toJson 을 통해 직렬화를 해서 저장하고 있는데요. 이건 value 가 json 이 들어간다고 가정하고 이렇게 작성하신걸까요? 일반적인 경우에는 json 이 아닌 경우도 있을것 같아서 여쭤봅니다.
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [실습]
출시 예정 강의 질문
안녕하세요. 비록 아직 강의를 수강하지는 못하였지만,다음 출시 예정으로 생각하시는 강의에 대해 문의가 있어 이렇게 글을 남기게 되었습니다 ,, 혹시 강사님께서 카프카 관련 주제로 강의 출시 계획이 있으실지 문의 남기겠습니다. 감사합니다.
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [실습]
multiSet에서 key 입력 의미
multiSetData에서 키 저장시 key:1, key:2가 저장이 되는데 키 입력시 test_one은 별 의미가 없지 않나요?아니면 원래 의도가 test_one:1, test_one:2 인지 궁금합니다
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [실습]
윈도우에서 작업 질문
redis 설치까지는 다 끝냈습니다. 윈도우에서는 어떻게 작업하나요?
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [이론편]
Redis SSD
안녕하세요 이미 커뮤니티에 올라온 글인데 제대로 이해를 못한 것 같아서요.redis를 사용할 때 기본적으로 메모리를 이용해 캐싱을 하고, AOF나 스냅샷을 저장하기 위한 용도로 SSD를 사용한다고 이해한 것이 맞을까요..? 강의 잘 듣고 있습니다 :) https://www.inflearn.com/community/questions/1400475/redis%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EC%A7%88%EB%AC%B8%EC%82%AC%ED%95%AD
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [이론편]
Redis Collection BitMaps
안녕하세요 강의를 듣다가 잘 이해가 안가는 부분이 있어서 질문드립니다.비트맵을 사용할 때, 사용자 수가 100만명인데 실제 사용자 수는 100명인 경우 메모리 낭비가 발생한다는 부분은 이해했습니다.이를 위해 구분자가 있는 데이터를 지정하고 활용하는 것이 좋다고 말씀하셨는데 구분자가 있는 데이터가 어떤 것인지 제대로 이해가 안 가서 질문드립니다.. 섹션3 - BitMaps Collectionshttps://www.inflearn.com/course/redis-%EC%95%BC%EB%AC%B4%EC%A7%80%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%EC%9D%B4%EB%A1%A0%ED%8E%B8/unit/244780
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [이론편]
수업이랑 살짝 관계없는데 신경써주시면 좋을점입니다.
아키텍처 관련 설명하실때 듀퐁 라이터(?) 같은걸 열고 닫는 소리가 주기적으로나는데 생각보다 신경쓰여서요 다른 강의 예정이 있으시다면 영상 찍고나서 주변소음 관련해서 신경써주세요
-
해결됨실전! Redis 활용
장바구니 구현 관련
강의를 들으며 공부하다 궁금한 점이 생겨 질문 드립니다.초반 부분에서 Redis는 RAM에 데이터를 저장한다고 말씀해주셨습니다.그리고 장바구니 구현 설명에서 set을 활용하여 쉽게 구현하는 방법을 설명해주셨는데장바구니라는 것이 담기만 하고 실제 구매로 이어지지 않을 수 있어 데이터를 계속 가지고 있어야 할 필요가 있는 반영구적인 기능이라 생각하는데, TTL 설정을 따로 하지 않는 식으로 구현하면 될지 궁금합니다.또한, 사용자가 늘어나면 늘어날수록 Redis, 즉 RAM에 저장되는 데이터 또한 늘어날 것이라 생각하는데 이와 관련하여 발생할 수 있는 메모리 문제 같은 것은 없는지 궁금합니다.
-
미해결백엔드 개발자 성능 개선 초석 다지기
현업에서의 부하테스트 및 Ngrinder 설치
현업에서의 부하테스트와 Ngrinder 사용에 대해 질문드립니다. 현업에서는 부하테스트용 서버(e.g. Stage 서버)를 두고 해당 서버에 부하테스트를 하는지, 아니면 로컬에 띄워두고 간략하게 테스트를 하는지 궁금합니다.Ngrinder를 어떤 서버에 설치하는지 궁금합니다.별도의 IDC 서버에 설치한 후, 팀 내 개발자가 모두 해당 Ngrinder를 사용해 부하테스트를 적용하는지, 아니면 Ngrinder는 로컬 컴퓨터(개인 노트북 등)에 설치 후 부하 테스트 대상 서버만 Stage 환경 등에 두고 해당 서버에 테스트를 하는지 궁금합니다.
-
해결됨대기업 근무하며 경험한 Redis를 야무지게 사용하는 방법 [이론편]
Redis란 무엇인가? 질문사항
강의의 내용중 3:35 즈음에인메모리 같은 ssd 같은 메모리를 사용을 한다고 하셨는데궁금증이 생겼습니다.ssd는 제가 알기로는 디스크로 알고 있는데메모리라고 하셔서 궁금하고,ssd를 언급하신 이유는 지속성 옵션 관련해서 말씀 하신건지이 두 부분이 궁금하여 질문 올려봅니다:)