헥사고날 아키텍쳐

출처 : https://reflectoring.io/spring-hexagonal/

목표 : 응용 프로그램의 비즈니스 로직을 외부 세계로부터 격리시켜 유연하고 테스트하기 쉬운 구조를 만드는 것

핵심 비즈니스 로직은 중앙의 도메인 영역에 위치하며, 입력과 출력을 처리하는 포트와 어댑터를 통해 외부와 소통한다.

헥사고날 아키텍쳐의 장단점

장점

  • 유연성 : 외부 시스템이나 인프라와의 의존성을 낮추어, 구성 요소를 쉽게 교체하거나 업데이트 할 수 있다.
  • 테스트 용이성 : 비즈니스 로직을 독립적으로 테스트할 수 있어 품질 향상과 개발 속도 향상에 도움이 된다.
  • 유지보수성 : 책임이 분리되어 있어, 코드의 이해와 수정이 용이하며, 변화에 빠르게 대응할 수 있다.

단점

  • 구현 복잡성 : 포트와 어댑터를 구성하고 관리하는데 약간의 복잡성이 따른다.
  • 초반 개발 시간 증가 : 아키텍처를 처음 구축할 때 시간과 노력 필요

헥사고날 아키텍쳐 구조

포트 (인터페이스)

포트는 애플리케이션 코어의 경계를 정의하며, 애플리케이션 코어가 제공해야 할 기능을 나타내며 어댑터를 통해 애플리케이션 코어에 접근하는 인터페이스이다.

Input Port : 외부 요청이 애플리케이션 코어로 들어오는 경로를 정의한다. 예를 들어, 웹 요청, GUI 이벤트, 스케줄링 이벤트 등이 인커밍 포트를 통해 애플리케이션 코어로 들어올 수 있다.

  • Spring Web MVC의 Controller와 Service 사이의 인터페이스

Output Port : 애플리케이션 코어가 외부 세계에 서비스를 제공하기 위한 경로를 정의한다. 예를들어 데이터베이스, 메시징 시스템, 웹 서비스 등에 데이터를 전송하거나 요청하는 경우에 사용한다.

  • Spring web MVC의 Repository와 Service 사이의 인터페이스

어댑터 (구현체)

어댑터는 포트를 통해 애플리케이션 코어와 외부 세계를 연결한다. 어댑터는 특정 외부 기술이나 프레임워크에 의존적인 로직을 담당하며, 이를 통해 애플리케이션 코어는 외부와의 결합도를 최소화하고, 어댑터를 통한 교환 가능성을 확보한다.

Input Adapter : 사용자 인터페이스, 테스트 또는 외부 시스템으로부터의 요청을 애플리케이션 코어로 주도하는데 사용된다.

  • Spring Web MVC의 Controller

Output Adapter : 애플리케이션 코어에서 외부에 데이터를 전달하는 역할을 담당한다. 예를들어, 데이터베이스에 데이터를 저장하거나 외부 시스템에 메세지를 전송하는 등의 역할을 한다.

  • Spring Web MVC의 Repository

유즈케이스

Clean Architecture에서 사용하는 용어로 Spring Web MVC의 Service 영역에 해당한다.

헥사고날 아키텍쳐 예시

Kafka와 같은 외부 시스템과 연동하는 경우 헥사고날 아키텍쳐의 장점을 알 수 있다.

3계층 아키텍쳐에서 kafka 적용

// KafkaProducer.java
public class KafkaProducer {
    public void send(User user) {
        // 카프카에 사용자 정보 전송
    }
}

// UserService.java
public class UserService {
    private UserRepository userRepository;
    private KafkaProducer kafkaProducer;

    public UserService(UserRepository userRepository, KafkaProducer kafkaProducer) {
        this.userRepository = userRepository;
        this.kafkaProducer = kafkaProducer;
    }

    public void createUser(String name, String email) {
        User user = new User(name, email);
        userRepository.save(user);
        kafkaProducer.send(user);
    }
}

헥사고날 아키텍쳐에서 kafka 적용

// OutputPort.java
public interface OutputPort {
    void sendMessage(User user);
}

// KafkaAdapter.java
public class KafkaAdapter implements OutputPort {
    private KafkaProducer kafkaProducer;

    public KafkaAdapter(KafkaProducer kafkaProducer) {
        this.kafkaProducer = kafkaProducer;
    }

    public void sendMessage(User user) {
        kafkaProducer.send(user);
    }
}

// CreateUserUseCaseImpl.java
public class CreateUserUseCaseImpl implements CreateUserUseCase {
    private UserRepository userRepository;
    private OutputPort outputPort;

    public CreateUserUseCaseImpl(UserRepository userRepository, OutputPort outputPort) {
        this.userRepository = userRepository;
        this.outputPort = outputPort;
    }

    public void createUser(String name, String email) {
        User user = new User(name, email);
        userRepository.save(user);
        outputPort.sendMessage(user);
    }

}

기존 아키텍쳐의 경우 UserService 클래스가 카프카와 직접 연결되어 있다. 이 경우 UserService는 Kafka에 대한 의존성을 가지게 된다.

반면 헥사고날 아키텍쳐에서는 OutputPort 인터페이스를 통해 외부 시스템과의 의존성을 분리한다.

이러한 구조를 통해 외부 시스템 변경이 있을 때에도 비즈니스 로직에 영향을 최소화 하고 유연성을 확보할 수 있다.

 

만약 메세지 서비스를 Kafka에서 RabbitMQ로 변경한다고 생각해보자.

