GitLab Backup

  • GitLab에 대한 모든 설정은 /etc/gitlab/gitlab.rb에서 관리할 수 있습니다.

  • 보편적인 방법으로 Omnibus package로 설치한 경우 백업은 다음과 같습니다.

    $ sudo gitlab-rake gitlab:backup:createDumping database tables:
    - Dumping table events... [DONE]
    - Dumping table issues... [DONE]
    - Dumping table keys... [DONE]
    - Dumping table merge_requests... [DONE]
    - Dumping table milestones... [DONE]
    - Dumping table namespaces... [DONE]
    - Dumping table notes... [DONE]
    - Dumping table projects... [DONE]
    - Dumping table protected_branches... [DONE]
    - Dumping table schema_migrations... [DONE]
    - Dumping table services... [DONE]
    - Dumping table snippets... [DONE]
    - Dumping table taggings... [DONE]
    - Dumping table tags... [DONE]
    - Dumping table users... [DONE]
    - Dumping table users_projects... [DONE]
    - Dumping table web_hooks... [DONE]
    - Dumping table wikis... [DONE]
    Dumping repositories:
    - Dumping repository abcd... [DONE]
    Creating backup archive: $TIMESTAMP_gitlab_backup.tar [DONE]
    Deleting tmp directories...[DONE]
    Deleting old backups... [SKIPPING]

 

 

GitLab Restore

  • 생성된 백업파일은 기본적으로 /var/opt/gitlab/backups에 저장됩니다.

    • 로컬에 저장된 백업파일은 100% 안전하다고 할 수 없기때문에 반드시 외부 환경에 소산 보관해야 합니다.

    • GitLab서버와 FreeNAS와 같은 외장 스토리지 서버와 네트워크로 연결하는 (CIFS 등) 방법도 고려해야 합니다.

      • 네트워크 환경에서 백업 환경을 구축할 때, 대규모 환경에서는 SAN으로 구성하는 것이 좋은 대안이 될 수 있으나 소규모 환경에서는 비용문제가 많이 발생할 수 있기 때문에 내부 네트워크 환경에서 시행하는 것이 좋습니다.

      • 단, 같은 네트워크 환경에서 백업이 같이 이루어질 경우 네트워크 장비의 용량에 따라 내부 네트워크의 전체 속도가 떨어질 수 있기 때문에 네트워크 사용량이 적은 시간에 진행하거나 네트워크를 분리하는 방법도 고려하는 것이 좋습니다.

GitLab Service 중단 및 백업$ sudo gitlab-ctl stop unicorn
$ sudo gitlab-ctl stop sidekiq
# Verify
$ sudo gitlab-ctl status# 백업 파일이 하나일 경우
$ sudo gitlab-rake gitlab:backup:restore

# 백업 파일이 여러개 있을 경우
$ sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2018_04_25_10.6.4-ce

  • 기본적인 백업 디렉토리에 백업 파일이 존재하면 자동으로 기존에 있던 데이터베이스가 삭제되고, 백업데이터로 변경됩니다.

    • 새롭게 설치한 GitLab 서버에도 다음과 같이 진행하면 기존에 사용하던 환경으로 마이그레이션이 완료됩니다.

  • 이때, 백업 당시에 사용했던 GitLab 버전과 사용중인 GitLab 버전이 다를 경우에는 복구가 진행되지 않기 때문에 버전과 동일한 백업 파일을 생성하여 진행할 수 있도록 합니다.

GitLab Service 시작 $ sudo gitlab-ctl restart
$ sudo gitlab-rake gitlab:check SANITIZE=true



Gitlab 을 설치해서 사용하고 있는데, 업로드 파일 사이즈가 작아서 트레이닝을 위한 데이타 업로드가 되지 않는 문제가 발생하였다.

찾아보니 비슷한 문제로 고통 받는 사람들이 많았고 Github 에서도 동일한 문제가 있어서 git-lfs, BFG Repo-Cleaner 등과 같은 사용하고 있더라....

다행이 설치해서 사용하고 있어 아래처럼 설정을 바꾸니 문제가 해결 되었다.

Gitlab 설치 후 업로드 할 수 있는 사이즈 변경, port 변경과 같은 작업을 하기 위해서는 설정 파일을 수정해야 합니다.


### 설정 파일 위치 /etc/gitlab/gitlab.rb
### 설정 변경 내용 
# 파일 사이즈 변경
nginx[‘enable’] = true
nginx[‘client_max_body_size’] = ‘2G’
# 포트 변경 
unicorn[‘listen’] = ‘127.0.0.1’
unicorn[‘port’] = 8580


은행이나 금융권 개발 쪽 말을 들어보면 트랜잭션이란 말이 많이 나온다. 또 MySQL 이용자들이 InnoDB를 사용하는 가장 큰 이유도 트랜잭션을 지원하기 때문이라고도 한다. 굉장히 좋다는 건 뭔지 알겠는데 이름만 들어서는 도통 감이 안온다. 어느 정도냐면 필자가 트랜잭션이란 말을 처음 접했을 때는 트랜지스터처럼 뭔가를 쪼끄맣게 만드는 것이나 DB에서 상거래를 이용할 수 있게 하는 기술인 줄 알았다. 게다가 트랜잭션이란 코드에서 처리하는게 아니라 MySQL쿼리문만 처리가능한 기술이라고 말도 안되는 오해를 할 때도 있었다. 물론 모두 틀렸다. 트랜잭션에 대한 개념을 간략하게 설명하자면 다음과 같다.

트랜잭션은 현대의 웹 보안에 있어서 매우 중요한 역할을 차지하며 DB와 JAVA 언어가 데이터를 주고 받는 과정에 원자성을 부여하는 수단을 일컫는다. 

트랜잭션에 대해 잘 모르는 독자들은 이 말만 들어서는 트랜잭션이 뭔지 감이 안올 수도 있을 것이다. 필자도 트랜잭션이 뭔지 알고 나서야 이 말의 감을 잡았지 결코 위의 글을 읽고 "트랜잭션이란 정말 좋은 기술이구나. 나도 해봐야겠다!"라고 생각하진 않았으니 말이다.

과거에 트랜잭션이란 소수의 웹개발자들만이 공유하는 기술로 인식되곤 했다. 굉장히 고급기술이고 손이 많이 가는 작업인지라 트랜잭션이 일반화되지 않았을 때는 트랜잭션과 비슷한 기술인 프로시져를 더 선호할 때도 있었다. 물론 아직도 트랜잭션보다 프로시져를 선호하는 사람들이 존재한다. 하지만 이런 발달된 프레임워크 덕분에 트랜잭션을 이용하는 방법이 쉬워졌고 트랜잭션의 장점 또한 더욱 부각되면서 자바진영에서만큼은 프로시져보다 트랜잭션을 선호하는 추세이다.


그렇다면 트랜잭션과 프로시져란 무엇일까? 우리가 데이터베이스와 힘겹게 씨름하며 개발을 해가다보면 어느 순간 도저히 쿼리 한줄로는 끝낼 수 없는 상황에 부닥치게 되는데 이런 단일 쿼리로 해결할 수 없는 로직을 처리할 때 필요한 기술이 바로 트랜잭션이다. 먼저 트랜잭션을 설명하는데 있어서 가장 훌륭한 예제인 전자상거래를 살펴보도록 하자.


위의 예제는 우리가 한줄로 처리할 수 없는 쿼리의 가장 좋은 예이다. 먼저 쇼핑몰에서 상품을 구매할 때 회원의 잔여금액이 있는지확인하고 잔여금액이 상품가격보다 많을 때 선택한 상품을 구매하는 로직으로 넘어간다. 그리고 선택한 상품의 재고가 있는지 확인하여 재고가 있다면 회원의 잔여금액을 상품가격만큼 감소시키고 로직을 종료한다.

로직만 본다면 완벽한 것 같기도 하다. 근데 만약의 상황을 가정해보자. 예를 들어 선택상품구매 로직에서 Exception()이 발생하여 상품이 없음에도 있다고 된 것이다. 또는 잔여금액이 감소하는 찰나에 서버의 전원이 나가 상품을 구매해버렸는데 회원의 잔여금액은 감소하지 않는 상황 말이다. 이런 문제는 곧바로 엄청난 비용손실을 발생시킬 것이다. 예를 바꾸어 금융권 경우는 어떨까? 은행같은 곳에서 특정 입력 시에 발생하는 오류를 크래커가 알아내어 송금을 하여도 자신의 계좌에서 돈이 차감되지 않는 사실을 알게 됬다면? 그 누구도 이런 전자상거래는 이용하고 싶지 않을 것이다.

트랜잭션이란 바로 이런 문제를 해결하기 위해 탄생한 기술력이다. 2개 이상의 쿼리를 하나의 커넥션으로 묶어 DB에 전송하고 이 과정에서 어떠한 에러가 발생할 경우 자동으로 모든 과정을 원래 상태로 돌려놓는다. 그야 말로 All Or Nothing 전략이다. 프로시져도 같은 맥락이긴 하지만 차이점은 로직을 Java에서 처리하는게 아니라 DB상에서 처리한다. 과거 트랜잭션 구현이 어렵고 프로시져 구현이 더 간편했을 때만 하더라도 많은 웹어플리케이션이 비지니스 로직을 Java가 아니라 DB에서 처리하는게 유행했던 시절도 있었다.

좀 황당한 사실이긴 하지만 진짜다. 혹자는 이 때는 일컬어 자바의 암흑기라고도 한다. 자바 프로그래머들이 고작 하는 거라곤 DBA(DataBase Administrator)가 짜준 로직을 호출하고 결과값을 대충 조작해 리턴해주는게 대부분이었다. 이런 개발 방식에서는개발자가 DBA님을 상전 받들 듯이 모셔야 하며 모든 로직을 구현하는데 있어 DBA님이 선사하신 프로시져를 감사한 마음으로 사용하다가 만에 하나 오류가 발생하면 이게 프로시져의 문제인지 내 코드의 문제인지 밤잠설치며 고민하다가 은근슬쩍 DBA님에게 커피라도 한잔 마치면서 넌지시 물어봐야 했던 것이다.

