레이블이 Redis인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Redis인 게시물을 표시합니다. 모든 게시물 표시

20200415

Redis 6 ACL 운영 도입

redis 6 GA 됐고 ACL이 제일 기다렸던 기능. 지금까진 requirepass 하나로 "다 되거나 안 되거나"였는데 이제 유저 단위 권한 줄 수 있음.

설정 예.

ACL SETUSER reporter on >s3cret ~stats:* +@read
ACL SETUSER worker on >w0rkerpw ~jobs:* +@read +@write -@admin
ACL SETUSER default off

설명:

  • ~stats:* : key 패턴 제한
  • +@read : read 계열 명령만 허용
  • -@admin : admin 명령 차단
  • default off : 무인증 접속 차단

우리는 prod에서 앱 서비스별로 별도 유저 생성. reporter는 읽기 전용, worker는 쓰기/읽기, backup은 스캔 + 덤프 계열만 허용.

aclfile /etc/redis/users.acl로 외부 파일로 빼서 관리. ConfigMap에 올리고 SIGHUP으로 리로드. CONFIG REWRITE는 ACL 반영이 완전하지 않아서 aclfile 쓰는 게 낫다.

운영 팁:

  1. 패스워드는 >로 평문 넣지 말고 SHA256 해시로 # 접두어 써서 넣기
  2. MONITOR 명령은 기본적으로 큼직한 권한이라 운영 유저엔 주지 말 것. -monitor 박자
  3. slow log 같은 건 +@slow+slowlog로 따로

기존 requirepass 써드파티들은 호환 모드로 그대로 돌아감. 다만 redis-cli 쓸 때 6 이후로는 AUTH 명령이 AUTH user pass 형태로 변해서 스크립트들 수정 필요.

클러스터에도 적용하려면 각 노드에 같은 ACL 배포 필요. 아직 통합 관리는 없고 Sentinel도 따로. 이 부분은 개선 여지.

20180423

Redis 4 모듈 — RediSearch 시험

redis 4.0에서 모듈 API가 정식으로 들어오면서 RediSearch, ReJSON, ReBloom 같은 모듈이 쓸 만해졌다. RediSearch 1.0이 작년에 GA, 지금 최신이 1.0.9. 사내 관리자 페이지 제품 검색을 Elasticsearch에서 걷어낼 수 있을지 궁금해서 실험.

띄우기. 모듈 직접 빌드해서 --loadmodule로 붙이거나 redislabs 배포 이미지.

# redis.conf
loadmodule /usr/lib/redis/modules/redisearch.so

# or docker
docker run -p 6379:6379 redislabs/redisearch:1.0.9

인덱스 정의. 스키마 기반이라는 게 Elasticsearch의 mapping과 비슷. FT.CREATE로 필드별 타입(TEXT/TAG/NUMERIC/GEO)과 옵션(WEIGHT, SORTABLE, NOINDEX)을 지정.

FT.CREATE products
  SCHEMA
    name   TEXT WEIGHT 5.0 PHONETIC dm:en
    desc   TEXT WEIGHT 1.0
    brand  TAG  SEPARATOR ","
    cat    TAG  SEPARATOR "|"
    price  NUMERIC SORTABLE
    stock  NUMERIC
    loc    GEO

FT.ADD products p:1 1.0 FIELDS
  name  "acme drill 18v"
  desc  "cordless drill set with battery"
  brand "acme"
  cat   "tools|power"
  price 129.0
  stock 42
  loc   "-73.98,40.76"

FT.SEARCH products "drill @price:[50 150] @brand:{acme}" LIMIT 0 10

동작 원리 메모. 내부적으로 전형적인 역색인을 레디스 해시 자료구조로 구현. 각 토큰별로 posting list(문서 ID + 위치)를 압축해 저장하고, 쿼리 시 intersection을 byte-level로 뛰면서 스코어 계산. scoring은 기본 TF-IDF이고 SCORER로 BM25 전환 가능. 1.0.x 기준 BM25는 preview 수준이라 대체로 기본값 사용.

Aggregation 지원(FT.AGGREGATE)이 의외로 쓸 만함. GROUPBY/REDUCE로 패싯 카운트 뽑기 가능.

FT.AGGREGATE products "@brand:{acme}"
  GROUPBY 1 @cat
    REDUCE COUNT 0 AS cnt
  SORTBY 2 @cnt DESC
  LIMIT 0 20

