회사에서 Service Trust 라는 이름의 프로젝트를 진행한다.

거기에 나도 같이 참여하여 서비스의 신뢰성을 높이게 되었다.


기준을 어떻게 가져갈까 하다가 딜리버리 히어로의 신뢰성 선언문을 보았다.

 

인상적인 내용 몇 개만 정리해본다.

내용 중 핵심에 진하게 표시하여 나타냈다.


아키텍처 및 기술 부채

A-1 아키텍처를 문서화합니다. 모든 서비스와 해당 종속성은 아키텍처 가이드라인에 설명된 규칙에 따라 아키텍처 차트에 문서화되어야 합니다.

A-2 우리는 이름 지정에 신경을 씁니다. 모든 서비스에는 명확하고 이해하기 쉬운 이름이 있어야 합니다. 

A-3 모놀리스로 시작합니다. 모놀리스 첫 번째 패턴을 따르면 빠르게 움직일 수 있으며 나중에 요구 사항에 대한 더 나은 지식을 갖게 되면 하위 서비스로 나눌 수 있습니다. 

A-4 우리는 마이크로서비스 원칙을 수용합니다 . 새로운 서비스는 다음을 충족해야 합니다.

  • API나 메시지를 통해서만 통신하세요.
  • 다른 서비스와 데이터 저장 공간을 공유하지 마세요.
  • 기술적 구성 요소가 아닌 제품 도메인(제한된 컨텍스트)의 일부가 되어야 합니다. 
  • 단일 책임 원칙을 따르고 한 가지 일을 잘 수행하십시오.
  • 단 하나의 분대만이 소유할 수 있습니다. 
  • 2개 서비스보다 깊은 동기식 호출 체인을 피하세요. 
  • 데이터와 도구를 사용하여 서비스의 상태와 동작을 관찰하여 사고로부터 복원하는 데 걸리는 시간을 최소화합니다.
  • 오버헤드를 정당화할 수 있을 만큼 충분한 크기를 갖습니다(나노 서비스 없음).
  • 데이터 일관성 보장(예: 최소 1회 메시지 전달)을 전달합니다.

A-5 우리는 Data mess를 수용합니다. 데이터 생산자는 자신의 도메인 데이터를 공통 데이터 플랫폼(Datahub)에 제품으로 게시할 책임이 있으며 다음 사항을 보장합니다.

  1. 검색 가능.
  2. 주소 지정 가능.
  3. 신뢰할 수 있고 진실합니다.
  4. 자기 설명적 의미와 구문.
  5. 상호 운용 가능하며 글로벌 표준에 따라 관리됩니다.
  6. 글로벌 액세스 제어로 안전하고 관리됩니다.

A-6 아키텍처 검토: 새로운 서비스는 부족 및 수직 기술 리더와의 기술 설계 검토 회의를 통과해야 합니다. 부족의 기술 리더는 2년에 한 번씩 전체 아키텍처를 동료에게 발표합니다. 두 회의 모두 관심 있는 누구에게나 열려 있습니다. 

A-7 우리는 부채를 알고 있습니다 . 분기별로 기술 부채에 동의하고 추적합니다.

A-9 우리는 중요한 사항을 최소화합니다. 우리는 서비스가 주문 이행을 위한 중요한 경로의 일부가 되는 것을 피합니다. 중요하지 않은 범위를 다른 곳으로 이동하여 중요한 서비스를 간결하게 유지합니다.

A-10 우리는 결정을 문서화합니다. 우리는 RFC  아키텍처 결정 기록을 사용하여 중요한 아키텍처 결정을 그 맥락 및 결과와 함께 조정하고 문서화합니다.

A-11 우리는 잘 정의된 API를 좋아합니다. 우리는 다른 팀에 공개된 모든 API를 문서화하고 API 라이브러리에 문서를 게시합니다.

배달

D-1 매일 배포합니다 . 우리의 목표는 근무일 기준으로 엔지니어당 최소 한 번의 프로덕션 배포를 수행하는 것입니다.

D-2 배포 추적 : 모든 배포는 배포 이벤트를 통해 추적되어야 합니다.

D-3 연중무휴 24시간 운영: 모든 서비스는 가동 중지 시간 없는 배포를 지원해야 합니다. 피크 시간대와 금요일 에는 배포가 안전합니다.

D-4 안전망을 사용하여 배포 : 모든 Tier 1 서비스는 자동화된 카나리아 배포를 지원해야 합니다.

D-5 우리의 인프라는 코드입니다 . 모든 인프라는 코드와 저장소에 구성되어야 합니다. 모든 인프라 변경 사항은 CI/CD 파이프라인을 통해 실행됩니다.

D-6 우리는 테스트를 자동화합니다 . 우리는 프로덕션 코드가 단위 테스트를 통해 60-80%의 코드 적용 범위를 가질 것으로 기대합니다. 또한 주요 애플리케이션 흐름은 통합 테스트를 통해 다루어져야 합니다.

D-7 우리는 절대 혼자 코딩하지 않습니다 . 새롭고 복잡한 코드의 경우 쌍 프로그래밍을 사용합니다. 모든 프로덕션 코드 변경 사항은 코드 검토를 거칩니다.

우리는 실패를 고려합니다.

더보기

실패 처리 전략

