Mysql 인덱스 사용법 및 실행 계획 정리

데이터베이스/mysql|2020. 6. 10. 19:18

mysql 인덱스에 대한 정확한 이해도 없이 사용을 하다보니 조금 개념적으로 헷갈리는게 많이 있었다. 이 부분에 대해 한번 정리하고 넘어가고자 기록해본다.

 

인덱스


인덱스는 빠르게 특별한 컬럼과 함께 값을 찾는데 사용된다. 인덱스가 없으면 Mysql은 처음 행부터 전체 테이블을 읽어 들여서 데이터를 찾는다. 거대한 테이블에서 이런 행동은 비용이 상당히 많이 들어가게 된다. 만약에 테이블이 인덱스를 가지고 있으면 빠르게 접근할 수 있게 된다.

대부분의 Mysql 인덱스 (PRIMARY KEY, UNIQUE, INDEX, and FULLTEXT)는 B-tree안에 저장된다. 예외적으로 spatial 데이터 타입은 R-tree를 사용, 메모리 테이블은 또한 hash index를 지원, InnoDB는 FULLTEXT 인덱스를 위해 inverted list를 사용한다.

 

 

인덱스 동작 방식


- 행을 찾기 위해서 매칭되는 WHERE 구문을 빠르게 찾는다.

 

- 조건으로 부터 불필요 행을 제거한다. 만약에 여러 인덱스가 있는 경우 Mysql은 가장 적은 수의 행을 사용하는 인덱스를 선택한다. (Mysql은 한번에 하나의 인덱스만 사용할 수 있다.)

 

- 만약 테이블이 multiple column 인덱스를 가지고 있으면 인덱스의 가장 왼쪽에 컬럼을 사용하여 옵티마이저를 통해 행을 찾는다. 예를 들어 만약에 (col1, col2, col3)을 사용하는 인덱스가 있는 경우 인덱스는 이 순서로 검색을 진행한다. (col1), (col1, col2), (col1, col2, col3)

 

- 조인이 있는 경우 다른 테이블에서 행을 찾는다. Mysql은 동일한 유형과 사이즈로 되어 있는 열을 index로 사용할 때 더욱 효과적으로 행을 찾는다. VARCHAR, CHAR는 두개를 같은 사이즈로 명시 하였을 경우에 같은 타입으로 고려되어 사용된다. 예를 들어 VARCHAR(10) = CHAR(10)이지만 VARCHAR(10) ≠ CHAR(15)는 같지 않다.

 

- binary가 아닌 문자열 사이를 비교하기 위해서는 동일한 문자열 집합을 사용해야한다. utf8과 latin1열을 비교할 경우 인덱스를 사용할 수 없다.

 

- 타입이 다른 문자열과 숫자 등을 비교하려고 할때도 마찬가지로 인덱스를 탈 수 없다. (묵시적 형변환)

 

- index로 사용되는 key_col에서 min(), max()의 값을 찾기 위해 인덱스에서 key_col 이전에 발생하는 모든 키 파트에서 WHERE key_part_n = const를 사용하는지 여부를 확인하기 위해서 전처리기를 통해서 최적화가 진행된다. 이 경우에서 Mysql은 각 min() 또는 max() 표현식에 대해 단일키 조회를 수행하여 상수를 대체한다. 모든 표현식이 상수로 바뀌고 나면 쿼리가 한번에 반환된다.

SELECT MIN(key_part2),MAX(key_part2)
  FROM tbl_name WHERE key_part1=10;

 

 

실행계획


- 실행계획 필드 정리

  • Id
    • Select 구문 구분 ID
  • select_type
    • SIMPLE : 단순 SELECT
    • DERIVED : 서브 쿼리 중 가장 안쪽에 있는 쿼리 
    • PRIMARY : 서브 쿼리 바깥쪽에 있는 쿼리
    • DEPENDENT SUBQUERY  : 조건절 내부에서 외부 쿼리와 연결된 SELECT 
// PRIMARY
SELECT * FROM ( 
    // DERIVED : 서브 쿼리 중 가장 안쪽에 있는 쿼리
    SELECT * FROM timeline_item ti
) tt;


// DEPENDENT SUBQUERY
SELECT * FROM timeline_item t1 WHERE EXISTS ( SELECT * FROM timeline_site t2 WHERE t1.id = t2.id)
  • table

    • 참조하는 테이블 이름
  • type

    • 조인 혹은 조회 타입 (아래로 갈수록 성능 하락)
      1. System : 테이블에 데이터가 하나만 있는 경우
      2. const : SELECT에서 Primary Key 혹은 Unique Key를 상수로 조회하는 경우
      3. eq_ref : 조인할 때 Primary, Unique Key로 매칭하는 경우
      4. ref : 조인할 때 Primary, Unique Key로 매칭하지 않은 경우
      5. ref_or_null : ref와 같지만 NULL이 추가되어 검색된 경우
      6. index_merge : 두개의 인덱스가 병합되어 검색이 된경우
      7. unique_subquery : In절 내부 서브쿼리에서 Primary Key가 있는 경우
      8. index_subquery : In절 내부 서브쿼리에서 Primary Key가 아닌 인덱스가 있는 경우
      9. range : 특정 범위 내에서 인덱스를 사용하여 데이터 추출 하는 경우
      10. index : 인덱스를 처음부터 끝까지 찾아서 검색하는 경우로 일반적인 인덱스 풀스캔
      11. all : 테이블 풀스캔
  • possible_keys

    • 데이터 조회 시 DB에서 사용할 수 있는 인덱스 리스트
  • key

    • 실제로 사용할 인덱스
  • key_len

    • 실제로 사용할 인덱스 길이
  • ref

    • key 안의 인덱스와 비교하는 컬럼(상수)
  • rows

    • 쿼리 실행 시 조사하는 행수
  • extra

    • 추가 정보 (데이터가 많고 Using filesort, Using temporary 상태가 나온다면 무조건 최적화 필요)

      1. Using Index

        커버링 인덱스라고 하며 인덱스 자료구조를 이용하여 데이터를 추출

      2. Using where

        where 조건으로 데이터를 추출 (Type이 All 또는 Index와 같이 표현될 시 성능이 안좋다는 뜻)

      3. Using filesort

        데이터 정렬이 필요한 경우로, 메모리 혹은 디스크 상에서의 정렬을 모두 포함 (데이터 많을 시 성능 하락)

      4. Using Temporary

        쿼리 처리 시 내부적으로 Temporary 테이블이 사용됨

 

 

