형 질문있어. 페이징 기반 ItemReader 에서는 예제와 같이 ORDER BY 를 추가해야 한다. ORDER BY 가 없으면 매 페이지를 읽을 때마다 데이터의 순서가 보장되지 않아 일부 데이터가 누락되거나 중복될 수 있다. 라고 했잖아. 이 말은 곧 " JpaCursorItemReader 는 ORDER BY를 추가하지 않아도 괜찮다"로 들리는데 맞아? GPT는 아니라고 하거든. cursor 기반도 마찬가지로 ORDER BY가 없으면 재실행마다 DB에 정렬 순서를 위임하는데, DB는 쿼리 플랜이 변경되는 등 여러 원인들에 의해 실행마다 달라질 수 있대. 뭐가 맞아?
안녕하세요! 페이징 처리 방식에 대해 궁금한 점이 있어 질문드립니다. 코드를 보니 요청값으로 offset과 limit을 받고 이를 Pageable로 변환하고 있습니다. data class OffsetLimit( val offset: Int, val limit: Int, ) { fun toPageable(): Pageable { return PageRequest.of(offset / limit, limit) } } 그런데 이 방식은 offset이 limit의 배수가 아닌 경우 의도하는 바와 다르게 동작할 수 있을 거 같다는 생각이 들었습니다. 예를 들어 offset=15, limit=10인 경우 15번째 데이터부터 10개인 15~19번째 데이터를 조회하는 것이 자연스럽다고 생각했습니다. 그런데 현재 구현에서는 offset/limit 계산으로 인해 PageRequest.of(1,10)이 되고 결과적으로 10~19번째 데이터가 조회될 거 같습니다. 제가 이해한 내용이 맞을까요?? 그렇다면 이 방식은 더보기나 무한 스크롤처럼 offset이 항상 limit의 배수로 증가한다는 제약이 있을 때만 안전하게 사용할 수 있는 방식인지 궁금합니다. 예를 들어 클라이언트가 항상 아래처럼 요청한다고 정해져 있다면 문제가 없다고 봐도 될까요? offset=0, limit=20 offset=20, limit=20 offset=40, limit=20 또 이런 제약이 있다면 limit을 request로 받기보다 서버에서 고정값으로 관리하는 것이 더 나은지도 궁금합니다. 마지막으로 어떤 API에서는 offset,limit을 받고 또 어떤 API에서는 page,size를 받는 경우가 있는데 실무에서는 각각 어떤 기준으로 선택하는지도 알고 싶습니다. 취준 준비하면서 제미니님의 강의 도움 많이 받고 있습니다!! 감사합니다 ^^ <정리> PageRequest.of(offset/limit,limit) 방식은 offset이 limit의 배수일 때만 안전한 방식인가요? 더보기/ 무한 스크롤에서는 offset,limit을 사용하는 게 더 자연스러운가요?? limit이 정책적으로 정해져 있다면 request로 받지 않고 서버에서 고정하는 게 더 좋을까요? offset/limit 방식과 page/size 방식은 어떤 기준으로 선택하면 좋을까요??
안녕하세요. 이번에 Node.js and CS Part 1을 수강했고 이어서 Part 2도 열심히 수강할 예정입니다. 이전에도 느꼈던 부분이지만 AI 시대에는 프론트엔드와 백엔드를 엄격하게 나누는 의미가 점점 줄어들고 있다는 생각이 들었습니다. 그런 흐름 속에서 강의가 저에게 정말 많은 도움이 되었습니다. 좋은 강의 만들어주셔서 감사합니다. 혹시 Node.js 시리즈가 Part 3까지 이어지는 것으로 보이는데 그 다음 강의에 대해서도 계획하고 계신 내용이 있을까요? 개인적으로는 운영/배포 과정에서의 실무 노하우나, 확장 가능한 프로젝트 구조 설계, npm 패키지 제작, 디자인 패턴 같은 주제에도 관심이 많아서 이후 강의에서 이런 내용들도 다뤄주신다면 정말 재미있게 들을 것 같습니다. 다음 강의도 기대하고 열심히 공부해보겠습니다.
유튜브에서 보다가 합격하기 위해서 결제하였습니다. 궁금한 점이 있습니다만. 유튜브에서 강의하시던 곳에서 첫번째 문제풀이 하시는 곳있으시자나용. 거기서 첫번째 문제에서 비용효율적인 측면을 생각했을 때 glacier가 훨씬 싸니 이득이라고 생각해서 전 a라고 생각했었는데요. 물론 법정 감사 목적이 아니면 쓰지 않는다는것을 알지만 !가장! 비용효율적인 것을 생각해보면 glacier 가 맞다고 생각이 드는데요 혹시 a가 오답이고 b가 정답인 이유는 무엇일까요? 데이터의 내구성이 매우 중요합니다. 라고 문제에 적혀있는데 내구성면에서는 비용쪽을 생각해서라도 standard쪽으로 가는걸까요? 하지만 그렇게 되면 가장 비용효율적인 면에서 glacier를 이기지 못할것같다는 생각이 꼬리에 꼬리를 또 꼬리에 꼬리를 무한대로 물어 지금 정신병원에 가야될것같습니다.
안녕하세요 강사님, 강의 내용 너무 유익하였습니다. 덕분에 개념 학습에 도움이 많이 되었습니다. 완강과 함께 질문 2가지가 생겨 문의 드리게 되었습니다. 제가 무료 문제 사이트에서 문제를 풀다가 강의에서 다루지 않은 개념들이 많이 나오는 것을 느꼈어요. 예를 들자면 'AWS 서비스 카탈로그', '패시브-패시브 연결', ' 편지 대기열(DLQ)' 등의 내용입니다. 강의 외의 개념들은 시험에서 크게 중요하지 않아서 포함이 안되어 있었던 것일까요? 추가 개념 학습이 필요할 지 여쭤봅니다 ! 두번째로는, 강의용 문제와 타 사이트 문제의 유형이 조금 다른 것 같아 여쭤봅니다. 강의 문제 자료에서는 상황을 설명하고 그에 해당하는 솔루션을 답안에서 찾는 것이 핵심이었다면, 타 사이트에 있는 문제는 상황에 맞는 솔루션 제시까지 문제에 포함되어 있고 답안에서는 솔루션을 구현하는 상세한 방법을 고르는 내용이었습니다. 실제 시험은 어떠한 유형으로 주로 출제되며, 어떠한 전략으로 접근을 하여야 할까요 !? 읽어주셔서 감사합니다 ^^
안녕하세요. aws 자격증을 따보자 따보자 하면서 생각만하고 벌써 반년을 버려버린 사람입니다. 그러던중 다시 공부를 시작하려고 작년에 구매한 유데미를 보려고 하다가 이 강의를 알게 되었습니다. 수강평이랑 이런게 너무 매력적이길래 아무것도 생각안하고 구매했네요... 한가지 질문이 있는데 시험을 칠때 영어로 치는게 더 도움될지 한국어로 쳐도 될지에 대해서 문의드릴려고 합니다.. 구매하고 바로 질문을 쓰는거라 아직 수업 내용을 못봐 수업에 나와있을수도 있는데 이렇게 질문을 드려서 죄송합니다. 열심히 해보겠습니다.
안녕하세요. 강사님 캐시전략 - Write-behind 전략을 설명해주셨는데, 인스타라이브나 유튜브라이브에서 좋아요를 한 사용자가 여러번 누를 수 있는데, 이때가 아마 Write-behind 전략을 적용할 수 있을 거 같습니다. 1. 좋아요를 레디스 캐시에 카운트 증가 2. 좋아요 누른 개수를 몇 초마다 flush로 카프카 큐에 발행 3. 카프카 consumer에서 db저장 이런 방식으로 설계가 가능할 거 같습니다. 강의에서는 Write-behind DB에 나중에 저장한다고 말씀하셨는데 그럼 이런 라이브 상황에서 DB에 좋아요를 언제 저장하는 것이 바람직할까요? 그 기준을 어떤 식으로 잡으면 좋을지도 선생님의 고견이 듣고 싶습니다. 좋은 강의 감사합니다.
안녕하세요, 2회독 하는중입니다. 확실히 강의 활용하면서 진행하니깐 느끼기(?)가 뭔지 알것같고 궁금한점이 생기네요. CouponService 에서 이미 다운받은 쿠폰은 내려주지 않는 로직을 만들면서 궁금한점이 여러개 생겼습니다. 작성하면서 정리하니깐 궁금한점이 계속 꼬리물어서 생긴다고 해야하나 그래서 의식의 흐름대로 적는 것 같아서 미리 죄송합니다 🥲 /// CouponService public List<Coupon> getCouponsForMenus(Principal principal, Collection<Long> couponIds) { Set<Long> applicableCouponIds = couponFinder.findApplicableCouponIds(couponIds); Set<Long> downloadedCouponIds = issuedCouponFinder.findDownloadedCouponIds(principal.getId()); Set<Long> downloadableCouponIds = applicableCouponIds.stream() .filter(id -> !downloadedCouponIds.contains(id)) .collect(Collectors.toSet()); return couponFinder.findAllById(downloadableCouponIds); } // CouponFinder public Set<Long> findApplicableCouponIds(Collection<Long> menuIds) { List<CouponTargetEntity> menuTargets = couponTargetRepository.findByTargetTypeAndTargetIdIn( CouponTargetType.MENU, menuIds ).stream().filter(CouponTargetEntity::isActive).toList(); List<CouponTargetEntity> categoryTargets = couponTargetRepository.findByTargetTypeAndTargetIdIn( CouponTargetType.MENU_CATEGORY, menuCategoryRepository.findByCategoryIdIn(menuIds).stream() .filter(MenuCategoryEntity::isActive) .map(MenuCategoryEntity::getCategoryId).collect(Collectors.toSet()) ); return Stream.concat(menuTargets.stream(), categoryTargets.stream()) .map(CouponTargetEntity::getCouponId) .collect(Collectors.toSet()); } CouponService 에서 implement 계층 CouponFinder, IssuedCouponFinder 를 의존하고 있는 상황에서 각 계층끼리 소통할 때 강사님 Q/A 답변중에서 가능한 db 엔티티 대신에 개념객체를 넘기는 방향으로 설계하는걸 지향하신다고 말씀하신 답변이 기억이 나는데요, 지금 상황에서는 개념객체까지 변환하는것보다는 id 컬렉션만 뽑아내서 넘기는게 더 효율적이고 깔끔한 것 같습니다. 이런 패턴(?) 이 자주 나올 것 같아서 괜찮은지 궁금 합니다. 꼭 id 컬렉션을 넘기는게 아니더라도 entity를 반환해야만 매핑하기 편한 경우가 많을 것 같더라구요 회원/비회원 요구사항 https://inf.run/GTxAx 해당 Q/A 를 보기도 했고 마침 강의 활용해서 커피 판매 어플리케이션을 만들고 싶어서 비회원처리가 필요했습니다. public class PrincipalArgumentResolver implements HandlerMethodArgumentResolver { private static final String PRINCIPAL_ID_HEADER = "Gu-Coffee-Principal-Id"; private static final String PRINCIPAL_TYPE_HEADER = "Gu-Coffee-Principal-Type"; @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().isAssignableFrom(Principal.class); } @Override public @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request == null) throw new CoreException(ErrorType.INVALID_REQUEST, null); Authenticated annotation = parameter.getParameterAnnotation(Authenticated.class); String id = request.getHeader(PRINCIPAL_ID_HEADER); String type = request.getHeader(PRINCIPAL_TYPE_HEADER); validatePrincipal(annotation, id, type); return Principal.of(id, type); } private void validatePrincipal(Authenticated annotation, String id, String type) { boolean isRequired = (annotation != null && annotation.required()); if (isRequired) { if (id == null) throw new CoreException(ErrorType.UNAUTHORIZED, null); if (!PrincipalType.USER.name().equals(type)) { throw new CoreException(ErrorType.UNAUTHORIZED, "회원 전용 서비스입니다."); } } if (type == null) { throw new CoreException(ErrorType.INVALID_REQUEST, null); } } } @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Authenticated { boolean required() default true; } 저는 Authenticated 라는 메타 어노테이션을 사용해서 @GetMapping("/v1/issued-coupons") private ApiResponse<List<IssuedCouponResponse>> getIssuedCoupons(@Authenticated Principal principal) { List<IssuedCoupon> issuedCoupons = issuedCouponService.getIssuedCoupons(principal); return ApiResponse.success(IssuedCouponResponse.from(issuedCoupons)); } @PostMapping("/v1/coupons/{couponId}/download") private ApiResponse<?> download( Principal principal, @PathVariable Long couponId) { couponService.download(principal, couponId); return ApiResponse.success(); } public void download(Principal principal, Long couponId) { if (principal.getType().equals(PrincipalType.GUEST)) { throw new CoreException(ErrorType.UNAUTHORIZED, "비회원은 쿠폰을 다운로드할 수 없습니다."); } 애초에 회원만 접근 가능한 api 는 어노테이션으로 처리하던가 서비스 단에서 예외를 던지게 할 수도 있을 것 같아서 이렇게 만들어 봤습니다. 이런식의 접근이 괜찮은지 당장은 인증객체(Principal)? 랑 User 랑은 다른 개념일 것 같아서 강의내 User 대신에 사용했습니다. @Table(name = "cart_item") @Entity public class CartItemEntity extends BaseEntity { private Long userId; private String guestKey; @Enumerated(EnumType.STRING) private PrincipalType principalType; private Long menuId; private Long quantity; } public class Cart { private Principal principal; private List<CartItem> items; } 마지막으로 장바구니에 비회원도 담을 수 있다는 요구사항이 있다고 가정하면 테이블에는 userId 만 둬서 null 로 구분하는 것 보다 key 필드를 추가하고 public interface CartItemRepository extends JpaRepository<CartItemEntity, Long> { @Query("SELECT c FROM CartItemEntity c " + "WHERE (:type = 'USER' AND c.userId = :id) " + "OR (:type = 'GUEST' AND c.guestKey = :key)") List<CartItemEntity> findByPrincipal( @Param("type") String type, @Param("id") Long id, @Param("key") String key ); } 이런식으로 쿼리로 분기처리해야지 생각했는데요. 예전 강사님 Q/A 답변 중에서 쿼리중심의 어플리케이션이 되는걸 지양한다는 느낌의 답변을 봤던 것 같습니다. AI 한테 물어봤을때는 해당 로직은 필터링 목적이니 쿼리로 처리하는게 성능상 효과적이라는 답변을 줬는데요. List<IssuedCouponEntity> issuedCoupons = issuedCouponRepository.findByUserId(userId) .stream() .filter(IssuedCouponEntity::isActive) .toList(); isActive 인 데이터를 가져올 때 강사님이 findByXXAndStatus 로 한번에 가져오거나 혹은 위처럼 filter로 처리하는 두가지 패턴을 보여주셨습니다. 이걸 처음 봤을 때, 미세한 성능보다는 filter 로 직관적으로 활성상태인 데이터만 가져온다는 목적을 코드로 보여주는게 더 괜찮겠다고 생각했는데요. 근데 이것도 결국 필터링 목적이니깐 메서드 네이밍(findByXXAndStatus)으로 처리하는게 일관성 있는게 아닌지 궁금합니다.
안녕하세요. 수파베이스 ORM 섹션을 듣고나니 일반적인 백엔드 서버와 api 라우트를 정의하고 데이터를 주고받는게 아닌 클라이언트에서 직접 수파베이스 ORM를 호출할 수 있는 것으로 확인되는데요! 현재 회사에 프론트엔드 개발자 2명이고 백엔드 지식이 많지않아 next.js + 수파베이스로 MVP 검증부터 진행할 것같습니다. 수파베이스로 MVP 검증후 별도의 백엔드 서버로 분리하게되었을때(분리가 꼭 필요한지도 사실 잘 모르겠습니다.) 분리비용을 최소화히기위해 api호출 과정을 next.js의 api라우트를 무조건 거쳐서 수파베이스를 접근하게하려고하는데 이런 과정이 유의미한 작업이 될 수 있는지 궁금합니다.
안녕하세요. 프론트엔드만하다가 강의보고 백엔드에대해 알아가는중입니다. 백엔드쪽은 아예 기반이 없다보니 하나하나 해보면서 따라가고있는중입니다. 1. 트리거는 ui로 설정을안하고 SQL 에디터로 설정을 해주셨는데 따로 이유가 있을까요?? 2. 트리거와 그 안에서 작성해주신 업데이트 함수가 별도로 관리되는것같더라고요. functions가 유틸함수고 Triggers가 시점과 적용대상(어느 테이블에 적용할지?)를 관리하는 영역으로 이해하는게 맞을까요? 3. 트리거는 테이블영역에서 적용이 불가능한가요? RSL처럼요!! 뭔가 분산되어있는 느낌이 있어서요. 4. 숙제내용 제출
💡 [CS 실무 Point: OS 계층의 역량 활용 (epoll / kqueue)] 단일 스레드라고 해서 컴퓨터가 정말로 혼자 일하는 것은 아닙니다. 파일 읽기나 네트워크 통신 같은 무거운 작업은 Node.js가 직접 처리하지 않고, 운영체제 커널이 제공하는 고성능 이벤트 통지 API(Linux의 epoll, macOS의 kqueue 등 시스템 콜) 를 통해 운영체제에게 철저하게 위임(Offloading)합니다. 즉, Node.js의 메인 스레드는 교통정리만 아주 빠르게 수행할 뿐, 실제 무거운 짐은 OS 커널이 백그라운드에서 나르고 있는 것입니다. 안녕하세요, 위 설명에서 질문이 있습니다. 운영체제에 위임하는 작업과 libuv 스레드 풀의 워커 스레드가 어떤 연관이 있고 어떻게 유기적으로 동작하는지 궁금합니다.