기존 3계층 아키텍쳐의 경우 KafaProducer라는 클래스를 직접 의존하고 있기에, RabbitMQProducer라는 새로운 클래스를 생성하고 이를 사용하게 될 것이다.

하지만 핵사고날 아키텍쳐에서는 Output Port의 구현체를 KafkaAdapter 에서 새로 생성한 RabbitMQAdapter로 변경해주면 비즈니스 로직의 변화 없이 구현체만 바꾸는 것으로 빠르게 대응할 수 있을 것이다.

마무리

블로그 탐색중에 헥사고날 아키텍쳐라는 새로운 키워드를 발견하여 이에대해 간단히 조사해 보았다. 키워드를 발견했을때는 아키텍쳐에 깜짝 놀랄만한 변화가 있을 것 같았지만, 읽어보니 느낀점은 Spring 웹 서비스에서는 메세징 서비스와 같은 외부와 통신하는 부분을 한번 더 인터페이스로 감싸서 접근하는 방식으로 의존성을 줄인 것이라는 느낌이 든다.

 

아직 경험이 많이 부족하여 크게 와닿지 않는 것 같지만, 소스코드 탐색중에 Port, Adapter, UseCase와 같은 생소한 단어가 나와도 대응할 수 있는 얕은 지식이 생겼다. 또한, 다음 서비스를 개발 할 때 위 처럼 외부와 통신하는 서비스의 변경을 고려한 설계를 하게 된다면, 헥사고날 아키텍쳐를 도입하는 것을 고려해 볼 수 있을 것 같다.

Reference

https://tech.osci.kr/hexagonal-architecture/
https://velog.io/@coconenne/%ED%97%A5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%96%B4%EB%8C%91%ED%84%B0%EC%99%80-%ED%8F%AC%ED%8A%B8-%EA%B2%B0%ED%95%A9%EB%8F%84%EB%A5%BC-%EB%82%AE%EC%B6%B0%EB%B3%B4%EC%9E%90

 

헥사고날 아키텍처: 어댑터와 포트! 결합도를 낮춰보자

학습 계기 최근 항상 의문이었던 계층간 책임에 대해 자세히 알아보기 시작하였다. 전통적인 웹-도메인-영속성 구조에서는 한 계층의 변화가 다른 계층에도 영향을 끼칠 수 있다는 것을 알게 됐

velog.io

 

 

헥사고날 아키텍처(Hexagonal Architecture) : 유연하고 확장 가능한 소프트웨어 디자인 🌟 feat. Java Exam

오픈소스컨설팅 테크블로그 헥사고날 아키텍처(Hexagonal Architecture) : 유연하고 확장 가능한 소프트웨어 디자인 🌟 feat. Java Example 헥사고날 아키텍처란?Uncategorized -

tech.osci.kr

 

'TIL' 카테고리의 다른 글

구글 코랩에서 Stable Diffusion WebUI API 호출하기  (2) 2024.10.18
2024.09.27  (6) 2024.09.27
Retry 전략  (7) 2024.09.25
Vite, Webpack, Babel 관계 정리  (0) 2024.05.24
자바스크립트 비동기 처리 알아보기  (3) 2024.04.01

개요

개인 프로젝트 기획 중 Google Colab을 통해 생성한 모델에서 API를 통해 프롬프트를 전달하고, 해당 정보로 API를 호출하는 서비스를 만들기 위해서 Stable Diffusion WebUI에서 API 호출을 확인해야 했다. 이를 어떻게 해결했는지 공유하고자 글을 작성한다.

 

API  호출하기

API를 호출하기 위해서는 검색해보니 python 코드를 수정해야 한다는 내용을 몇가지 찾을 수 있었는데, 별도의 코드 수정없이 간단하게 API를 호출할 수 있는 주소를 알아낼 수 있었다. 바로 Stable Diffusion WebUI 하단에 API를 클릭하면 사용가능한 API 목록을 swagger를 통해 알 수 있다.

Swagger 페이지를 통해 내가 호출하고자 하는 API를 확인했고, 나의 경우 txt2img API를 호출하기 위한 정보를 찾아봤다.

하지만 많은 파라미터가 있어 내가 설정했던 방법을 간단히 살펴보자.

{
  "prompt": "sparkling eyes, giving a playful, resembling a fantasy or magical theme, The character has a joyful and friendly expression, with a child-friendly design that uses soft curves and bright, smooth textures", // 프롬프트
  "negative_prompt": "Avoid dark or muted colors, no sharp or aggressive features, no realistic textures, avoid mature themes, no complex backgrounds, no human-like proportions, no gothic, horror, or creepy aesthetics, avoid exaggerated shadows or dramatic lighting, and no realistic animal features", // Negative 프롬프트
  "styles": [
    ""
  ],
  "seed": -1, // 시드 (-1 : 랜덤)
  "subseed": -1,
  "subseed_strength": 0,
  "seed_resize_from_h": -1,
  "seed_resize_from_w": -1,
  "sampler_name": "DPM++ 2M", // 샘플러 name
  "scheduler": "",
  "batch_size": 1,
  "n_iter": 1,
  "steps": 20, // Sampling Steps
  "cfg_scale": 7,
  "width": 512,
  "height": 512,
  "restore_faces": false, // default true여서 false로 변경
  "tiling": false, // default true여서 false로 변경
  "do_not_save_samples": false,
  "do_not_save_grid": false,
  "eta": 0,
  "denoising_strength": 0,
  "s_min_uncond": 0,
  "s_churn": 0,
  "s_tmax": 0,
  "s_tmin": 0,
  "s_noise": 0,
  "override_settings": {
      "sd_model_checkpoint": "Hyeonwoo/Hyeonwoo_5200.safetensors [d85b89c9ef]" // 사용할 model check point
  },
  "override_settings_restore_afterwards": true,
  "refiner_checkpoint": "",
  "refiner_switch_at": 0,
  "disable_extra_networks": false,
  "firstpass_image": "",
  "comments": {},
  "enable_hr": false,
  "firstphase_width": 0,
  "firstphase_height": 0,
  "hr_scale": 2,
  "hr_upscaler": "string",
  "hr_second_pass_steps": 0,
  "hr_resize_x": 0,
  "hr_resize_y": 0,
  "hr_checkpoint_name": "",
  "hr_sampler_name": "",
  "hr_scheduler": "",
  "hr_prompt": "",
  "hr_negative_prompt": "",
  "force_task_id": "",
  "sampler_index": "Euler",
  "script_name": "",
  "script_args": [],
  "send_images": true,
  "save_images": false,
  "alwayson_scripts": {},
  "infotext": ""
}