WHERE 조건문 주의사항


  • 묵시적 형변환에 조심하라
  • 무턱되고 함수를 사용하면 옵티마이저가 데이터 분포도 체크를 하지 못하기에 사용하지 말 것
// 함수 사용
SELECT * FROM timeline_item ti WHERE DATE_FORMAT(modified_at, '%Y%m%d') <= '20200402'

// 대안방안
SELECT * FROM timeline_item ti WHERE modified_at <= '2020-04-02'
  • Like 검색은 % 위치에 따라 다르게 수행 된다. %123, %125%의 경우에는 데이터 풀 스캔이 발생된다. 하지만 12312%와 같은 경우에는 인덱스를 사용해서 진행됨. 하지만 1%등과 같이 데이터 분포도에 비해 너무 추상적으로 %를 사용하게 될 경우에는 옵티마이저가 인덱스 사용과 데이터 풀스캔의 효율성 판단 했을 때 풀스캔이 효율적이다고 생각하고 데이터 풀스캔이 실행된다. 

댓글()

Spring5 리액티브 스트림 정리 및 api 전달 방식 정리

web/Spring|2019. 8. 16. 22:22

리액티브 또는 리액티브 스트림은 오늘날 spring framework에서 뜨거운 토픽으로 자리잡고 있다. 

그래서 나도 이전 포스팅에서도 정리도 하고 했었는데 아직 확실히 개념이 서질 않아서 다시 정리해봤다.

 

리액티브 스트림 (Reactive Stream) 이란?


