블로그 이미지
OSSW(Open Source System SoftWare

calendar

        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  

Notice

'1. 미들웨어이야기/06. redis'에 해당되는 글 4

  1. 2018.10.29 redis 보안
  2. 2018.10.17 redis 대량 데이터 입력
  3. 2018.07.30 지리정보와 redis
  4. 2018.06.16 rdb 테이블을 redis로

redis 보안

서버 실행

일반 사용자가 실행할 것
root 실행 금지

서버 실행 환경 설정 파일 접근 권한

chmod og-rw 환경설정파일
그룹 및 다른 사용자 읽기, 쓰기 금지

snapshot 기능을 쓰는 경우(자료를 디스크에 저장하는 경우)

서버 실행 전 해당 OS 계정 umask 값이 0066 으로 그룹 및 다른 사용자가 rdb 파일 읽기, 쓰기 금지할 것
umask 0066 && redis-server redis.conf

외부 접속을 허락 하는 경우

bind 0.0.0.0
설정을 한 경우는 반드시
requirepass 설정을 할 것, 기본값 사용 금지 - plain text 로 conf 파일을 OS 다른 사용자가 사용할 수 없도록 파일 접근 권한 조정 필수
protected-mode no 설정 금지

redis는 자체 클라이언트 접근 제어 기능이 없음으로 반드시 아주 보수적인 OS 방화벽 정책이 필요하다.

unix domain socket을 사용하는 경우

unixsocket /tmp/redis.6379.sock
설정을 하는 경우

unixsocketperm 설정에 신경 써야 한다.
서버가 실행되는 호스트를 여러 OS 계정이 함께 사용한다면,
unixsocketperm 값을 700 으로 사용해서 redis 서버를 실행한 OS 계정만 사용할 수 있도록 하거나,
다른 사용자도 해당 소켓을 사용해야한다면, 707 로 설정하되 반드시 requirepass 값을 지정해야 한다.

redis-cli 사용시

-a 옵션을 이용한 비밀번호 지정 실행 가급적 금지  - ps 명령으로 비밀번호가 보인다.
~/.rediscli_history 파일 접근 권한 0600 인지 꼭 확인해야 함




'1. 미들웨어이야기 > 06. redis' 카테고리의 다른 글

redis 보안  (0) 2018.10.29
redis 대량 데이터 입력  (0) 2018.10.17
지리정보와 redis  (0) 2018.07.30
rdb 테이블을 redis로  (0) 2018.06.16
posted by 김상기 ioseph


redis 대량 데이터 입력


이 글은

https://gist.github.com/Squab/52d42652719cc28451d7

코드의 사용법에 대한 이야기입니다.


1. redis-cli --pipe 이야기


redis에서 자료를 입력하는 방법은 여러가지가 있습니다.


가장 간단하게,

일반 텍스트 파일에 redis-cli 에서 사용하는 명령 구문 형태로 자료 입력 명령을 쭉 입력하고, OS 파이프 기능을 이용해서, 표준 출력 내용을 redis-cli 쪽 표준 입력으로 보내서 자료를 입력하는 방법입니다.



$ cat medi_code.txt
HSET medi_code 100 의료보험
HSET medi_code 110 직장
HSET medi_code 120 지역
HSET medi_code 130 "공무원 및 사립학교 교원"
HSET medi_code 200 의료보호
HSET medi_code 210 1종보호
HSET medi_code 211 거택보호자
HSET medi_code 212 시설보호자
HSET medi_code 213 국가유공자
HSET medi_code 214 월남귀순자
HSET medi_code 215 인간문화재
HSET medi_code 216 성병감염자
HSET medi_code 217 의사상자
HSET medi_code 218 이재자
HSET medi_code 220 2종보호
HSET medi_code 221 자활보호자
HSET medi_code 230 의료부조
HSET medi_code 231 기타보호자
$ time redis-cli  < medi_code.txt
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1

real    0m0.005s
user    0m0.000s
sys    0m0.004s

5ms 걸렸네요.


여기서 하나 기억해야 할 것은 redis-cli 명령에서 하나의 속성 값 안에 공백 문자가 있다면,

"공무원 및 사립학교 교원" 처럼 큰 따옴표로 둘러싸 이것이 하나의 속성 값으로 처리하도록 해야 한다는 점입니다.


문제는 이렇게 입력해야 할 자료가 100만 건 경우, 이 방법은 그 다지 좋은 방법이 아닙니다.


공동데이터포털 상가 정보 자료 (https://www.data.go.kr/dataset/15012005/fileData.do)를 대상으로

상점 고유 번호와 상점명만을 추출 해서 100만 건 자료를 만들고 자료를 위와 같은 방법으로 넣어보면,


$ wc -l kostore01.txt
1000000 kostore01.txt
$ tail kostore01.txt
HSET kostore 25779768 "청산전통시장"
HSET kostore 25612795 "원주옻진액"
HSET kostore 25619783 "일영첨성대불한증막"
HSET kostore 25673160 "호곡리손칼국수"
HSET kostore 25681491 "대광기계"
HSET kostore 25757971 "해뜨는집"
HSET kostore 25757966 "부자세탁소"
HSET kostore 25723808 "새한태권도"
HSET kostore 25773734 "선화공주"
HSET kostore 25786315 "대동설렁탕"
$ time redis-cli < kostore01.txt

...

(integer) 1

(integer) 1

real    0m48.909s
user    0m10.349s
sys    0m20.190s
$ redis-cli HLEN kostore
(integer) 1000000

49초 걸렸습니다. 초당 약 2만 건 정도의 자료가 들어갑니다. 뭐 나쁜 속도는 아니지만, 이 정도 성능을 쓰려고 redis를 사용하는 것은 아닙니다. 왜냐 하면, PostgreSQL에서도 이런 자료라면, 2초면 입력할 수 있기 때문입니다. (OS 디스크 캐시에 해당 자료가 있다고 가정 했을 때)


redis-cli 명령은 --pipe 옵션으로 이 한계를 극복합니다.

기본 개념은 입력되는 자료는 이미 redis 클라이언트-서버간 프로토콜을 저수준 양식에 맞춰있고,

client를 그냥 서버로 보내고, 서버는 받으면 그냥 저장하는 식입니다.



2. redis 자료 입력 저수순 양식


이 내용은

https://redis.io/topics/mass-insert

페이지에서 자세히 다루고 있습니다.


이것 기반으로

https://gist.github.com/Squab/52d42652719cc28451d7

페이지에서 python 코드 하나가 만들어졌고,

이것을 사용하면 됩니다.


단, python 3.x 버전에는 멀티바이트 문자 처리가 기본 유니코드로 바뀌어, 손을 좀 봐야합니다.

OS 기본 내장된 2.x 버전에서 코드 수정 없이 바로 쓸 수 있습니다.


하지만, 위에 큰 따옴표로 둘러싸는 경우 처럼 한 속성에 공백 문자가 있는 경우는


맨 마지막 줄에 있는

print proto(line.rstrip().split(' ')),

부분을

print proto(line.rstrip().split('\t')),

로 바꿔서 각 명령 속성은 탭문자로 분리한다고 변경 해서 사용하는 것이 편합니다.



자료 준비를 다시 합니다. 이번에는 tab 구분으로.


$ wc -l kostore02.txt
1000000 kostore02.txt
$ tail kostore02.txt
HSET    kostore    25008500    에코맘에프에스
HSET    kostore    24902357    햇살홈케어
HSET    kostore    24882499    이리온샵
HSET    kostore    25036790    터울림
HSET    kostore    24987409    테라아로마
HSET    kostore    20768375    강일렌트카
HSET    kostore    22079059    지에스카넷
HSET    kostore    20507522    애플빈
HSET    kostore    24995995    코리아세븐청원오송생명점
HSET    kostore    25000634    에스에이치종합상사
$ wc -l kostore02.txt
1000000 kostore02.txt
$ time python redis-mass.py /tmp/kostore02.txt | tail
*4
$4
HSET
$7
kostore
$8
25000634
$27
에스에이치종합상사

real    0m4.471s
user    0m4.519s
sys    0m0.119s
$ time python redis-mass.py /tmp/kostore02.txt | redis-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000

real    0m5.054s
user    0m5.117s
sys    0m0.411s


탭 문자로 분리된 자료를 redis 저수준 명령 구문으로 변환하는데, 약 4.6초, 전체 처리가 5초, 즉, redis 작업은 0.5초에 끝납니다.  놀랄만한 속도입니다.



3. 마치며

실무 안에서 원본 자료는 다양하게 있을 수 있을 수 있습니다.
이 경우 적당히 redis-mass.py 파일을 수정해서 redis-cli --pipe 명령이 사용할 수 있는 형태로만 만들어 낸다면,  그 나머지 처리는 엄청난 속도로 진행됩니다.


'1. 미들웨어이야기 > 06. redis' 카테고리의 다른 글

redis 보안  (0) 2018.10.29
redis 대량 데이터 입력  (0) 2018.10.17
지리정보와 redis  (0) 2018.07.30
rdb 테이블을 redis로  (0) 2018.06.16
posted by 김상기 ioseph

redis geo... 명령어들


redis에서 지리 정보를 다루는 것은 딱 좌표(2차원 점)에 대한 것만이다.


여러 지리 정보를 다루는 다른 소프트웨어에 비교하면, 참 단순하다.


공식 홈페이지의 이 관련 명령어는

https://redis.io/commands#geo

페이지를 살펴보면 된다.


국내 홈페이지인 redisgate에는 아직 다루고 있지 않는다.

그나마 joinc에서 간략하게 다루긴 한다.

https://www.joinc.co.kr/w/man/12/REDIS/geo



geoadd

좌표 추가

geopos

추가한 좌표 보기

geodist

좌표간(점들 사이) 거리 구하기

georadius... 명령어들

주변 좌표 찾기


실무에서 쓰일 예제 이야기는 다음에.

- posted by 김상기


'1. 미들웨어이야기 > 06. redis' 카테고리의 다른 글

redis 보안  (0) 2018.10.29
redis 대량 데이터 입력  (0) 2018.10.17
지리정보와 redis  (0) 2018.07.30
rdb 테이블을 redis로  (0) 2018.06.16
posted by 김상기 ioseph

관계형 테이터베이스 테이블을 redis로 옮기기


1. redis 자료형

  • 문자열, String: 일반적인 key - value, hash로 보면 된다. range, between 이런 것 없다. 그냥 오직 그 key
  • 목록, List: 그냥 배열, push & pop 용, 순서가 색인, 중복된 value, 목록 이름이 key. 하나의 목록을 샤딩할 수는 없다.
  • 집합, Set:: 중복된 value를 가질 수 없는 집합 - RDB 인덱스의 value가 된다. insert = sadd, delete = srem , index key update = smove
  • 해시, hash: RDB의 테이블, key가 테이블이름, field가 Primary Key, value가 json 칼럼들값들, 하나의 해시는 샤딩할 수 없다.
  • 정렬된 집합, sorted set: btree 인덱스 구현용, 하나의 집합을 샤딩할 수 없다. 아래에서 설명
여기서 보듯이 scale out 관점에서 문자열 자료형(key-value)을 제외하고는 모두 scale out에 유연하지 않다. 즉, 문자열 자료형을 외 자료형을 사용한다면, 분산 처리는 응용프로그램에서 독자적으로 구현해야한다. 이 부분에 대해서는 좀 더 고민해 보고 다음 글에서 다루기로 하겠다.

2. RDB 테이블을 redis로.

위에서 살펴 보았듯이, 하나의 테이블은 하나의 해시로 만들면 된다.

문제는 하나의 해시는 redis 서버에서 안쓰면 버린다는 expire 설정을 하는 것도 비효율적이다. 버렸는데, 다시 필요하다면, RDB 에서 테이블 전체를 다시 가져와야하기 때문이다.
즉, 어느 테이블을 redis 서버로 가져올 것인가는 결국 그 redis 서버에서 사용할 수 있는 메모리 크기와 직접적으로 관련된다. swap 메모리를 사용해 가면서까지 굳이 RDB 테이블을 모두 redis로 가져오겠다는 생각은 안하는 것이 좋을 것이다. 

여기서는 커뮤니티 게시판을 서비스를 대상으로 rdb를 redis로 옮기는 것을 살펴보고자 한다.

기존 rdb의 erd는 다음과 같다.


2.1. 사용자들 테이블

사용자들 테이블은 그룹들 테이블과 M:M 관계이다. redis와 같은 key-value 형태의 데이터베이스에서는 이런 다대다 관계는 문자열 키로 값은 그냥 json array로 푸는 것이 제일 편하다.

SET 사용자들:1 '{"로그인ID": "gildong", "이름": "홍길동", "비밀번호": sha256(passwd), "이메일": "user@gmail.com", "사용여부": "Y", "groups"=[1,2,3]}'

그룹들 테이블은 단순하게,

SET 그룹들:1 관리자
SET 그룹들:2 일반사용자
SET 그룹들:3 공동저자
....
이런 형태로 문자열 키로 지정할 수도 있고,

HSET 그룹들 1 관리자
HSET 그룹들 2 일반사용자
....
이런 형태의 hash 테이블 형태로 구현할 수도 있다.

redis 서버의 key 관리 측면에서는 hash 테이블 형이 유용하겠지만, 훗날 분산환경을 위해서는 위의 문자열 키 형태가 더 유용하다.

한편, 관리 차원에서 전체 회원수를 알기 위해서,

SCAN 0 MATCH "사용자들:*" count 100000000000

이런 형태의 명령을 사용하는 것은 정말 위험한 짓이다. 이런 경우를 대비에서 "totalcount::사용자들" 같은 문자열 키를 하나 할당하고, 그 값으로 전체 사용자 수를 기록하고 있는 것이 바람직하다. 이 값은 회원 가입이 있을 때, INCR, 탈퇴할 때, DECR 명령을 이용해서 증,차감 하면 될 것이다.

2.1.1. 사용자들 테이블의 인덱스들

redis에서 그 키에 대한 값을 구할 때, '=' 연산만 가능하다.
즉 >, < , between 같은 연산이 필요하다면, 하나의 정렬된 집합 키(RDBMS의 인덱스가 된다)를 만들고, ZRANGE 명령을 이용해야 한다.

사용자들 테이블에는 총 3개의 인덱스가 정의되어있다.

하나는 기본키이며, 이 키를 사용해서는 오직
= 연산으로만 검색하겠다고 결정해서, 사용자들:1, 사용자들:2... 같은 문자열키를 사용했다.
즉,
SELECT * FROM 사용자들 WHERE 사용자번호 BETWEEN 100 AND 200
같은 쿼리를 사용하지 않겠다는 것이 전제 되어 있다.

두번째 인덱스는 로그인ID에 대한 유니크 인덱스이다.
로그인ID로 검색하는 것을 오직 = 연산만 허용하겠다면,
앞에서 언급한 HSET을 이용한 hash 테이블로도 충분하다.

HSET 사용자들_로그인ID_색인 a@emai.com 1

이 hash 테이블의 키는 email, 값은 사용자 번호가 될것이다.

즉,
SELECT * FROM 사용자들 WHERE 이메일 = 'a@email.com'
형태의 쿼리 작업을 redis로 푼다면,

다음 절차를 거친다.
  1. HGET 사용자들_로그인ID_색인 a@email.com
  2. GET 사용자들:1
한편,
SELECT * FROM 사용자들 WHERE 이메일 like 'a@email%'
형태의 쿼리를 인덱스를 사용해서 처리하고자 한다면,
상황은 좀더 복잡해진다.

이것을 구현하려면, redis 정렬된 집합을 사용해야한다.

ZADD 사용자들_이메일_색인 1 a@email.com
형태로 색인자료를 만드며, email 값 앞에 있는 1은 사용자번호다.

그리고, 이 색인의 범위검색을 하려면, '[', '(' 문자를 추가해서 범위 검색을 한다.
ZRANGEBYLEX 사용자들_이메일_색인 '[a@email' '(a@emaim'
형태로 사용한다.
SQL 구문으로 바꾸면,
WHERE 이메일 >= 'a@email' and 이메일 < 'a@emiam'
이 될것이다.
즉, 이메일 like 'a@email%' 는 위와 같이 변환해서 사용해야한다.

찾으려고 하는 값인 한글인 경우는 좀 더 복잡해진다.

where 이름 like '대한민%'
이라면, '[대한민' '(대한믽' 이 될것이다. 
마지막 한 글자의 unicode 다음 글자다. 어떻게 구하는지는 스스로 공부를.

이렇게 해서, 정렬된 집합을 이용한 색인 검색이라면, 다음 절차를 거친다.
  1. ZRANGE|ZRANGEBYSCORE|ZRANGEBYLEX 사용자들_이메일_색인 최소값 최대값
  2. 위명령 결과의 배열로 해서,
    for 값 in 값배열
        ZSCORE 사용자들_이메일_색인 값
    형태로 또 사용자 번호 배열을 만들고,
  3. for 사용자번호 in 사용자번호배열
       GET 사용자들:사용자들번호
형태의 복잡한 작업을 해야한다.
물론 2번 작업을 단순화 하기 위해서, 앞에서 이야기한 이메일 기준 hash 테이블도 만들고, 그것을 조회하는 것도 한 방법일 것이다.

세번째 인덱스인 로그인ID에 대한 유니크 인덱스다.
이 인덱스도 email 인덱스 처리방식과 똑같다.

2.2 게시판 테이블

게시판 테이블도 사용자 테이블과 크게 다르지 않기 때문에 설명은 생략하고,
여기서는 드디어 작성일시 기준 비유니크 인덱스를 구현해야 한다.
즉, 같은 작성일시에, 여러개의 게시물이 존재할 수 있음을 가정한 것이다.

(쓸데 없이 옆길로 샌 이야기: 단일 redis 서버를 사용하고, 위 모델링을 실재 구현한다면,
게시물의 작성일시는 절대로 같을 수 없다. 왜냐하면, redis 서버는 단일 프로세스 단일 쓰레드로 사용자 명령을 처리하기 때문에, 모든 명령이 직렬화 된다. 즉 같은 시간이 있을 수 없다. 하지만 사용자 편의를 위해 작성일시를 초단위까지만 사용하겠다면, 충분히 같은 작성일시에 여러개의 게시물이 있을 수 있다)

이렇게 비유니크 인덱스도 정렬된 집합을 사용해서 구현한다.

2.2.1. 게시물 작성일시 색인

redis에서는 시간자료형이 없다. 작성일시 값은 결국 문자나 숫자로 저장되어야한다.
2018-07-01 12:30:25 형태의 문자로 저장하든가, 이 시간의 unixtimestamp 값으로 저장한다.

문자로 저장한다면, ZRANGEBYLEX로 범위검색을 할것이고, 숫자로 저장한다면,
ZRANGEBYSCORE로 검색하면 된다.

작성일시를 문자열로 처리하는 경우
ZADD 게시판_작성일시_색인 1 '2018-07-01 12:30:25'
형태가 될것이고, 앞에서 다룬 것과 똑같이 1은 게시판 기본키인 게시물번호다.

작성일시를 숫자로 처리한다면,
ZADD 게시판_작성일시_색인 1530621697 1
형태가 될 것이고, 1530621697 숫자는 unixtimestamp 값이고, 뒤에 1은 게시물번호가 된다.

이 색인을 이용한 =, >, <, >=, <=, between 연산은 직접 구현해야한다.


2.3 태그 테이블

태그 테이블은 단순하게 생각하면, 집합을 사용하면 된다.

SADD 태그:서울 1
SADD 태그:서울 5
SADD 태그:서울 100
....
서울이라는 태그를 지정한 모든 게시물 번호를 찾으려면,
SMEMBERS 태그:서울
형태로 게시물 번호를 요소로 하는 배열을 만들고,
그 배열의 각 요소를 기본키로해서 게시판:게시물번호 문자열 키에 대한 값을 구하면 된다.

여기서 중요한 것은 SMEMBERS는 현재, limit offset count 기능이 아직까지는 구현되지 않았기에,
한 태그의 값인 게시물번호를 모두 찾아 응용프로그램에게 넘겨준다.
그렇기 때문에, 한 키에 대한 SMEMBERS의 결과값이 많으면 많을수록 이 명령의 성능이 더 떨어진다.

3. 마무리

INSERT INTO t VALUES (.....)
=> SET t:pk 'json'

SELECT * FROM t where pk_column = pk_value
=> GET t:pk_value

SELECT * FROM t WHERE int_column >= start_value and int_column <= end_value
=> ZRANGEBYSCORE t_int_column_index start_value end_value
     loop primary_keys
        GET t:pk_value

SELECT * FROM t WHERE text_column LIKE 'find_str%'
=> ZRANGEBYLEX t_text_column_index '[find_str' '(find_sts'
     loop primary_keys
       GET t:pk_value

SELECT t.* FROM t, tags WHERE t.pk = tags.pk_value AND tags.tag = 'tag_value'
=> SMEMBERS tags tag_value
     loop pk_values
       GET t:pk_value


- posted by 김상기

'1. 미들웨어이야기 > 06. redis' 카테고리의 다른 글

redis 보안  (0) 2018.10.29
redis 대량 데이터 입력  (0) 2018.10.17
지리정보와 redis  (0) 2018.07.30
rdb 테이블을 redis로  (0) 2018.06.16
posted by 김상기 ioseph
prev 1 next