심지어 현장에 투입된 신입프로그래머들은 웹어플리케이션마다 제각각으로 짜진 난해한 프로시져들을 완전히 습득하기 전엔 키보드에 손도 못올릴 때가 있었다. 그 뿐이던가. 초기에 가난한 벤쳐회사가 MySQL을 이용해 서비스를 하다가 어느 정도 성장해 Oracle 같은 유료 데이터베이스로 교체할 때, 또는 하나의 웹어플리케이션에 2개 이상의 데이터베이스를 사용할 때에는 그야말로 개발자와 DBA가 서로 멱살을 움켜지며 매일같이 댄싱파티를 열어대는 장관이 연출될 때도 있었다.

왜냐하면 자바는 mysql 커넥터에서 oracle 커넥터로 라이브러리만 교체해주면 그만이지만 DBA들은 기존의 DB에 있는 프로시져들을 몽땅 새로운 DB에 옮겨 주어야 했기 때문이다. 게다가 옮기는 과정에서 생기는 오타나 데이터베이스 제품마다 다른 성질로 발생하는 문제까지 테스트 하자면 개발자들은 연이어 발생하는 트러블 때문에 모든 다시 코드를 뜯어봐야 했고 수정해주어야만 했다.

그러나 이제 트랜잭션 기술의 발달로 이런 문제는 옛날 일이 되었다. 물론 프로시져가 트랜잭션보다 속도가 빠르다는 이유로 아직도 프로시져를 사용하는 그룹이 있긴 하다. 하지만 서버의 퍼포먼스 향상과 기술이 발달로 그런 속도차는 극단적인 상황이 아닌 이상 미미한게 사실이다.

다시 본론으로 돌아가자면 트랜잭션은 하나 이상의 쿼리에서 동일한 Connection 객체를 공유하는 것을 뜻한다. 이게 뭔말인가 하면 Java에서 DB로 연결할 때는 java.sql.Connection이란 객체를 이용하는데 이 Connection 객체에는 setAutoCommit(자동커밋)이라는 메서드가 존재한다. 이 메서드와 연결된 autoCommit 객체는 true란 기본값을 가지고 있으며 한번의 연결 이후 자동으로 커넥션을 커밋해 종료시켜준다. 근데 트랜잭션을 이용하려면 이 자동커밋을 false로 바꾸고 수동커밋으로 변경해야만 한다. 즉 이용자가 직접 커넥션에 대해 커밋과 롤백을 해준다는 뜻이다.

Commit(커밋) : 해당 Connection의 요청을 완료하고 특별한 에러가 없다면 결과를 DB에 반영한다. 
RollBack(롤백) : 해당 Connection 수행 중 예기치 않은 에러가 발생하였다면 모든 과정을 취소하고 DB를 Connection이 수행되기 이전상태로 변경한다.

물론 스프링의 트랜잭션 기술을 이용하면 이런 과정을 직접 해줄 필요는 없다. 스프링은 단 몇줄의 코드만으로 다이나믹 프록시와 AOP라는 기술을 통해 크게는 인터페이스 단위에서 클래스, 작게는 메서드까지 세분화하여 트랜잭션을 컨트롤할 수 있게 한다. 거기다 애노테이션 기술로 @Transactional을 설정하는 것만으로도 메서드 또는 클래스, 인터페이스까지 트랜잭션 설정이 가능하게 해준다.

우리는 여러 오픈소스 개발자들의 노력 덕분에 트랜잭션을 편리하게 이용할 수 있게 됬으며 금융권, 상거래에도 문제없이 비지니스 로직의 주도권을 찾아올 수 있게 되었다. 다음 장에서는 본격적으로 스프링의 트랜잭션에 대해 다뤄보도록 하겠다. 마지막으로 덧붙이자면 우리가 이처럼 트랜잭션 쉽게 구현할 수 있고 또 이용할 수 있다면 이젠 트랜잭션도 선택이 아닌 필수 사항이 되어야만 한다. 감당할 수 있는 에러라도 발생하지 않도록 하는 것이 가장 현명한 처사 아니겠는가!


IOCP
Input/Ouptput Completion Port
: Proactor 방식의 고성능 I/O Notification Model로 asynchronous I/O 지원
: Windows OS가 직접 효율적인 스레드 풀링 제공으로 context switching을 줄임
: Overlapped I/O를 확장 시킨 개념으로 커널영역과 유저영역의 버퍼 공유 (memory page-locking)

 

- 기본 동작 구조 (출처: http://www.slideshare.net/sm9kr/windows-registered-io-rio)
 : I/O initiation -> I/O processing -> I/O completion







- 1_IOCP

: worker thread 도입




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#pragma comment(linker, "/subsystem:console")
 
#define WIN32_LEAN_AND_MEAN 
#include <stdio.h>
#include <WinSock2.h>
#include <Windows.h> 
#pragma comment(lib, "ws2_32.lib")
 
#pragma comment(linker, "/subsystem:console")
 
// Overlapped 작업의 종료를 대기할 스레드
DWORD __stdcall EchoThread( void* p )
{
    // 여기서 비동기작업의 종료를 대기하면 됩니다.
    // 즉 WSAWait....()
}
 
int main()
{
    WSADATA w;
    int ret = WSAStartup( MAKEWORD(2,2), &w);   
     
    //-------------------------------------------
    // 1. Overlapped io를 위한 소켓 생성
    // 표준 C 네트워크 함수 : 소문자()
    // WSAxxxx() 함수들 : windows socket 2.0 부터 지원되는
    //                  windows만의 개념들..
    int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
                                000,
                                WSA_FLAG_OVERLAPPED);
                                 
 
 
 
    // 2. 소켓에 주소 지정(bind)
    SOCKADDR_IN addr;
 
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(4000);
    addr.sin_addr.s_addr = INADDR_ANY;
     
    bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
 
 
    listen(listen_sock,  5);
 
    while(1)
    {
        struct sockaddr_in addr2;
        int sz = sizeof addr2;
 
 
        int link_sock = accept( listen_sock,
                              (struct sockaddr*)&addr2, &sz);
 
        printf("클라이언트가 접속되었습니다\n");
 
        // Overlapped로 수신 하는 코드
        WSAEVENT ev = WSACreateEvent();
        WSAOVERLAPPED ov = {0};
        ov.hEvent = ev;
 
        // 수신 버퍼..
        char s[1024= {0};
        WSABUF buf;
        buf.buf = s;
        buf.len = 1024;
        DWORD flag = 0;
        DWORD recvBytes = 0;
 
        int n = WSARecv( link_sock,
                         &buf, 1,  // 버퍼와 버퍼 갯수
                         &recvBytes, // 받을 data 크기
                         &flag,
                         &ov, // overlapped 구조체
                         0);
        // 새로운 스레드를 생성해서 비동기 작업의 완료를 대기한다.
        CreateThread( 00, EchoThread, (void*)ev, 00);
    }
 
 
    closesocket( link_sock);
    //------------------------------
    WSACleanup();
}
 
cs

- 2_IOCP
 : worker thread 구현
 : IOCP 생성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#pragma comment(linker, "/subsystem:console")
 
#define WIN32_LEAN_AND_MEAN 
#include <stdio.h>
#include <WinSock2.h>
#include <Windows.h> 
#pragma comment(lib, "ws2_32.lib")
 
DWORD __stdcall EchoThread( void* p )
{
    HANDLE hPort = (HANDLE)p;
    WSAOVERLAPPED* pov;
    DWORD bytes, key;
 
    while1 )
    {
        // IOCP에 있는 완료큐에 작업이 들어올때를 대기한다.
        GetQueuedCompletionStatus( hPort, &bytes, &key,
                &pov, INFINITE);
 
        printf("비동기 작업 완료 : %d bytes,  key : %d\n",
                    bytes, key);
    }
    return 0;
}
 
int main()
{
    WSADATA w;
    int ret = WSAStartup( MAKEWORD(2,2), &w);   
     
 
    //--------------------
    // 1. 입출력 완료 포트(IOCP)를 생성합니다.
    HANDLE hPort = CreateIoCompletionPort(
                        (HANDLE)-1// IOCP에등록할 파일(소켓)
                        0// 이미 존재 하는 IOCP핸들
                        0// 완료키
                        2);// IOCP에서 비동기작업을 대기할 스레드
                        // 갯수 (CPU의 갯수만큼이 가장좋다.)
 
    // 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    HANDLE h1 = CreateThread( 00, EchoThread, (void*)hPort,
                            00);
    HANDLE h2 = CreateThread( 00, EchoThread, (void*)hPort, 00);
 
 
    //-------------------------------------------
    int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
                                000,
                                WSA_FLAG_OVERLAPPED);
    SOCKADDR_IN addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(4000);
    addr.sin_addr.s_addr = INADDR_ANY;
     
    bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    listen(listen_sock,  5);
 
    int cnt = 0;
    while(1)
    {
        struct sockaddr_in addr2;
        int sz = sizeof addr2;
        int link_sock = accept( listen_sock,
                              (struct sockaddr*)&addr2, &sz);
 
        // Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
        // IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두
        // 아래 함수를 사용합니다.
        CreateIoCompletionPort( (HANDLE)link_sock,
                                hPort,
                                cnt++,
                                2);
 
 
 
        printf("클라이언트가 접속되었습니다\n");
 
        // Overlapped로 수신 하는 코드
        WSAEVENT ev = WSACreateEvent();
        WSAOVERLAPPED ov = {0};
        ov.hEvent = ev;
 
        // 수신 버퍼..
        char s[1024= {0};
        WSABUF buf;
        buf.buf = s;
        buf.len = 1024;
        DWORD flag = 0;
        DWORD recvBytes = 0;
 
        int n = WSARecv( link_sock,
                         &buf, 1,  // 버퍼와 버퍼 갯수
                         &recvBytes, // 받을 data 크기
                         &flag,
                         &ov, // overlapped 구조체
                         0);
    }
    //------------------------------
    WSACleanup();
}
 
 

cs


- 3_IOCP
 : overlapped 구조체 확장 -> 입출력당 1개의 자료 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#pragma comment(linker, "/subsystem:console")
 