위와 같이 설정 후 Stable Diffusion Web UI를 통해 생성한 데이터와 거의 비슷한 결과값을 얻을 수 있었다.

응답값은 image base64 인코딩 데이터와 생성에 사용된 메타데이터들이 응답으로 왔었고, 이미지 데이터는 base64 디코딩 사이트를 통해 확인할 수 있었다.

 

API를 통해 생성된 이미지

Stable Diffusion Web UI를 통해 생성된 이미지

'TIL' 카테고리의 다른 글

헥사고날 아키텍쳐 (Hexagonal Architecture)란?  (9) 2024.11.13
2024.09.27  (6) 2024.09.27
Retry 전략  (7) 2024.09.25
Vite, Webpack, Babel 관계 정리  (0) 2024.05.24
자바스크립트 비동기 처리 알아보기  (3) 2024.04.01

Stable Diffusion 이란?

텍스트를 입력받아 해당 텍스트와 일치하는 이미지 생성 딥러닝 모델

Stable Diffusion 특징

  1. 고품질 이미지 생성: Stable Diffusion은 복잡한 텍스트 입력에도 고해상도 이미지를 생성할 수 있습니다.
  2. 텍스트-이미지 변환: 사용자가 입력한 텍스트를 기반으로 이미지를 생성하므로, 창의적인 아이디어를 시각적으로 표현하는데 유용
  3. 오픈소스: 많은 연구자와 개발자가 모델을 개선하고 활용

Stable Diffusion 모델의 학습 과정

  1. 데이터 수집: 모델을 학습시키기 위해서는 대규모의 텍스트-이미지 페어 데이터셋이 필요
  2. 데이터 전처리: 수집된 데이터를 모델에 맞게 전처리 (이미지 정규화 및 텍스트 토큰화)
  3. 모델 학습: 전처리된 데이터를 사용하여 모델을 학습. 이 과정에서 이미지와 텍스트 간의 관계를 학습
  4. 모델 평가: 학습된 모델의 성능을 평가하고 모델 개선

Stable Diffusion 파인 튜닝

파인튜닝이란 사전학습된 모델을 특정 작업이나 데이터셋에 맞추기 위해 추가 학습하는 과정

DreamBooth 파인 튜닝

사용자 지정 데이터셋을 활용하여 사전 학습된 모델을 특정 요구 사항에 맞추도록 조정한다. DreamBooth는 이미지 생성 모델, 특히 텍스트-이미지 변환 모델에 자주 사용된다.

세부 조정: 특정한 소수의 이미지를 사용하여 모델을 파인튜닝 할 수 있다.
높은 품질: 입력 데이터의 세밀한 특징을 학습하여 높은 품질의 출력을 생성한다.
전용 모델 생성: 특정 인물이나 스타일을 학습하여, 유사한 결과물을 일관되게 생성할 수 있다.

자언여 처리 (NLP)

머신러닝을 사용하여 텍스트의 구조와 의미를 파악

토크나이저

토크나이저는 텍스트를 단어, 서브 단어, 문장 부호 등의 토큰으로 나누는 작업을 수행
텍스트 전처리의 핵심 과정이다.

공백 기반 토크나이저

텍스트를 공백으로 구분하여 토크나이징하는 가장 간단한 방법. 영어와 같이 공백으로 단어가 구분되는 언어에서 잘 작동한다.
중국어나 한국어와 같이 공백이 없거나 연결되어 있는 경우에 적합하지 않다.

기본 정규식 토크나이저

정규식을 사용하여 텍스트를 분할하는 방식. 사용자가 원하는 기준에 따라 토크나이징 가능하다.

BPE (Byte Pair Encoding)

서브 워드 토크나이징을 위한 알고리즘으로, 일정한 크기의 보카뷰러리를 만들기 위해 데이터셋에서 가장 많이 발생하는 문자쌍을 병합하는 방식으로 토큰 생성

Reference

https://contentstailor.com/entry/Stable-Diffusion%EA%B3%BC-%ED%8C%8C%EC%9D%B8-%ED%8A%9C%EB%8B%9D-%EB%B0%A9%EB%B2%95-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C

https://contentstailor.com/entry/%ED%85%8D%EC%8A%A4%ED%8A%B8-%ED%88%AC-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B3%80%ED%99%98-Hugging-Face-Diffusers-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%8B%A4%EC%8A%B5

https://contentstailor.com/entry/DreamBooth%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D-%EC%BD%94%EB%93%9C%EC%99%80-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95

https://databoom.tistory.com/entry/NLP-%ED%86%A0%ED%81%AC%EB%82%98%EC%9D%B4%EC%A0%80-Tokenizer

