묻고 답해요
167만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
레디선 테스트코드에서 채널이름은 없어도 되는건가요?
안녕하세요.레디선을 CLI 에서 테스트할 때는 채널명이 있었는데,자바소스로 옮겨서 테스트 할 때는 채널명을 지정하지 않은 것 같아서요.. 이 부분이 이해가 안되서 질문드립니다.
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
70강 - 글로벌 액터로 격리된 Protocol 을 extension 에서 conform 시 타입 본체의 격리 수준 질문
강의에서는 5:16 쯤 "프로토콜 자체가 글로벌 격리로 구현된 프로토콜의 경우, 동일한 소스파일(.swift) 인 경우 확장에 프로토콜을 채택하더라도 타입 전체가 글로벌 격리로 추론됨" 이라고 설명합니다.하지만 동일한 파일에서 아래와 같이, extension 에 protocol 을 채택해도 타입 본체는 nonisolated context 인것을 확인할 수 있었습니다.@MainActor protocol GloballyIsolated { func updateUI(vc: UIViewController) } struct S { func updateUI2(vc: UIViewController) { // nonisolated context vc.view.backgroundColor = .red // 💥💥💥 Main actor-isolated property 'backgroundColor' can not be mutated from a nonisolated context } } extension S: GloballyIsolated { func updateUI(vc: UIViewController) { // MainActor context vc.view.backgroundColor = .red } } 설명하시는 시점과, 현재 시점의 동작이 달라진건가? 생각도 했었는데, 강의 6:58 쯤에서는 "GlobalProtocol 을 확장해서 채택한다하더라도 본체 자체는 비격리 된다" 라고 설명합니다. 즉, 동일한 코드에 대해 설명이 다른듯 하여 조금 헷갈리는 부분이 있어서요!현재 시점에는 "프로토콜 자체가 글로벌 격리로 구현된 프로토콜의 경우, 동일한 소스파일(.swift) 인 경우 확장에 프로토콜을 채택하더라도 타입 전체가 글로벌 격리로 추론되지 않음" 으로 동작하는것 같은데 이 부분에 대한 강의 자료가 잘못된건지, 아니면 제가 놓친 부분이 있는지 궁금합니다. 감사합니다.
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
모니터와 synchronized, ReentrantLock, 원자적연산 CAS관련해서 추가적으로 더 깊게 공부했는데 제가 이해한 것이 맞나요??
운영체제 관점의 실제 모니터 명세는 조건변수를 추가할 수 있습니다.synchronized는 자바가 구현한 모니터인데 조건변수의 추가가 불가능해서 생산자 소비자 문제를 해결할 수 없습니다.ReentrantLock은 자체적으로 구현한 락을 사용하지만 그 락의 구현은 실제 조건변수를 추가할 수 있는 모니터의 명세를 따르고 있어 모니터를 구현했다고 할 수 있습니다.모니터는 CAS를 내부적으로 사용합니다. CAS를 반복문으로 돌리며 바쁜대기를 하거나 Park로 락을 획득하고 반환합니다.원자적 연산도 CAS를 구현하지만 단순 속도가 synchronized와 ReentrantLock보다 더 빠른 이유는 락의 존재 유무입니다. 혹시 제가 이해한 내용들이 맞을까요?? 현재 81강까지 들었습니다!
-
미해결모든 개발자의 실무를 위한 올인원 기본기 클래스
mac python 3.10 - permission denied
기본 파이썬 명령어의 버전을 바꾸기 위해 심볼링 링크를 업데이트 하려고 하니 permission denied가 뜹니다. 이미 파이참이 설치 되어 있어서 가상환경 외부 의존성을 설치하려고 경로를 맞추려고 하는데 해당 부분을 어디서 진행해야하는지 모르겠습니다.
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
Future cancel기능을 사용했지만 interrupt가 발생하지 않을 때 어떻게 처리해야하나요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]안녕하세요.Future의 cancel기능에 대해 실험하면서 해결하기 어려운 부분을 만나 질문드립니다.📋실험 내용Callable의 call메서드 내부에서 I/O와 같은 블로킹 작업 진행(or Interrupt가 되지 않는 작업)Future의 cancel 진행스레드 호출 대상은 CancellationException 예외 발생 후 진행※ 스레드 풀의 스레드는 계속 실행 중임을 확인 💥문제 사항이런 방식으로 로직이 지속 실행되면 스레드 풀의 스레드는 종료되지 않으면서 고갈되어 가고 결국엔 작업이 쌓이다가 에러를 뱉어내고 오작동 하게 될 것이라 추측됩니다. ❓질문 내용Q. cancel기능은 Interrupt 를 발생해주면서 작업을 중단합니다. 만약 I/O 작업 처럼 Interrupt가 먹히지 않는 로직에서는 어떻게 스레드의 실행을 종료하고 스레드 풀로 반납할 수 있을까요?💻 코드public class FutureCancelMain { private static boolean mayInterrupIfRunning = true; public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(1); Future<String> future = es.submit(new MyTask()); log("Future.state: " + future.state()); sleep(3000); log("future.cancel(" + mayInterrupIfRunning + ") 호출"); boolean cancelResult = future.cancel(mayInterrupIfRunning); log("cancel(" + mayInterrupIfRunning + ") result: " + cancelResult); try { log("Future result: " + future.get()); } catch (CancellationException e) { log("Future는 이미 취소되었습니다."); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } es.close(); } static class MyTask implements Callable<String> { @Override public String call(){ boolean flag = true; // Scanner scanner = new Scanner(System.in); while(flag) { log("무한 작업 중..."); } return "Completed"; } }
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
2.스레드 생성과 실행 - 17P 그림 문의
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]안녕하세요, 강의 잘 수강하고 있습니다. 17P에 시간의 흐름으로 분석 이라는 그림이 있습니다.그런데, 마치 스레드 생성 이후에, start()를 통해 실행을 하는 것 처럼 그림이 그려져 있습니다. 'start() 호출 전 3. 호출 후' 라는 프린트가 중간에 끼어있으니 더 그런것 같은데. 제가 알기로 start() 명령어와 동시에 실제 스택이 생성되고 실행되는 것으로 알고 있습니다. 그렇다면 '1 start() 호출 전' 이 main 박스 맨 위로 가고, 스레드 생성이 호출 뒤로 가야 하는거 아닌가 싶습니다.
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
자바 동시성 현업에서 사용예시가 궁금합니다
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문 전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요?예3. 질문 잘하기 메뉴얼을 읽어보셨나요?예[질문 내용]안녕하새요 강사님 수업내용이 너무 알차서 정말 잘 듣고 있습니다 근데 하나 궁금한 점이 생겨서요!요즘 백엔드는 서버가 여러대인 환경이라 각 서버의 메모리가 공유되지 않아서 자바에서 서로 락을 걸어도 서로의 RAM이 다르기때문에 재고쪽 문제에서 생기는 좋아요 수라던가 재고 같은 문제는 자바 쪽 락보다는 DB의 낙관적락 또는 비관적 락으로 막아주는경우가 많다고 보게 되었는데 또 다른 예시로는 버튼을 따닥 해서 여러 요청이 들어오는 경우도 자바 스레드 제어보다는 DB에서 유니크 키로 막을수 있다고 생각이 드는데 그렇다면 현업에서 ConcurrentHashMap이나 Atomic, synchronized 같은 자바 동시성 기술은 구체적으로 어떤 상황에서 필수적으로 사용되나요?DB 부하를 줄이기 위래 또는 성능 최적화를 위해 멀티스레드를 어떻게 활용하는지도 궁금합니다 !
-
미해결고수가 되는 파이썬 : 동시성과 병렬성 문법 배우기 Feat. 멀티스레딩 vs 멀티프로세싱 (Inflearn Original)
Pros and Cons 관련하여 질문드립니다
안녕하세요 강사님11강 24분쯤에 indent안하게 되면 무한반복되는데 이유가 왜그런지 이해가안됩니다.indent 되든 안되든 결국 event.set()는 호출되고 결국 멈춰야하는거 아닌가요?어떻게 무한반복될수있는 구조인가요?
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
ReentrantLock 동작 원리에 대한 질문입니다.
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]ReentrantLock에 대한 순서 보장에 대해 궁금증이 생겨 질문을 남깁니다.producerCond에서 대기하고 있던 스레드가 producerCond.signal() 호출로 깨어났을 때 ReentrantLock의 lock을 얻기 위한 대기 큐로 들어가는 것으로 알고 있습니다.근데 이때 signal() 호출로 깨어난 A 스레드가 lock 대기 큐로 들어갔는데 앞에 이미 스레드 B가 있어서 B가 ReentrantLock의 lock을 얻어버렸다고 가정했을 때 A는 다시 producerCond.await() 호출로 producerCond 스레드 대기 큐에 들어가면 맨 뒤로 이동할 것 같습니다. 근데 저는 B는 await()에 넣고 A를 실행하는 순서로 순서를 보장하고 싶은데 이것도 더 세밀하게 구현을 할 수 있는 것일까요?? 제가 질문에 대한 설명을 잘 못해서.. 혹시 이해가 되신다면.. 답변해주신다면 감사하겠습니다!
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
synchronized 대신 join 을 사용하여 순서를 보장할 경우 해법이 아닌가요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]synchronized 이전 강의에서 join 을 활용하여 순서를 쓰레드의 순서를 보장하여 실행하는 방법에 대해 배웠습니다. 만약 동시에 같은 값에 대해 접근하는 것이 문제 라고 가정 할 경우 join 을 활용하여 특정 쓰레의 종료 이전 까지 다음 쓰레드 를 시작하는 것을 막는 다면 해결할수 있는 문제가 아닌가 생각합니다.예를들어 다음과 같이 t1.join() 을 사용하면 t1 의 쓰레드가 종료 이전 까지는 t2 는 대기 상태가 될 것이고 t1 에서 최종 결과가 나오기 전까지는 t2 는 실행하지 못할 것 입니다.public class BankMain { public static void main(String[] args) throws InterruptedException { //BankAccount account = new BankAccountV1(1000); BankAccount account = new BankAccountV1(1000); Thread t1 = new Thread(new WithdrawTask(account, 800), "t1"); Thread t2 = new Thread(new WithdrawTask(account, 800), "t2"); t1.start(); t1.join(); sleep(500); log("t1 state :"+t1.getState()); log("t2 state :"+t2.getState()); t2.start(); //t2.join(); log("최종 잔액 : "+account.getBalance()); } }
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
메모리 가시성 문제 질문
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]메모리 가시성 문제 가 발생하는 이유는 쓰레드가 공유 된 runFlag 값에 대해 반영이 않되어서 발생하는 문제라고 설명을 해주셨습니다. 예를들어 main 쓰레드에서는 false 를 true 로 변경하였으나 work 쓰레드에서는 해당 값이 변경되었는지 모르기 때문에 계속 동작하는 상태 입니다. 여기서 이해가 않가는 부분이 모든 프로그램은 보조기억 장치에서 실행을 하고 실행에 필요한 변수가 값 들은 모두 ram 과 같은 주 기억 장치에 저장되고 사용되는 걸로 알고 있습니다. 그렇다면 이 과정에서 쓰레드는 캐시메모리가 아닌 주기억 장치에서 값을 읽어서 사용해야 합니다. 지금 과정으로 설명된 것을 이해 하면 주기억 장치 또한 단순히 값을 저장하는 것 뿐이고 실제로는 캐시 메모리에서 한번더 데이터를 저장하고 cpu 는 캐시메모리에 접근하여 데이터를 연산한다고 이해 했습니다. 맞을까요?
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
20강 Task 내부에서 nonisolated async 호출 예제 질문드립니다
18분 55초 쯤 예시를 보여주시면서/// 2) (액터 외부) 동기 코드가 (일반) 비동기 코드에서 불려지면.. 어떤 (컨텍스트에 해당하는) 작업(Task) 내부에서 실행됨 ===> Task 격리 func readAny(with books: [Book]) async { if let book = books.randomElement() { readBook(with: book) } } Task { let account = LibraryAccount(idNumber: 1) /// 액터 let books = await account.booksOnLoan /// 액터내부의 (상태)값 가져오기 (Sendable타입) await readAny(with: books) /// 액터 외부에서 실행 (일반적으로 이런 구현은 옳지 않음) }위와 같은 코드에서 Task 가 3번 스레드에서 돌아가고 있었다고 치면, account.booksOnLoan 을 가져오는건 2번 스레드로 갈 수 있다고 쳐도, await readAny(with:) 는 다시 Task 와 동일한 3번 스레드에서 돌아가게 될거다! 라고 말씀을 주셨는데요사실 nonisolated async func 인 readAny 를 await 로 호출을 하는 순간, 해당 함수는 Task 의 스레드는 무관한게 아무 스레드에서나 돌아갈 수 있다고 생각을 했습니다 (실제로 Task 를 mainActor context 로 두고 해당 코드를 돌려봐도 readyAny 는 main 에서 돌아가지 않았구여!)이해 편의상 돌려서 설명을 주시건가??? 생각이 들지만 혹시 정확히 어떤 의미로 요렇게 말씀을 주신건지 궁금합니다~감사합니다저장
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
19강 Actor 의 동작 관련해 질문드립니다
안녕하세요 앨런님! 19강에서 actor 를 이해 편의상 "특정한 하나의 스레드에서만 실행"되는 것처럼 그림을 첨부해주셨는데요 (물론 설명으로는 한번에 하나씩 실행되는건 맞지만 스레드를 특정할 수는 없다" 라고 말씀주셨지만요)그러면 이해 편의상이 아니라, 좀 더 정확하게는 제가 이해한 내용이 맞을지 한번 확인 부탁드려도 될까요?항상 좋은 강의 감사합니다. 기본적으로 actor 의 실행 스레드는 고정되어있지 않음Actor 의 메서드나 데이터에 접근하면 serial executor 에 작업이 쌓일 것이때 actor 는 선입 선출로 동작하지 않으므로, 우선 순위에 따라 실행 순서는 달라질 수 있음Actor 의 작업이 실행될때 어느 스레드에서 실행할건지는 상관없고 중요하지도 않음. 한 작업이 끝나고 나면, serial executor 에서 대기하고 있던 작업은 어느 스레드에서든 heap 에 저장된 데이터 읽어서, 그 데이터 기반으로 작업하면 되는 것.중요한건 데이터 경쟁이 발생하지 않게 serial executor 에 넣어서 actor 관련 작업은 한번에 하나의 스레드에서만 돌게 하는 매커니즘
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
Future4-이유 질문
1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]안녕하세요.Future를 거치는 SumTaskMainV2에서 future2.get()이 future1.get() 실행 후 task1의 call() 완료를 기다리지 않고 바로 실행되는 것을 콘솔 로그로 확인 가능했는데요.그렇다면 Future를 거치지 않고 결과를 반환한다 가정할 때, task2가 task1 완료를 기다린 후 수행된다고 나와있는데, "Future는 한 스레드 작업의 future1.get() 실행 후, 다른 스레드의 작업 future2.get()은 future1의 call() 완료까지 기다리지 않는 기능이 있다"이렇게 이해해도 될까요?
-
미해결고수가 되는 파이썬 : 동시성과 병렬성 문법 배우기 Feat. 멀티스레딩 vs 멀티프로세싱 (Inflearn Original)
daemon=True가 안 먹히네요
안녕하세요. 저는 daemon=True 해도 서브가 모두 돌고 끝납니다. 무슨 이유가 있을까요? # 스레드 실행 함수 def thread_func(name, d): logging.info('Sub-Thread %s: starting', name) for i in d: print(i) logging.info('Sub-Thread %s: finished', name) ... # 함수 인자 확인 # daemon : default False x = threading.Thread(target=thread_func, args=('First', range(20000)), daemon=True) y = threading.Thread(target=thread_func, args=('Second',range(10000)), daemon=True)19:16:32: Main-Thread: all done012...1999919:16:32: Sub-Thread Second: finished19:16:32: Sub-Thread First: finished
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
Actor에서 Task vs Task.detached 사용 시 재진입 문제 질문
actor TaskImageDownloader { /// (다운로드) 상태를 저장하기 위한 열거형 정의 enum DownloadState { case completed(UIImage) case loading(Task<UIImage, Error>) case failed } private(set) var cache: [String: DownloadState] = [:] func image(from url: String) async throws -> UIImage { /// 기존에 저장된 상태가 있는 경우 (캐시 먼저 확인) if let cachedState = cache[url] { switch cachedState { case .completed(let image): /// 이미지 리턴 return image case .loading(let task): /// 작업(Task)을 기다렸다가 ===> 이미지 리턴 return try await task.value case .failed: throw "이미지 다운로드 실패" } } /// 작업(Task)을 생성 let task = Task.detached<UIImage, Error> { let image = try await downloadImage(from: url) return image } /// 일단 (완료되지 않은) 작업 상태를 보관 cache[url] = .loading(task) do { /// 작업의 완료를 기다렸다가 ===> 완료되면 ===> 완료상태(이미지)로 바꿔서 보관 let image = try await task.value cache[url] = .completed(image) return image } catch { cache[url] = .failed // 에러 발생의 경우 throw "이미지 다운로드 실패" } } } Actor에서 Task vs Task.detached 사용 시 재진입 문제 질문안녕하세요.위 코드에서 Task.detached 대신 일반 Task {}를 사용하면 재진입 문제가 발생할 수 있을 것 같아 질문드립니다.제가 이해한 바로는:Task {}를 사용하면 생성된 작업이 actor의 context를 상속받아 serial executor에서 실행됩니다다운로드 작업 중 await를 만나면 actor가 suspension되고, 이 시점에 다른 스레드에서 해당 actor에 재진입할 수 있습니다따라서 동일한 URL에 대해 중복으로 다운로드 작업이 생성될 가능성이 있습니다반면 Task.detached를 사용하면 actor context와 독립적으로 실행되어 이러한 재진입 문제를 방지할 수 있는 것으로 이해했습니다.제가 이해한 내용이 맞는지 확인 부탁드립니다. 감사합니다.
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
낙관적 락을 사용할 떄 차이점
@Version만 붙여도 자동으로 낙관적 락을 적용 시켜주는 걸로 알고 있습니다. 근데 강의에서는 @Lock(LockModeType.OPTIMISTIC) 을 이용해서 조회 시점부터 락을 거는 이유가 있을까요?@Lock(LockModeType.OPTIMISTIC) 를 제거하고 동시성 테스트 했을 떄도 정상적으로 돌아가긴 합니다. 어떤 차이가 있을까요
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
동시성 검증 코드에 관한 문의
안녕하세요. 동시성 검증 코드 관련하여 궁금한 점이 있어 문의드립니다.강의에서 ExecutorService를 사용하여 동시성 검증을 수행하신 것으로 보았는데, 이 방식만으로 실제 동시성 문제를 충분히 검증할 수 있을지 궁금합니다.컨텍스트 스위칭 타이밍에 따라 경합이 발생하지 않을 가능성도 있을 것 같습니다.예전에 면접에서 비슷한 질문을 받았는데, ExecutorService 기반 테스트만으로는 실제 운영 환경(수십만 건의 요청)에서 발생할 수 있는 동시성 이슈를 완전히 검증하기 어렵다는 지적을 받은 적이 있습니다.혹시 이러한 한계를 보완할 수 있는 다른 검증 방법이나 접근 방식이 있을까요?
-
미해결김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성
오타 제보
4번 스레드 제어와 생명 주기2 PDF의 24페이지 "그리고 스캐줄링 큐 ..." -> "그리고 스케줄링 큐 ..." 7번 고급동기화 PDF의 1페이지 "LockSupport의 대표적인 기능은 가능과 같다" -> "LockSupport의 대표적인 기능은 다음과 같다"
-
미해결재고시스템으로 알아보는 동시성이슈 해결방법
단일연산
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. final Stock stock = stockRepository.getByProductId(productId); stock.decrease(quantity); stockRepository.save(stock) public void decrease(final Long quantity) { if (this.quantity - quantity < 0) { throw new RuntimeException("재고 부족"); } this.quantity -= quantity; } 락을 건 이유는 decrease 로직에서 quantity를 줄이고 다시 저장했기 때문이라 생각했습니다. stock 상태가 코드레벨에서 변하기 때문에 발생하기 때문입니다.다만 코드레벨에서 변경하지 않고 디비에서 직접 변경한다면(update stock quantity=quantity-1 과 같이) update 자체가 배타락이기 때문에 굳이 락을 걸지 않아도 된다고 판단하였습니다. 그래서 단일 연산으로 해결할 수 없는 상황은 quantity 필드 뿐만 아니라 다른 필드에도 영향이 미칠 때라 생각했습니다. stock 필드에 soft delete 필드 delYn을 추가하고,코드 상에서 만약 재고가 0이면 delYn=True 로 바꾼다 이런 경우일 것 같은데 제가 생각한게 맞을까요? 단일 연산으로도 정합성을 유지할 수 있는데 왜 stock을 다시 저장하는지 모르겠습니다