#define WIN32_LEAN_AND_MEAN 
#include <stdio.h>
#include <WinSock2.h>
#include <Windows.h> 
#include <stdlib.h> 
#pragma comment(lib, "ws2_32.lib")
 
#define READ_MODE  1
#define WRITE_MODE 2
 
// OVERLAPPED 구조체는 보통 확장해서 사용하게 됩니다.
// 입출력당 1개의 자료 구조
struct IO_DATA
{
    WSAOVERLAPPED ov;
    WSABUF        wsaBuf;
    char          buff[1024];
    int           mode;
};
 
DWORD __stdcall EchoThread( void* p )
{
    HANDLE hPort = (HANDLE)p;
    WSAOVERLAPPED* pov;
    DWORD bytes, key;
 
    while1 )
    {
        // IOCP에 있는 완료큐에 작업이 들어올때를 대기한다.
        GetQueuedCompletionStatus( hPort, &bytes, &key,
                &pov, INFINITE);
 
        printf("비동기 작업 완료 : %d bytes,  key : %d\n",
                    bytes, key);
 
        IO_DATA* ioData = (IO_DATA*)pov;
 
        printf("수신된 data : %s\n", ioData->buff);
 
        CloseHandle( ioData->ov.hEvent);
        free(ioData);
    }
    return 0;
}
 
int main()
{
    WSADATA w;
    int ret = WSAStartup( MAKEWORD(2,2), &w);   
     
 
    //--------------------
    // 1. 입출력 완료 포트(IOCP)를 생성합니다.
    HANDLE hPort = CreateIoCompletionPort(
                        (HANDLE)-1// IOCP에등록할 파일(소켓)
                        0// 이미 존재 하는 IOCP핸들
                        0// 완료키
                        2);// IOCP에서 비동기작업을 대기할 스레드
                        // 갯수 (CPU의 갯수만큼이 가장좋다.)
 
    // 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    HANDLE h1 = CreateThread( 00, EchoThread, (void*)hPort,
                            00);
    HANDLE h2 = CreateThread( 00, EchoThread, (void*)hPort, 00);
 
 
    //-------------------------------------------
    int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
                                000,
                                WSA_FLAG_OVERLAPPED);
    SOCKADDR_IN addr;
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(4000);
    addr.sin_addr.s_addr = INADDR_ANY;
     
    bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    listen(listen_sock,  5);
 
    int cnt = 0;
    while(1)
    {
        struct sockaddr_in addr2;
        int sz = sizeof addr2;
        int link_sock = accept( listen_sock,
                              (struct sockaddr*)&addr2, &sz);
 
        // Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
        // IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두
        // 아래 함수를 사용합니다.
        CreateIoCompletionPort( (HANDLE)link_sock,
                                hPort,
                                cnt++,
                                2);
 
 
 
        printf("클라이언트가 접속되었습니다\n");
 
        // IO작업당 아래 구조체를 만들어서 사용한다.
        IO_DATA* ioData = (IO_DATA*)malloc(sizeof(IO_DATA));
 
        memset(ioData, 0sizeof(IO_DATA));
 
 
        ioData->wsaBuf.buf = ioData->buff;
        ioData->wsaBuf.len = 1024;
        ioData->mode = READ_MODE;
        ioData->ov.hEvent = WSACreateEvent();
 
 
        DWORD flag = 0;
        DWORD recvBytes = 0;
 
        int n = WSARecv( link_sock,
                         &(ioData->wsaBuf), 1,  // 버퍼와 버퍼 갯수
                         &recvBytes, // 받을 data 크기
                         &flag,
                         (WSAOVERLAPPED*)ioData, // overlapped 구조체
                         0);
    }
    //------------------------------
    WSACleanup();
}
 
 

cs



- 4_IOCP
 : 소켓 구조체 구현 
 : write 구현 -> WSASend 도 완료되면 GetQueuedCompletionStatus로 통보된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#pragma comment(linker, "/subsystem:console")
 
#define WIN32_LEAN_AND_MEAN  
#include <stdio.h>
#include <WinSock2.h> 
#include <Windows.h>
#include <stdlib.h>  
#pragma comment(lib, "ws2_32.lib"
 
#define READ_MODE  1
#define WRITE_MODE 2
 
// 클라이언트와 연결된 소켓정보를 관리하는 구조체
struct SOCKET_DATA
{
    int sock;
    SOCKADDR_IN addr;
};
 
// OVERLAPPED 구조체는 보통 확장해서 사용하게 됩니다.
// 입출력당 1개의 자료 구조
struct IO_DATA
{
    WSAOVERLAPPED ov;
    WSABUF        wsaBuf;
    char          buff[1024];
    int           mode;
};
 
DWORD __stdcall EchoThread( void* p )
{
    HANDLE hPort = (HANDLE)p;
    WSAOVERLAPPED* pov; 
    DWORD bytes, key;
 
    while1 )
    {
        GetQueuedCompletionStatus( hPort, &bytes, &key,
                &pov, INFINITE);
 
        printf("비동기 작업 완료 : %d bytes,  key : %d\n",
                    bytes, key);
 
        SOCKET_DATA* pSock = (SOCKET_DATA*)key;
 
        IO_DATA* ioData = (IO_DATA*)pov;
 
        if ( ioData->mode == READ_MODE )
        {
            printf("수신된 data : %s\n", ioData->buff);
 
            // 수신에 사용한 버퍼를 사용해서 송신한다.
            // 수신중에 overlapped 구조체의 내용은 변경되어 있게됩니다
            memset( &(ioData->ov), 0sizeof(WSAOVERLAPPED));
 
            strcat( ioData->buff, " from server");
            ioData->mode = WRITE_MODE;
            //-----------------------------------
            WSASend( pSock->sock, 
                 &(ioData->wsaBuf), 1,
                 &bytes, 0&(ioData->ov), 0);
            //-----------------------------
        }
        else
        {
            closesocket(pSock->sock);
            free( pSock );
            CloseHandle( ioData->ov.hEvent);
            free(ioData);
        }
    }
    return 0;
}
 
int main()
{
    WSADATA w;
    int ret = WSAStartup( MAKEWORD(2,2), &w);    
     
 
    //--------------------
    // 1. 입출력 완료 포트(IOCP)를 생성합니다.
    HANDLE hPort = CreateIoCompletionPort( 
                        (HANDLE)-1// IOCP에등록할 파일(소켓)
                        0// 이미 존재 하는 IOCP핸들
                        0// 완료키
                        2);// IOCP에서 비동기작업을 대기할 스레드
                        // 갯수 (CPU의 갯수만큼이 가장좋다.)
 
    // 2. 비동기 작업이 완료 될때를 처리할 스레드 생성
    HANDLE h1 = CreateThread( 00, EchoThread, (void*)hPort,
                            00);
    HANDLE h2 = CreateThread( 00, EchoThread, (void*)hPort, 00);
 
 
    //-------------------------------------------
    int listen_sock = WSASocket( PF_INET, SOCK_STREAM,
                                000,
                                WSA_FLAG_OVERLAPPED);
    SOCKADDR_IN addr; 
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(4000);
    addr.sin_addr.s_addr = INADDR_ANY; 
     
    bind( listen_sock, (struct sockaddr*)&addr, sizeof addr);
    listen(listen_sock,  5); 
 
    int cnt = 0;
    while(1)
    {
        struct sockaddr_in addr2;
        int sz = sizeof addr2;
 
        int link_sock = accept( listen_sock, 
                              (struct sockaddr*)&addr2, &sz);
 
 
        // 소켓당 하나의 구조체
        SOCKET_DATA* pSock = (SOCKET_DATA*)malloc(
                                    sizeof(SOCKET_DATA));
        pSock->sock = link_sock;
        pSock->addr = addr2;
 
 
        // Client와 연결된 소켓의 핸들을 IOCP에 등록한다.
        // IOCP를 만들때와 IOCP에 장치(파일)을 등록할때 모두 
        // 아래 함수를 사용합니다.
        CreateIoCompletionPort( (HANDLE)link_sock,
                                hPort, 
                                (ULONG_PTR)pSock,
                                2);
 
 
 
        printf("클라이언트가 접속되었습니다\n");
 
        // IO작업당 아래 구조체를 만들어서 사용한다.
        IO_DATA* ioData = (IO_DATA*)malloc(sizeof(IO_DATA));
 
        memset(ioData, 0sizeof(IO_DATA));
 
 
        ioData->wsaBuf.buf = ioData->buff;
        ioData->wsaBuf.len = 1024;
        ioData->mode = READ_MODE;
        ioData->ov.hEvent = WSACreateEvent();
 
 
        DWORD flag = 0;
        DWORD recvBytes = 0;
 
        int n = WSARecv( link_sock,
                         &(ioData->wsaBuf), 1,  // 버퍼와 버퍼 갯수
                         &recvBytes, // 받을 data 크기
                         &flag,
                         (WSAOVERLAPPED*)ioData, // overlapped 구조체
                         0);
    }
    //------------------------------
    WSACleanup(); 
}
 
 
cs

