묻고 답해요
156만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨실습으로 배우는 선착순 이벤트 시스템
강사님 강의를 듣고 실제 프로젝트에서 이벤트 응모 시스템을 만들어 봤습니다.
안녕하세요 강사님!강사님의 동시성 이슈와 선착순 이벤트 시스템 강의를 듣고 실제 프로젝트에서 이벤트 응모 시스템을 만들어 봤습니다. 아래는 제가 해당 이벤트 응모 시스템을 만들면서 겪었던 이슈와 고민을 블로그 글로 적었습니다.우선 강사님 덕분에 프로젝트 잘 끝낼 수 있어서 감사했습니다.블로그 글을 쓰면서 든 생각은 여기서 더 개선 시킬점은 없을까? 좀 더 고민 해볼만한 부분은 없을까? 라는 생각이 들었습니다.개발에 정답은 없지만 그래도 좋은 방향은 있다고 생각해서 최근에 제가 쓴 글을 다시 보면서 어떤 부분을 고치면 좋을까 라는 생각을 하고 있습니다.혹시 강사님께서 제 글을 보시고 만약 면접관이시라면 어떤식으로 질문이 들어올 수 있을지 특히 꼬리 질문에 대해 얘기를 해주실 수 있을까요 해주신 내용을 바탕으로 깊게 고민을 해보고 면접 대비도 하고 싶습니다.
-
해결됨실전! 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 활용
분산 락에 대해 질문 있습니다.
이전에 분산 락을 구현해본 경험이 있습니다.그때는 간단하게 구현을 하다보니 분산 락에 대해 꼬리 질문이 들어 온다면 어떤 질문들이 들어올지 궁금합니다.강사님께서 생각하시기에 분산 락 관련하여 질문을 한다면 혹시 어떤 식으로 질문을 하실지 궁금합니다.
-
미해결15일간의 빅데이터 파일럿 프로젝트
머하웃 추천 ml 실행 안됨
안녕하세요! 머하웃에서 실행하는 추천 잡이 실행이 안되어서 질문 남깁니다..! 제가 시도한 방법은 다음과 같습니다휴에서 보이는 잡들 모두 제거하고 실행클라우데라에서 클러스터1을 재시작하고 실행우지를 종료하고 실행이 방법들로는 해결되지 않았습니다.. 어찌해야 할 지 모르겠어 질문 남깁니다..!!
-
미해결15일간의 빅데이터 파일럿 프로젝트
Storm관련 NoClassDefFoundError문의
현재 실시간 데이터 수집부분에서 Storm Topology를 배포하는 단계에서 다음의 스크린샷과 같은 에러가 발생하였습니다. 참고로 저는 storm 버전 0.9.7을 설치하여 실습을 진행중이며 에러 관련 스크린샷은 아래와 같습니다.어떻게 해결하면 좋을지 문의드립니다. 제공하여주신 jar파일에 의존성을 추가해서 해당 의존성을 다운로드해야 할 지 아니면 스톰의 버전을 정확하게 맞추어야 할 지 고민이 되어서 문의드립니다. 감사합니다.
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
메시지 이벤트 발행시 에러 발생 관련 질문이 있습니다.
이번에 면접을 보면서 확실하게 대답을 못해서 고민 끝에 선생님에게 질문 드립니다. 우선 시나리오 말씀드리자면 client 가 해당 게시글에 "좋아요" 등록을 하게 된다면 좋아요 등록 관련 DB 테이블에 insert 동시에 outbox 테이블에도 insert 하도록 설계 되어 있습니다. commit 이 정상적으로 발생되면 kafka 서버에게 이벤트 메시지를 발행 하게 되는데요. 만약 갑자기 이벤트 발행시 kafka 서버가 죽었다고 하면 복구 될때 까지 기다리다가 retry 통해 아직 메시지 발행 하지 못한 메시지 outbox 테이블에 조회해서 메시지 발행 하면 문제가 없을 것 같은데요. 하지만 다시 처음부터 설명하면 client 가 좋아요 등록 후 DB 서버가 죽어서 "좋아요" 관련 테이블 및 "outbox" 테이블에 insert 를 못했다고 가정 했을때 어떻게 대처 해야 하는지 역으로 질문 받았습니다. 일단 DB 서버가 죽었으면 빠르게 고객에게 에러 메시지를 전달과 동시에 담당 개발자에게 빠르게 전달 할 수 있도록 전달 해야 한다고 했습니다. (회사에서 slack 메신저 사용하면 메신저 통해 알림) 혹시 이것보다 더 좋은 방법이 있을까요? 재대로 대답하지 못해 찜찜해서 이렇게 선생님에게 질문 드리네요.
-
해결됨개발자라면 알아야 할 redis 기본
레디스 pub/sub 질문
안녕하세요 강의 잘 듣고 있습니다제가 현재 채팅 프로그램을 만들고 있는데 채팅 서버가 하나이면 굳이 레디스를 사용할 필요가 없겠네요?
-
미해결15일간의 빅데이터 파일럿 프로젝트
5. 빅데이터 클러스터 구성2- CM 소프트웨어 설치 오류
안녕하세요, 선생님. Cloudera Manager 소프트웨어 설치 중 오류가 해결되지 않네요. 도움 부탁드려요! 오류 내용입니다.
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
실무에서의 락
안녕하세요. 현재 좋아요 부분에서 락에 관련해서 강의를 듣고 있습니다. 궁금한 점은 실무에서는 어떻게 사용을 하시는지 궁금합니다.왜냐하면 대부분 실무에서는 테이블 자체를 논리적 외래키로 전부 가져가는 경우를 많이 봤었습니다.외주를 부탁한 외부 업체 또한 낙관적 락을 사용하는 케이스를 봤구요. 강사님께서 일하시는 곳에서는 락의 3가지 케이스를 다양하게 필요에 맞게 사용하는 건가요?!
-
미해결스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
안녕하세요! 테스트 질문입니다
강의에서는 인기글을 테스트할때 DataInitializer클래스에서 아래와 같은 코드를 사용했습니다. void createComment(Long articleId, long commentCount) { while(commentCount-- > 0) { commentServiceClient.post() .uri("/v1/comments") .body(new CommentRequest.Create(articleId, "content",null, 1L)) .retrieve(); } }이렇게 사용해봤더니 카프카에 전달이 되지 않더라구요 void createComment(Long articleId, long commentCount) { while (commentCount-- > 0) { CommentRequest.Create request = new CommentRequest.Create(articleId, "content", null, 1L); try { // 요청 로깅 추가 ObjectMapper objectMapper = new ObjectMapper(); System.out.println("Request body: " + objectMapper.writeValueAsString(request)); commentServiceClient.post() .uri("/v1/comments") .body(request) .retrieve() .toBodilessEntity(); // 응답 처리 추가 } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } } }이렇게 사용해야 인기글 서비스에서 아래처럼 로그가 찍히는걸 확인했습니다. 어디를 확인해야할까요 .. 몇시간째 해결을 못해서 질문드려요!![HotArticleEventConsumer.listen] received message = {"eventId":136009554918850560,"type":"COMMENT_CREATED"
-
미해결15일간의 빅데이터 파일럿 프로젝트
리듀스가 네트워크를 타고 들어오는 경우
안녕하세요. 열강해주셨던 강의 들으며 공부하다 질문이 있어 글 남깁니다. 스파크에 대한 설명에서 '그리고 이 리듀스가 네트워크를 타고 들어온 로컬에 떨어진 앞에 리듀스의 결과를 다시 또 로컬로 떨어뜨린다'는 설명이 있었는데, 이 설명이 어떤 경우가 있는지 감이 잘 안와서 질문드립니다. 리듀스가 네트워크를 타고 들어온다는 것이 어떤 상황을 말하는건가요..? 감사합니다.
-
미해결실습으로 배우는 선착순 이벤트 시스템
쿠폰 발급 개수 제한
쿠폰을 100개까지만 발급하고 싶으면 조건이 count >= 100 return이 맞지 않을까요?
-
미해결15일간의 빅데이터 파일럿 프로젝트
자바 설치 관련 질문
안녕하세요. 개발 환경 구성에서 자바를 설치하는 부분에 궁금한 점이 있어 글을 남깁니다. 제가 이미 JDK 17이 설치되어 있는데, 혹시 다시 1.8을 설치해야 하나요? 만약 해야 한다면, 충돌하는 문제는 없을까요? 답변 부탁드립니다.감사합니다 :)
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
안녕하세요! 강의 잘 듣고 있습니다!!
게시글 구현까지 보고 질문드립니다!지식공유자님의 깊이가 느껴지는 강의인 것 같습니다.. 구현 난이도(저점-고점)에 상관없이 쭉쭉 구현해나가시는게 대단하십니다조그맣게 몇가지 질문이 있습니다강의와 관련된 질문과 그렇지 않은 질문이 섞여있는점 양해부탁드립니다..! 테스트코드@Test를 만드시고, 따로 밑에 메서드를 추가하시는건 반복호출을 위해서인게 맞을까요!? 그렇다면 JUnit의 @ParameterizedTest, @CsvSource 이거를 활용하면 좋을것같은데 사용 안하신 이유나 실무에서 요거를 잘 안쓰시는지 궁금합니다!저도 WebClient나 RestClient로 api테스트를 하긴 하는데요! 그 API Docs 만들어주는 RestDocs는 테스트객체: RestTemplate, WebTestClient, RestClient 와 테스트방법(WebMvcTest, SpringBootTest)과 상관없이 플러그인만 추가하면 api docs가 만들어지는걸까요? 지식공유자님은 현업에서 Swagger, RestDocs중에 어떤걸 쓰시는지 궁금합니다TestContainer 등의 방법은 사용하지 않으시는지!?JPA & SpringController에서 @PageableDefault()로 받는 방법은 주로 사용되지 않는 것일까요..?DTO로 반환해서 Response를 내려주시긴 하시지만 ResponseBody나 ResponseEntity 등으로 감싸서 내려주시지는 않으시는데, 이유가 있으신지요!?강의에서처럼 커버링인덱스와 무한스크롤을 구현하려면 nativeQuery를 사용하지 않고 JPA와Hibernate로 해결하는 방법(JQPL/QueryDsl/Creteria)이나 Raw Library(Spring Data JDBC/JdbcTemplate)으로 해결하는 방법은 없는걸까요? Next제가 아직 모든 강의를 다 본것은 아니지만.. 챕터를 보면 각각 다른 모듈끼리 Join을 하는 경우는 없는 것 같아보입니다..! 혹시 나중에 또 강의를 내신다면 샤드키와 DB 이중화의 fail over에 대한 실전강의, 다른 DB 스키마, 모듈을 사용하는 상황에 하나의 View에 다건의 Join이 들어갈 경우 설계 방법..이나DDD, 클린 아키텍처에 대해서도 다룰 에정이 있으신지 궁금합니다!마지막 덧붙임정말 잘 보고 있습니다!! 미취업자(취준생)에 비해서 중-고급 경력직은 그렇게 많지 않아서 강의 수요가 적기도 하고 각자 나름의 위치에서 배운 self best practice가 있어서 그들만의 생각이나 태클이 들어올 수 있을 것 같은데..이런저런 이유에도 불구하고 이런 귀한 중고급 강의를 내주셔서 정말 감사합니다!강의 끝까지 수강하고 궁금한점 생기면 종종 질문 올리겠습니다
-
미해결15일간의 빅데이터 파일럿 프로젝트
yum install python27 커맨드 에러
안녕하세요 강사님.4.탐색 파일럿 실행 2단계 - 03.휴(Hue) 구성 강좌를 실습 중인데,아래와 같이 yum install centos-release-scl 커맨드와 yum install scl-utils 커맨드는 모두 정상 수행하였으나 이후 yum install python27 커맨드에서 에러가 발생하여 질문드립니다. 에러 메세지로는 패키지가 없다고 뜨는데, 이전 커맨드에서도 mirrorlist 문제가 발생하여서 다른 질문과 답변에 남겨주신 아래 mirrorlist로 갱신을 하였습니다. centos EOS 이슈인 듯 한데.. 혹시 추가로 더 수행해야할 작업이 있을까요? $ echo "http://vault.centos.org/6.10/os/x86_64/" > /var/cache/yum/x86_64/6/base/mirrorlist.txt$ echo "http://vault.centos.org/6.10/extras/x86_64/" > /var/cache/yum/x86_64/6/extras/mirrorlist.txt$ echo "http://vault.centos.org/6.10/updates/x86_64/" > /var/cache/yum/x86_64/6/updates/mirrorlist.txt$ echo "http://vault.centos.org/6.10/sclo/x86_64/rh" > /var/cache/yum/x86_64/6/centos-sclo-rh/mirrorlist.txt$ echo "http://vault.centos.org/6.10/sclo/x86_64/sclo" > /var/cache/yum/x86_64/6/centos-sclo-sclo/mirrorlist.txt
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
실무에서 Primary Key 생성 전략 질문 있습니다.
Primary Key 생성 전략에서 많은 것들을 알기 쉽게 설명해주셔서 감사합니다 🙂 제가 개인적으로 생각하는 가장 베스트 방법 2가지만 설명 드리도록 하겠습니다. Primary Key 값을 Snowflake 알고리즘으로 해결장점인덱스 탐색에 있어서 정렬된 순서로 차례대로 저장 하다보니 범위 검색에 있어서 장점이 될 수 있다.단점Secondary Index 생성시 각각 Leaf Node 에 PK 값 (포인터) 를 가지고 있다보니 아무래도 생성된 Snowflake 알고리즘 값은 길이가 길어서 인덱스 저장용량이 증가 될 수 있다.PK 생성 전략을 Auto_Increment 로 하고 샤딩키(article_id) 값을 Snowflake 알고리즘으로 설정 해서 저장 한다. (단 client 에게는 PK 값 대신 article_id 으로 노출 한다.)장점Secondary Index 생성시 각각 Leaf Node 에 PK 값 (포인터) 값이 용량이 작아 인덱스 저장 용량 부담이 없다.인덱스 탐색에 있어서 정렬된 순서로 차례대로 저장 하다보니 범위 검색에 있어서 장점이 될 수 있다.단점client 으로 부터 요청시 샤딩키(article_id) 값으로 데이터 조회를 해야 하기 떄문에 Secondary Index -> Clustered_Index 까지 두번 인덱스 트리를 탐색 해야 하는 단점이 있다. 일단 이렇게 각각의 장단점을 설명 드렸습니다.여기서 제가 궁금한것은 실무에서 데이터베이스 테이블 설계시 이 두가지 방법 중 하나를 선택 하는데 있어서 각각 어떤 경우에 적합한지 판단 내리기가 힘든 부분이 있습니다. 각각 케이스 마다 장단점을 알고 있지만 아무래도 자세하게 수치화 된 지표 가 없어 선택하는데 있어서 어려움이 있는데요.선생님 경우는 실무에서 이 두가지 중 선택시 어떤 경우에 적절하게 판단 하시는지 노하우를 알려주시면 정말 감사 하겠습니다 🙂
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
멀티 모듈 방식 질문입니다.
안녕하세요 강의 잘 듣고 있습니다.현재 게시글 조회수 부분 보고있는데요 헷갈리는게 있습니다.멀티 모듈 방식 이라 aricle과 view 모듈이 나눠져 있는데요현재는 게시글 조회 api와 게시글 조회수 증가 api가 나뉘어져 있는거같은데요 프론트에서 게시글 조회 시 이 2개의 api를 사용하는 걸까요?게시글 조회 api에서 게시글 조회 -> redis 조회수 1 증가 이렇게 하나의 api가 아니라 실무에서도 api를 나누는 방식으로 진행하는걸까요?
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
강의자료 파워포인트 기준 361~362 페이지를 보면
이렇게 나타나고 있는데요.path > '00a0z' 아닌가요??
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
ArticleRepository의 네이티브 쿼리부분 질문드립니다...
안녕하세요~~ 강의 너무 잘 듣고 있는 1인입니다.다름이 아니라 네이티브 쿼리 작성 부분에서 에러가 나는데 이게 이곳저곳 찾아봐도 해결이 안돼서요ㅠㅠ 자꾸 아래 부분에서 에러가 납니다... 참고로 툴은 vscode로 spring extension받아서 사용중입니다... LIMIT :limit OFFSET :offset " @Query( value = "SELECT article.article_id, article.title, article.content, article.board_id, article.writer_id, " + "article.created_at, article.modified_at " + "FROM ( " + " SELECT article_id " + " FROM article " + " WHERE board_id = :boardId " + " ORDER BY article_id DESC " + " LIMIT :limit OFFSET :offset " + ") t " + "LEFT JOIN article ON t.article_id = article.article_id", nativeQuery = true ) List<Article> findAll( @Param("boardId") Long boardId, @Param("offset") Long offset, @Param("limit") Long limit );
-
해결됨스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판
Selet All 쿼리에서 반복적으로 Clustered Index 탐색 하는지 궁금 합니다.
select * from article where board_id = 1 order by article_id desc limit 30 offset 1499970; 해당 SQL 문을 실행하면 선생님이 설명 해주신 것 처럼 먼저 Secondary Index 에서 offset 0 부터 탐색이 일어나는데요.Secondary Index 에서 offset 0 조회 한 다음 Clustered Index 에서 데이터를 찾는다고 해주셨습니다. 이러한 과정을 offset 1499999 까지 반복 과정이 발생 하는데왜 Secondary Index 에서 탐색 후 Clustered Index 에 데이터를 찾는 과정이 필요한지 궁금 합니다. 그러니깐 Secondary Index 에서만 일단 1499999 번 탐색한 다음에 Clustered Index 을 통해 그 외 데이터 (select *) 을 가져오면 되는것이 아닌가요? 제가 아직 인덱스에 대한 지식이 부족해 이런 질문을 한건데요ㅠ 다시 정리해서 질문 드리자면 왜 Secondary Index 을 통해 offset 순번 1499999 까지 도착 하지 않았는데 불필요하게 Clustered Index 까지 탐색하는가 입니다!