리액티브 스트림은 무엇인가? 정확하게 공식문서에는 다음과 같이 기록되어 있다. (https://www.reactive-streams.org/)
Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure.This encompasses efforts aimed at runtime environments (JVM and JavaScript) as well as network protocols.

이런 Reactive stream을 spring5에서 포함되었다.

  • Spring core framework는 Reactor와 RxJava를 통해 built-in 리액티브 프로그램을 할 수 있는 새로운 spring-flux 모듈을 추가하였다.
  • Spring security 5도 또한 reactive feature를 추가했다.
  • Spring Data umbrella project에서 Spring Data Commons에 새로운 ReactiveSortingRepository가 추가되었는데 가장먼저 redis, mongo, cassandra가 reactive에 지원한다. 불항하게도 일반적인 JDBC 드라이버의 블록킹 프로세스를 할 수 밖에 없는 디자인 때문에 Spring Data JPA는 이 특징에서 이점이 없다.
  • Spring Session또한 reactive feature를 추가하였고 2.0.0.M3qnxj SessionRepository내에 추가되었다.

 

Webflux 어플리케이션 만들기


스프링5를 통해서 reactive 프로그램을 만들어보면서 서비스를 확인해보자.

필요한 라이브러리
spring-boot-starter-parent
spring-webflux
jackson-databind
reactor-core
logback
lombok

데이터를 주고 받을 entity Post 객체

package com.study.webflex.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * spring-boot-study
 *
 * @author wedul
 * @since 2019-08-14
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Post {
    private int id;
    private String title;
    private String content;
}

 

데이터를 전달받을 Repository 클래스 DataRepository
- 우선 당장 데이터베이스를 선택하지 않고 이해를 먼저 돕기 위해서 가짜 데이터를 미리 static 블록을 이용해서 넣어놓자.

package com.study.webflex.dao;

import com.study.webflex.dto.Post;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * spring-boot-study
 *
 * @author wedul
 * @since 2019-08-14
 **/
@Repository
public class PostRepository {

  private static final Map<Integer, Post> DATA = new HashMap<>();
  private static int ID_COUNTER = 0;

  static {
    // initial data
    Arrays.asList("First Post", "Second Post")
      .stream()
      .forEach(title -> {
          int id = ID_COUNTER++;
          DATA.put(id, Post.builder().id(id).title(title).content("content of " + title).build());
        }
      );
  }

  Flux<Post> findAll() {
    return Flux.fromIterable(DATA.values());
  }

  Mono<Post> findById(Long id) {
    return Mono.just(DATA.get(id));
  }

  Mono<Post> createPost(Post post) {
    int id = ID_COUNTER++;
    post.setId(id);
    DATA.put(id, post);
    return Mono.just(post);
  }

}

WebFlux를 사용하기 위한 어노테이션 @EnableWebFlux와 @Configuration을 달아준다.


WebFluxApi
webFlux는 기존 mvc또한 지원하기 때문에 아래와 같이 Controller를 만들어 엔드포인트를 정의하여 사용할 수 있다. 내부에서는 HttpServletRequest, HttpServletResponse객체 대신 ServerHttpRequest와 ServerHttpResponse 객체로 동작한다.

package com.study.webflex.controller;

import com.study.webflex.dto.Post;
import com.study.webflex.service.PostService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * spring-boot-study
 *
 * @author wedul
 * @since 2019-08-15
 **/
@RestController
@RequiredArgsConstructor
public class PostController {

  private final PostService postService;

  @GetMapping(value = "")
  public Flux<Post> all() {
    return postService.findAll();
  }

  @GetMapping(value = "/{id}")
  public Mono<Post> get(@PathVariable(value = "id") int id) {
    return postService.findById(id);
  }

  @PostMapping(value = "")
  public Mono<Post> create(Post post) {
    return postService.createPost(post);
  }

}

실행하면 원하는 데이터를 추출해서 볼 수있다.

webflux mvc로 출력된 결과

그리고 또다른 형태로도 사용할 수 있게 제공하는데 RouterFunction과 HandelrFunction을 정의해서 구현해야한다.

HandlerFunction은 http요청을 ServletRequest객체로 가져와서 Mono형태로 값을 반환하고 RouterFunction은 http요청을 HandlerFunction으로 다시 Mono의 형태로 라우팅해준다.

우선 요청을 받아서 작업을 진행할 Handler를 정의한다.

package com.study.webflex.handler;

import com.study.webflex.dto.Post;
import com.study.webflex.service.PostService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

/**
 * spring-boot-study
 *
 * @author wedul
 * @since 2019-08-15
 **/
@Component
@RequiredArgsConstructor
public class PostHandler {

  private final PostService postService;

  public Mono<ServerResponse> all(ServerRequest serverRequest) {
    return ServerResponse.ok().body(postService.findAll(), Post.class);
  }

  public Mono<ServerResponse> get(ServerRequest serverRequest) {
    String id = serverRequest.path();
    return ServerResponse.ok().body(postService.findById(Integer.valueOf(id)), Post.class);
  }

  public Mono<ServerResponse> create(ServerRequest serverRequest) {
    return serverRequest.bodyToMono(Post.class).doOnNext(post -> postService.createPost(post)).then(ServerResponse.ok().build());
  }

}

그리고 RouterFunction을 정의하자 하나의 route를 정의하여 엔드포인트를 지정할 수 있고 추가적으로 andRoute를 통해 연속적으로 지정할수도 있다.

/** * spring-boot-study * * *@author *wedul * *@since *2019-08-15 **/@ComponentScan@EnableWebFlux@Configurationpublic class WebFluxConfig {

  @Bean  
  public RouterFunction<?> routes(PostHandler postHandler) {
    return RouterFunctions.route(GET("/route").and(accept(APPLICATION_JSON)), postHandler::all)
      .andRoute(GET("/route/{id}").and(accept(APPLICATION_JSON)), postHandler::get)
      .andRoute(GET("/route/create").and(accept(APPLICATION_JSON)), postHandler::create);  
    }
}

RouterFunction으로 나온 결과

동일하게 결과가 잘 나오는것을 확인할 수 있다.


그럼 여기서 왜, 그리고 언제 spring reactive를 사용하는게 좋은것일까? 아직까지는 위의 예제를 봐도 크게 어떤 부분 때문에 비동기, 논 블록킹을 스프링에서 사용하는지 언제 사용하는게 효율적인지 잘 모르겠다. 이게 나도 가장 궁금해서 이유를 찾아봤다.

일반적인 상황에서 쓰레드는 요청이 들어오면 끝날때까지 유지된다. 만약 데이터에 접근하고 기록하고 하는 작업이 있다면 이 작업들이 마무리 될 때까지 기다리고 있어야해서 쓰레드 낭비가 커진다.

그래서 응답은 바로 전달하는 non blocking에 비동기로 작업이 진행되도록 하는게 유리하다.
그런데 궁금한게 또 생겼다. 비동기-논블록킹으로 api를 만들면 비동기로 작업이 진행중인데 작업이 종료된 후 어떻게 client에서 결과를 가져올 수 있는건가?

바로 SSE (Server Sent Event) 개념을 이용하여 데이터가 전달된다. 예전에 spring 3.2 부터 추가 되었던 비동기 프로세스에 정리한적이 있었다. https://wedul.site/157

마찬가지로 spring5 reactor에서도 이 개념을 이용하여 동작한다.

우리가 spring webFlux를 사용할 때 내부적으로는 다양한 변화가 발생한다. reactor api에서 제공하는 publisher정보를 우리가 subscribe할 때 publisher는 client에게 각각의 아이템을 serialize하여 대량으로 전달한다.

이런 방식으로 우리는 많은 쓰레드를 생성하지 않고도 대기하고 있는 쓰레드를 이용하여 비동기적으로 데이터를 받을 수 있다. webflux에서 이런 로직을 사용하기 위해서 별도의 작업이 필요하지 않다. 알아서 지원해준다.

위에 정리했었다고 언급했던 spring mvc 3.2부터 추가된 AsyncResult, DefferedResult, Ssemiter등을 사용하면 webflux와 비슷하게 사용하는 것 같지만 사실은 내부적으로 Spring mvc는 스레드를 하나 생성하여 long polling 작업을 위해서 쓰레드를 대기하고 있기 때문에 비동기의 장점을 이용하기에는 어렵다.

실제로 예전 직장에서 long polling으로 client와 세션을 유지시키고 있을 때 대량의 사용자가 붙으면 설정했던 thread 개수를 초과해서 문제가 생긴 경험이 있다.

webFlux에서 이문제가 해결된다니 정말 좋은 것 같다. 왜 사용하는지 조금은 이해가 되는 것 같다.

그럼 말로만 하지말고 실제로 클라이언트에게 값을 전달을 해주는지 테스트해보자. 위에서 했던 소스는 데이터 양도 적고 값이 바로 나오기 때문에 정말 그렇게 나오는지 알 수가 없었다.

그럼 대기시간을 부여해서 확인해보자.


 

쓰레드 반환 후 결과값은 추후에 client에게 전달해주는지 테스트


우선 FouterFunction에 엔드포인트를 하나 더 추가하자.

@Bean
public RouterFunction<?> routes(PostHandler postHandler) {
  return RouterFunctions.route(GET("/route").and(accept(APPLICATION_JSON)), postHandler::all)
    .andRoute(GET("/route/{id}").and(accept(APPLICATION_JSON)), postHandler::get)
    .andRoute(GET("/route/create").and(accept(APPLICATION_JSON)), postHandler::create)
    .andRoute(GET("/delay/client").and(accept(APPLICATION_JSON)), postHandler::clientDelay);
}

그리고 delay기능을 추가하여 ServerResponse를 반환해보자.
만약 정상적인 결과라면 위에 println이 먼저 로그에 찍히고 클라이언트에서 데이터는 3초뒤에 나올 것 이다.

public Mono<ServerResponse> clientDelay(ServerRequest serverRequest) {
  Flux<Post> post = Flux.interval(Duration.ofSeconds(2))
    .take(3)
    .flatMap(number -> postService.findById(number.intValue()));

  System.out.println("test");
  return ServerResponse.ok().body(post, Post.class);
}

예상대로 로그는 먼저 찍힌다.

그리고 브라우저에서 결과는 예상대로 3초뒤에 출력되었다.


이제 정리가 되었다.

결론을 내리면 결과값을 기다릴 필요가 없이 비동기 논블록킹으로 동작하고 쓰레드를 반환하면 더 효율적인 운영이 가능할 것 같다. 그리고 webflux api를 사용할 경우에 걱정할 필요없이 값이 완료되면 클라이언트에게 전달되는 걸 확인 할 수 있었다.

비동기-논블록킹 프레임워크에서 중간에 블록킹이 걸리면 비효율적일 것 같다. 그래서 당장은 jdbc를 쓰는 경우에서는 쓰기 어렵겠지만 NoSql을 사용하는 경우에는 충분히 고려해볼만 할 것 같다.


공부에 사용한 저장소
https://github.com/weduls/spring5


출처 및 도움이 되었던 사이트
https://supawer0728.github.io/2018/03/11/Spring-request-model3/
https://techannotation.wordpress.com/2018/04/24/spring-reactive-a-real-use-case/
https://inyl.github.io/programming/2018/03/10/springboot2_api.html
https://stackabuse.com/spring-reactor-tutorial/
https://supawer0728.github.io/2018/03/15/spring-http-stereamings/

댓글()

[번역] Redis partitioning

데이터베이스/Nosql|2019. 3. 6. 22:12

파티셔닝 공부를 위해 아래 페이지의 내용을 번역하며 정리해봤다.

https://redis.io/topics/partitioning


Redis Partitioning: 여러 레디스 인스턴스로 데이터 분배하기


파티셔닝은 데이터를 여러 레디스 인스턴스로 분할하여 모든 인스턴스가 자기가 소유한 키의 집합들만 소유하도록 하는 프로세스이다. 먼저 파티셔닝 개념에 대해 설명하고 레디스 파티셔닝에 대한 대안을 소개한다.

파티셔닝이 효율적인 이유

레디스에서 파티셔닝을 하기는 다음 두개의 이점이 있다.
1. 하나의 컴퓨터로 메모리의 양이 제한되는 경우에 파티셔닝을 사용하여 더 큰 데이터베이스와 메모리를 가질 수 있다.
2. 여러 개의 코어와 여러 대의 컴퓨터에 연산 능력을 확장하고 네트워크 대역폭을 여러 대의 컴퓨터와 네트워크 어댑터로 확장할 수 있다.

기본 파티셔닝 방법 (range partitioning)

파티셔닝에는 여러 기준이 있다. Redis 인스턴스 R0, R1, R2, R3 그리고 많은 사용자를 대표하는 키인 user:1, user:2와 같은 존재한다고 가정해보자. 이때 해당 키들을 어느 인스턴스에 어떻게 넣어야 하는지에 대한 여러 방법을 가지고 있다. 다른말로 말하면 주어진 키들을 주어진 인스턴스에 어떻게 매핑 할것인지에 대한 여러 방법이 있다.

가장 간단한 방법으로 range partitoning이 있다. 이 방법은 특정 범위에 있는 데이터는 특정 인스턴스에 매핑시켜서 데이터를 분배한다. 예를 들면 1 ~ 10000 까지의 데이터는 R0, 10001 ~ 20000 까지는 R1 식으로 저장 할 수 있다. 이 방식은 어떤 범위에 키를 어느 인스턴스로 매핑할지에 대한 정리가 되어있는 테이블이 필요하다.

이 테이블은 관리가 필요하고 모든 번위에대한 정리가 되어있어야 한다. 그래서 매우 불편하여 다른 파티셔닝 기법을 사용하여 이 번거로움을 대체한다.

해시 파티셔닝 Hash Partitioning

이 방식은 키와 함께 동작하고 object_name:<id>형식으로 키를 만들어서 사용하지 않아도 된다. 동작방식은 간단하다. 우선 키 이름을 crc32 해시 함수를 이용해서 숫자로 변경한다. 예를 들면 foobar라는 키는 93024922로 변경한다. 그리고 인스터스 개수 만큼 % 연산을 진행한다. 만약 인스턴스가 4개라면 93024922 % 4는 2이기 때문에 2번째 인스턴스에 들어간다.

다른 파티셔닝 종류

몇몇의 레디스 클라이언트와 프록시로 부터 hash function을 향상시켜서 만든 파티셔닝으로 consistent hashing라고 불린다.

Client side 파티셔닝
- 클라이언트에서 직접적으로 키를 가지고 읽고 기록할 노드를 선택한다. 많은 레디스 클라이언트는 이 파티셔닝을 구현한다.

Proxy assisted 파티셔닝
- 레디스 클라이언트가 바로 레디스 인스턴스에 요청을 보내지 않고 프록시에게 전송한다.
이 프록시는 적절하게 설정된 파티셔닝 스키마 대로 레디스 인스턴스에 저장하고 클라이언트에게 응답한다. 레디스와 Memcached에 대표적으로 Twemproxy가 존재한다.

Query 라우팅
- 임의의 인스턴스로 전달된 쿼리가 올바른 노드로 리다이렉션 되는 것을 말한다. redis cluster는 클라이언트에 도움을 받아서 하이브리드 형태의 쿼리 라우팅을 구현한다.

여러 파티셔닝이 있지만 기본 베이스는 기폰 파티셔닝과 해시 파티셔닝에서 구현된것이기 때문에 이 두가지가 기본이다.

Data store or Cache?

레디스에서 파티셔닝은 개념적으로 데이터 스토어와 캐시로 사용할 때 동일하지만 사실 데이터스토어로써 파티셔닝을 사용할 때는 약간의 제약이 존재한다. 레디스가 데이터 스토어로 사용될 때 키는 항상 같은 레디스 인스턴스에 있어야한다. 하지만 레디스가 캐시로 사용될 때 주어진 노드를 사용할 수 없을 때 다른 노드를 사용한다고 해서 큰문제가 되지 않는다. 이 경우에는 인스턴스 맵을 변경하여 수정할 수 있다. 위에서 제시되었던 파티셔닝에서 기존에 가야할 노드가 사용불가능할 경우 다른 노드로 저장될 수 있다. 비슷하게 만약 새로운 노드가 추가되면 새로운 키의 일부는 새로운 노드에 저장될 수 있다.

정리된 컨셉은 다음과 같다.
- 레디스를 캐시로 사용할 경우 scaling up and down이 자유롭다.
- 레디스를 데이터 ㅈ장소로 써 사용할 경우에는 고정된 키-인스턴스 맵이 존재해야하고 인스턴수의 개수는 그렇게 크지 않게 고정되어 있어야 한다. 그렇지 않으면 인스턴스가 추가되거나 제거 될 때 인스턴스간에 키를 리밸런싱 할 수 있는 시스템이 필요하다. 현재는 redis cluster만 이 기능을 제공한다.


PreSharding

위에 본거와 같이 레디스를 캐시로써 사용하지 않는이상 파티셔닝에 단점이 있느 것을 확인 할 수 있다.

하지만 데이터 스토어는 매일 많이 사용된다. 오늘 10개의 레디스 인스턴스 노드를 사용한다고 해도 다음날 50개가 필요할 수도 있다. 그렇기 때문에 고정된 인스턴스로 키-인스턴스 맵으로 관리하는 방식으로는 데이터 스토어로써 레디스를 사용하는데 어려움이 있다.

레디스가 필요 리소스가 적기 때문에 이 문제에 대한 간단한 접근방법은 애초에 많이 생성하는 것 입니다. 만약 하나의 서버로 서비스를 시작한다면 하나의 서버안에서 파티셔닝을 통해 여러 레디스를 구동할 수 있다. 그래서 처음부터 32개 또는 64개의 인스턴스를 만들어서 충분하게 사용자들이 사용할 수 있도록 설계할 수 있다. 이러한 방식으로 인스턴스를 크게 늘리고 만약 데이터 저장소가 더 필요하고 레디스 서버가 더 필요하다면 간단하게 인스턴스를 다른 서버로 이동 시킬 수 있따. 만약 부가적인 서버가 추가된다면 레디스 인스턴스 반을 추가된 서버로 이동 시킬 수 있다.
Redis 복제를 사용하면 사용자를위한 중단 시간이 거의 없거나 전혀 없을 때 이동을 수행 할 수 있다.


레디스 파티셔닝 사용

이론을 공부했다. 이제 어떻게 사용해야하는지 보자.

Redis Cluster
redis cluster는 자동으로 샤딩을 하고 높은 가용성을 가지는것을 선호한다. 2015년 4월 1일 부터 redis cluster를 사용할 수 있다. redis cluster는 query routing과 client side 파티셔닝을 섞어놓은 방식으로 진행된다.

Twemproxy
Twemproxy는 memcached ASCII와 redis 프로토콜을 위해서 트위터에서 개발된 프록시 이다. 싱글스레드이고 C로 개발되어 전적으로 빠르다. 여러 레디스 인스턴스에서 자동으로 샤딩이 되는 것을 지원하며 하나의 인스턴스가 사용이 불가능하면 다른 인스턴스로 전환되는 것을 지원한다.


주의사항
http://www.zdnet.co.kr/view/?no=20131119174125
여기에 보면 주의사항이 나오는데 핵심은 redis의 경우 싱글 스레드로 돌아가기 때문에 작업이 오래 발생되는 keys나 flushall은 사용하지 말아라. 1만건 이하에 데이터를 조작하는 경우에는 사용해도 되는데 그 이상 사용하는 경우에는 주의하라는 뜻.

댓글()

nginx 정리와 설치 및 기본 설정방법

IT 지식/nginx|2019. 2. 15. 22:51

Ngnix 설명

nginx는 기존 웹서버에서 많은 트래픽을 감당하기 위해서 확정성을 가지고 설계된 비동기 이벤트 드라이븐 방식의 웹서버를 칭한다.


Nginx 설치

nginx를 맥이 있으면 brew를 통해서 간단하게 설치가 가능하다.

1
brew install nginx
cs


Nginx 프로세스

nginx는 하나의 마스터 프로세스와 여러 worker 프로세스를 가진다. 마스터 프로세스의 주요 목적은 read 권한 그리고 성능 측정과 worker 프로세스 관리이다. worker 프로세스는 요청을 처리한다. nginx는 event-based 모델을 사용하고 worker 프로세스 사이에 요청을 효율적으로 분배하기 위해서 os에 의존하는 매커니즘을 사용한다. worker 프로세스에 개수는 설정 파일에서 정의되며 정의된 프로세스 개수와 사용가능한 cpu 코어 숫자에 맞게 자동으로 조정된다.


Nginx 동작 -S

nginx를 동작시키기 위해서는 다음 옵션을 붙여서 진행한다.

1
nginx -s signal
cs


signal의 종류는 다음과 같다.

 종류

설명 

 stop

 빠른 중지

 quip

 graceful stop

##graceful stop은 요청이 중단되었을 때 로드밸런스에게 해당 노드에 요청을 추가적으로 못하게 하고 현재 노드에 작업을 중지시키고 정상적으로 끝내는 것을 말한다.

 reload

 설정 파일 재 로드

 reopen

 로그 파일  재 오픈


quip를 진행하면 현재 요청을 워커 프로세스에게 끝내라고 보낸다. 그럼 해당 워커 프로세스에 새로운 요청을 보내지 않고 워커 프로세스가 진행중이던 작업이 마무리되면 정상적으로 애플리케이션이 꺼진다. 이를 graceful stop이라고 한다.

reload를 진행하면 syntax 체크를 진행하고 성공하게 되면 older worker 프로세스를 중지 시키고 새로운 worker 프로세스를 만들어서 동작 시킨다. 만약 설정이 이상하면 마스터 프로세스는 작업을 롤백하고 예전 설정으로 동작한다. 중지 요청을 받은 workder 프로세스들은 새로운 요청을 받는 것을 멈추고 자신이 하고 있는 동작을 마무리 한다. 이 동작이 완료되면 그때 older worker 프로세스는 죽는다.

Nginx 설정파일

nginx 정책과 그 모듈들에 동작은 /usr/local/nginx/conf, /etc/nginx, /usr/local/etc/nginx 위치중에 존재하는 nginx.conf 파일 내에서 설정한대로 동작한다. (brew로 설치하는 경우 /usr/local/etc/nginx에 위치)

설정 파일 구조.

설정 파일에 입력하는 값들은 두 가지 형태로 입력이 가능하다. simple 디렉티브와 block 디렉티브 두 가지이다. simple은 이름과 파라미터로 구성되어있고 마지막에 ;이 붙는다. block 디렉티브은 simple과 구조가 같지만 끝에 ; 대신 ({ })이 붙는다. block 명령은 내부에 다른 명령들을 포함할 수있다.

events, http 명령은 main context에 위치하고 server, location은 http에 위치한다.


static content
웹 서버에서 중요한 역할중 하나는 image, html pages와 같은 정적 페이지를 전달하는 것이다. 이를 위해서 http 블록 내부에 있는 server와 location 블록을 수정해보자.

일반적으로 설정 파일은 server_name과 listen port로 구분되는 여러 server 블록을 가지고 있다.

1
2
3
4
http {
  server {
  }
}
cs

기본형태는 이렇다.


그럼 이걸 이용해서 static content에 접근할 수 있게 해보자.

아래와 같이 설정하면 /dd로 오는 요청은 무조건 저쪽 static 페이지를 통하게 된다. 

1
2
3
4
location /dd {
    root /Users/we/Documents
    index index.html
  }
cs

만약 호출 uri가 중첩되는 경우가 아래와 같이 발생하면 가장 긴 uri의 매칭을 따르게 된다.

1
2
3
4
5
6
7
8
9
server {
    location / {
        root /data/www;
    }
 
    location /images/ {
        root /data;
    }
}
cs


만약 설정이 정상적으로 동작하지 않을 때는 로그를 확인해봐야 한다. 로그에는 access.log, error.log가 존재한다. 맥 기준으로 log 폴더는 /usr/local/var/log/nginx에 존재한다.


proxy 설정

정적 페이지 연결과 더불어서 proxy로서 역할을 진행할 수 있다. 여러 서버들 사이에서 요청을 분배하는 것을 reverse proxy라고 한다.

proxy설정은 정적 페이지와 동일하게 http 블록 내에 server 블록으로 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
server {
  listen 80;
  
  location / {
    proxy_pass http://127.0.0.1:8081;
  }
 
  location /dd {
    root /Users/we/Documents/static;
    index index.html;
  }
}
cs

만약 listen을 기재하지 않으면 80으로 기본으로 매핑된다. 만약 아래와 같이 server 바로 밑에 root를 적게되면 server에 기재한 root에서 정적페이지 index.html을 찾고 각 location에 root를 입력해도 무시된다.

1
2
3
4
5
6
7
server {
    listen 8080;
    root /data/up1;
 
    location / {
    }
}
cs


또한 경로 매핑을 정규식으로도 진행할 수 있다. 아래와 같이 설정하면 .gif, .jpg, .png 형태로 들어오는 경우에 매핑 된다.

1
2
3
location ~ \.(gif|jpg|png)$ {
    root /data/images;
}
cs


설정 파일 폴더 구조

  • sites-available : 가상 서버 환경들에 대한 설정 파일들이 위치하는 부분. 가상 서버를 사용하거나 사용하지 않던간에 그에 대한 설정 파일들이 위치하는 곳
  • sites-enabled : sites-available에 있는 가상 서버 파일들중에서 실행시키고 싶은 파일을 symlink로 연결한 폴더. 실제로 이 폴더에 위치한 가상서버 환경 파일들을 읽어서 서버를 세팅한다.
  • nginx.conf : Nginx에 관한 설정파일로 Nginx 설정에 관한 블록들이 작성되어 있으며 이 파일에서 sites-enabled 폴더에 있는 파일들을 가져온다. (include 명령어를 사용).


gzip

gzip은 전달해오는 요청을 압축해서 진행하기 위해서 사용하는 옵션이다. types를 통해서 수용하는 타입도 정의할 수 있다.


apache와 많은 부분이 다르다. 정확하게 어떤 웹 서버가 좋다고 할 수는 없지만 많은 트래픽을 받는 경우에는 nginx를 사용하면 좋을 것 같다.

'IT 지식 > nginx' 카테고리의 다른 글

nginx 정리와 설치 및 기본 설정방법  (0) 2019.02.15

댓글()

Elasticsearch 기본 정리

Definition

- 엘라스틱서치는 색인 기능이 추가된 NoSQL DBMS이다.

- Full Text Search(전문검색) 문서의 점수화를 이용한 정렬데이터증가량에 구애받지 않는 실시간 검색 등을 제공

여러개의 노드로 구성된 분산시스템이다 노드는 데이터를 색인하고 검색기능을 수행하는 단위 프로세스이다. 각 노드는 복사본과 원본을 다른 위치에 저장하고 있어서 안전하다.

- 검색  서로 다른 인덱스의 데이터를 바로 하나의 질의로 묶어서 여러 검색 결과를 하나의 출력으로 도출할  있는 멀티 테넌시를 제공한다.

- 모든 데이터는 JSON 구조로 저장된다.

- RestFul API 지원하므로 URI 사용한 동작이 가능. (이런 Restful api 활용한 쿼리를 dsl 쿼리라고 한다.)



용어

Indexing : 검색할 데이터를 검색할 있는 구조로 변경하기 위해 원본 문서를 변환하여 저장하는 일련의 과정

Index : indexing 과정을 거친 결과물로 색인된 데이터가 저장되는 데이터 공간

searching : 색인에 들어있는 토큰을 기준으로 해당하는 토큰이 포함되는 문서를 찾는 과정

query : 사용자가 원하는 결과를 출력하기 위해 검색  입력하는 검색조건.

field : 엘라스틱서치 문서는 JSON으로 되어있고  프로퍼티를 field라고 부른다.

mapping : 인덱스/타입/문서의 규칙을 정의하는 .

type하나의 색인에서 하나 이상의 유형을 정의할  있다. (색인을 논리적으로 분류/구분하는 사용자가 정의하는 .)

document : 색인화   있는 기본 정보 단위. JSON 형식으로 존재.

shard : 조각으로 분할하는 기능을 제공 (콘텐츠 볼륨의 수평 분할/확장이 가능), 작업을 병렬화 함으로써 성능/처리량을 늘릴  있다.

replica : 고가용성을 높이기 위해서 복제 (기본적으로 elasticsearch  색인은 기본 shard 5섯개replica 1개를 보유) 

댓글()

node.js express 모듈 - router

web/node.js|2018. 10. 5. 00:03

Express 모듈은 node.js에서 핵심 모듈인 http와 connect 컴포넌트를 기반으로 하는 웹 프레임워크이다.

여기서 사용되는 router 기능에 대해 정리해보자.

기본적으로 express 모듈을 사용하기 위해서는 다음과 같이 모듈을 로드해야한다. router 의 기본 형태는 다음과 같다. 

1
2
3
4
5
6
const express = require('express');
const router = express();
 
// 기본 동작 형태
router.get('/' , (req, res, next) => {    
});
cs


기본 router 등록은 위와 같다. http 요청 메서드에 따라 메서드의 이름은 post, put, delete등으로 변경해서 사용하면 되고 첫번째 파라미터는 경로 두번째 파라미터는 callback 함수를 기재한다. 이 callback 함수들에 대해서는 밑에 자세히 적어보자.


요청 경로 형태

요청 경로는 일반적으로 정확하게 적을수도 있고 정규식을 사용하여 적을 수도 있다.
/ab?cd => /abcd, /acd (존재 또는 존재 하지 않음)
/ab+cd => /abcd, /abbcd (하나 또는 그이상)
/ab*cd => /abcd, abxcd, abkdjfalksdfjcd (중간에 아무값이나)
/ab(cd)?e => /abe, /abcde
/.*fly$/ => /butterfly, /dragonfly  fly 시작하는 어떤 문자


콜백함수

콜백함수는 한 개 이상 적을 수 있으며 기재한 순서대로 실행된다. 아래 예를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 콜백
var cb0 = function (req, res, next) {
  console.log('CB0');
  next();
}
 
var cb1 = function (req, res, next) {
  console.log('CB1');
  next();
}
 
app.get('/example/d', [cb0, cb1], function (req, res, next) {
  console.log('the response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from D!');
});
cs


위에 정의한 경로 /example/d로 요청이 들어온 경우 이런 순서로 출력된다.
CB0 => CB1 => the response will be sent by the next function => Hello From D!

이런 전달되는 callback 함수 형태를 사용하여 요청된 경로전에 검증 작업을 진행할 수 있다.
예를 들어 위에 함수에서 두 번재 함수인 cb1에서 Error을 던지면 그 다음 전파가 가지 않는다.

1
2
3
4
var cb1 = function (req, res, next) {
  console.log('CB1');
  next(new Error('test'));
}
cs


Response 응답 종류
res 응답을 전달하고 요청-응답 주기를 종료할  있다만약 어떠한 응답도 하지 않을경우에는 클라이언트 요청이 계속 대기중에 빠지기 때문에 무조건 res.end()라도 해줘야 한다.

메소드
설명
파일이 다운로드되도록 프롬프트합니다.
응답 프로세스를 종료합니다.
JSON 응답을 전송합니다.
JSONP 지원을 통해 JSON 응답을 전송합니다.
요청의 경로를 재지정합니다.
보기 템플리트를 렌더링합니다.
다양한 유형의 응답을 전송합니다.
파일을 옥텟 스트림의 형태로 전송합니다.
응답 상태 코드를 설정한  해당 코드를 문자열로 표현한 내용을 응답 본문으로서 전송합니다.


요청 모듈화

route를 하나의 파일에서 다 정의하는건 힘든일이다. 그래서 요청을 분할 할 수 있다. 각 router를 정의할 js 파일에서 express 모듈의 router를 추출한 후 경로를 지정한다. 그리고 app.js에서 각 모듈의 시작점을 지정해서 연결시켜준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#user.js
const express = require('express');
const router = express.router();
 
router.get(‘/’, () => {
    console.log(‘test’);
});
 
#product.js
const express = require(‘express’);
const router = express.router();
 
router.get(‘/‘, () => {
    console.log(‘product’);
});
 
#app.js
const express = require('express');
const app = express();
 
app.use(‘/user’, require(‘./user.js));
app.use(‘/product’, require(‘./product.js’);
cs


그러면 /user/으로 실행하는 경우 test가 /product/로 호출하는 경우 product가 출력된다.


댓글()

Mysql Exists와 IN절 설명과 차이점

데이터베이스/mysql|2018. 10. 3. 23:34

두 개 모두 where절에 조건을 보고 결과를 걸러낼때 사용하는데 정리가 잘 안되서 정리해봤다.

Exists 
서브쿼리가 반화나는 결과값이 있는지를 조사한다.
단지 반환된 행이 있는지 없는지만 보고 값이 있으면 참 없으면 거짓을 반환한다.

1
SELECT * FROM sample1;
cs

1
SELECT * FROM sample2;
cs


두 개의 테이블중 조건에 맞는 Row만 추출된다.

1
SELECT * FROM sample1 s1 WHERE EXISTS(SELECT * FROM sample2 s2 WHERE s1.no = s2.no);
cs

그럼 반대로 조건에 맞지 않는 ROW만 추출하고 싶으면 어떻게 해야할까?

1
SELECT * FROM sample1 s1 WHERE NOT EXISTS(SELECT * FROM sample2 s2 WHERE s1.no = s2.no);
cs


IN 
집합 내부에 값이 존재하는지 여부 확인한다. 
실제로 존재하는 데이터의 값을 비교하기 때문에 Exists보다 속도가 느린경우가 있다.

두가지 경우로 사용이 가능하다. 

1.집합군

1
SELECT * FROM sample1 s1 WHERE NO IN (45);
cs


2.서브쿼리

1
SELECT * FROM sample1 s1 WHERE NO IN (SELECT NO FROM sample2 s2);
cs

반대로 포함되지 않은 경우를 추출하고 싶은경우에는 NOT IN 사용

1
SELECT * FROM sample1 s1 WHERE NO NOT IN (SELECT NO FROM sample2 s2);
cs


※ 주의
하지만 NOT IN에 경우에 조건에 맞는 데이터가 있어도 중간에 NULL이 존재하게되면 no rows selected가 나오게 되니 NVL 처리로 NULL 처리를 해야한다.

NULL이 포함된 sample3 테이블

쿼리 조회에 사용될 sample1 테이블


NOT IN절

1
SELECT * FROM sample1 s1 WHERE a IN (SELECT val FROM sample3 s3);
cs

sample1테이블에 null 값이 포함되어 있기 때문에 검색시 no rows selected 결과가 나온다.


NVL 처리 후 확인

1
SELECT * FROM sample1 s1 WHERE a Not IN (SELECT IFNULL (val, 'd'FROM sample3 s3);
cs




댓글()

자바 Annotation 만들기

JAVA/JAVA 관련|2018. 5. 28. 22:28

자바에서 Annotation은 별도의 properties파일이나 xml같은 설정파일에 작성하는 부가적인 정보를 어노테이션으로 간편하게 설정할 수 있다.


형태


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target(ElementType.TYPE)
 
@Retention(RetentionPolicy.RUNTIME)
 
@Documented
 
public @interface Anno {
 
 public String defaultVal()  default “OK”;
 
 public String val();
 
}
 
 
cs



Target은 어노테이션의 적용 대상을 선정하고, Retension은 이 어노테이션의 정보가 어디 까지 유지되는지 설정한다.


@Target - Constructor, Field, Enum, Local Variable, Method, Package, Parameter, Type(Class)
@Retention - Source, Class, Runtime
@Documented - 어노테이션을 javadoc에 포함한다.
@Inherited - 어노테이션 상속을 가능케 한다.  

구현된 어노테이션 사용은 다음과 같이 사용한다.


 


1
2
3
4
5
6
7
8
9
10
@Anno(val = "Annotation!!"
public class Member { 
.....
}  
 
정의한 어노테이션에 대한 접근은 다음과 같다.
 
Member member = new Member(); 
System.out.println(member.getClass().getAnnotation(Anno.class).val()); 
System.out.println(member.getClass().getAnnotation(Anno.class).defaultVal());  
cs



참고 사이트 : http://blog.naver.com/cracker542/40159657935

댓글()