- PAGE_LOCKING (참고: http://www.slideshare.net/sm9kr/windows-registered-io-rio, http://ozt88.tistory.com/26)
 
 : 완벽한 통지모델로 보이는 IOCP에도 문제는 있다.
 : 하나의 I/O operation마다 버퍼 영역에 대한 page-lock/unlock
  -> 특정 메모리에 대한 pin/unpin은 많은 CPU cycle 요구
  -> 그래서 RECV를 posting 할 때, page-locking을 피하여 CPU cycle을 줄이기 위해 zero-byte recv로 최소화
 : 하나의 I/O operation마다 시스템콜 호출
  -> 유저모드-커널모드 전환 발생


 ■ Zero Byte Recv?
  : 실제 수행을 시작하는 때를 감지할 수 있다면 이 문제를 해결할 수 있다. 
  : 비동기 작업을 명령하기 전에 먼저 0바이트를 읽는 Recv작업을 요청하는 것이다. 0바이트 읽는 작업이므로 필요한 버퍼도 0바이트, 그러므로 PAGE_LOCKING이 발생하지 않는다. 비동기 명령 프로세서를 사용하기 때문에 이 명령이 요청된 시점에는 작업이 바로 가능한지 불확실하지만, 이 명령이 완료된 시점에는 다음 작업을 바로 시작할 수 있을 가능성이 매우 높다. 그러니까 이때부터 본격적인(큰 용량의 버퍼를 사용하는) 작업을 요청하는 것이다. 이렇게 하면 최대한 쓸데없이 LOCKING된 메모리를 줄일 수 있다.

 ■ SO_RCVBUF 옵션
  이 옵션은 소켓 버퍼의 크기를 설정하는 옵션이다. 이 옵션을 사용하여 버퍼 크기를 0으로 설정하는 경우, 커널이 따로 소켓버퍼(send, recv 버퍼)를 사용하지않고 직접 유저가 설정한 버퍼(메모리)에 직접 I/O를 때려박는다. 필자는 Memory-mapped file같은 느낌이라고 이해하고 있다. 0으로 설정하면 커널 버퍼를 거치지 않고 직접 유저의 버퍼에 데이터가 저장되기 때문에, 불필요해 보이는 복사가 수행되지 않아서 성능상에 이점이 있다. 위에서 살짝 언급한것처럼 가상메모리의 영역이 하드웨어 메모리 영역과 깊은 커플링을 맺게되면서, PAGE_LOCKING의 원인이 된다. 그렇다면 유저는 복사를 하지 않는 성능 이점과 PAGE_LOCKING의 이슈를 저울질 하여 옵션을 선택해야 할 것이다.



출처: http://dev-ahn.tistory.com/114 [Developer Ahn]



출처 http://moss.tistory.com/entry/Redis-%EC%84%9C%EB%B2%84-%EC%84%A4%EC%A0%95-%EC%A0%95%EB%A6%AC


들어가며

Redis 서버 설정을 위해서 작성하는 redis.conf 파일에 대해서 정리한다.

오역 및 잘못된 내용이 있을 수 있습니다. 참고 용로도만 사용해 주세요.

대상 파일: https://raw.github.com/antirez/redis/2.4.15/redis.conf


요약

기본설정


daemonize (daemon으로 실행 여부 설정)

pidfile (daemon 실행시 pid가 저장될 파일 경로)

port (접근을 허용할 port 설정)

bind (요청을 대기할 interface[랜카드] 설정)

unixsocket, unixsocketperm (요청을 대기할 unix 소켓 설정)

timeout (client와 connection을 끓을 idle 시간 설정)

loglevel (loglevel 설정)

logfile (log 파일 경로 설정)

syslog-enabled (system logger 사용 여부 설정)

syslog-ident (syslog에서의 identity 설정)

syslog-facility (syslog facility 설정)

databases (database 수 설정)

REPLICATION


slaveof (master server 설정)

masterauth (master server 접근 비밀번호 설정)

slave-server-stale-data (master와 connection이 끊긴 경우 행동 설정)

repl-ping-slave-perid (미리 정의된 서버로 PING을 날릴 주기 설정)

repl-timeout (reply timeout 설정)

SECURITY


requirepass (server 접근 비밀번호 설정)

rename-command (command 이름 변경)

LIMITS


maxclients (최대 client 허용 수 설정)

maxmemory (최대 사용가능 메모리 크기 설정)

maxmemory-policy (maxmemory 도달 시 행동 설정)

maxmemory-samples (LRU 또는 mnimal TTL 알고리즘 샘플 수 설정)

APPEND ONLY MODE


appendonly (AOF 사용여부 설정)

appendfilename (AOF 파일명 설정)

appendsync (fsync() 호출 모드 설정)

no-appendfsync-on-rewrite (background saving 작업시 fsync() 호출 여부 설정)

auto-aof-rewrite-percentage, auto-aof-rewrite-min-size (AOF file rewrite 기준 설정)

SLOW LOG


slowlog-log-slower-than (slow execution 기준 설정)

slowlog-max-len (최대 저장 slow log 수 설정)

VIRTUAL MEMORY


vm-enabled (vm 모드 사용여부 설정)

ADVANCED CONFIG


hash-max-zipmap-entries, hash-max-zipmap-value (hash encode 사용 기준 설정)

list-max-ziplist-entries, list-max-ziplist-value (list encode 사용 기준 설정)

set-max-intset-entries (set encode 사용 기준 설정)

zset-max-ziplist-entries, zset-max-ziplist-value (sorted set encode 사용 기준 설정)

activerehashing (자동 rehashing 사용 여부 설정)

기본설정

daemonize [boolean] (기본값: no)


Redis는 기본적으로 daemon으로 실행하지 않는다. 만약 Daemon으로 실행하고 싶다면 'yes'를 사용해라.

Redis는 daemon으로 실행될 때 '/var/run/redis.pid' 파일에 pid를 기록할 것이다.


예) daemonize no


pidfile [file path] (기본값: /var/run/redis.pid)


daemon으로 실행 시 pid가 기록될 파일 위치를 설정한다. 값이 설정되지 않으면 '/var/run/redis.pid'에 pid를 기록한다.


예) pidfile /ver/run/redis.pid


port [number] (기본값: 6379)


Connection을 허용할 Port를 지정한다. 기본값은 6379이다.

만약 port 값을 0으로 지정하면, Redis는 어떤 TCP socket에 대해서도 listen하지 않을 것이다.


예) port 6379


bind [ip] (기본값: 모든 인터페이스)


Redis를 bind 할 특정 interface(랜카드)를 지정할 수 있다. 만약 명시하지 않으면, 모든 interface로부터 들어오는 요청들을 listen할 것이다.


예) bind 127.0.0.1


unixsocket [path], unixsocketperm [number] (기본값: 없음)


들어오는 요청을 listen할 unix socket의 결로를 지정한다. 이 설정에는 기본 값이 없다. 따라서 값이 지정되지 않으면 unix socket에 대해서는 listen하지 않을 것이다.


예) unixscoket /tmp/redis.sock

예) unixsocketperm 755


timeout [second]


client의 idle이 N 초 동안 지속되면 connection이 닫힌다. (0으로 지정하면 connection이 계속 유지된다.)


예) timeout 0


loglevel [level]


logl evel 을 지정한다. Log level 에는 아래 4가지 중 하나를 지정할 수 있다.


loglevel 설 명

debug 엄청나게 많은 정보를 기록한다. 개발과 테스테 시 유용하다.

verbose 유용하지 않은 많은 양의 정보를 기록한다. 하지만 'debug level'만큼 많지는 않다.

notice 제품을 운영하기에 적당한 양의 로그가 남는다.

warning 매우 중요하거나 심각한 내용만 남는다.

예) loglevel verbose


logfile [file path]


로그 파일을 명시한다.'stdout'로 Redis가 the standard output에 로그를 기록하도록 할 수 있다. 만약 'stdout'를 명시했으나 Redis가 daemon으로 동작한다면 로그는 /dev/null logfile stdout 로 보내질 것이다. 따라서 로그가 남지 않을 것이다.


예) logfile stdout


syslog-enabled [boolean]


system logger를 사용해서 logging 할 수 있게 한다. 단지 'yes'로 설정하고 추가적 설정을 위해서 다른 syslog parameter들을 설정할 수 있다.


예) syslog-enabled no


syslog-ident [identity]


syslog identity를 지정한다.


예) syslog-ident redis


syslog-facility [facility]


syslog facility(시설)을 지정한다. 반드시 USER 또는 LOCAL0 - LOCAL7 사이 값이 사용되어야 한다.


예) syslog-facility local0


databases [size]


dababase들의 숫자를 설정한다. 기본 dababase는 DB 0이다. 물론 connecton당 'SELECT <dbid>' 사용해서 다른 database를 선택할 수 있다. dbid는 0 과 'database 수 - 1'사이의 수이다.


예) databases 16


SNAPSHOTTING (RDB 지속성 설정)

save [seconds] [changes]


disk에 DB를 저장한다. DB에서 주어진 값인 seconds와 changes를 모두 만족시키면 DB를 저장 할 것이다.

이 설정은 여러번 설정할 수 있다.


아래는 예제를 설명한 것이다.

900초(15)분 동안에 1개 이상의 key 변경이 발생했다면 DB를 저장한다.

300초(5)분 동안에 10개 이상의 key 변경이 발생했다면 DB를 저장한다.

60초(1)분 동안에 10000개 이상의 key 변경이 발생했다면 DB를 저장한다.

실제 서비스에서는 너무 자주 동기화가 일어나게 설정하면 안된다.


주목: DB를 저장하고 싶지 않으면 모든 save 라인들을 주석 처리한다.


예) save 900 1

예) save 300 10

예) save 60 10000


rdbcompression [boolean] (기본값: yes)


.rbd database를 덤플 할 때 LZF를 사용해서 문자열 부분을 압축할지 설정한다.

압축하는 것은 대부분의 경우 좋기 때문에 기본값은 'yes'이다.

만약 child set을 저장할 때 CPU 사용을 절약하고 싶다면 'no'로 설정한다. 하지만 values 또는 keys가 압축이 가능했다면, dataset은 보다 커질 것이다.


예) rdbcompression yes


dbfilename [file path]


DB가 dump될 파일을 설정한다.


예) dbfilename dump.rdb


dir [directory path]


DB가 기록될 디렉토리를 설정한다. DB dump파일은 위의 dbfilename과 함께 최종적으로 파일이 기록될 곳이 지정된다.

Append Only File 또한 이 디렉토리에 파일을 생성할 것이다.

반드시 여기에 디렉토리를 설정해야 한다. file name에 설정하면 안 된다.


예) dir ./


REPLICATION (복제 셋 구성 설정)

slaveof [masterip] [masterport]


Master-Slave replication을 구성하기 위해서 slaveof를 사용한다. 이것은 Redis instance가 다른 Redis server의 복사본이 되게 한다.

slave에 대한 설정은 local에 위치한다. 따라서 slave에서는 내부적으로 따로 DB를 저장 하거나, 다른 port를 listen하는 등 slave만의 설정이 가능하다.