- Timeout: Beyond a certain wait interval, a successful result is unlikely or simply too late.
- Retry: Many faults are transient and may self-correct after a short delay – use exponential backoff and jitter to avoid cascading failures.
- Circuit Breaker: When a system is seriously struggling, failing fast is better than making clients wait to avoid running out of critical resources.
- Fallback: Things will still fail – plan what you will do when that happens.
- Throttling: Prevent misbehaving clients bringing your service down.
We prepare for rapid growth with ongoing load-tests:ts bringing your service down.
- Idempotence: Multiple identical requests made to a service apply only once.
- Recoverable: Your service has a process to replay messages to recover from an outage of a downstream service.
- Dead-letter queues: Erroneous messages do not stop a service processing valid messages and are published to a dead-letter queue for later analysis.


https://tech.deliveryhero.com/our-reliability-manifesto/

 

https://tech.deliveryhero.com/our-reliability-manifesto/

Our Reliability Manifesto is a succinct collection of rules, guidelines, and best practices that reflect our current thinking on what it takes to build a reliable system. At Delivery Hero, we solve complex challenges at scale – from how to get an ice cre

tech.deliveryhero.com

 

'개발 관심사' 카테고리의 다른 글

MSA 관련 자료집  (0) 2022.04.13
웹 서비스 서버 성능 지표들  (0) 2022.02.21
Gradle과 Maven  (0) 2022.02.16
Apache Log4j 원격코드 실행 취약점 (cve-2021-44228)  (0) 2021.12.14

양자화 머고, 왜 하는 걸까


양자화란 연속적으로 보이는 양을 자연수로 셀 수 있는 양으로 재해석하는 과정이라고 한다. 기본적으로 용량이나 계산량을 줄이는 것이다. 
현실에서 예를 들면, 현실의 연속적으로 보이는 순간을 비디오로 찍으면 비디오 찍으면 비디오의 픽셀 단위로 각 색상을 인식해 영상화 한다. 그 과정에서 연속적인 현실의 모습이 자연수로 셀 수 있는 양으로 양자화되어 비디오로 만들어진다.

 

 

기본적으로, 데이터 손실에 해당한다. 그렇지만 우리의 머니와 시간은 한정되어 있기 때문에, 연산량과 저장량을 줄이기 위해 필수적으로 수행해야한다.

 

양자화 코드 예시

import yaml

from furiosa.models.vision import SSDMobileNet
from furiosa.quantizer import quantize
from furiosa.runtime.sync import create_runner

image = ["image path"]

mobilenet = SSDMobileNet()
onnx_model = mobileNet.origin
calib_range = mobileNet.tensor_name_to_range

quantized_onnx = quantize(onnx_model, calib_range)

with create_runner(quantized_onnx, devic="warbox*1") as runner:
    inputs, contexts = mobilenet.preprocess(image)
    outputs = runner.run(inputs)
    mobilenet.postprocess(outputs, contexts[0])

CPU보다 GPU가 연산이 빠르다만 알고 있지 말고, 왜 그런 지 자세히 알아보자

 


🚀 CPU와 GPU 내부 구조

CPU와 GPU는 둘 다 데이터를 읽어들여 연산처리를 통해 답을 도출하는 기능을 수행합니다. 컴퓨터에서의 두뇌 역할을 한다는 점에서는 비슷합니다. 다만, 프로세서 내부의 구조를 살펴보면 CPU와 GPU는 차이가 큽니다.

CPU와 GPU 같은 프로세서 내부는 크게 연산을 담당하는 산출연산처리장치(ALU)와 명령어를 해석하고 실행하는 컨트롤유닛(CU), 각종 데이터를 담아두는 캐시로 나뉘게 됩니다.

 

CPU

ALU 이외에도 맥락을 저장할 수 있도록 뇌의 형태를 카피한 형태로 되어 있다. 기본적으로 DRAM과 결합하여 컴퓨팅을 수행할 수 있도록 구성되어 있다. 일반적으로 ALU (core) 의 수와 DRAM 혹은 cache 의 성능과 비례한다. 계산만을 수행하기 위한 기기는 아니고, 전체 job을 컨트롤 하며 인간이 이해할 수 있는 프로그래밍 언어를 컴퓨터의 언어 (0101010 이진수로 된 명령어)로 풀어내서 수행하는 연산을 내부적으로 수행한다. 대부분의 작업은 순차적으로 이루어진다.

 

GPU

ALU 머신이다. 계산을 병렬적으로 하기 위해 설계 되었다. 캐시메모리를 두어 계산하는 형태보다는 연산만을 위한 기기이다.

더 자세한 GPU의 아키텍처는 다음 링크를 참고해라

https://www.techspot.com/article/2151-nvidia-ampere-vs-amd-rdna2/

🪁 CPU와 GPU 쓰임새

이렇듯 CPU와 GPU는 그 구조가 다른만큼 쓰임새도 다릅니다.

쓰임새를 알기 전에 컴퓨터가 데이터를 저장하는 방법에 대해 알아야 합니다.

  • 정수 : 0과 1의 수로 구분하여 저장
  • 실수 : 고정소수점 혹은 부동소수점이라는 데이터 저장방식으로 저장
    • 고정소수점(fixed point) : C#에서 decimal가 고정소수점 방식. 정밀한 표현이 가능하지만 표현할 수 있는 범위가 적습니다.
    • 부동소수점(floating point) : C#에서 float, double가 부동소수점 방식. 훨씬 더 넓은 범위까지 표현할 수 있지만 오차가 발생합니다.