지수 백오프 (Exponential Backoff)

네트워크 상에서 일시적인 오류가 발생했을 때, 재시도 간격을 점진적으로 늘려가며 재시도를 수행하는 알고리즘.

주로 네트워크의 혼잡을 피하거나, 서버가 과부하 상태일 때 과도한 요청을 방지하기 위해 사용된다.

방법의 핵심은 "지수적으로" 대기시간을 늘리는 것

지수 백오프의 필요성

네트워크 통신 중 오류가 발생할 경우, 클라이언트는 보통 재시도를 통해 이를 해결하려고 한다. 하지만 모든 클라이언트가 동시에 재시도를 한다면, 서버는 한꺼번에 많은 요청을 처리해야 하므로 부하가 증가할 수 있다. 이러한 현상을 "재시도 폭주"라고 하며, 이를 방지하기 위해 지수 백오프를 사용한다.

하지만 이 방법도 한계가 있다. 어차피 동시에 요청이 몰린다면 똑같은 시간 간격으로 모든 재시도가 동일하게 몰릴 것이기 때문이다.

지연변이 (Jitter)

Jitter는 데이터 통신 용어로 사용할 때는 패킷 지연이 일정하지 않고, 수시로 변하면서 그 간격이 일정하지 않는 현상을 의미한다. Jitter 개념을 Retry에 이용하면 API를 요청하는 클라이언트 간의 동일한 재시도 시간 간격에 무작위성을 추가하여 서로 요청하는 시간대의 동시성?을 분산시킬 수 있다.

위 두 개념을 적용하면 지수로 증가하는 Backoff 시간에 일정 범위 안의 랜덤 대기 시간을 추가적으로 더하는 것으로 요청을 분산할 수 있다.

어디서 쓰나

Reference

https://1yoouoo.tistory.com/37
https://jungseob86.tistory.com/12

 

[기타] Retry 전략에 대해서(Exponential Backoff, Jitter)

최근 각광받는 MSA 구조, 분산 시스템 구조에서 서버간 네트워크 통신은 매우 중요하다. 특히 네트워크를 통한 API 호출은 언제 어떻게 실패할지 예측하기 어렵기 때문에 이를 감시할 수 있는 모

jungseob86.tistory.com

 

 

지수 백오프(Exponential Backoff) 알아보기

저번에 웹소켓을 이용하여 React로 채팅을 구현한 경험이 있다. [바로가기] 리액트 웹소켓(Stomp) 이번 토이프로젝트로 MBTI 유형을 선택해서 채팅을 할 수 있는 서비스를 구현했다. 서버는 Java, Spring

1yoouoo.tistory.com

 

개요

JavaScript 라이브러리를 개발하며 React, Vue와 같은 프레임워크를 제외하고 빌드와 관련된 여러 도구들의 관계를 정리하고자 포스팅한다.

1. WebPack

1 - 1. 웹팩이란?

웹팩은 웹 어플리케이션에서 사용하는 수많은 리소스를 하나의 파일로 병합 및 압축 해주는 모듈 번들러이다.

1. 자바스크립트에서의 모듈

자바스크립트에서의 모듈이란 특정 기능을 가진 코드들을 하나의 파일로 관리하는 것

// date.js
export const getDate() => new Date().valueOf();

2. 웹팩에서의 모듈

웹팩에서의 모듈은 자바스크립트 모듈 뿐만이 아니라 HTML, JavaScript, Images, Font등 웹 애플리케이션을 구성하는 모든 자원들을 모듈이라고 한다.

3. 모듈 번들링

출처 : https://joshua1988.github.io/webpack-guide/webpack/what-is-webpack.html#%EC%9B%B9%ED%8C%A9%EC%97%90%EC%84%9C%EC%9D%98-%EB%AA%A8%EB%93%88

위 이미지처럼 여러 모든 모듈을 하나의 파일로 병합하는 행위를 모듈 번들링이라고 한다.

1 - 2, 웹팩의 등장 배경

  1. 파일 단위의 자바스크립트 모듈 관리의 필요성
  2. 웹 애플리케이션의 빠른 로딩 속도와 높은 성능

1. 파일 단위의 자바스크립트 모듈 관리의 필요성

자바스크립트의 변수 유효 범위 (스코프)는 기본적으로 전역 범위를 기본으로 가진다. 즉 어디서든 변수에 접근할 수 있다는 말이다.

파일 단위로 자바스크립트 코드를 관리할 때 모듈화 하지 않으면 어떤 문제가 발생하는지 예를 들어 알아보자.

// module1.js
var foo = 10

// module2.js
foo = 20

// main.html
<body>
<script src="./module1.js"></script>
<script src="./module2.js"></script>
<script>console.log(foo) // 20 </script>
</body>

위 코드에서 foo는 전역변수이기 때문에 어디서든 접근할 수 있고 값을 변경할 수 있다. 프로젝트가 커지고 이러한 변수가 많아진다면 남이 사용한 변수를 실수로 변경할 수 있는 문제등이 발생한다.

그리고 script의 순서를 바꾸면 foo의 값도 바뀌게 된다.

// main.html
<body>
<script src="./module2.js"></script>
<script src="./module1.js"></script>
<script>console.log(foo) // 10 </script>
</body>

이러한 문제를 해결하기 위해 javascript 파일을 모듈로 관리하게 되었고, CommonJS, ESModule 등의 모듈 시스템이 만들어졌다.

웹팩은 다양한 모듈화 방식 중 ESModule 방식을 사용한다.

2. Vite

1. Vite란?