벤치. 우리 레포 제품 110만 건.

  • 인덱싱 처리량: 약 9,500 docs/sec(1노드, 코어 4). bulk는 pipeline으로 1000건 단위. ES(5.6) 같은 머신 싱글 샤드 기준 약 14,000 docs/sec. 인덱싱은 ES가 우위
  • 검색 latency: 단일 키워드 쿼리 p50 1.3ms, p99 4.2ms. ES는 p50 3.1ms, p99 12ms. 복합 필터 있는 쿼리에서도 RediSearch가 대략 2배 빠름
  • 메모리: RediSearch가 전부 메모리. 110만 건 색인이 대략 1.9GB. ES는 JVM + OS page cache 합쳐 3GB 수준인데 디스크 기반이라 훨씬 큰 데이터를 다룰 수 있다는 장점
  • 디스크: RediSearch RDB 저장 시 850MB. ES는 세그먼트 포함 4.3GB

한계 — 우리 케이스 기준.

  • 한글 형태소 분석 없음. 1.0은 영어 + 중국어(실험적)만. 공백 기준 tokenize라 "무선 드릴"을 검색해도 "무선 드릴셋트" 같은 복합어가 매칭 안 됨. ES + nori(지금 개발 중) 혹은 은전한닢 같은 대안이 필요
  • Phonetic matching(PHONETIC dm:en)은 영어 대상. 영문 제품명 오타 내성에는 도움
  • Suggestion API(FT.SUGADD/SUGGET)는 별도 자료구조(Trie). 자동완성은 깔끔
  • 분산 검색 아직 없음. 1.0은 단일 노드. 샤드 분산은 Enterprise 전용 기능. 오픈소스 1.x에서는 앱 레벨에서 샤딩하거나 데이터 크기가 단일 노드에 맞을 때만

운영 주의. 모듈이 포함된 RDB는 동일 모듈이 로드된 노드에서만 로드 가능. master/replica가 버전 미스매치면 replication 깨짐. 배포 시 모든 노드 동일 .so 버전 고정 필수. 또 DEBUG RELOAD로 로컬 검증해도 모듈 상태까지 돌아오는지 매번 확인.

인덱스 파괴(FT.DROP)는 기본 옵션이면 색인만 제거하고 원본 해시(FT.ADD로 넣은 데이터)는 보존. 오히려 DD 플래그로 문서까지 지워야 완전 삭제. 실수 방지용으로 좋기도 하고, 인덱스만 재구성할 땐 편함.

결론: 영문 + 소규모(단일 노드에 들어가는) 검색이면 RediSearch가 latency/간결함에서 이긴다. 다국어 + 수천만 건 규모면 ES가 여전히 맞는 선택. 우리는 B2B 관리자 내부 도구 제품 검색(영문 SKU 위주, 12만 건) 한 군데 먼저 빼서 올려 보고 판단 예정.

20160407

Redis 3 클러스터 운영 메모

Redis 3.0 cluster 모드 운영 시작한지 석달. 장단점 메모.

우리 구성: 6노드 (마스터 3 + 슬레이브 3). 3노드씩 다른 서버에 분산. 해시슬롯 16384개를 마스터 3개가 나눠 가짐.

셋업

redis-trib.rb create --replicas 1 \
  10.0.1.11:6379 10.0.1.12:6379 10.0.1.13:6379 \
  10.0.1.11:6380 10.0.1.12:6380 10.0.1.13:6380

예전엔 redis-trib.rb (ruby) 로만 가능했는데 곧 redis-cli에 cluster 명령 통합될거라고 함.

클라이언트. redis-py의 경우 redis-py-cluster 별도 라이브러리 써야 함. MOVED 응답 받으면 클라이언트가 다른 노드로 재전송. 일반 redis-py로는 cluster 못 씀.

제약사항:

  • multi-key 연산은 모든 키가 같은 슬롯에 있을 때만 동작. MSET key1 v1 key2 v2 에서 key1/key2가 다른 마스터면 에러
  • hash tag로 같은 슬롯에 묶을 수 있음: user:{123}:profileuser:{123}:settings 는 {123} 기반으로 같은 슬롯
  • Lua 스크립트도 같은 슬롯 키만 가능
  • SELECT (DB 선택) 안됨. 0번 DB만 씀

장애 시나리오 경험담: 마스터 노드 하나가 메모리 부족으로 죽음. Redis sentinel 기반이 아니라 cluster 모드는 노드들끼리 gossip protocol로 감시. cluster-node-timeout (우리 설정 15초) 지나면 슬레이브가 마스터로 승격. 대충 20초 정도의 다운타임.

클라이언트 앱에서 이 기간 동안 에러를 어떻게 처리할지가 포인트. 우리는 재시도 로직을 래퍼에 넣어놔서 사용자 체감은 거의 없었음. 다만 재시도는 "읽기" 에만 적용. 쓰기는 retry하면 중복될 수 있으니 조심.