CPU보다 GPU를 사용해야 하는 경우 (AWS 설명)

CPU와 그래픽 처리 장치(GPU) 사이에서 선택할 때 서로 배타적이지 않다는 점에 유의해야 합니다. 클라우드의 모든 서버 또는 서버 인스턴스를 실행하려면 CPU가 필요합니다. 그러나 일부 서버에는 GPU를 추가 코프로세서로 포함하기도 합니다. 특정 워크로드는 특정 함수를 보다 효율적으로 수행하는 GPU가 있는 서버에서 실행하는 것이 더 적합합니다. 예를 들어, GPU는 부동 소수점 숫자 계산, 그래픽 처리 또는 데이터 패턴 매칭에 유용할 수 있습니다.

다음은 CPU보다 GPU를 사용하는 것이 더 유용한 몇 가지 분야입니다.

딥 러닝

딥 러닝은 인간의 두뇌에서 영감을 얻은 방식으로 데이터를 처리하도록 컴퓨터를 가르치는 인공 지능(AI) 방식입니다. 예를 들어, 딥 러닝 알고리즘은 그림, 텍스트, 사운드 및 기타 데이터의 복잡한 패턴을 인식하여 정확한 인사이트와 예측을 생성할 수 있습니다. GPU 기반 서버는 기계 학습, 신경망 및 딥 러닝 작업에 고성능을 제공합니다.

딥 러닝에 대해 읽어보기 »

기계 학습에 대해 읽어보기 »

신경망에 대해 읽어보기 »

고성능 컴퓨팅

고성능 컴퓨팅이라는 용어는 매우 뛰어난 컴퓨팅 성능이 필요한 작업을 의미합니다. 다음은 몇 가지 예입니다.

  • 지구과학 시뮬레이션과 지진 처리를 빠르게 대규모로 실행해야 하는 경우
  • 제품 포트폴리오 위험, 헤지 기회 등을 식별하도록 재무 시뮬레이션을 계획해야 하는 경우
  • 의학, 유전체학 및 신약 개발 분야에서 예측, 실시간 또는 회귀적 데이터 과학 애플리케이션을 구축해야 하는 경우

GPU 기반 컴퓨터 시스템은 이와 같은 고성능 컴퓨팅 작업에 더 적합합니다.

고성능 컴퓨팅에 대해 읽어보기 »

자율 주행 교통 수단

고급 운전자 지원 시스템(ADAS)과 자율 주행 교통 수단(AV) 시스템을 개발하고 배포하려면 확장성이 뛰어난 컴퓨팅, 스토리지, 네트워킹 및 분석 기술이 필요합니다. 예를 들어, 데이터 수집, 레이블링 및 주석, 지도 개발, 알고리즘 개발, 시뮬레이션 및 검증을 위한 기능이 필요합니다. 이러한 복잡한 워크로드에서는 효율적인 연산을 위해 GPU 기반 컴퓨터 시스템의 지원이 필요합니다.

 

  CPU 그래픽 처리 장치(GPU)
함수 서버의 주요 처리 연산을 해결하는 일반화된 구성 요소 병렬 컴퓨팅에 탁월한 전문화된 구성 요소
처리 중 직렬 명령 처리를 위해 설계됨 병렬 명령 처리를 위해 설계됨
설계 코어 수는 더 적지만 코어 성능은 더 강력함 CPU보다 코어 수는 더 많지만 CPU 코어보다 성능이 떨어짐
가장 적합한 용도 범용 컴퓨팅 애플리케이션 고성능 컴퓨팅 애플리케이션



참고
CPU vs GPU nvidia

https://www.youtube.com/watch?v=-P28LKWTzrI

https://velog.io/@ckstn0777/%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B5%AC%EC%A1%B0-CPU%EC%99%80-GPU

 

CPU와 GPU

CPU와 GPU는 둘 다 데이터를 읽어들여 연산처리를 통해 답을 도출하는 기능을 수행합니다. 컴퓨터에서의 두뇌 역할을 한다는 점에서는 비슷합니다. 다만, 프로세서 내부의 구조를 살펴보면 CPU와 G

velog.io

https://aws.amazon.com/ko/compare/the-difference-between-gpus-cpus/

서버리스(Serverless)란 무엇인가?

서버(Server) + 리스(Less)의 합성어라 간혹 '서버가 없다'라고 문자 그대로 이해할 수 있지만, 절대 그렇지 않다.

서버리스(Serverless)는 클라우드 컴퓨팅의 모델 중 하나로 개발자가 서버를 직접 관리할 필요가 없는 아키텍처를 의미한다.

예를들어, 서버의 사용자가 1000명이 될 걸 예상하고 그에 맞는 용량의 서비스를 구입했다면 실제 사용자가 1000명이든 0명이든 같은 금액을 내야 할 것이다. 이는 크던 작던 손실을 일으키기 마련이다.

하지만 서버리스는 동적으로 서버의 자원을 할당한다.