Vite는 네이티브 자바스크립트 모듈 (ESModule)을 기반으로 한 데브 서버이다. 이미 현대 프런트엔드 개발 생태계는 웹팩을 중심으로 개발 환경과 배포 시스템이 구축되어 있습니다. 그런데 왜 비트와 같은 도구들이 나왔을까요? 그 이유는 웹팩을 사용할 때보다 훨씬 더 빠르게 개발하고 배포할 수 있기 때문입니다. 왜 빠른지 이해하기 위해서는 먼저 번들링과 자바스크립트 네이티브 모듈을 이해해야 합니다.

2. ESM (자바스크립트 네이티브 모듈)

ESM은 모듈화 문법인 import, export를 별도의 도구 없이 브라우저 자체에서 소화해 낼 수 있는 모듈 방식을 의미합니다. 만약 아래와 같은 코드를 웹팩과 같은 번들러 없이 브라우저에서 실행하면 에러가 발생합니다.

// app.js
import { sum } from "./math.js"

console.log(sum(10,20))

<script src="./app.js"></script>

기존의 브라우저에서는 importexport를 해석할 수 있는 능력이 없었습니다. 하지만, 이제는 script 태그에 아래와 같이 type="module" 속성을 추가하면 정상 동작하는 것을 볼 수 있습니다.

<script type="module" src="./app.js"></script>

브라우저에서 importexport를 소화할 수 있는 능력이 바로 ESM입니다.

3. Vite 특징

비트는 로컬에서 개발할 때 번들링을 하지 않고 ESM 방식을 사용하기 때문에 로컬 서버 구동 속도가 매우 빠릅니다. 500개 정도 되는 모듈을 갖고 있는 웹 서비스를 웹팩 데브 서버 (opens new window)와 비트로 비교해 본다면 실행 시간이 20 ~ 30배 이상 차이가 납니다. 웹팩 데브 서버는 처음 로컬 서버를 시작할 때 관련 있는 모듈들을 번들링 해서 메모리에 적재하는 시간이 필요하기 때문에 당연히 어느 정도의 시간이 필요합니다. 반면 비트는 번들링을 하지 않고 바로 서버를 실행하기 때문에 명령어를 실행함과 동시에 서버가 바로 구동됩니다.

3. Babel

1. Babel 이란?

Babel is a JavaScript compiler.

정확히는 babel 은 javascript 로 결과물을 만들어주는 컴파일러입니다.소스 대 소스 컴파일러 (transpiler)라고 불립니다.

2. ESNext and Legacy..

지금도 프론트엔드는 너무나도 빠르게 발전되고 있습니다. 심지어 최신 브라우져 조차도 지원하지 못하는 문법과 여러 기술들이 출연하고 있습니다. 새로운 ESNext 문법을 기존의 브라우져에 사용하기 위해서 babel 은 필수적입니다. 모든 사람들이 새로운 브라우져를 쓰면 좋겠지만, 슬프게도 아직도 많은 사람들이 예전 브라우져 예전 OS 를 사용하고 있습니다.

 

이런 하위 호환성은 외면하기에는 쉽지 않습니다. 이또한 babel 을 쓰는 강력한 이유가 됩니다. (babel-polyfill 부분을 참고하세요!)

3. polyfill

폴리필(polyfill) 은 개발자가 특정 기능이 지원되지 않는 브라우저를 위해 사용할 수 있는 코드 조각이나 플러그인을 의미 합니다. 브라우저에서 지원하지 않는 기능들에 대한 호환성 작업을 채워 넣는다고 해서 polyfill 이라고 칭합니다.

babel 은 이러한 polyfill 을 손쉽게 지원하기 위해 babel-polyfill 기능을 지원합니다.
아까 이미 문법을 컴파일 해서 javascript 로 compile 한다고 했는데… 왜 polyfill 이 필요할까요?

babel 을 사용한다고 최신 함수를 사용할 수 있는 건 아닙니다. babel 은 문법을 변환하여 javascript 로 변환하는 transpiler 역할만 할 뿐입니다.

앞에서 설명한대로 polyfill 은 프로그램이 처음에 시작될 때 지원하지 않는 기능들을 추가하는 것입니다. 즉, babel 은 컴파일시에 실행되고 babel-polyfill 은 런타임에 실행되는 것입니다.

Reference

[Webpack] 웹팩이란? 웹팩을 사용하는 이유

Introduction | Cracking Vue.js

babel 이란 무엇인가?

개요

2024-03-15일 저녁에 공통 프로젝트 팀원들과 회식을 하면서 비동기 처리 관련 글을 포스팅 했던 얘기가 나왔었는데, 여기서 내가 글을 썼음에도 여러가지 부족한 점들이 느껴져서 조금만 더 정리해본다. 그 중 자바스크립트 에서 비동기 처리를 어떻게 하는것인가에 대해서 얘기하다 어물쩡 거려가지고 좀 부끄러웠다. 어떻게 진행되는지 한번 보자.

동기와 비동기

동기

동기(Synchronous)는 순차적인 흐름을 가진다. 하나의 작업이 실행되는 동안, 뒤의 다른 작업들은 그대로 멈춘 채 자신의 차례가 오기까지를 기다린다.

이 일련의 과정 중에서 동기식 프로그램의 특징을 알 수 있다. 손님이 주문을 요청하면, 점원은 그 요청에 대한 처리를 거처 응답을 내놓는다. 손님은 응답이 돌아오기까지 카운터를 떠나지 않고 기다린 뒤, 응답을 받고 나서야 다음 행동을 할 수 있다.