예) slaveof 127.0.0.1 6379


masterauth [master-password]


만약 master에 password가 설정되어 있다면 replication synchronization(복제 동기화) 과정이 시작되기 전에 slave의 인증이 가능하다. (이것은 "requirepass" 설정으로 가능하다). 만약 password가 틀리다면 slave의 요청은 거절될 것이다.


예) masterauth password1234 


slave-server-stale-data [boolean] (기본값: yes)


만약 slave가 master와의 connection이 끊어 졌거나, replication이 진행 중일 때는 아래의 2가지 행동을 취할 수 있다.


'yes(기본값)'로 설정한 경우, 유효하지 않은 data로 client의 요청에 계속 응답할 것이다. 만약 첫번째 동기화 중이였다면 data는 단순히 empty로 설정될 것이다.

'no'로 설정한 경우, INFO와 SLAVEOF commad(명령)을 제외한 모든 command들에 대해서 "SYNC with master in progress" error로 응답할 것이다.

예) slave-server-stale-data yes


repl-ping-slave-perid [seconds] (기본값: 10)


Salve가 내부적으로 미리 정의된 server로 지정된 시간마다 PING을 보내도록 설정한다.


예) repl-ping-slave-perid 10


repl-timeout [seconds] (기본값: 60)


'bulk transfer I/O(대량 전송 I/O) timeout'과 'master에 대한 data또는 ping response timeout'을 설정한다.


이 값은 'repl-ping-slave-period' 값 보다 항상 크도록 설정되어야 한다. 그렇지 않으면 master와 slave간 작은 traffic이 발생할 때 마다 timeout이 인지될 것이다.


예) repl-timeout 60


SECURITY (보안 설정)

requirepass [password]


client에게 다른 command들을 수행하기 전에 password를 요구하도록 설정한다. 이것은 redis-server에 접근하는 client들을 믿을 수 없는 환경일 때 유용한다.


반대로 개방적으로 사용하기 위해서는 주석 처리 되어야 한다. 왜냐하면 대부분의 사람들은 auth가 필요하지 않기 때문이다.


주의: Redis는 매우 빠르기 때문엔 좋은 환경에서는 외부 사용자가 1초 당 15만개의 password를 시도할 수 있다. 따라서 매우 강력한 password를 설정해야 한다.


예) requirepass foobared


rename-command [target-command] [new-command]


Commnad renaming


공유되는 환경에서는 위험한 command들의 이름을 변경할 수 있다. 예를 들어서 CONFIG command를 추측하기 어려운 다른 값으로 변경할 수 있다. 물론 internal-use tool로는 해당 명령어가 사용하지만, 일반적인 외부 client들에 대해서는 불가능하다.


예) rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52


또한 command를 공백으로 rename해서 완전히 사용 불가능하게 할 수도 있다.


예) rename-command CONFIG ""


LIMITS (접속 및 메모리 설정)

maxclients [size] (기본값: 제한 없음)


한번에 연결될 수 있는 최대 client 수를 설정한다. 기본값은 제한이 없으나, Redis process의 file descriptor의 숫자만큼 연결가능하다. 특별히 '0' 값은 제한 없음을 의미한다.

limit에 도달했을 때 새로운 connection들에 대해서는 'max number of clients reached' error를 전송한고 connection을 close 한다.


예) maxclients 128


maxmemory [bytes]


명시한 bytes의 양보다 많은 메모리를 사용하지 말아라.

memory가 limit에 도달했을 때 Redis는 선택된 eviction policy(제거 정책)(maxmemory-policy)에 따라서 key들을 제거할 것이다.


만약 Redis가 eviction policy에 의해서 key를 제거하지 못하거나 polict가 'noeviction'으로 설정되어 있다면, SET, LPUSH와 같이 메모리를 사용하는 command들에 대ㅐ서는 error를 반환하고, 오직 GET 같은 read-only command들에 대해서만 응답할 것이다.


주의: maxmoery 설정 된 instance에 slave가 존재 할 때, slave에게 data를 제공하기위해서 사용되는 output buffer의 size는  used memory count에서 제외된다. 왜냐하면 network 문제나 재동기화가 keys들이 제거된 loop에 대한 trigger를 발생시키지 않게 하기 위해서이다. loop가 발생하면 slave의 output buffer가 제거된 key들의 DEL 명령으로 가득 찰 것이다. 그리고 이것은 database가 완전히 빌 때 까지 지속된다. (좀 더 정확히 알아볼 필요가 있다. 확실히 이해가 안 됨)


간단히 말하면, 만약 slave를 가진다면, slave output buffer를 위해서 system에 약간의 free RAM이 존재시키기 위해서, maxmoery에 약간 낮은 limit를 설정하는 것이 추천된다. (하지만 policy가 'noeviction'이면 필요하지 않다.)


큰 메모리를 표시하기 위해서 아래와 같이 표기가 가능하다.


1k 1,000 bytes

1kb 1024 bytes

1m 1,000,000 bytes

1mb 1024*1024 bytes

1g 1,000,000,000 bytes

1gb 1012*1024*1024 bytes

예) maxmoery 1000


maxmemory-policy [policy] (기본값: volatile-lru)


MAXMEMORY POLICY: maxmemory에 도달했을 때 무엇을 삭제 할 것인지 설정한다. 아래 5개 옵션 중 하나를 선택할 수 있다.


옵션 설 명

volatile-lru expire가 설정된 key 들 중 LRU algorithm에 의해서 선택된 key를 제거한다.

allkeys-lru 모든 key 들 중 LRU algorithm에 의해서 선택된 key를 제거한다.

volatile-random expire가 설정된 key 들 중 임의의 key를 제거한다.

allkeys-random 모든 key 들 중 인의의 key를 제거한다.

volatile-ttl expire time이 가장 적게 남은 key를 제거한다. (minor TTL)

noeviction 어떤 key도 제거하지 않는다. 단지 쓰기 동작에 대해서 error를 반환한다.

예) maxmemory-policy volatile-lru


maxmemory-samples [size] (기본값: 3)


LRU와 minimal TTL algorithms(알고리즘)은 정확한(최적) 알고리즘들은 아니다. 하지만 거의 최적에 가깝다. 따라서 (메모리를 절약하기 위해서) 검사를 위한 샘플의 크기를 선택할 수 있다. 예를 들어, Redis는 기본적으로 3개의 key들은 검사하고 최근에 가장 적게 사용된 key를 선택할 것이다. 하지만 아래 예와 같이 샘플의 크기를 변경할 수 있다.


예) maxmoery-samples 3


APPEND ONLY MODE (AOF 지속성 설정)

appendonly [boolean] (기본값: no)


Redis는 기본적으로 비동기로 disk에 dataset의 dump를 남긴다. 이 방법은 crash가 발생했을 때 최근의 record를 손실되어도 문제가 없을 때 좋은 방법이다. 하지만 하나의 record도 유실되지 않기를 원한다면 append only mode를 활성화 시키는 것이 좋을 것이다.: 이 mode가 활성화 되면, Redis는 요청받는 모든 write operation들을 appendonly.aof 파일에 기록할 것이다. Redis가 재 시작될 때 memory에 dataset를 rebuild하기 위해서 이 파일을 읽을 것이다.


주목: 원한다면 async dumps와 asppend only file을 동시에 사용할 수 있다. 만약 append only mode가 활성화 되어 있다면, startup 때 dataset의 rebuild를 위해서 append only mode의 log file을 사용하고, dump.rdb 파일은 무시할 것이다.


중요: 순간적으로 write operation이 많을 때 background에서 어떻게 append log file을 rewirte 하는 방법을 확인하기 위해서 BGREWRITEAOF를 확인해라.


예) appendonly no


appendfilename [file name] (기본값: appenonly.aof)


append only file의 이름을 설정한다.

파일이 저장되는 디렉토리는 SNAPSHOTTING의 dir 속성을 사용한다.


예) appendfilename appendonly.aof


appendsync [option]


fsync() call 은 Operating System(운영체제)에게 output buffer 내에 보다 많은 데이터들을 기다리지 않고, disk에 실제로 data를 작성하라고 말한다. 어떤 OS 는 실제로 disk에 data를 기록할 것이고, 어떤 OS들은 가능한 빨리 data를 기록하려고 시도할 것이다.


Redis는 3가지의 다른 mode들을 지원한다.


옵션 설 명

no fsync()를 호출하지 않는다. data의 flush를 OS에 맡긴다. 빠르다.

always append only log에 wrtie 할 때 마다 fsync()를 호출한다. 느리다, 안전하다.

everysec 매 초 마다 fsync()를 호출한다. 절충안

기본값인 'everysec'은 일반적으로 속도와 데이터 안정성 사이에서 적절한 절충안이다. 이것은 당신의 관대함(?)에 달려있다. 만약 'no'로 설정한다면 OS는 적절한 타이밍에 output buffer를 flush하고 보다 좋은 성능을 낼 것이다. (그러나 만약 data loss가 발생해도 문제가 없다면 기본 persistence(영속성) mode는 snapshotting일 것이다.) 반면에 "always"를 사용하면 매우 느릴 것이다. 하지만 'everysec'보다 조금 더 안전할 것이다.


만약 확실하지않다면 'everysec'를 사용해라.


예) appendfasync everysec


no-appendfsync-on-rewrite [boolean]


AOF fsync policy를 always 또는 everysec로 설정했다면, background saving process(background 저장 또는 AOF log backgournd rewriting)은 disk에 대해서 매우 많은 I/O를 발생시킬 것이다. 따라서 어떤 Linux에서 fsync() call은 매우 긴 block를 발생시킬수도 있다.


주목: 현재 이것에 대한 fix(수정)은 없다. 다른 thread에서 fsync를 수행하더라도 동시에 발생하는 wirte call(2)이 block 될 것이다.


이 문제를 완화 시키기 위해서 BGSAVE 또는 BGREWRITEAOF가 수행중인 동안에 메인 process에서 fsync()가 호출되는 것을 막을 수 있는 no-appendfsync-on-rewirte option을 사용하는 것이 가능하다.