사용자가 없다면 자원을 할당하지 않고 대기하다 요청이 들어오면 그 때 자원을 할당해서 요청을 처리하고 다시 대기 상태로 들어가게 된다.
즉, 자원을 효율적으로 사용할 수 있는 것이다.
비용 또한 대기상태를 제외한 실제 사용 자원에 대해서만 청구되기 때문에 굉장히 경제적이며,
해당 서버는 클라우드 제공 기업에서 전적으로 관리하기 때문에 개발자는 스케일링, 업데이트, 백업, 보안 등 서버에 대해 일절 관리하거나 신경 쓸 필요가 없어 비즈니스 로직에 집중하여 개발을 할 수 있다.

서버리스 아키텍처(Serverless Architecture)의 구현 방식

서버리스 아키텍처의 대표적인 두 가지 구현 방식은 다음과 같다.

FaaS (Function as a Service) : AWS Lambda, Microsoft Azure Function, Google Cloud Functions ... 등
BaaS (Backend as a Service) : Firebase, Kinvey, Parse ... 등
두 가지 구현 방식으로 나뉘지만, 일반적으로 서버리스라고 하면 FaaS를 가리킨다.

FaaS(Function as a Service)

FaaS는 Function 즉, 함수를 서비스로 제공한다.

사용자가 작성한 코드(백엔드)를 서버리스 제공자의 서버에 업로드하면 해당 서버는 업로드한 코드를 함수 단위로 쪼개 대기상태로 두게된다.
그러다 요청이 들어오면 서버가 대기상태에 두었던 함수를 실행시켜 처리한 후 작업이 끝나면 다시 대기상태로 만드는 구조이다.
비용은 함수 호출 횟수에 따라 청구된다.

쉽게 말해 업로드한 코드는 평소에 자고있고, 요청이 들어오면 서버가 코드를 깨워 요청을 처리한 후 다시 재운다.

FaaS의 특징

  1. Stateless

FaaS 서비스는 함수가 실행되는 동안에만 관련된 자원을 할당하게 되며, 함수가 항상 같은 머신에서 실행된다는 보장이 없다. 따라서 함수 실행 시 로컬에서 어떤 상태(State)가 유지될 수 없다.
이를 해결하고 싶다면 메모리에 상태를 저장하지 않고 AWS의 경우 S3을 이용하거나 아예 DB를 사용해야 한다.

  1. Ephemeral

위에서 말한 것과 같이 함수는 특정 이벤트가 발생했을 때만 컨테이너로서 배포되고, 실행이 끝난 후엔 자원이 회수되므로 일시적으로만 배포된다고 할 수 있다.

BaaS (Backend as a Service)

BaaS는 일반적으로 SPA, 안드로이드와 같은 클라이언트 중심으로 개발된 어플리케이션이다.

클라이언트단에서 BaaS가 제공하는 인증, DB, 사용자 관리 등과 같은 백엔드 관련 외부 서비스를 사용해서 대부분의 비즈니스 로직을 처리한다.

쉽게말해 SNS연동이나 DB와 같이 백엔드에 필요한 기능들을 사용자가 직접 구현할 필요 없이 제공하는 API로 해당 기능을 구현할 수 있게 해주는 것이다.
클라우드 공급자가 백엔드 개발 환경까지 제공해준다고 보면 될 것 같다.

누가 BaaS를 사용해야 할까?
BaaS 플랫폼은 기술적인 서비스이고, 앱 개발자들을 위해 만들어졌다.

  • Backend 개발에 있어 한정된 지식을 가진 Frontend 개발자
  • 빠른 개발을 원하는 Backend 개발자

어떤 유형의 프로젝트가 BaaS와 잘 어울릴까?

  • 실시간 응용프로그램(채팅, 메시지 앱)
  • 교통 앱
  • 소셜-네트워크 유형 앱
  • 전자상거래 앱
  • 음악 또는 영상 스트리밍 앱
  • 게임

서버리스(Serverless)의 장단점

장점

기존 IaaS나 PaaS와는 다르게 실제 사용량에 대해서만 비용이 청구되므로 경제적
서버에 신경 쓸 필요가 없으므로 개발자 생산성 향상 및 개발 시간 단축
자동 스케일 업 및 스케일 다운
간단한 패키징 및 배포

단점

Cold Start
서버가 항시 요청에 대기하고 있는게 아니라 느리다. 프로젝트 규모가 작다면 크게 신경쓸만한 사항은 아니지만 규모가 커지거나 속도를 요구하는 프로젝트라면 서버리스는 좋은 선택이 아닐 수 있다.

실행 시간 한계
서버리스는 단순 작업(댓글 쓰기, 이메일 보내기 등)에는 적합하지만 긴 시간이 필요한 작업(동영상 업로드, 데이터 백업 등)에는 굉장히 비효율적이다. 서버리스는 함수가 1회 호출 될 때 사용할 수 있는 메모리 및 시간에 제한이 있기 때문이다. 만약 작업이 끝나지 않은채로 해당 시간이 지나면 작업이 완료될때까지 일정 시간마다 계속 함수를 다시 호출하게 된다.
클라우드 제공 플랫폼에 종속적

디버깅 및 테스트 불편
서버리스(Serverless)를 추천하는 대상
서버리스는 사이드 프로젝트(토이 프로젝트)나 빠르게 시제품(초기 서비스)을 런칭하고 싶은 경우에 추천한다.
쉽고 빠르게 제품을 출시할 수 있으며, 돈도 매우 절약된다.