아쉬운 점: 샤드 리밸런싱이 아직 손이 많이 감. 자동 rebalance가 기본 활성화 아님. redis-trib.rb reshard 수동 실행. 클러스터 노드 추가/제거도 운영 절차 나름 복잡. AWS ElastiCache 쓸까 이런 고민도 함.

20140125

Redis 2.8 persistence 옵션 비교

Redis 2.8 새로 깔면서 RDB, AOF, 둘 다 중 뭘 고를지 꽤 오래 고민. 정리해둔다.

RDB는 포인트-인-타임 스냅샷. save 900 1, save 300 10 같은 조건 만족하면 bgsave 트리거. 내부적으로 fork() 떠서 자식 프로세스가 Copy-On-Write로 메모리 스냅샷을 dump.rdb로 덤프. 장점은 파일이 작고(LZF 압축) 복구가 매우 빠름. rdb 로드는 메모리에 순차 write라 10GB도 몇 분이면 올라온다. 단점은 마지막 스냅샷 이후 분 단위 데이터가 날아갈 수 있다는 점.

AOF는 모든 쓰기 커맨드를 파일에 append. 복구할 땐 서버 시작 시 커맨드를 재생해서 상태 복원. appendfsync가 핵심.

  • always: 매 커맨드마다 fsync. 안전하지만 느림. SSD여도 TPS가 2~3배 떨어짐
  • everysec: 초당 한 번. 장애 시 최대 1초 손실. 기본값이고 실용적
  • no: fsync는 OS가 알아서. 빠르지만 데이터 손실 범위가 불명확(수십 초 가능)

AOF 파일은 시간이 갈수록 커지니까 BGREWRITEAOF로 현재 메모리 상태를 복원하는 최소 커맨드 집합으로 압축 재작성. 2.4부터 들어간 기능인데 2.8에서는 조건부 자동 rewrite(auto-aof-rewrite-percentage)가 안정화. 이 rewrite 역시 fork 떠서 자식이 수행.

2.8 신기능: RDB + AOF 병행. 원래도 둘 다 켤 수 있었는데 AOF를 우선 로드하는 규칙이 명확해짐. 재시작 시 appendonly.aof → dump.rdb 순. 더 안전한 쪽(AOF)이 기본 선택.

우리 서비스는 캐시 + 세션 저장소. 세션은 날아가도 재로그인으로 복구 가능하고, 캐시는 정의상 휘발성. 대신 재시작 부팅 시간이 민감해서 RDB만 선택. AOF는 끈다.

save 900 1
save 300 10
save 60 10000
rdbcompression yes
rdbchecksum yes
stop-writes-on-bgsave-error yes
dir /var/lib/redis/
dbfilename dump.rdb
appendonly no

# 2.8에서 유용한 옵션
repl-diskless-sync no   # 마스터→슬레이브 풀싱크를 디스크 경유
maxmemory 12gb
maxmemory-policy allkeys-lru

stop-writes-on-bgsave-error yes — 디스크 풀이면 bgsave 실패하는데, 이 옵션 on이면 redis가 client의 쓰기를 거부함. 알림 안 걸어놓으면 서비스 전체가 죽은 것처럼 보인다. 모니터링에 rdb_last_bgsave_status 체크 걸기 필수.

fork + COW 함정. bgsave 시작할 때 fork() 떠서 자식이 RDB 쓰기를 수행. 부모는 계속 요청 처리. COW라 실제 복사는 "수정된 페이지"만 일어나는데, 쓰기 트래픽이 높으면 짧은 시간에 메모리 점유량이 순간 2배 근처까지 튄다. 피크 메모리 여유가 없으면 OOM killer한테 잡히거나 swap 타면서 latency 급등.

대응: vm.overcommit_memory=1 커널 파라미터 필수(안 켜면 fork 실패). 스왑은 끄는 쪽이 낫고(장애로 swap 타면 차라리 빨리 죽는게 나음) Transparent Huge Pages도 꺼 둔다(echo never > /sys/kernel/mm/transparent_hugepage/enabled). THP가 켜져 있으면 2MB 페이지 단위라 COW 증폭 효과가 커진다.

모니터링용 INFO 값 메모.

  • rdb_changes_since_last_save: 마지막 저장 이후 쓰기 건수
  • rdb_bgsave_in_progress: 현재 bgsave 중인지
  • rdb_last_bgsave_time_sec: 지난 bgsave 소요 시간. 이게 10초 넘으면 슬레이브 replication에 영향
  • used_memory_peak vs used_memory: fork 피크 추정

일단 이 설정으로 하루 돌려보고, 문제없으면 슬레이브 한 대 붙여서 복제 구성까지 붙일 예정. slave-read-only yes로 두고.