이것은 Redis의 영속성을 저장하는 동안에 다른 자식은 "appendfsync none"으로 설정한 것과 동일하다. 쉽게 말하면, (Linux의 기본 설정에 의해서) 최악의 경우 30초 가량의 log가 손실될 수 있다는 것이다. (이해가 잘 안됨)


이로 인해서 잠재적 문제가 있다면 이 옵션을 'yes'로 설정해라. 하지만 다른 경우에는 'no'로 남겨두는 것이 영속성의 view(?)부터 가장 안전한 선택이다.


예) no-appendfsync-on-rewrite no


auto-aof-rewrite-percentage [percente], auto-aof-rewrite-min-size [bytes]


Append only file의 Automatic rewrite(자동 재 작성)

Redis는 AOF log의크기가 명시된 퍼센트를 넘어갔을 때, 자동적으로 BGREWRITEAOF를 호출함으로서 log file를 재작성 하는 것이 가능하다. (비교 대상이 무엇인지 모르겠다.해당 Drive의 전체 크기인가?)


작동법: Redis는 최근 rewirte후에 AOF file의 크기( 또는 restart이후  rewirte가 발생하지 않았다면, startup 때 사용된 AOF의 크기)를 기억하고 있다.


이 기본 크기는 현재 크기와 비교되어진다. 만약 현재 크기가 설정된 퍼센트보다 크다면, rewrite가 동작하게된다. 또한 rewritten(재작성되는) AOF 파일의 최소 크기를 지정해 주어야 한다. 이것은 비록 퍼센트 증가가 설정 값에 도달하더라도 여전히 작은 크기일 때 AOF file이 rewriting되는 것은 피하는데 유용하다.


Automatic AOF rewirte을 비활성화 시키기 위해서는 percentage값을 0으로 설정해라.


예) auto-aof-rewrite-percentage 100

예) auto-aof-rewrite-min-size 64mb


SLOW LOG

Redis Slow Log는 설정된 실행 시간을 초과한 쿼리들의 로그를 남기는 시스템이다. 실행 시간은 talking with the client, sending the reply와 같은 I/O operation들은 포함되지 않는다. 단지 command를 수행하는데 필요한 시간(command 실행을 위해서 threaad가 block되어서 다른 request를 처리할 수 없는 시간)만이 측정된다.


2개의 parameter들를 통해서 slow log를 설정할 수 있다. : 첫번째 parameter는 Redis가 slow log를 기록하기 위해서  어떤 execution time이 느린 것인지 알려주기 위해서 microsencond로 설정한다. 두번째 parameter는 기록될 수 있는 slow log의 길이이다. 새로운 command가 log 되었을 때, 가장 오래전에 기록된 log가 제거된다. FIFO 형태인 것이다.


slowlog-log-slower-than [microseconds]


microsencod값으로 slow execution time를 설정한다. 1000000은 1초와 같다.


주의: 음수를 설정한 경우 slow log를 비활성화 시킨다. 0으로 설정한 경우 모든 command에 대해서 logging 수행된다.


예) slowlog-log-slower-than 10000


slowlog-max-len


길이에는 제한이 없다. 단지 이것은 memory를 소모하는 것은 인지하고 있어야 한다.

SLOWLOG RESET 명령으로 slow log에 의해서 사용된 memory를 반환 시킬 수 있다.


예) slowlog-max-len 128


VIRTUAL MEMORY

vm-enabled


Virtual Memory는 Redis 2.4에서 제거되었다. vm-enabled no로 설정해서 사용하지 않는다.


예) vm-enabled no


ADVANCED CONFIG

hash-max-zipmap-entries, hash-max-zipmap-value


hash들은 elements의 숫자가 설정된 entries(개수)에 도달하고 가장 큰 element가 설정된 threshold(기준치)를 초과하지 않으면 특별한 방법으로(보다 효과적인 메모리 사용법으로) encoded(인코딩)되어진다.


특별한 방법: hashtable 또는 zipmap


예) hash-max-zipmap-entries 512

예) hash-max-zipmap-value 64


list-max-ziplist-entries, list-max-ziplist-value


hash와 비슷하게 작은 list도 공간을 절약하기 위해서 특별한 방법으로 encode되어진다. 오직 설정된 값 보다 아래에 있을 때 특별한 방법이 사용되어진다.


특별한 방법: linkedlist 또는 ziplist


예) list-max-ziplist-entries 512

예) list-max-ziplist-value 64


set-max-intset-entries


Set은 오직 한 가지 경우에만 특별한 방법으로 encoded 되어진다. Set이 오직 string(문자열)로만 구성되었을 때, 64bit signed integer 범위의 radix 10의 integer로 변환된다.

아래 설정은 특별한 메모리 저장 인코딩을 사용하기 위해서 set 의 크기 제한을 설정한다. (정확한 확인 필요, 아마도 아래보다 크지 않을 때 사용하지 않을까 싶다.)


예) set-max-intset-entries 512


zset-max-ziplist-entries, zset-max-ziplist-value


hash 및 list와 비슷하게, sorted set도 많은 공간을 절약하기 위해서 특별하게 encoded되어진다. 이 Encoding은 sorted set의 element와 length가 설정치를 초과하지 않을 때 사용되어 진다.


예) zset-max-ziplist-entries 128

예) zset-max-ziplist-value 64


activerehashing


rehashing을 활성화 하면 main Redis has table(최상위 key-value hash table)을 rehashing하는 것을 돕기 위해서 CPU time의 매 100 millisecond 마다 1 millisencond를 사용한다. redis가 사용하는 hash table 구현은 lazy rehashing으로 동작한다. rehashing이 수행되는 hash table에서 많은 operation을 수행 할 수록, 보다 많은 rehashing 'steps'이 수행된다. 따라서 server가 idle 상태이면 rehashing은 결코 완료되지 않고, 약간의 메모리가 hash table에 의해서 사용되어진다.


기본값은 가능할 메모라는 free하게 만들기 위해서, main dictionary들의 rehashing을 active하는데 매 초마다 10 millisecond를 사용하는 것이다.


만약 힘든 잠재적 요구사항이나 당신의 환경에 적합하지 않아서 확신이 들지 않는다면 'activerehashing no'를 사용해라. 이 설정으로 인해서 Redis가 request들에 대해서 reply하는데 2 milliseoncds의 delay(지연)가 발생할 것이다.


만약 어려운 환경이 아니고 가장한 빠르게 메모리는 free하게 하고 싶다면 'activerehashing yes'를 사용해라.


예) activerehashing yes




[redis]


설치한 버전 3.0.6 64비트


// 레디스가 패키지 리스트에 없다면

$ sudo add-apt-repository -y ppa:rwky/redis



$ sudo apt-get update

$ sudo apt-get install redis-server


$ redis-server --version

$ sudo vi /etc/redis/redis.conf

// bind-address 주석처리


/etc/redis/redis.conf 에 requirepass 를 써주시고 비밀번호를 적어주시면 됩니다.

requirepass 패스워드


$ service redis-server restart


레디스는 RDB, AOF 라는 방식의 백업 방식을 지원한다.

RDB : 특정 간격마다 메모리에 있는 레디스 전체를 디스크에 쓴다.

AOF : 명령이 실행될때마나 기록되는 방식



// 백업 기능 관련 설명

$ sudo /etc/redis/redis.conf

RDB

stop-writes-on-bgsave-error : yes or no, default yes

이값이 yes일때 레디스는 RDB 파일을 디스크에 저장하다 실패하면 모든 쓰기 요청을 거부한다. 쓰기에 문제가 발생했으니 빨리 조치 하라는 것이다. default는 yes이다. 만약 no 로 설정 했다면 저장에 실패 하더라도 모든 요청을 정상적으로 처리한다.


rdbcompression : yes or no, default yes

RDB 파일을 쓸때 압축 여부를 정한다. 압축률은 그다지 높지 않다고 한다.


dbfilename dump.rdb

RDB 파일명을 지정하나 Path는 지정할 수 없다. Path 는 working directory 에 따른다.


save 라는 항목이 있다. 여러개의 조건을 걸수있다.

이 항목을 주석처리하면 RDB기능을 사용하지 않는다.


AOF

appendonly : yes or no

AOF 기능을 사용하거나 사용하지 않는다. yes일때만 AOF파일을 읽는다.


appendfilename

AOF파일을 지정한다. Path는 RDB와 같다. 지정할 수 없고 기본을 따른다.


appendfsync

세가지의 방법이 있다.

always : 명령을 실행 할때마다 기록된다. 데이터가 손실 되지는 않으나 성능이 떨어진다.

everysec : 1초마다 AOF에 기록된다. 그 사이에 데이터가 유실 될수 있으나 always보다는 성능이 좋고 데이터도 가능한 많이 보존할 수 있으며 일반적으로 권장하는 방법이다.

no : OS 가 알아서 하는 방식이긴 하나 데이터 유실이 큽니다.


AOF rewrite

auto-aof-rewrite-percentage 100 : AOF 파일 사이즈가 100% 이상 커지면 rewirte 한다. 처음에는 레디스 서버가 시작할 시점의 AOF파일 사이즈 기준으로 한다. Rewrite 하면 rewirte 후 파일 사이즈 기준으로 계산한다.


auto-aof-rewrite-min-size 64mb

AOF 파일 사이즈가 64MB이하면 rewirte 하지 않는다. 파일이 작을때 rewirte가 자주 발생하는 것을 막는다.

만약 0으로 설정 했다며 rewirte를 하지 않는다.



1. REDIS 소개

REDIS는 BSD 라이센스 기반의 Key-value 캐쉬 & Store 소프트웨어다. String, hash, lists, sets, sorted set, bitmap, hyperloglogs 등 다양한 데이터 구조를 저장할 수 있기 때문에, data structure server라고 부르기도 한다.

메모리에 데이터를 쓰는 In memory 데이터베이스 그리고 NoSQL 데이터베이스로 분류된다. 데이터에 대한 읽기와 쓰기가 많은 서비스에 사용 할 수 있다. Memcached와 비슷한 스팩을 가지고 있는데, 다양한 유형의 데이터를 지원한다는게 장점이다.