안녕하세요 임승현의 이력서입니다 😀

크게 보고 싶으신 분들은 PDF 다운로드해서 봐보시면 좋을 거 같습니다.

프로덕트를 만들가는 메이커, 임승현 68b4f85dab85460780be670f091f6326.pdf
0.55MB

 

 

오랜만에 주말 풀이~

스케쥴링 구현 문제였고 DP 메모라이제이션을 넣어서 풀었다.


문제 설명

https://school.programmers.co.kr/learn/courses/30/lessons/214288

문제 설명 궁금하면 아래 더보기 누르기

더보기

현대모비스는 우수한 SW 인재 채용을 위해 상시로 채용 설명회를 진행하고 있습니다. 채용 설명회에서는 채용과 관련된 상담을 원하는 참가자에게 멘토와 1:1로 상담할 수 있는 기회를 제공합니다. 채용 설명회에는 멘토 n명이 있으며, 1~k번으로 분류되는 상담 유형이 있습니다. 각 멘토는 k개의 상담 유형 중 하나만 담당할 수 있습니다. 멘토는 자신이 담당하는 유형의 상담만 가능하며, 다른 유형의 상담은 불가능합니다. 멘토는 동시에 참가자 한 명과만 상담 가능하며, 상담 시간은 정확히 참가자가 요청한 시간만큼 걸립니다.

참가자가 상담 요청을 하면 아래와 같은 규칙대로 상담을 진행합니다.

  • 상담을 원하는 참가자가 상담 요청을 했을 때, 참가자의 상담 유형을 담당하는 멘토 중 상담 중이 아닌 멘토와 상담을 시작합니다.
  • 만약 참가자의 상담 유형을 담당하는 멘토가 모두 상담 중이라면, 자신의 차례가 올 때까지 기다립니다. 참가자가 기다린 시간은 참가자가 상담 요청했을 때부터 멘토와 상담을 시작할 때까지의 시간입니다.
  • 모든 멘토는 상담이 끝났을 때 자신의 상담 유형의 상담을 받기 위해 기다리고 있는 참가자가 있으면 즉시 상담을 시작합니다. 이때, 먼저 상담 요청한 참가자가 우선됩니다.

참가자의 상담 요청 정보가 주어질 때, 참가자가 상담을 요청했을 때부터 상담을 시작하기까지 기다린 시간의 합이 최소가 되도록 각 상담 유형별로 멘토 인원을 정하려 합니다. 단, 각 유형별로 멘토 인원이 적어도 한 명 이상이어야 합니다.

예를 들어, 5명의 멘토가 있고 1~3번의 3가지 상담 유형이 있을 때 아래와 같은 참가자의 상담 요청이 있습니다.

참가자의 상담 요청

참가자 번호시각상담 시간상담 유형

1번 참가자10분60분1번 유형
2번 참가자 15분 100분 3번 유형
3번 참가자 20분 30분 1번 유형
4번 참가자 30분 50분 3번 유형
5번 참가자 50분 40분 1번 유형
6번 참가자 60분 30분 2번 유형
7번 참가자 65분 30분 1번 유형
8번 참가자 70분 100분 2번 유형

이때, 멘토 인원을 아래와 같이 정하면, 참가자가 기다린 시간의 합이 25로 최소가 됩니다.

1번 유형2번 유형3번 유형

2명1명2명

1번 유형

1번 유형을 담당하는 멘토가 2명 있습니다.

  • 1번 참가자가 상담 요청했을 때, 멘토#1과 10분~70분 동안 상담을 합니다.
  • 3번 참가자가 상담 요청했을 때, 멘토#2와 20분~50분 동안 상담을 합니다.
  • 5번 참가자가 상담 요청했을 때, 멘토#2와 50분~90분 동안 상담을 합니다.
  • 7번 참가자가 상담 요청했을 때, 모든 멘토가 상담 중이므로 1번 참가자의 상담이 끝날 때까지 5분 동안 기다리고 멘토#1과 70분~100분 동안 상담을 합니다.

2번 유형

2번 유형을 담당하는 멘토가 1명 있습니다.

  • 6번 참가자가 상담 요청했을 때, 멘토와 60분~90분 동안 상담을 합니다.
  • 8번 참가자가 상담 요청했을 때, 모든 멘토가 상담 중이므로 6번 참가자의 상담이 끝날 때까지 20분 동안 기다리고 90분~190분 동안 상담을 합니다.

3번 유형

3번 유형을 담당하는 멘토가 2명 있습니다.

  • 2번 참가자가 상담 요청했을 때, 멘토#1과 15분~115분 동안 상담을 합니다.
  • 4번 참가자가 상담 요청했을 때, 멘토#2와 30분~80분 동안 상담을 합니다.

상담 유형의 수를 나타내는 정수 k, 멘토의 수를 나타내는 정수 n과 참가자의 상담 요청을 담은 2차원 정수 배열 reqs가 매개변수로 주어집니다. 멘토 인원을 적절히 배정했을 때 참가자들이 상담을 받기까지 기다린 시간을 모두 합한 값의 최솟값을 return 하도록 solution 함수를 완성해 주세요.

 