한번에 하나의 태스크를 수행하는 것은 싱글 스레드에서 코드가 동작할 경우이다. 요청에 대한 처리를 할 수 있는 주체가 하나만 존재하기 때문에, 위와 같이 순차적인 흐름을 가질 수 밖에 없다.

비동기

비동기(Asynchronous)는 반대로, 어떠한 요청을 보내면 해당 요청의 응답에 관계없이 바로 다음 동작이 실행된다. 즉, 실행중인 작업이 끝나기를 기다리지 않고 바로 다음 작업으로 넘어갈 수 있다.

점원들의 작업은 비동기식으로 처리되고 있다. 점장은 점원1에게 요청을 보낸 후, 응답을 받지 않은 상태에서 점원2에게 다른 요청을 보낸다. 둘 다 완료 보고라는 응답이 돌아오지 않았지만 점장은 신경쓰지 않고 다음 작업으로 넘어갈 수 있다.

멀티 스레드 방식에서는 요청을 처리할 수 있는 작업 단위가 여러 개이기 때문에, 하나의 태스크가 끝나기를 기다리지 않아도 다음 실행을 할 수 있게된다.

자바스크립트 엔진이란?

자바스크립트의 코드를 해석하고 분석하고 실행하는 인터프리터, 즉 해석기이다. 주로 웹 브라우저에서 사용된다. (자바스크립트 엔진은 브라우저 안에 내장되어 있다.) 엔진에 의해 해석하기 때문에 컴파일 과정일 불필요하다. 웹 브라우저에서 곧바로 해석하고 실행한다.

브라우저별 엔진 종류

  • V8 : 자바스크립트 대표 엔진이다. 구글에서 개발했으며, 크롬과 노드js에서 사용한다.
  • 웹킷 : 애플에서 개발한 오픈소스 엔진이고, 사파리에서 사용한다.
  • 스파이더 몽키, 차크라 등등

자바스크립트 엔진 구조

자바스크립트 엔진은 크게 메모리 힙과 콜 스택으로 나눌 수 있다. 이벤트 루프, 콜백 큐와 같은 엔진 외부 요소들과 함께 코드를 실행한다.

메모리 힙과 콜스택

메모리 힙

객체, 함수, 참조 타입과 같은 데이터를 저장하는 공간.

콜 스택

코드를 실행할 때 코드 안의 실행 순서를 기록하고 순서대로 코드가 실행될 수 있도록 도와주는 스택이다. 자세히 말하자면 함수를 호출 시 함수 실행 컨텍스트가 스택에 쌓이면서 순서대로 실행된다.

<aside> 💡 실행 컨텍스트란? 브라우저가 HTML 문서를 해석할 때, <script> 태그로 감싸진 자바스크립트 코드 또는 onClick과 같은 속성을 가진 태그를 만나면 이것을 자바스크립트에 보낸다. 그리고 자바스크립트 엔진이 브라우저가 넘겨준 코드를 변환하고 실행시키기 위해 특별한 환경을 구성하는데, 이것을 실행 컨텍스트 라고한다.

</aside>

자바스크립트는 어떻게 비동기 실행이 가능할까

자바스크립트 코드가 비동기 실행으로 보여지게 만들어 주는 것은 바로 브라우저이다. 브라우저는 자바스크립트 엔진 외에도 태스크 큐, 이벤트 루프, Web API를 가지고 있다.

  • 태스크 큐 : 비동기 함수의 콜백 함수가 임시 보관되는 공간
  • 이벤트 루프 : 자바스크립트 엔진의 콜스택이 비어있는지, 태스크 큐에 대기 중인 함수가 있는지 확인
  • Web API : 브라우저 환경에서 제공하는 API
    • ex) setTimeout, DOM API, AJAX통신, Canvas, WEBGL, 오디오 또는 비디오 API

비동기 실행에서 브라우저의 역할

자바스크립트 엔진은 싱글 스레드로 동작하지만, 브라우저 엔진은 멀티 스레드로 동작한다. 멀티 스레드로 동작하는 브라우저가 제공하는 Web API를 함께 사용하여 비동기 동작을 구현할 수 있는 것 이다.

자바스크립트 엔진이 코드를 읽어 실행하는 과정

  1. 자바스크립트 엔진이 실행 컨텍스트를 차례대로 콜스택에 쌓는다.
  2. 쌓인 코드들이 순서에 따라 콜스택을 빠져나가며 실행된다.
  3. 코드 중 비동기 함수, 즉 setTimeout 또는 setInterval, 이벤트 핸들러와 같은 Web API 함수를 만나면 자바스크립트 엔진이 이들을 Web API로 보낸다.
  4. Web API는 전달받은 함수의 콜백 함수를 꺼내어 태스크 큐로 보낸다. 이때 딜레이 인자가 잇는 경우는 딜레이 시간만큼 Web API에서 대기하게 된다.
  5. 콜스택에 있던 함수들의 실행이 모두 완료되어 빈 공간이 되면, 이벤트 루프가 태스크 큐에서 대기중인 콜백함수 하나를 콜스택에 넣어 바로 실행시킨다.
  6. 다시 콜스택이 비면 이벤트 루프가 태스크큐가 빌때까지 위의 동작을 반복

결론

자바스크립트엔진은 싱글 스레드가 맞다. 그리고 멀티 스레드로 동작하는 브라우저의 도움이 있기에 비동기 통신을 효율적으로 처리할 수 있는것이다. 회식 하면서 갑자기 헷갈렸던 부분은 “자바스크립트 엔진은 어떻게 싱글 스레드이면서 이벤트 루프를 돌며 WEB API가 실행한 결과를 받아오나” 였는데, 브라우저가 이벤트 루프를 관리하기 때문에 그랬던 것으로 정리할 수 있겠다.