일단 REDIS를 빠르게 설치하고, 몇몇 기능들을 테스트 한 다음에 곧바로 응용 프로그램을 만들어 볼 것이다.

2. REDIS 설치 및 테스트

2.1. 환경

설치 및 테스트 환경은 다음과 같다.

  • 가상환경 : VirtualBox를 이용한다.
  • Ubuntu 14.04

테스트를 위해서 두 대의 머신을 준비했다.

  • Redis-server : Redis server가 설치된다.
  • Redis-client : Redis client가 설치된다.

2.2. Redis 서버 설치

1
2
3
# apt-get install redis-server
# ps -ef | grep redis
redis 1743 1 0 15:56 ? 00:00:01 /usr/bin/redis-server 127.0.0.1:6379

127.0.0.1:6379에 bind 됐다. 원격에서 테스트하기 위해서 bind 정보를 변경하고 restart 했다.

1
2
3
4
5
6
7
8
9
10
# cat /etc/redis/redis.conf
.....
bind 0.0.0.0
# /etc/init.d/redis-server restart
Stopping redis-server: redis-server.
Starting redis-server: redis-server.
# netstat -nap | grep 6379
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 1811/redis-server 0

설치한 Redis의 버전이다.

1
2
# redis-server --version
Redis server v=2.8.13 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=cee0f9a49c3c27aa

2.3. Redis 클라이언트 설치

Redis-server를 테스트하기 위해서, redis-client에 redis 클라이언트 프로그램을 설치했다.

1
# apt-get install redis-tools

서버 연결 테스트

1
2
# redis-cli -h 192.168.57.2 ping
PONG

성공 !!!. 시작이 절반인데, 연결 테스트까지 했으니 75%는 끝난거라고 봐야 겠네.

3. Redis 자료구조 테스트

Redis-cli를 이용해서 자료구조를 테스트 한다.

1
2
# redis-cli -h 1972.168.57.2
>

3.1. Strings

키에 대한 값으로 문자열(string)를 저장한다. 단순한 타입으로, redis를 사용한다고 하면 가장 먼저 고려해볼만한 타입이다. JSON, XML등 문자열로 된 데이터들을 저장할 수 있다. 웹 서비스를 한다면, HTML 문서의 전체 혹은 일부분을 캐쉬하기 위해서 사용할 수 있다.

1
2
3
4
> set mykey myvalue
OK
> get mykey
"myvalue"

SET을 이용해서 값을 저장하고, GET을 이용해서 값을 가져올 수 있다. 이미 있는 key에 대해서 값을 설정하면, 값을 덮어쓴다.

특이한 점은 string이라고 해서 문자열만 저장하는게 아니고, 바이너리(binary) 데이터도 저장할 수 있다는 거다(그냥 바이너리 데이터를 저장할 수 있습니다 하면 좋았을 것을) png이미지를 저장하고, 읽는 테스트를 해봤다.

1
2
3
# cat test.png | redis-cli -h 192.168.57.2 -x set myimage
OK
# redis-cli -h 192.168.57.2 get myimage > ok.png

Atomic increment 같은 연산도 가능하다.

1
2
3
4
5
6
7
8
> set counter 1000
OK
> INCR counter
(integer) 1001
> INCR counter
(integer) 1002
> INCRBY counter 50
(integer) 1052

MSET과 MGET을 이용해서 한 번에 여러 개의 key, value를 저장하고 읽을 수 있다.

1
2
3
4
5
6
7
8
9
> MSET key1 "Hello" key2 "world"
OK
> get key1
"Hello"
> get key2
"world"
> mget key1 key2
1) "Hello"
2) "world"

3.2. 리스트

LPUSH를 이용해서 리스트의 맨 앞(왼쪽-left)에, RPUSH를 이용해서 리스트의 맨 뒤에 값을 밀어넣을 수 있다. LRANGE로 일정 범위의 값을 읽을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
> rpush mylist A
(integer) 1
> rpush mylist B
(integer) 2
> lrange mylist 0 -1
1) "A"
2) "B"
> lpush mylist first
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"

LRANGE는 시작과 끝을 위한 두 개의 index 값이 필요하다. 인덱스가 마이너스(-)이면, 리스트의 끝(오른쪽)을 기준으로 인덱스 값을 메긴다. 오른쪽 끝의 인덱스는 -1이다. 따라서 "0 -1"은 0번째 부터 마지막 까지의 범위를 의미한다.

한번에 여러개의 값을 저장할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
> rpush mylist 1 2 3 4 5 "foo bar"
(integer) 9
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "foo bar"

pop은 Redis list에서 가장 중요한 연산일 것이다. 이 연산은 리스트에서 값을 읽는게, 아니라 꺼낸다. 읽으면서 지운다라고 생각하면 되겠다.

1
2
3
4
5
6
7
8
> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"

3개의 값을 모두 꺼냈다. 비어있는 리스트에 대해서 pop을 하면 "nil"을 반환한다.

1
2
> rpop yourlist
(nil)

3.2.1. Capped Lists

소셜 네트워크 메시지 서비스혹은 로그 경우 마지막에 저장된 값만 필요한 경우가 많다. LTRIM을 이용해서 특정 영역의 값만 남기고 나머지는 삭제할 수가 있다.

1
2
3
4
5
6
7
8
> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"

ltirm을 이용해서 인덱스 0에서 2사이의 값만 남기고, 삭제해버렸다. 일반적인 경우에는 처음 값이 아닌, 마지막 값을 남기는게 더 유용할 것이다. 이런 류의 서비스를 위해서는 LPUSH와 LTRIM을 사용하면 된다.

1
2
LPUSH myliste <>
LTRIM mylist 0 99

이렇게 하면 최근 100개의 값만 유지할 수 있다.

3.2.2. Blocking operation on lists

REDIS는 list 데이터 타입에 대해서 Blocking operation을 지원한다.

list로 부터 값을 꺼내기(POP)위한 가장 방법은 주기적으로 rpop를 호출하는 것이다. 이 방식은 주기를 조절하는게 애매모호하기 때문에 그다지 좋아 보이지 않는다. POP을 호출하는 시점에 읽을 데이터가 없다면, 읽을 데이터가 준비될 때까지 blocking 되는게 깔끔해 보인다. 한번의 호출로 데이터를 읽을 수 있기 때문이다.

BRPOP과 BLPOP 명령을 이용하면, blocking 작업이 가능하다. 이들 명령을 호출하면, 데이터가 없을 경우 데이터가 준비될 때까지 block된다. 물론 block 시간 설정도 가능하다.

1
> brpop tasks 5

rpush로 데이터를 밀어 넣어보자.

1
2
> rpush tasks 1 2 3
(integer) 3

실행 결과 결과

1
2
3
4
> brpop tasks 5
1) "tasks"
2) "3"
(3.50s)

하나 이상의 리스트로 부터 가져오는 것도 가능하다.

1
> BRPOP list1 list2 5

그런데, Range로 가져올 수가 없어서 brpop만 사용하기에는 좀 애매모호하다. 예를들어 list에 한번에 10개의 값을 밀어 넣었을 경우, brpop를 10번을 연속 호출하게 된다. 이 문제는 brpop로 값을 가져온 후에, lrange 0, -1등을 호출하는 식으로 해결해야 할 것 같다. 혹은 결과를 모아서 하나의 리스트에 보내는 식의 소프트웨어 방법을 적용할 수 있을거다.

3.3. Hash

Hash는 값으로 field&value의 쌍으로 이루어진 테이블을 저장할 수 있는 데이터 타입이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
> hmset user:1000 username yundream birthyear 1997 verified 1
OK
> hget user:1000
(error) ERR wrong number of arguments for 'hget' command
> hget user:1000 username
"yundream"
> hgetall user:1000
1) "username"
2) "yundream"
3) "birthyear"
4) "1997"
5) "verified"
6) "1"

3.4. Set

Set은 정렬되지 않은 string의 집합이다. SADD 명령을 이용해서 set에 새로운 값을 추가할 수 있다.

1
2
3
4
5
6
> sadd myset 1 2 3
(integer) 0
> smembers myset
1) "1"
2) "2"
3) "3"

SISMEMBER명령으로 값이 set에 있는지 확인할 수 있다.

1
2
3
4
> SISMEMBER myset 3
(integer) 1
> SISMEMBER myset 30
(integer) 0

3.5. Sorted Set

Set과 hash를 섞은 데이터 타입이라고 보면 되겠다. Set과 마찬가지로 키는 유니크하며, 키로 정렬된다. 유명한 해커들의 이름과 태어난 해를 Sorted set 으로 저장했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ redis-cli -h 192.168.56.5
> zadd hackers 1940 "Alan Kay"
(integer) 1
> zadd hackers 1957 "Sophie Wilson"
(integer) 1
> zadd hackers 1953 "Richard Stallman"
(integer) 1
> zadd hackers 1949 "Anita Borg"
(integer) 1
> zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
> zadd hackers 1914 "Hedy Lamarr"
(integer) 1
> zadd hackers 1916 "Claude Shannon"
(integer) 1
> zadd hackers 1969 "Linus Torvalds"
(integer) 1
> zadd hackers 1912 "Alan Turing"
(integer) 1

ZADD의 사용법은 SADD와 비슷하다. 정렬에 사용 할 score 매개변수가 하나 더 추가된다는 것만 다르다. 이제 값을 가져 오면 score를 키로 정렬된 결과를 받아볼 수 있다. Sorted set은 값을 입력 할 때, 정렬이 되기 때문에, O(long(N))의 시간 복잡도를 가진다.

1
2
3
4
5
6
7
8
9
10
> ZRANGE hackers 0 -1
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
6) "Richard Stallman"
7) "Sophie Wilson"
8) "Yukihiro Matsumoto"
9) "Linus Torvalds"

리누즈 토발즈가 제일 어리군.

역순으로 가져올 수도 있다.

1
2
3
4
5
6
7
8
9
10
> ZREVRANGE hackers 0 -1
1) "Linus Torvalds"
2) "Yukihiro Matsumoto"
3) "Sophie Wilson"
4) "Richard Stallman"
5) "Anita Borg"
6) "Alan Kay"
7) "Claude Shannon"
8) "Hedy Lamarr"
9) "Alan Turing"