제한사항

  • 1 ≤ k ≤ 5
  • k ≤ n ≤ 20
  • 3 ≤ reqs의 길이 ≤ 300
    • reqs의 원소는 [a, b, c] 형태의 길이가 3인 정수 배열이며, c번 유형의 상담을 원하는 참가자가 a분에 b분 동안의 상담을 요청했음을 의미합니다.
    • 1 ≤ a ≤ 1,000
    • 1 ≤ b ≤ 100
    • 1 ≤ c ≤ k
    • reqs는 a를 기준으로 오름차순으로 정렬되어 있습니다.
    • reqs 배열에서 a는 중복되지 않습니다. 즉, 참가자가 상담 요청한 시각은 모두 다릅니다.

입출력 예

3 5 [[10, 60, 1], [15, 100, 3], [20, 30, 1], [30, 50, 3], [50, 40, 1], [60, 30, 2], [65, 30, 1], [70, 100, 2]] 25
2 3 [[5, 55, 2], [10, 90, 2], [20, 40, 2], [50, 45, 2], [100, 50, 2]] 90

풀이 방법

1번

각 유형별로 req를 모아둔다.

2번

각 유형별로 멘토가 배정될 수 있는 모든 경우의 수를 가지고 for문을 돌린다.
중복 조합을 사용하였다.

5명 멘토 /.3개 과제

3 1 1
1 2 2
1 3 1
2 1 1
2 1 2

3번

각 과제에 멘토가 배정된 경우일 때 스케쥴링을 돌린 후 기다린 시간의 총합을 계산한다.

4번

각 과제에 멘토가 배정된 수에 따라 기다린 시간의 총합을 일정하므로 그 값을 재활용할 수 있게 dp map에 저장해서 쓴다.

1번 과제에 1명의 멘토가 붙는 경우
1번 과제에 2명의 멘토가 붙는 경우

요런 값들은 기다린 시간의 총합이 정해져있기 때문에 다음 계산 시에도 사용 가능하다.

풀이 코드


from itertools import combinations_with_replacement
from heapq import heappush, heappop
from collections import deque

dp = {}

def solution(k, n, reqs):
    answer = 300 * 1000 + 1

    reqs_by_type = [[] for i in range(k+1)]
    for req in reqs:
        reqs_by_type[req[2]].append(req)
    for req in reqs_by_type:
        req.sort(key = lambda x: x[0])

    if (n == k):
        return get_total_waiting_time(reqs_by_type, [1] * (k+1))

    # 배정한 거 다 돌려보기
    for cwr in combinations_with_replacement(list(range(1, k+1)), n-k):
        mentor_counts = [1] * (k+1)
        for c in cwr:
            mentor_counts[c] += 1
        answer = min(answer, get_total_waiting_time(reqs_by_type, mentor_counts))
    return answer

def get_total_waiting_time(reqs_by_type, mentor_counts) :
    time = 0
    for type_num, m in enumerate(mentor_counts):
        if type_num == 0 : continue
        time += get_waiting_time_by_type(reqs_by_type[type_num], type_num, m)
    return time

def get_waiting_time_by_type(reqs, type_num, mentor_count):
    if not reqs : return 0

    key = (type_num, mentor_count)
    if key not in dp:
        waiting_time = 0
        heap = [(0, i) for i in range(mentor_count)]

        reqs = deque(reqs[:])
        cur_time = 0
        while reqs:
            end_time, mentor_idx = heappop(heap)
            cur = reqs.popleft()
            cur_time = max(cur[0], end_time)
            waiting_time += max(0, end_time - cur[0])
            heappush(heap, (cur_time + cur[1], mentor_idx))
        dp[key] = waiting_time
    return dp[key]

'알고리즘' 카테고리의 다른 글

My github as algorithm hub  (0) 2021.06.16
[이코테] 구현 - 게임 개발  (0) 2021.06.13
[stack] 2504번 괄호의 합  (0) 2019.06.03

참고 할만한 링크

   - <https://mangkyu.tistory.com/76> 글에서 대부분을 참고했음을 알립니다.

   - Spring Security 제대로 이해하기 <https://gngsn.tistory.com/160>

   - Spring Security의 권한 부여 처리 흐름 [<https://velog.io/@cjstk3221/Spring-Security의-권한-부여-처리-흐름>](<https://velog.io/@cjstk3221/Spring-Security%EC%9D%98-%EA%B6%8C%ED%95%9C-%EB%B6%80%EC%97%AC-%EC%B2%98%EB%A6%AC-%ED%9D%90%EB%A6%84>)

   - Spring Seucurity 인가 처리 흐름 [<https://velog.io/@penrose_15/Spring-Security의-인가-처리-흐름>](<https://velog.io/@penrose_15/Spring-Security%EC%9D%98-%EC%9D%B8%EA%B0%80-%EC%B2%98%EB%A6%AC-%ED%9D%90%EB%A6%84>)

해보기

  • 임의의 필터를 만들어서 권한을 부여하고 부여한 권한이 있는 경우에만 특정 API 처리하도록 만들어보기
-> **참고 코드**

http.addFilterBefore(new LshFilter(), UsernamePasswordAuthenticationFilter.class);

.antMatchers("/lsh").hasRole("lsh");

/////////

public class LshFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        SecurityContextHolder.setContext(new SecurityContextImpl(new UsernamePasswordAuthenticationToken("lsh", "lsh")));
    }

}

1. Spring Security란?


인증, 인가