Windows 10 에어팟 케이스 충전 불가능 현상

종종 Windows 10 PC에 에어팟을 연결하여 충전을 하거나 연결하는데, 에어팟 케이스가 충전되지 않는 현상이 있어 이를 해결하는 방법을 적어본다.

Windows 키를 눌러 Bluetooth 및 기타 장치를 입력한 후 확인해보면 오디오에 연결된 에어팟의 이름과 기타 디바이스에 에어팟 케이스가 함께 표시되는데, 나의 경우 이 에어팟 케이스 아래에 드라이버 문제라는 텍스트가 함께 표시 되는 문제가 있었다. 

해결방법

해결방법은 아주 간단했다. 먼저 기타 디바이스에 표시된 AirPod Case 장치 제거 버튼을 클릭하여 장치를 제거해주고, 오디오 카테고리 아래에 있는 OO's AirPods 도 동일하게 장치 제거 버튼을 클릭해준다.

이후 에어팟을 다시 PC와 페어링 하여 연결 이후 정상적으로 충전할 수 있었다.

 

개요

SSAFY 계절학기 강의 수강 중 싱글스레드에서 비동기 요청을 할 수 있다는 내용이 있었는데, 필자가 개발하면서 싱글 스레드 환경에서 비동기 요청을 한 기억이 없어서 그런지 잘 이해가 되지 않았다. 이를 이해하고자 문서를 작성해본다.

자바스크립트에서 싱글 스레드와 비동기 요청

아래와 같이 “싱글스레드 비동기” 라는 키워드로 구글에 검색을 해봤다. 그러나 이 부분은 자바스크립트 언어가 어떻게 싱글 스레드에서 비동기 요청 처리를 가능하게 하는 것인지 말해주는 내용이 대다수 였다. 글들을 읽어보면 결국에는 자바스크립트 언어 자체는 싱글 스레드로 동작하는 것이고, 비동기 요청의 경우 Browser API를 통해 비동기 요청을 처리 후 콜백 처리를 해주는 것으로 이해했다.

따라서, 자바스크립트도 결국에는 완전한 싱글 스레드가 아니라고 생각했다.

그렇다면 완전한 싱글스레드 환경에서 비동기 요청은 가능한 것일까?

알고리즘을 풀 때 처럼 main 함수만 있는 환경에서도 비동기 요청이 가능할 지 궁금했다.

대표적인 비동기 요청 사례인 File I/O를 통해 이를 알아보자.

동기 및 비동기 I/O

동기 파일 I/O에서 스레드는 I/O 작업을 시작하고 I/O 요청이 완료될 때까지 즉시 대기 상태로 돌입합니다. 비동기 파일 I/O를 수행하는 스레드는 적절한 함수를 호출하여 커널에 I/O 요청을 보냅니다. 커널에서 요청을 수락하는 경우, 커널이 스레드에 I/O 작업이 완료되었다는 신호를 보낼 때까지 호출 스레드가 다른 작업을 계속 처리합니다. 그런 다음 현재 작업을 중단하고 필요에 따라 I/O 작업에서 데이터를 처리합니다.

 

아래는 동기 I/O와 비동기 I/O를 그림으로 표현한 것이다.

 

간단하게 정리해보면, I/O 처리의 경우 커널모드 스레드에서 수행된다. 싱글 스레드라고 믿었던 프로그램도 사실은 커널모드 스레드를 통해 I/O 작업을 수행하고 있다. 따라서 위와 같은 파일 I/O 작업의 경우 싱글스레드 프로그램에서도 비동기 요청이 가능하다. 다만, 파일 I/O의 경우 커널모드의 스레드를 이용하므로, 이와 같이 타 스레드에 작업을 위임하지 않는 이상 싱글 스레드에서 비동기 요청은 불가능하다고 생각한다.

싱글 스레드에서 비동기, 효율적인가?

서비스를 개발하면서 싱글 스레드에서 비동기 요청을 하는 경우를 많이 이용해본 적이 없다. 왜일까? 만약, 파일 읽기 요청과 다른 작업을 함께 수행한다고 생각해보면 굉장히 불편할 것이라고 생각한다. 다른 작업을 수행하면서 수시로 커널에 I/O가 완료 되었는지 물어봐야 할 것이고, 에러처리도 어려울 것이다.

비동기 처리 아키텍쳐

Spring, Android와 같은 많은 프레임워크에 비동기 처리를 지원하는 API를 포함하거나, 라이브러리 형태로 사용하기 좋게끔 만들어져 있다. 이러한 라이브러리들은 어떤 구조로 만들어져 있을까? 대표적인 비동기 요청 라이브러리인 Spring WebClient를 통해 알아보자.

Spring WebFlux includes a client to perform HTTP requests with. WebClient has a functional, 
fluent API based on Reactor, see Reactive Libraries, which enables declarative composition 
of asynchronous logic without the need to deal with threads or concurrency. It is fully 
non-blocking, it supports streaming, and relies on the same codecs that are also used to 
encode and decode request and response content on the server side.

Spring WebFlux에 내장된 WebClient의 경우 netty를 기반으로 만들어졌다고 한다. netty는 Reactor Pattern으로 만들어졌다고 하는데, Reactor Pattern에 대해서도 간단히 알아보자.

Reactor Pattern

하나 이상의 클라이언트로부터의 요청을 동시 처리하기 위해서 사용하는 패턴이다.