WITHSCORES옵션으로 score 값도 함께 읽을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> ZREVRANGE hackers 0 -1 withscores
1) "Linus Torvalds"
2) "1969"
3) "Yukihiro Matsumoto"
4) "1965"
5) "Sophie Wilson"
6) "1957"
7) "Richard Stallman"
8) "1953"
9) "Anita Borg"
10) "1949"
11) "Alan Kay"
12) "1940"
13) "Claude Shannon"
14) "1916"
15) "Hedy Lamarr"
16) "1914"
17) "Alan Turing"
18) "1912"

3.5.1. Operation on ranges

Sorted set은 범위 검색을 위한 몇 가지 툴을 제공한다. ZRANGEBYSCORE 명령을 이용해서 1950년까지 태어난 해커들을 검색했다.

1
2
3
4
5
6
7
8
9
10
11
> ZRANGEBYSCORE hackers -inf 1950 withscores
1) "Alan Turing"
2) "1912"
3) "Hedy Lamarr"
4) "1914"
5) "Claude Shannon"
6) "1916"
7) "Alan Kay"
8) "1940"
9) "Anita Borg"
10) "1949"

1940년에서 1960년 사이에 태어난 해커들이 몇 명인지 알아보자.

1
2
> ZREMRANGEBYSCORE hackers 1940 1960
(integer) 4

3.5.2. Lexicographical scores

Redis 2.8에 lexicographical scores가 추가됐다. score가 같을 경우, 값을 비교해서 정렬한다. 비교 함수로 C의 memcmp 함수를 사용한다.

1
2
3
> zadd hackers 0 "Alan Kay" 0 "Sophie Wilson" 0 "Richard Stallman" 0
"Anita Borg" 0 "Yukihiro Matsumoto" 0 "Hedy Lamarr" 0 "Claude Shannon"
0 "Linus Torvalds" 0 "Alan Turing"

score가 같기 때문에, 값을 비교해서 정렬한다.

1
2
3
4
5
6
7
8
9
10
> zrange hackers 0 -1
1) "Alan Kay"
2) "Alan Turing"
3) "Anita Borg"
4) "Claude Shannon"
5) "Hedy Lamarr"
6) "Linus Torvalds"
7) "Richard Stallman"
8) "Sophie Wilson"
9) "Yukihiro Matsumoto"

3.6. Bitmaps

독립적인 데이터 타입이라고 하기에는 좀 애매모호 하다. 바이너리 데이터에 대한 bit 연산 기능이라고 봐야 겠다. setbit 로 bit를 설정할 수 있고, getbit로 bit 설정 여부(1인지)를 확인할 수 있다.

Bitmap의 가장 큰 장점은 0과 1의 상태를 가지는 아이템들을 대단히 효율적으로 저장하고, 읽을 수 있다는데 있다. 하루 동안의 유니크 방문자를 계산해야 한다고 가장해보자. 유저가 방문할 때, 유저 아이디에 해당하는 bit 값을 1로 만들어 주는 걸로 간단하게 유저의 방문여부를 알 수 있다. 이때 필요한 메모리는 단지 1bit이기 때문에 512MB의 메모리로 40억(512 * 1024 * 1024 * 8)의 유저의 방문정보를 기록할 수 있다.

1
2
3
4
5
6
7
8
9
10
> setbit visitor 10 1 # 10
> setbit visitor 1000 1 # 1000
> setbit visitor 101 1 # 101
> setbit visitor 16
> getbit visitor 16 # 16
(integer) 1
> getbit visitor 15 # 15
(integer) 0
> bitcount visitor #
(integer) 4

3.7. HyperLogLogs

어떤 데이터셋에서 집합에서 유일한 원소의 갯수를 검사하기 위해서 사용하는 알고리즘이다. 세익스피어 전집에서 unique한 단어의 갯수를 계산하려면 매우 큰 메모리가 필요할 것이다. HyperLogLogs를 이용하면 약간의 오차를(대략 1billion에 대해서 2% 정도) 허용하는 대신 매우 작은 메모리로 유니크한 원소의 갯수를 검사할 수 있다. 셰액스 피어의 전집에 나오는 유일한 영어 단어 개수를 세는 것으로 비교하자면, HashSet을 이용할 경우 10,4447,016 크기의 메모리가 필요한 반면, HyperLogLogs는 512바이트가 필요하다. 물론 Hashet의 오차는 0%, HyperLogLogs는 3%의 오차가 발생한다.

1
2
3
4
5
6
7
8
> PFADD hll foo bar zap
(integer) 1
> PFADD hll zap zap zap
(integer) 0
> PFADD hll foo bar
(integer) 0
> PFCOUNT hll
(integer) 3

SADD등을 이용해서 새로운 값을 추가 할 때마다, PFADD를 이용해서 unique 요소를 연산하는 식의 응용이 가능하겠다.

4. Redis 프로그래밍 테스트

Ruby로 테스트 한다. Redis gem을 설치했다.

1
# gem install redis

1
2
3
4
5
6
7
require "redis"
redis = Redis.new(:host=>'192.168.56.5', :port=>6379, :db=15)
#
redis = Redis.new(:url =>"redis://:password@192.168.56.6:6379/15")
# Unix domain socket
redis = Redis.new(:path => "/tmp/redis.sock")

String 값을 저장하는 간단한 예제

1
2
3
4
5
require 'redis'
redis = Redis.new(:host=>'192.168.56.5', :port=>6379, :db => 15)
redis.set 'mykey', 'hello world'
puts redis.get('mykey')

5. 참고



ftp 설정



$ sudo apt-get update

$ sudo apt-get install vsftpd


// 설정파일 백업

$ sudo cp /etc/vsftpd.conf /etc/vsftpd.conf.orig


// 방화벽 상태 조회

$ sudo ufw status


// 방화벽에 사용할 포트 등록

$ sudo ufw allow 20/tcp

$ sudo ufw allow 21/tcp

$ sudo ufw allow 990/tcp

$ sudo ufw allow 40000:50000/tcp


// 여기서는 20, 21, 990, 40000~50000 을 등록


// 유저 추가

$ sudo adduser testuser


// 디렉토리 생성

$ sudo mkdir /home/testftp

// 디렉토리의 권한 설정

$ sudo chown nobody:nogroup /home/testftp

$ sudo chmod a-w /home/testftp


// 권한이 제대로 적용된는지 조회

sudo ls -la /home/testftp

// 예시

4 dr-xr-xr-x  2 nobody nogroup 4096 Aug 24 21:29 .

4 drwxr-xr-x 3 testuser  testuser   4096 Aug 24 21:29 ..


$ sudo chown testuser:testuser /home/testftp


// 테스트용 파일 만들기

$ echo "vsftpd test file" | sudo tee /home/testftp/test.txt



// ftp 설정파일 

$ sudo nano /etc/vsftpd.conf


// 익명의 사용자 블럭

anonymous_enable=NO

// 로컬 유저 사용?

local_enable=YES

// 쓰기 가능 YES로 변경

write_enable=YES

// 지정된 루트 상위로 조회 가능여부 YES

chroot_local_user=YES


// 추가 

// 추가된 유저의 이름을 키워드로 유저이름의 폴더를 지정

user_sub_token=$USER

local_root=/home/$USER/ftp



// 패시브 모드로 사용할 경우 포트 지정

pasv_min_port=40000

pasv_max_port=50000


// 유저 리스트 사용 여부

userlist_enable=YES

// 유저 리스트의 파일 경로

userlist_file=/etc/vsftpd.userlist

// 유저 리스트의 블럭 사용 여부

userlist_deny=NO


// 유저 리스트 파일 만들기

$ echo "testuser" | sudo tee -a /etc/vsftpd.userlist

// 유저 리스트 파일 출력

$ cat /etc/vsftpd.userlist

// 출력

testuser


// vsftpd 재시작

$ sudo systemctl restart vsftpd


// 콘솔에서 테스트

ftp -p 아이피주소

Output

Connected to 아이피주소

220 (vsFTPd 3.0.3)

Name (203.0.113.0:default): testuser



참조

https://www.digitalocean.com/community/tutorials/how-to-set-up-vsftpd-for-a-user-s-directory-on-ubuntu-16-04


출처 : https://www.linux.co.kr/home/lecture/index.php?cateNo=&secNo=&theNo=&leccode=247




passwd 

사용자의 패스워드를 입력및 변경하는 명령어이다. 
패스워드는 리눅스서버의 일차적인 보안을 위해 반드시 설정해야한다. 
많은사용자가 사용하는 서버에서는 가끔식 관리자의 실수로 인하여 패스워드가 설정되어 있지않은 계정이 존재하는 실수를 범할 수도 있다.

서버관리자는 계정을 생성한 후에는 반드시 패스워드를 생성해야한다.

아래와 같이 useradd로 bible2라는 계정사용자를 생성한 후에는 passwd라는 명령어로 bible2의 패스워드를 입력한다.

[root@host1 root]# 
[root@host1 root]# useradd bible2
[root@host1 root]# 
[root@host1 root]# 
[root@host1 root]# passwd bible2
Changing password for user bible2.
New password: (새로운 패스워드 입력)
Retype new password: (새로운 패스워드 재입력)
passwd: all authentication tokens updated successfully.
[root@host1 root]#

위의 예와 같이 root는 일반계정사용자의 패스워드를 마음대로 변경할 수 있다. 

하지만 일반계정사용자는 자기자신의 패스워드만을 변경할 수가 있으며 자기자신의 패스워드를 변경할 경우에도 기존의 사용중인 패스워드를 입력해야만 변경할 수 있다. 

다음의 예는 bible이라는 사용자가 자기의 패스워드를 변경하는 예이다.

[bible2@host1 bible2]$ passwd
Changing password for user bible2.
Changing password for bible2
(current) UNIX password:  (기존의 패스워드입력)
New password: (새로운 패스워드 입력)
Retype new password: (새로운 패스워드 재입력)
passwd: all authentication tokens updated successfully.
[bible2@host1 bible2]$

+ Recent posts