[ Spring Security란? ]

Spring Security는 Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다. Spring Security는 '인증'과 '권한'에 대한 부분을 Filter 흐름에 따라 처리하고 있다. Filter는 Dispatcher Servlet으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받지만, Interceptor는 Dispatcher와 Controller사이에 위치한다는 점에서 적용 시기의 차이가 있다. Spring Security는 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있다.

이러한 Spring Security의 아키텍쳐는 아래와 같다.


[ 인증(Authorizatoin)과 인가(Authentication) ]

  • 인증(Authentication): 해당 사용자가 본인이 맞는지를 확인하는 절차
  • 인가(Authorization): 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차

Spring Security는 기본적으로 인증 절차를 거친 후에 인가 절차를 진행하게 되며, 인가 과젱에서 해당 리소스에 대한 접근 권한이 있는지 확인을 하게 된다. Spring Security에서는 이러한 인증과 인가를 위해 Principal을 아이디로, Credential을 비밀번호로 사용하는 Credential 기반의 인증 방식을 사용한다.

  • Principal(접근 주체): 보호받는 Resource에 접근하는 대상
  • Credential(비밀번호): Resource에 접근하는 대상의 비밀번호

2. Spring Security 모듈


[ Spring Security 주요 모듈 ]

Spring Security의 주요 모듈은 아래와 같이 구성되며 각 항목들에 대해서 간단히 살펴보도록 하자.

 

[ SecurityContextHolder ]

SecurityContextHolder는 보안 주체의 세부 정보를 포함하여 응용프래그램의 현재 보안 컨텍스트에 대한 세부 정보가 저장된다. SecurityContextHolder는 기본적으로 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL 방법과SecurityContextHolder.MODE_THREADLOCAL 방법을 제공한다.

[ SecurityContext ]

Authentication을 보관하는 역할을 하며, SecurityContext를 통해 Authentication 객체를 꺼내올 수 있다.

[ Authentication ]

Authentication는 현재 접근하는 주체의 정보와 권한을 담는 인터페이스이다. Authentication 객체는 Security Context에 저장되며, SecurityContextHolder를 통해 SecurityContext에 접근하고, SecurityContext를 통해 Authentication에 접근할 수 있다.

public interface Authentication extends Principal, Serializable {
// 현재 사용자의 권한 목록을 가져옴
    Collection<? extends GrantedAuthority> getAuthorities();

// credentials(주로 비밀번호)을 가져옴Object getCredentials();

    Object getDetails();

// Principal 객체를 가져옴.Object getPrincipal();

// 인증 여부를 가져옴boolean isAuthenticated();

// 인증 여부를 설정함void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

[ UsernamePasswordAuthenticationToken ]

UsernamePasswordAuthenticationToken은 Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로, User의 ID가 Principal 역할을 하고, Password가 Credential의 역할을 한다. UsernamePasswordAuthenticationToken의 첫 번째 생성자는 인증 전의 객체를 생성하고, 두번째 생성자는 인증이 완려된 객체를 생성한다.

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
// 주로 사용자의 ID에 해당함private final Object principal;
// 주로 사용자의 PW에 해당함private Object credentials;

// 인증 완료 전의 객체 생성public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		setAuthenticated(false);
	}

// 인증 완료 후의 객체 생성public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
			Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		super.setAuthenticated(true);// must use super, as we override
	}
}

public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
}

[ AuthenticationProvider ]

AuthenticationProvider에서는 실제 인증에 대한 부분을 처리하는데, 인증 전의 Authentication객체를 받아서 인증이 완료된 객체를 반환하는 역할을 한다. 아래와 같은 AuthenticationProvider 인터페이스를 구현해서 Custom한 AuthenticationProvider을 작성해서 AuthenticationManager에 등록하면 된다.

public interface AuthenticationProvider {

// 인증 전의 Authenticaion 객체를 받아서 인증된 Authentication 객체를 반환Authentication authenticate(Authentication var1) throws AuthenticationException;

    boolean supports(Class<?> var1);

}

[ Authentication Manager ]

인증에 대한 부분은 SpringSecurity의 AuthenticatonManager를 통해서 처리하게 되는데, 실질적으로는 AuthenticationManager에 등록된 AuthenticationProvider에 의해 처리된다. 인증이 성공하면 2번째 생성자를 이용해 인증이 성공한(isAuthenticated=true) 객체를 생성하여 Security Context에 저장한다. 그리고 인증 상태를 유지하기 위해 세션에 보관하며, 인증이 실패한 경우에는 AuthenticationException를 발생시킨다.

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication)
		throws AuthenticationException;
}

AuthenticationManager를 implements한 ProviderManager는 실제 인증 과정에 대한 로직을 가지고 있는 AuthenticaionProvider를 List로 가지고 있으며, ProividerManager는 for문을 통해 모든 provider를 조회하면서 authenticate 처리를 한다.

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
    public List<AuthenticationProvider> getProviders() {
		return providers;
	}
    public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();
//for문으로 모든 provider를 순회하여 처리하고 result가 나올 때까지 반복한다.for (AuthenticationProvider provider : getProviders()) {
            ....
			try {
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
				prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow e;
			}
            ....
		}
		throw lastException;
	}
}