어플리케이션에서 이벤트를 처리하기 위한 루프를 도는 것이 아니라, 이벤트에 반응하는 객체(reactor)를 만들고, 이벤트가 발생하면 어플리케이션 대신 reactor가 반응하여 처리하는 것이다.

정리

이와 같이 싱글 스레드에서 비동기 요청을 할 수는 있지만, 사용되지 않는 이유가 있는 것 같다. 결론을 내보자면 아래와 같다.

  • 싱글 스레드에서 일부 작업의 경우 비동기 요청이 가능하다.
  • Reactor Pattern이 등장 한 것처럼 비동기 요청의 경우 멀티 스레드를 이용하여 구현하는 것이 효율적일 것이다.

Reference

동기 및 비동기 I/O - Win32 apps

WebClient :: Spring Framework

리액터패턴 / 프로액터패턴

 

개요

공통 프로젝트로 진행했던 SPOPARTY 서비스가 SSAFY에서 제공해주는 서버 기간 만료로 인해 다운되었었는데, 중간중간 서비스를 개발하면서 팀원들과 함께 게시판을 잘 이용했었다.

그래서 도메인 구매하여 서비스를 다시 올려놨었는데…

장애 발생

파일 업로드가 불가능한 현상 + 파일 삭제가 불가능한 현상 두가지가 함께 터져서 로그를 보면서 추적 해봤다.

파일 업로드 불가능 현상

파일 업로드의 경우 제보자가 보내준 내용 그대로였다.

nginx는 client_max_body_size가 1MB가 기본으로 설정되어 있었는데, 기존 서버에는 openvidu 컨테이너 내부 nginx에서 설정되어 있었던 것으로 보인다.

따라서 현재 서버의 nginx 설정에 아래의 옵션을 추가한 후 정상적으로 업로드가 가능해졌다.

client_max_body_size 10M;

파일 삭제 불가능 현상

 

파일 삭제의 경우 로그 확인 결과 S3 권한 문제였다.

레포지토리 내의 키값이 노출되면서 AWS 측에서 메일이 왔었는데, 확인을 못했다.

노출로 인한 피해를 막고자 aws 측에서 일부 기능을 막았다고 한다.

이 부분은 새로운 Spring Application에서 s3로 접근하는 access key와 secret key를 새로 발급받아서 해결했다.

장애 대응 완료

약 한시간 30분의 장애 대응을 해보면서 또 지식을 얻어갈 수 있었다.

 

늦은 시간까지 테스트를 함께 해준 팀원들 모두 감사합니다!!

 

AWS 에서 이런 메일도 왔다.. ㅋㅋㅋㅋ

 

개요

Jenkins Container 환경 설정 시 웹 페이지의 경로를 변경하고, nginx 프록시 할 수 있도록 구성하는 방법을 알아보자.

Host OS 내에 jenkins container를 이미 구성한 상태에서 접속할 수 있는 경로만 변경하는 작업을 포스팅 한다.

환경

필자의 경우 Host OS(Ubuntu Linux) 위에 Jenkins Container를 구성했고, Host OS에 Nginx를 설치하여 리버스 프록시를 활용하여 Jenkins 관리 웹 페이지로 이동시키게끔 구성했다.

Jenkins Container 설정

StackOverflow의 글에서 경로를 변경하기 위해서 환경변수를 설정할 수 있다는 글을 확인했다.

Change jenkins container deployment root path

Jenkins Container 생성 당시 입력했던 환경 변수를 확인해보자.

Host OS에서 아래의 명령어로 컨테이너 생성 당시 입력했던 환경변수를 확인 가능하다.

docker exec jenkins(컨테이너 이름) /usr/bin/env
PATH=/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=6719f87953bd
JENKINS_OPTS=--httpPort=8080
TZ=Asia/Seoul
LANG=C.UTF-8
JENKINS_HOME=/var/jenkins_home
JENKINS_SLAVE_AGENT_PORT=50000
REF=/usr/share/jenkins/ref
JENKINS_VERSION=2.447
JENKINS_UC=https://updates.jenkins.io
JENKINS_UC_EXPERIMENTAL=https://updates.jenkins.io/experimental
JENKINS_INCREMENTALS_REPO_MIRROR=https://repo.jenkins-ci.org/incrementals
COPY_REFERENCE_FILE_LOG=/var/jenkins_home/copy_reference_file.log
JAVA_HOME=/opt/java/openjdk
HOME=/root

위의 글에서 확인한 접속 prefix를 추가하기 위해서는 아래의 환경변수를 추가하면 된다.

JENKINS_OPTS="--prefix=/jenkins"

나는 아래의 문서 일부를 읽은 후 지원하지 않는 기능인 줄 알았는데, 최근 댓글을 보면 실행중인 컨테이너 위에 환경변수를 추가할 수 있는 방법이 있는 것 같다.

How to set an environment variable in a running docker container

필자의 경우 컨테이너에 환경변수를 추가하여 다시 올리는 방식으로 작업했다.

docker run -d --env JENKINS_OPTS="--httpPort=8080 --prefix=/build" --prefix=/build -v /etc/localtime:/etc/localtime:ro -e TZ=Asia/Seoul -p 8080:8080 -v /jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /usr/local/bin/docker-compose:/usr/local/bin/docker-compose --name jenkins -u root jenkins/jenkins:jdk17

또한 path/build 경로로 들어오는 요청을 nginx에서 8080 포트로 redirect 하도록 설정해줬다.

sudo vim /etc/nginx/sites-enabmed/default

...

location /build/ {
                proxy_pass <http://localhost:8080/build/>;
        }
...

이후 nginx를 재시작 해줬다.

sudo service nginx restart

+ Recent posts