묻고 답해요
156만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결개발자라면 알아야 할 redis 기본
MacOS에서 brew를 사용한 설치를 권장하지 않는 이유가 무엇인가요?
안녕하세요!brew를 통해 직접적으로 MacOS에 설치하는 것이 아닌 Docker Desktop을 설치하여 Docker Container 안에서 redis-server를 설치하라고 말씀해주셨는데 후반부에 말씀해주시는 redis-server 삭제 시 깔끔하게 제거되지 않을 가능성이 있기 때문에 brew를 통한 설치가 권장되지 않는다고 이해했는데 맞을까요?
-
해결됨실전! Redis 활용
pipeline 다이어그램 m/s 오타 제보
안녕하세요 동현님!다이어그램을 설명하는 과정에서 밀리세컨드라고 말씀하셨는데, m/s 라고 표기 되어 있어요!m/s 는 meter per second 를 의미하는 것으로 알고 있습니다! 강의를 수정하기 어려우면 ppt 자료에라두 ms 로 정정하는게 좋을 것 같습니다! 명절 연휴에 강의 너무너무 잘 듣고 있습니다!!감사합니다.
-
해결됨실전! 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 활용
분산 락에 대해 질문 있습니다.
이전에 분산 락을 구현해본 경험이 있습니다.그때는 간단하게 구현을 하다보니 분산 락에 대해 꼬리 질문이 들어 온다면 어떤 질문들이 들어올지 궁금합니다.강사님께서 생각하시기에 분산 락 관련하여 질문을 한다면 혹시 어떤 식으로 질문을 하실지 궁금합니다.
-
해결됨개발자라면 알아야 할 redis 기본
레디스 pub/sub 질문
안녕하세요 강의 잘 듣고 있습니다제가 현재 채팅 프로그램을 만들고 있는데 채팅 서버가 하나이면 굳이 레디스를 사용할 필요가 없겠네요?
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
k6의 결과 해석
안녕하세요. 강의에서 k6를 실행하기 위해 30명의 가상유저가 10초동안 요청을 무한히 보내도록 설정하셨습니다. 그 결과 http_reqs 값이 1.66/s 가 나왔습니다. 이 값은 시스템의 대역폭 max값으로 이해해도 되나요?30명, 10초 설정값이 아니더라도 100명, 10초 설정값으로 진행해도 1.66/s 에 근사한 값이 나와야하는지 궁금합니다. (시스템이 처리할 수 있는 능력 의 Maximum은 항상 일정해야하지 않을까 라는 생각에 질문드립니다.)
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
Jackson2JsonRedisSerializer에 대한 궁금증
GenericJackson2JsonRedisSerializer를 사용할 때 저장되는 캐싱 데이터는 다음과 같았습니다."[{\"@class\":\"org.example.package..\",\"id\":\"123984\",\"title\":\"title0123984\",...,강의상에 나온 데이터 순서?? 와는 조금 다르게 package 다음에 @class 이후 데이터가 저장되는 구조가 나오는 것을 확인했습니다.이때 캐싱된 데이터를 조회하려고 하는데 com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected VALUE_STRING: need String, Number of Boolean value that contains type id (for subtype of java.lang.Object) 에러가 발생했습니다.너무 궁금해서 GenericJackson2JsonRedisSerializer에 대해서 알아보고 있는데, 여전히 에러가 발생하는 이유를 잘 모르겠습니다ㅜㅜ데이터가 캐싱되어서 저장될 때 구조가 다르게 들어가는 이유가 궁금합니다 아 물론 강의 후반부처럼 Jackson2JsonRedisSerializer를 사용했을 때는 정상적으로 동작했습니다.
-
해결됨비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
사용된 어노테이션에 관한 질문
5:12분경 createdAt에서 @CreatedDate를 제외한 나머지 어노테이션들을 특별히 사용하신 이유가 있으실까요?
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
Redis 사용 방식에 대해서 질문 있습니다
안녕하세요 레디스를 공부하면서 궁금한게 있습니다.제가 토이프로젝트를 하면서 상품 검색 입력시 버튼 하나씩 누를때마다(이벤트 keyup 사용) 백엔드와 통신하여 입력창 하단에 자동완성 기능창을 만들려고 하고 있습니다.아무래도 버튼 하나씩 누를때마다 통신해야되니 빠르게 통신해야될 것 같아서 redis를 사용했습니다.상품 등록할 때 DB와 redis에 저장하고 (redis에는 상품 이름만 저장했습니다.)입력창 하단의 자동완성은 redis를 통해 상품 이름을 출력하고 출력된 상품이름을 선택시 DB를 통해서 상품 상세정보를 가져오는 식으로 했는데배운대로라면 Cache Aside 전략과 다른데 이럴때는 어떻게 구성해야되나요?아니면 SQL 튜닝만 잘해도 굳이 redis는 필요없는건지 궁금합니다.아무래도 버튼 누를때마다 통신하다보니 SQL은 느릴까봐 싶어 조바심이 나네요ㅠㅠ 알아보니 최근검색어나 인기검색어 같은 경우도 redis로 한다고하는데 이런것들도 굳이 출력하자면 DB로만 사용하여 출력이 될텐데 이게 Cache Aside 전략과 어떤 관계가 있는지 감이 안잡힙니다실무뛰면서 redis를 써본적이 없고 거의 db로만 해결하다보니 redis 사용예시를 잘 모르겠습니다
-
해결됨비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
aws elasticcache redis 툴 접속.
안녕하세요. 강의 잘 들었습니다.강의를 듣다보니 의문이 드는 점이 있어 질문드립니다.redis를 사용하다 보면 redisinsight같은 gui 툴을 통해 데이터를 확인하는데 운영관점에서 더 편한데요.aws elasticcache redis가 외부 로컬에서 접속이 안되면aws elasticcache는 데이터 확인을 툴로 못하는건가요? 실무에서 운영을 하다보면 분명 직접 데이터를 체크를 해야하는 케이스가 발생하는데 이걸 일일이 cli 명령어 찾는다는 것은 돈을 주고 aws 사용하는 고객입장에서는 너무 불편하다고 생각들어 문의드립니다.
-
해결됨실전! Redis 활용
장바구니 구현 관련
강의를 들으며 공부하다 궁금한 점이 생겨 질문 드립니다.초반 부분에서 Redis는 RAM에 데이터를 저장한다고 말씀해주셨습니다.그리고 장바구니 구현 설명에서 set을 활용하여 쉽게 구현하는 방법을 설명해주셨는데장바구니라는 것이 담기만 하고 실제 구매로 이어지지 않을 수 있어 데이터를 계속 가지고 있어야 할 필요가 있는 반영구적인 기능이라 생각하는데, TTL 설정을 따로 하지 않는 식으로 구현하면 될지 궁금합니다.또한, 사용자가 늘어나면 늘어날수록 Redis, 즉 RAM에 저장되는 데이터 또한 늘어날 것이라 생각하는데 이와 관련하여 발생할 수 있는 메모리 문제 같은 것은 없는지 궁금합니다.
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
Redis를 사용하는 이유
캐싱을 위해 Redis를 많이 사용한다고 생각하고 있었는데 SQL튜닝으로 해결되는 부분이 있는거군요! 그러면 일반적으로 현업에서 Redis를 사용하는 이유 중 어떤 이유로 가장 많이 사용하는지 여쭤볼 수 있을까요??
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
로컬에서 docker compose 명령어 실행 시
로컬에서 docker compose up --build -d 명령어 실행 시 아래와 같은 에러가 발생합니다.Cannot connect to the Docker daemon at unix:///Users/milaju/.docker/run/docker.sock. Is the docker daemon running?따로 설정을 해줘야 하는 부분이 있을까요?
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
EC2 서버 실행시키기 관련하여
# 스프링 프로젝트 경로로 들어가서 아래 명령어 실행$ ./gradlew clean build -x test $ cd build/libs$ java -jar -Dspring.profiles.active=prod {빌드된 jar 파일명}서버 실행시키기 명령어 단계에서 $ java -jar -Dspring.profiles.active=prod {빌드된 jar 파일명} 실행 시 ec2 터미널 창에서 아래와 같은 에러가 발생하게 됩니다. 현재 jar 파일은 그림과 같이 생성되었고 명령어는 java -jar -Dspring.profiles.activate=prod demo-0.0.1-SNAPSHOT.jar 이렇게 진행하였습니다.Failed to initialize JPA EntityManagerFactory: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided)2024-10-03T16:57:37.160Z WARN 5523 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided)2024-10-03T16:57:37.166Z INFO 5523 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]2024-10-03T16:57:37.213Z INFO 5523 --- [ main] .s.b.a.l.ConditionEvaluationReportLogger : Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.2024-10-03T16:57:37.243Z ERROR 5523 --- [ main] o.s.boot.SpringApplication : Application run failedorg.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1806) ~[spring-beans-6.1.13.jar!/:6.1.13] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.1.13.jar!/:6.1.13] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.13.jar!/:6.1.13] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.13.jar!/:6.1.13] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.13.jar!/:6.1.13] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.13.jar!/:6.1.13] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205) ~[spring-beans-6.1.13.jar!/:6.1.13] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:954) ~[spring-context-6.1.13.jar!/:6.1.13] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[spring-context-6.1.13.jar!/:6.1.13] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.3.4.jar!/:3.3.4] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.4.jar!/:3.3.4] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.4.jar!/:3.3.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.4.jar!/:3.3.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.4.jar!/:3.3.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.4.jar!/:3.3.4] at com.example.demo.DemoApplication.main(DemoApplication.java:10) ~[!/:0.0.1-SNAPSHOT] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:569) ~[na:na] at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:102) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT] at org.springframework.boot.loader.launch.Launcher.launch(Launcher.java:64) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT] at org.springframework.boot.loader.launch.JarLauncher.main(JarLauncher.java:40) ~[demo-0.0.1-SNAPSHOT.jar:0.0.1-SNAPSHOT]Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided) at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:276) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.boot.model.relational.Database.<init>(Database.java:45) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:221) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:189) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:171) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1431) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1502) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) ~[spring-orm-6.1.13.jar!/:6.1.13] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390) ~[spring-orm-6.1.13.jar!/:6.1.13] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-6.1.13.jar!/:6.1.13] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-6.1.13.jar!/:6.1.13] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366) ~[spring-orm-6.1.13.jar!/:6.1.13] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1853) ~[spring-beans-6.1.13.jar!/:6.1.13] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1802) ~[spring-beans-6.1.13.jar!/:6.1.13] ... 22 common frames omittedCaused by: org.hibernate.HibernateException: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided) at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.determineDialect(DialectFactoryImpl.java:191) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.buildDialect(DialectFactoryImpl.java:87) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentWithDefaults(JdbcEnvironmentInitiator.java:153) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:364) ~[hibernate-core-6.5.3.Final.jar!/:6.5.3.Final]
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
캐싱 객체 직렬화/역직렬화
안녕하세요! 강의를 수강하고 제 프로젝트에서 캐싱을 적용하고 싶어서 따라 적용해보고 있습니다. package com.ecommerceproduct.api.controller.product.dto.response; import com.ecommerceproduct.domain.product.repository.dao.ProductDetailDao; import com.ecommerceproduct.domain.product.type.OptionType; import com.ecommerceproduct.domain.product.type.ProductCategory; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import lombok.Builder; @Builder public record ProductDetailResponse( Long id, String name, StoreInfo store, int quantity, ProductCategory category, String thumbnailImgUrl, int basePrice, @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") @JsonSerialize(using = LocalDateTimeSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) LocalDateTime createdDateTime, List<ProductOptionInfo> options ) { public static ProductDetailResponse from(List<ProductDetailDao> daos){ List<ProductOptionInfo> options = new ArrayList<>(); daos.forEach(dao -> options.add(ProductOptionInfo.from(dao.option()))); ProductDetailDao dao = daos.get(0); return ProductDetailResponse.builder() .id(dao.id()) .name(dao.name()) .store(StoreInfo.from(dao.store())) .quantity(dao.quantity()) .category(dao.category()) .thumbnailImgUrl(dao.thumbnailImgUrl()) .basePrice(dao.basePrice()) .createdDateTime(dao.createdDateTime()) .options(options) .build(); } public record StoreInfo( Long storeId, String name ) { public static StoreInfo from(ProductDetailDao.StoreInfo daoStore){ return new StoreInfo(daoStore.storeId(), daoStore.name()); } } public record ProductOptionInfo( Long optionId, String name, int count, int price, OptionType optionType ){ public static ProductOptionInfo from(ProductDetailDao.ProductOptionInfo option){ return new ProductOptionInfo( option.optionId(), option.name(), option.count(), option.price(), option.optionType() ); } } } 해당 클래스를 반환하는 메서드에 @Cacheable을 적용하려고 합니다.@Cacheable(cacheNames = "getProduct", key = "'product:productId:' + #productId", cacheManager = "매니저이름") public ProductDetailResponse get(Long productId) { return ProductDetailResponse.from(productRepository.findWithOptions(productId)); }강의에서 알려주신 매니저와 동일하게 매니저를 bean으로 등록해서 사용해본결과직렬화/역질렬화가 안되는것으로 보입니다. 매니저에 설정을 serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( new Jackson2JsonRedisSerializer<>(objectMapper, ProductDetailResponse.class)))처럼 명시적으로 타입을 지정해주면 문제가 없는 것 같은데 record여서 안되는 걸까 싶어서 class로 바꾸어도 되지는 않더라고요! 강의에서의 Board 클래스는 문제없이 직렬화/역직렬화가 되는데 제가만든 dto가 안되는 이유는 강의의 Board가 @Entity클래스여서 인걸까요??그렇다면 dto로 쓰려면 매니저를 각각의 dto마다 매니저를 하나씩 만들어주어야할까요?
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
@Cacheable key 속성에서 page, size 파라미터값 null
BoardService.class@Cacheable( cacheNames = "getBoards", key = "'boards:page:' + #page + ':size:' + #size", cacheManager = "boardCacheManager" ) public List<Board> getBoards(int page, int size) { log.info("page={},size={}", page, size); Pageable pageable = PageRequest.of(page - 1, size); Page<Board> pageOfBoards = boardRepository.findAllByOrderByCreatedAtDesc(pageable); return pageOfBoards.getContent(); }BoardController.class@GetMapping public List<Board> getBoards( @RequestParam(name = "page", defaultValue = "1") int page, @RequestParam(name = "size", defaultValue = "10") int size ) { log.info("page={}, size={}", page, size); return boardService.getBoards(page, size); }#page, #size 값에 모두 null이 저장되는 것 같습니다.위 코드에서처럼 BoardService.getBoards(), BoardController.getBoards() 두 메서드 전부 page, size 값을 로그로 출력해보니 각각 1, 10으로 여기서는 정상적으로 나오는데, 어디서 문제가 있는지 모르겠습니다.구글링을 해도 마땅한 답을 구할 수 없어서 질문 남깁니다!
-
해결됨비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
도커 컴포즈
현재 개인 프로젝트에 EC2에 도커 컴포즈로 프론트, 백엔드, db까지 실행되도록 아키텍처를 구성 했습니다.그런데 말씀하신 것처럼 RDS, elastiCache 를 분리시켜서 아키텍처를 구성 해야 할 필요성이 있을까요?
-
미해결비전공자도 이해할 수 있는 Redis 입문/실전 (조회 성능 최적화편)
vCPU 할당량 증가
안녕하세요 강의 잘 듣고 있습니다!You have requested more vCPU capacity than your current vCPU limit of 1 allows for the instance bucket that the specified instance type belongs to. Please visit http://aws.amazon.com/contact-us/ec2-request to request an adjustment to this limit. <EC2, RDS, Spring Boot, Redis 셋팅>강의 중t3s.small 로 인스턴스를 설정하려 하는데 위와 같이 문구가 나오면서 '인스턴스 시작 실패'가 떴습니다.할당량 증가 요청을 보내기 위해 ' 새 할당량 값'을 넣어야 되는데 얼마를 넣어야 되는지 알 수 있을까요?