위에서 설명한 ProviderManager에 우리가 직접 구현한 CustomAuthenticationProvider를 등록하는 방법은 WebSecurityConfigurerAdapter를 상속해 만든 SecurityConfig에서 할 수 있다. WebSecurityConfigurerAdapter의 상위 클래스에서는 AuthenticationManager를 가지고 있기 때문에 우리가 직접 만든 CustomAuthenticationProvider를 등록할 수 있다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public AuthenticationManager getAuthenticationManager() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() throws Exception {
        return new CustomAuthenticationProvider();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(customAuthenticationProvider());
    }
}

[ UserDetails ]

인증에 성공하여 생성된 UserDetails 객체는 Authentication객체를 구현한 UsernamePasswordAuthenticationToken을 생성하기 위해 사용된다. UserDetails 인터페이스를 살펴보면 아래와 같이 정보를 반환하는 메소드를 가지고 있다. UserDetails 인터페이스의 경우 직접 개발한 UserVO 모델에 UserDetails를 implements하여 이를 처리하거나 UserDetailsVO에 UserDetails를 implements하여 처리할 수 있다.

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();

}

[ UserDetailsService ]

UserDetailsService 인터페이스는 UserDetails 객체를 반환하는 단 하나의 메소드를 가지고 있는데, 일반적으로 이를 구현한 클래스의 내부에 UserRepository를 주입받아 DB와 연결하여 처리한다. UserDetails 인터페이스는 아래와 같다.

public interface UserDetailsService {

    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;

}

[ Password Encoding ]

AuthenticationManagerBuilder.userDetailsService().passwordEncoder() 를 통해 패스워드 암호화에 사용될 PasswordEncoder 구현체를 지정할 수 있다.

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
	auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

@Bean
public PasswordEncoder passwordEncoder(){
	return new BCryptPasswordEncoder();
}

[ GrantedAuthority ]

GrantAuthority는 현재 사용자(principal)가 가지고 있는 권한을 의미한다. ROLE_ADMIN나 ROLE_USER와 같이 ROLE_*의 형태로 사용하며, 보통 "roles" 이라고 한다. GrantedAuthority 객체는 UserDetailsService에 의해 불러올 수 있고, 특정 자원에 대한 권한이 있는지를 검사하여 접근 허용 여부를 결정한다.

부록

  • 전체 그림
  • 필터 순서

[ Spring Security 처리 과정 요약 ]

!https://blog.kakaocdn.net/dn/UOabX/btqEJBBNixH/PGDv64FTKaBSLzMiiXkA3K/img.png

  1. 사용자가 아이디 비밀번호로 로그인을 요청함
  2. AuthenticationFilter에서 UsernamePasswordAuthenticationToken을 생성하여 AuthenticaionManager에게 전달
  3. AuthenticaionManager는 등록된 AuthenticaionProvider(들)을 조회하여 인증을 요구함
  4. AuthenticaionProvider는 UserDetailsService를 통해 입력받은 아이디에 대한 사용자 정보를 DB에서 조회함
  5. 입력받은 비밀번호를 암호화하여 DB의 비밀번호화 매칭되는 경우 인증이 성공된 UsernameAuthenticationToken을 생성하여 AuthenticaionManager로 반환함
  6. AuthenticaionManager는 UsernameAuthenticaionToken을 AuthenticaionFilter로 전달함
  7. AuthenticationFilter는 전달받은 UsernameAuthenticationToken을 LoginSuccessHandler로 전송하고, SecurityContextHolder에 저장함

3. Spring Security 샘플 코드 및 실행


[ Spring Security 예제 실행 방법 ]

  1. https://github.com/MangKyu/SpringSecurity-Example/tree/formbased으로부터 소스를 클론받는다.
  2. CREATE DATABASE security DEFAULT CHARSET UTF8; 으로 데이터베이스를 생성한다.
  3. application.properties에서 DB username과 password를 개인에 맞게 변경해준다.
  4. 서버를 실행시킨다.
  5. http://localhost:8081/user/init 로 접속해서 사용자 데이터를 초기화 한다.
  6. index 페이지에 접속하여 실행한다.

Kafka 모니터링 여러 가지 많이 써봤는데

요거만한 게 없다

 

https://plugins.jetbrains.com/plugin/12494-big-data-tools

 

Big Data Tools - IntelliJ IDEs Plugin | Marketplace

A plugin for data engineers and other specialists engaged with big data workloads. Installed in your favorite JetBrains IDE, Big Data Tools helps develop, visualize...

plugins.jetbrains.com

 

IntelliJ 플러그인이다.

JetBrains 사에서 만든 플러그인이다

 

다운 받으면 요런 식으로 왼쪽이나 오른쪽 사이드바에 붙어있다.

\

들어가면 요렇게 보인다.

+ 버튼 누르면 이렇게 추가할 수 있다.

name 내 맘대로하고 URL 카프카 url로 넣으면 된다.

 

각 환경마다 추가할 수 있고,

등록 후 더블 클릭하면 아래처럼 토픽 목록이 Replicas, Partitions 등등의 정보와 같이 뜬다.

 

왼쪽 아래의 Producer와 Consumer 를 눌러 내가 메세지 publish, consume 해볼 수 있다.

 

'Kafka' 카테고리의 다른 글

Kafka 좀 더 알아보기  (0) 2023.04.17
Kafka 알아보기  (0) 2023.04.16

+ Recent posts