티스토리 뷰

사전 안내

Rails에서 Redis를 다루는 법은 2개의 포스트로 나뉘어서 설명하겠습니다.
현재 포스트는 Rails에서 지원되는 Cache 메소드를 활용해서 Redis 개발을 할 수 있는 방법론을 다룹니다.

하지만 Redis의 문법(get, set 등)을 직접적으로 사용하지 않다보니, Redis 문법 개념이 좀 둔해질 수 있다는 단점이 존재합니다.

Redis의 문법을 직접 사용하면서, Redis 내부에서 어떠한 Flow로 캐싱이 되는지 보고 싶으신 분은 여기를 클릭해서 참고해주세요.

[Step 1]기존의 데이터베이스-서버 통신방식

과거의 서버와 클라이언트의 방식은 와와같은 흐름으로 이루어졌었습니다.

웹서버가 Database에 바로 참조를 하고 하는 방식이었는데, 위 방식은 딱 심플하고, 복잡(?)한게 없긴 한데, 한가지 문제점이 있습니다.

 

너무 많은 서버의 요청으로 인해 잦은 DB 접근을 할 시, (과도한) 부하로 인해 DB가 죽거나 속도가 느려질 수 있다는 문제점이 있습니다.

 

[Step 2] Redis에 데이터를 캐싱하는 방식

그래서 나오게 된 대안은 Redis 서버입니다.

Redis는 서버의 메모리에 데이터를 저장하는 방식입니다.

 

최초에 웹서버에 접속할 때에는 DB서버를 참조를 하지만, 이 때 DB서버를 참조를 하면서 메모리에 Key-Value로 이루어진 데이터를 저장하게 됩니다.

그리고 두번 째 부터는 Key를 참조해서 Value를 불러오는 방식입니다.

 

아까부터 제가 Key와 Value를 언급하고 있는데, 이게 뭔 소리인지 이해가 안가는분들이 있을텐데, 그림으로 표현하면 다음과 같습니다.

Key와 Value는 서로 한 페어로 이루어져 있는데, 위 도식과 같이 만약 '사용자로부터 주간 핫딜 요청'이 들어올 경우, 과거와 같이 DB에 직접 탐색을 하는게 아닌 사전에 Redis 메모리에 Key-Value를 등록한 값을 통해 조회를 하는 방식입니다.

 

Key와 Value를 가장 근접하게 볼 수 있는 일상생활 속 예시 : 색인

더 쉽게 나아가, 일상생활 속에 예시가 있다 하면 책의 뒷부분에 있는 색인이 좋은 예시라고 보면 되겠습니다.

 

이번 Redis 기술 적용을 통해 얻을 수 있는건 데이터를 불러옴에 있어서 큰 속도개선이 이루어질 수 있습니다.

 

91ms → 5ms로 줄어든 속도, 더군다나 캐싱이 된 데이터(두번 째 통신)는 SQL 탐색을 안합니다.

 

Redis CacheString, List, Set, Sorted set(zadd), Hash 등 다양한 방식으로 저장될 수 있으며, 더 나아가 확률적 알고리즘으로 불리우는 하이퍼로그로그(클릭) 방식으로도 Redis Key 저장을 할 수 있습니다.

 참고  하이퍼로그로그는 unique item에 대한 카운팅을 하는 알고리즘으로서, 정확한 수의 파악보다는 '약 n개' 라는 개략적인 수의 파악에 쓰입니다.

하이퍼로그로그는 정확성이 떨어지는 대신(약 3% 오차가 난다고 합니다.), 카운팅에 할애되는 메모리가 크게 줄어든다는 장점이 있습니다. 

 부록  NAVER D2 : 하이퍼로그로그 알고리즘 소개

 

하이퍼로그로그 기법과 HashSet 기법을 이용한 소설 셰익스피어 소설에서 나오는 '유일한 영어단어 갯수 카운팅'에 소비되는 cost / 이미지출처 : 네이버 D2 블로그

 

이번 글에서는 기본적으로 String 형식으로 데이터를 저장하는 방법에 대해 알아보겠습니다.

사전에 Redis에 String 형식의 Key-Value 저장방식의 단점을 말씀드리자면, Redis에 쓰이는 메모리 및 공간의 할당이 Hash에 비해 많이 발생한다는 겁니다.

 부록  Redis에 심플한 key-value 로 수 억개의 데이터 저장하기

 

앞에 말했다 싶이 원래 Redis는 String 형식의 Key/value 외에도 Hash, List, Sorted Set 등 다양한 방법으로 표현이 가능하나, 해당 글에서 소개될 방법은 String을 통한 표현만 지원되는 것 같아보입니다.

Hash 등 다양한 표현을 통한 Redis를 사용하고 싶다면, 이전에 작성했던 글을 참고해주세요.

 부록  Ruby on Rails : 데이터 캐싱 (Redis, 직접적인 redis 문법 활용)

 

  • Chapter 1 : Redis 설치

일단 Rails에서 Redis를 활용하기 전, 서버 내에 Redis를 설치해줘야 합니다.

 참고  AWS EC2의 Ubuntu 환경을 기준으로 설치방법 설명이 진행됩니다.

 

1. 외부 서버로부터 redis 설치 파일을 받아온 후, Redis를 설치합니다.

cd /tmp
wget http://download.redis.io/releases/redis-4.0.0.tar.gz
tar xzf redis-4.0.0.tar.gz
rm -rf redis-4.0.0.tar.gz
cd redis-4.0.0
make

 

2. 서버 내에 Redis 공간을 마련해줍니다.

그리고 실행파일(redis-server)과 Redis 설정파일(redis.conf)을 Copy/Paste 합니다.

sudo mkdir /etc/redis 
sudo mkdir /var/lib/redis
sudo cp src/redis-server src/redis-cli /usr/local/bin/
sudo cp redis.conf /etc/redis/

 

3. AWS EC2에서 Redis-server 이 돌아가게 하도록 EC2에 맞춘 redis-server 패키지 설치를 진행합니다.

cd /tmp
wget https://raw.github.com/saxenap/install-redis-amazon-linux-centos/master/redis-server

 

4. redis-server을 실행합니다.

redis-server /etc/redis/redis.conf

 참고1  redis-server 종료는 키보드에서  Ctrl+C  키를 눌러주면 됩니다.

 참고2   /etc/redis/redis.conf  는 redis 설정을 하는 파일입니다.

redis 파일을 설정할 일(redis 암호설정 등)이 있으면  /etc/redis/redis.conf  파일에서 하면 됩니다.

 

 

  • Chapter 2 : Rails에서 예제 프로젝트 생성

Ruby on Rails로 돌아와서, 새로운 프로젝트 생성부터 Gem 설치 및 일부 설정을 해보겠습니다.

Rails 프로젝트 생성은 API 설계에 집중된 api mode로 설치가 진행될 것이며, 이미 Ruby 및 Rails가 설치되었다는 가정하에 설명이 진행됩니다.

 

해당 부분을 Skip 후, Redis 설치 과정을 보고 싶을 경우,  Chapter 3  으로 넘어가주세요.

 

1. AWS EC2(Ubuntu) 서버에 새로운 Rails 프로젝트를 설치합니다.

# rails new [프로젝트이름] --api
rails new kcm --api

 참고  Ruby 및 Rails 패키지 설정법

 

2. 간단한 Scaffold를 하나 생성해보겠습니다.

터미널에 다음 명령어를 입력해주세요.

rails g scaffold post nickname title

 

3. 새로 생성된 테이블에 대해 스키마에 최신화 합니다.

rake db:migrate

 

4. 더미데이터를 생성을 위한 준비를 해보겠습니다.

 db/seeds.rb  파일을 열람 후, 다음 내용을 입력해주세요.

## db/seeds.rb
puts "테이블에 더미데이터 입력 시작"
nickname = ["뽀롱이", "사슴", "고래", "구구", "뽀삐", "리눅스 토발즈", "우주", "영칠", "철수", "영희", "심쿵", "냐옹이", "댕댕이"]
for i in 1..30000
    Post.create(nickname: nickname.sample(1)[0], title: "안녕하세요 #{SecureRandom.hex(6)}")
end
puts "테이블에 더미데이터 입력 종료"

 

seeds.rb 파일에 등록된 코드를 터미널에 아래와같이 입력해서 실행(excute)합니다.

rake db:seed

 

5. 레일즈 서버를 킵니다.

## 아래 명령어들 중 본인 환경에 맞는 명령어로 서버를 실행

# localhost
rails s

# 3000번 포트로 서버 실행
rails s -b 0.0.0.0 -p 3000

# 80번 포트로 서버 실행
rails s -b 0.0.0.0 -p 80

 

6. 더미데이터 생성이 끝난 후, 캐싱이 되기 전 속도를 알아보고자 API 통신을 해보겠습니다.

별도의 API 통신 툴(Postman 등)이 없는 경우, 터미널에 아래 명령어 입력을 통해 API 통신을 해볼 수 있습니다.

time curl -s http://localhost:3000/posts

직접적인 SQL 데이터 조회 방식 로그기록

통신 후 Rails 콘솔에서 로그기록을 보면 서버 접속까지 4085ms(약 4초)가 걸렸습니다..

일단 Redis를 사용하지 않은 지금 상황에서 보면 좀 뭔가.. 오래걸리는게 확인이 됩니다.

* 참고로 Redis를 적용한 후, 동일한 데이터 갯수에 대해 열람 시 약 1000ms(1초) 가 걸리는게 확인이 됩니다.

 

 

  • Chapter 3 : Rails 프로젝트에 Redis 설치/설정

Chapter 2까지의 방법을 적용 후, 홈페이지에 접근을 하면 오랜 시간이 걸립니다.

이번에는 이러한 시간을 단축시켜주는 Redis-server을 적용해보겠습니다.

 참고 1   Chapter 2  의 과정을 토대로 내용이 이어집니다.

 참고 2  Redis에는 데이터를 캐싱하는 방법이 크게 3가지가 존재합니다 : String, Array, Hash

해당 방법에서는 String 방식을 통한 데이터 캐싱을 진행해보겠습니다.

 

1.  Gemfile  로 이동 후, Redis 관련 Gem을 설치합니다.

## Gemfile

# Redis-server
gem 'redis-rails'

 

2. 그리고 터미널에 아래 명령어를 입력해서 Gem을 설치합니다.

bundle install

 

3.  config/environments/development.rb  파일로 이동 후, 아래의 코드를 찾아냅니다.

## config/environments/development.rb

if Rails.root.join('tmp', 'caching-dev.txt').exist?
  config.action_controller.perform_caching = true

  config.cache_store = :memory_store
  config.public_file_server.headers = {
  'Cache-Control' => "public, max-age=#{2.days.to_i}"
  }
  else
  config.action_controller.perform_caching = false
  config.cache_store = :null_store
end

 

4. 찾아낸 코드에서 보면 아래와 같은 코드가 있을겁니다.

config.cache_store = :memory_store
...
config.cache_store = :null_stor

위 2개의 config.cache_store 코드를 주석처리(혹은 제거) 해주세요.

 

5. 저희는 Rails 프로젝트에서 cache를 설정하는 코드를 추가할겁니다.

config.cache_store = :redis_store, {
    host: "127.0.0.1",
    port: 6379,
    db: 0,
    password: "5496@31",
    namespace: "my_redis"
  }, {
    expires_in: 5.minutes
  }

 참고  가급적, password 등의 내용은 환경변수를 통해 은닉처리를 해주세요.

 부록  환경변수 개념 및 설정법

 

Rails 내에서 cache를 설정하는 코드에 대해선 아래와 같이 추가해주세요.

## config/environments/development.rb

if Rails.root.join('tmp', 'caching-dev.txt').exist?
  config.action_controller.perform_caching = true

  # config.cache_store = :memory_store
  config.public_file_server.headers = {
  	'Cache-Control' => "public, max-age=#{2.days.to_i}"
  }
else
config.action_controller.perform_caching = false
# config.cache_store = :null_store
end
  
config.cache_store = :redis_store, {
  host: "127.0.0.1",
  port: 6379,
  db: 0,
  password: "5496@31",
  namespace: "my_redis"
}, {
  expires_in: 5.minutes
}

 참고1  암호(password)는 여러분들 스타일에 맞게 입력해주세요. 다음 Chapter에서 해당 부분에 대해 redis 서버쪽에서도 설정이 이루어질 예정입니다.

 참고2  코드를 보면 expires_in 이 보이는데, 이는 기본적인 캐싱 만료시간에 대해 설정합니다.

(메모리에 캐싱 될 데이터에 대해 따로 expire time을 설정 안할 시, 기본으로 5분으로 설정됩니다.)

 참고3  가급적, password 등의 내용은 환경변수를 통해 은닉처리를 해주세요.

 부록1  환경변수 개념 및 설정법

 부록2  Redis::Namespace란?

 

6.  app/controllers/Posts_controller.rb  파일을 열람합니다.

1) index 액션 부분을 다음과같이 수정해주세요.

@posts = cache_posts_index
## app/controllers/Posts_controller.rb
def index
  - @posts = Post.all
  + @posts = cache_posts_index
  render json: @posts
end

 참고1  @post 변수에는 cache_posts_index 함수에서 발생되는 이벤트(데이터 탐색 및 캐싱)가 들어갈 예정입니다.

 참고2  cache_posts_index 함수는 향후 helper에서 동작이 이루어집니다.

 

2) class 이름 밑에 다음 코드를 추가해주세요.

include RedisCacheHelper
## app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :update, :destroy]
  include RedisCacheHelper
  
  ... (내용 생략) ...
end

 

7. 6번의 2) 과정에서 보셨다싶이 저의는 helpers/redis_cache_helper.rb 라는 helper를 연동하는 코드를 추가했습니다.

하지만 API 기반의 Rails 프로젝트에서는 helper가 기본으로 제공되지 않습니다.

 

그러므로  app  디렉터리에  helpers  디렉터리를 만들고,  helpers  디렉터리 안에  redis_cache_helper.rb  파일을 추가해주세요.

 

8.  app/helpers/redis_cache_helper.rb  파일을 열람 후, 다음 코드를 추가해주세요.

## app/helpers/redis_cache_helper.rb

module RedisCacheHelper
  def cache_posts_index
    postsData = Post.all
    
    postsJson = Rails.cache.fetch('posts', expires_in: 7.minutes, race_condition_ttl: 30.seconds) do
      postsData.to_json
    end
    
    return JSON.parse(postsJson)
  end
end

 

여기서, Rails.cache.fetch 코드에 대해서 잠깐 살펴보자면

Rails.cache.fetch('posts', expires_in: 7.minutes, race_condition_ttl: 30.seconds) do
  postsData.to_json
end

해당 코드는 처음에 redis 내부에서  key(위 코드에서는 posts)가 있는지 검사합니다.

1) Key가 존재할 경우 그대로 value를 보여줍니다.

2) Key가 존재하지 않을 경우 Redis에서 posts 라는 이름의 Key가 생성되고, 그 블록 사이에 감싸진 json 형태의 데이터는 key의 value로서 저장이 됩니다.

 

또한, 위 코드에서는 expires_in Time 이 7분으로 설정되어있는데, 만약 해당 설정이 없을경우,

 config/environment/development.rb  에서 기본적으로 설정된 시간(5분)으로 설정이 됩니다.

 

 

  • Chapter 4 : Redis 암호 설정

과거 저희는  Chapter 3  에서  config/environments/development.rb  파일에서 Redis 설정을 할 때, password를 설정을 했었습니다.

그렇다보니 지금 당장 Rails 서버를 키고, API를 시도하면 다음과 같은 오류를 겪게 됩니다.

[Redis 암호 미일치 오류] Redis::CommandError: ERR invalid password

왜냐하면 애초에 redis의 암호는 설정이 안되어있기 때문입니다.

기본적으로 redis server의 password 암호는 설정이 되어있지 않다.

이로인해 저희는 Redis의 암호를 따로 설정해줘야 할 필요가 있습니다.

 

1. 터미널에 아래 명령어를 입력해서 redis 터미널을 켜주세요.

redis-cli

 

2. redis 터미널에 접속 후, 아래 내용을 입력해주세요.

config get requirepass

위 명령어는 redis에 설정되어있는 암호를 확인하는 명령어인데, 보시다싶이 아무런 설정도 안되어 있습니다.

 

3. redis 서버의 암호를 설정해줍니다.

# config set requirepass [설정하고자 하는 암호]

config set requirepass 5496@31

암호는 여러분 스타일에 맞게 입력해주세요.

저는 redis 설정 당시, 암호를 5496@31 라고 했으므로 위와같이 입력했습니다.

 

 참고  설정하려는 암호를 잘못 입력했을 경우

(error) NOAUTH Authentication required.

암호를 잘못 입력해서 다시 입력하려 하면 위와같이 접근비허가 에러를 겪을 수 있습니다.

해당 문제에 대한 해결법은 간단하게 암호인증을 하면 됩니다.

# auth [잘못 입력한 암호]

auth 549631

암호인증을 거치면 암호 재설정 가능

암호인증 후, 암호를 재설정 해주면 됩니다.

 

4. 하지만 문제가 있습니다..

 1) redis-cli는 그대로 냅두고, redis 서버만 껏다 켜주세요.

 2) redis-cli에서 다시한번 requirepass를 확인해보세요.

redis를 껏다 키면 암호가 다시 초기화가 되어있다..

설정했던 암호가 redis 서버를 껏다키면 다시 nil값으로 초기화 되어있습니다.

 

5. redis가 킬 때 부터 암호가 사전에 설정되게 해야합니다.

터미널에서 아래 명령어를 입력해서  redis.conf  파일을 열람해주세요.

sudo vi /etc/redis/redis.conf

 

6.  /etc/redis/redis.conf  파일을 열람 후, 약 500번 째 줄에 보면 암호설정에 대한 안내가 있습니다.

## vi /etc/redis/redis.conf

# requirepass [암호]
requirepass 5496@31

다음과 같이 암호를 설정 후, 파일을 저장하고 나가주세요.

 참고  파일 저장방법은 키보드에서  Shift + :  키를 누른 후, wq 를 입력 후 엔터

 

7.  redis.conf  파일을 설정 후, 암호가 잘 설정이 되었는지 확인해보겠습니다.

get hanguel

아무 Key에 대해서 조회를 시도하면 AUTH 에러가 발생할겁니다.

AUTH 에러가 발생하면 성공입니다.

 

 

  • Chapter 5 : API 통신 시도 및 구현의 문제점

서버 오픈

1. Rails 서버를 켜주세요. (이미 켜져있을 경우 껏다 켜주세요.)

## 아래 명령어들 중 본인 환경에 맞는 명령어로 서버를 실행

# localhost
rails s

# 3000번 포트로 서버 실행
rails s -b 0.0.0.0 -p 3000

# 80번 포트로 서버 실행
rails s -b 0.0.0.0 -p 80

 

2. Redis 서버를 켜주세요. (이미 켜져있을 경우 껏다 켜주세요.)

 참고  screen[클릭] 을 활용해서 redis server을 킬 것을 추천드립니다.

redis-server /etc/redis/redis.conf

 

통신 시도

별도의 API 통신 툴(Postman 등)이 없는 경우, 터미널에 아래 명령어 입력을 통해 API 통신을 해볼 수 있습니다.

time curl -s http://localhost:3000/posts

통신 로그기록

API 통신 시도 시, Redis 메모리에 기록되기 전 상황인 빨간 네모 때에는 5114ms 시간이 걸리는게 확인됩니다.

 

하지만 Redis에 기록된 후, 또 접속을 시도하면(초록네모) 이번엔 1407ms로 시간이 줄어들 뿐만 아니라 SQL 탐색없이 결과물을 보여주는게 확인이 됩니다.

 

 

문제점

' 메모리에 캐싱이 된 후, 데이터가 삭제 혹은 수정된다면? '

 

현재까지의 개발을 봤을 때, 위와같은 상황이 생길 경우, 아래와 같은 문제를 겪게될 수 있습니다.

 

1. 아래의 결과는 Redis에 캐싱 후 보여지는 데이터 입니다.

 

2. 위 상태에서 데이터를 하나 삭제(id: 1) 합니다.

 

3. 더불어, 다른 하나는 제목을 수정(id: 2) 합니다.

 

4.다시 데이터를 조회합니다.

하지만 데이터를 지웠음에도 불구하고 캐싱이 된 상태의 데이터가 그대로 보여지고 있습니다.

 

5. redis-cli에서 이전에 메모리에 등록했던 Key를 제거합니다.

## redis-cli

# del [Key]
del posts

 

6. 다시 조회 및 redis 캐싱을 해보면 이젠 최신 결과가 보여지는게 확인됩니다.

 

7. 결국에는 메모리에 올려진 데이터에 대해 최신의 데이터가 보여지도록 관리를 해야한다는건데, 이를 어떻게 해결해낼지는 다음  Chapter 6  과정에서 안내하겠습니다.

 

 

  • Chapter 6 : 이미 캐싱된 데이터를 최신 업데이트

캐싱된 데이터에 대해 최신 데이터가 보여지도록 관리하는 법에 소개하겠습니다.

 

1. 과거  Chapter 3  의 8번 과정의 코드를 갈아엎을 필요가 있습니다.

## 과거 Chapter 3 코드
## app/helpers/redis_cache_helper.rb

module RedisCacheHelper
  def cache_posts_index
    postsData = Post.all
    
    postsJson = Rails.cache.fetch('posts', expires_in: 7.minutes, race_condition_ttl: 30.seconds) do
      postsData.to_json
    end
    
    return JSON.parse(postsJson)
  end
end

 참고  cache.fetch는 Redis Key가 있는지 검사 및 없을 경우 Key를 생성하는데, 즉 Write/Read 역할을 동시에 담당합니다.

일단 위 코드는 다음과 같은 시나리오로 캐싱이 이루어집니다.

  1. posts_redis 변수를 정의하고, posts 라는 이름의 redis key가 있는지 찾아낸다.

  2. posts Key가 없을 경우, 현재 존재하는 데이터들은 posts 라는 이름의 redis key 생성 및 캐싱 유효시간을 7분으로 설정한다.

  3. 레일즈 콘솔 등을 이용해서 데이터를 수정/삭제를 해보자.

  4. 데이터를 수정/삭제 후 또다시 API 통신 시, (1번과정과 동일하게) posts Key가 있는지 시 탐색한다.

  5. 하지만 이미 posts Key가 존재한다. 그러므로 set/expire 없이 바로 value(2번 과정에서 캐싱했던 데이터)가 보여진다.

위 시나리오대로 캐시데이터가 조회되다 보니 캐싱 을 한 후, 데이터 수정/삭제을 하고 다시 key에 대해 재조회를 하더라도 계속 수정/삭제 전의 데이터가 담겨져 있는 value가 보여지게 됩니다.

 

이에대한 해결법으로선 탐색되는 데이터의 갯수 및 최신 수정기간(updated_at)에 대해서도 key에 정보를 담아둬야할 필요가 있습니다. 

 

2. redis Key 검사 및 생성에 대한 코드가 적혀있는  app/helpers/redis_cache_helper.rb  파일을 다음과같이 수정해보겠습니다.

postsJson = Rails.cache.fetch(Post.cache_in_redis, expires_in: 7.minutes, race_condition_ttl: 3) do
  postsData.to_json
end

위 코드를 아래 위치부분에 적용해서 수정하면 됩니다.

## app/helpers/redis_cache_helper.rb

module RedisCacheHelper
  def cache_posts_index
    postsData = Post.all
    
    - postsJson = Rails.cache.fetch('posts', expires_in: 7.minutes, race_condition_ttl: 30.seconds) do
    + postsJson = Rails.cache.fetch(Post.cache_post_index, expires_in: 7.minutes, race_condition_ttl: 30.seconds) do
      postsData.to_json
    end
    
    return JSON.parse(postsJson)
  end
end

코드가 살짝 변화가 생겼는데, 이제는 바로 헬퍼에서 'posts' 라는 이름의 key가 생성되지 않고, 또 cache_post 라는 메소드로 이동이 됩니다.

그리고 이제 cache_post 내에서 Redis에 Key가 쓰여질 예정입니다.

 참고  메소드 이름은 꼭 cache_post_index 가 아니여도 됩니다. 여러분들 스타일에 맞게 이름을 바꾸셔도 됩니다.

 

3.  app/models/post.rb  파일로 이동 후, 데이터 캐싱 역할을 맡는 메소드를 추가해보겠습니다.

def self.cache_post_index
  sql = "SELECT MAX(updated_at) max_updated_time, COUNT(updated_at) total_count FROM (SELECT posts.updated_at FROM posts) E;"
  latestData = ActiveRecord::Base.connection.execute(sql)

  {
    serializer: "posts",
    noticeDataTotalCount: latestData.first["total_count"] == 0 ? 0 : latestData.first["total_count"],
    latestDataUpdate: latestData.first["max_updated_time"].nil? ? nil : latestData.first["max_updated_time"].to_time.strftime('%Y-%m-%d %H:%M:%S UTC+0')
  }
end

 

위 코드를 Post Model 파일안에 아래와 같이 넣어주세요.

## app/models/post.rb

class Post < ApplicationRecord
  def self.cache_post_index
    sql = "SELECT MAX(updated_at) max_updated_time, COUNT(updated_at) total_count FROM (SELECT posts.updated_at FROM posts) E;"
    latestData = ActiveRecord::Base.connection.execute(sql)

    {
      serializer: "posts",
      noticeDataTotalCount: latestData.first["total_count"] == 0 ? 0 : latestData.first["total_count"],
      latestDataUpdate: latestData.first["max_updated_time"].nil? ? nil : latestData.first["max_updated_time"].to_time.strftime('%Y-%m-%d %H:%M:%S UTC+0')
    }
  end
end

 참고  데이터들 중 최신 updated_at 탐색을 SQL 말고 ORM 방식으로 쓰고싶다면? [클릭]

 

이제 위 코드를 통해 달라진 경우가 있다면, key에 추가적인 정보를 넣어둔다는 것입니다.

이를테면, 과거에는 단순히 Redis key 이름이 posts였다면, 현재는 다음과 같은 형태로 바뀝니다.

totalDataCount = 15
updateTime = "2020-04-05 13:55:51 UTC+0"
{
  serializer: "posts",
  noticeDataTotalCount: latestData.first["total_count"] == 0 ? 0 : latestData.first["total_count"],
  latestDataUpdate: latestData.first["max_updated_time"].nil? ? nil : latestData.first["max_updated_time"].to_time.strftime('%Y-%m-%d %H:%M:%S UTC+0')
}

=> "my_redis:postsTotalCount=15:latestUpdate=2020-04-05 18:10:38 UTC+0"

이제 key에 데이터의 수정날짜와 전체 데이터 갯수에 대한 정보가 들어갔으니, 수정/삭제에 대해서 대처를 할 수 있습니다.

 참고  Key에 유일성을 띄는 부연설명은 많으면 많을수록 좋습니다.

 

3. Model 파일을 수정 후, 다시 API 통신 / 중간에 데이터 수정 / 다시 API 통신 을 해보면 아래와같은 방식으로 통신이 오가는것을 확인할 수 있습니다.

* curl로 API 통신 시도 시, 최신 코드가 반영된 새로운 터미널을 켜서 해주세요.

비록 MAX와 COUNT를 위해 SQL 쿼리에 대해서는 고정적으로 돌아가는게 있긴 하나, 그래도 기존의 캐싱된 데이터가 수정/삭제가 될 때 마다 캐싱데이터를 최신화가 이루어진다는 것을 확인할 수 있습니다.

 

4. 이제는 캐싱 후 데이터가 수정/삭제 후 열람을 하더라도 최신데이터가 보여지게 하도록 할 수 있게 되었습니다.

redis 적용 후 서버 통신 과정 및 SQL 로그기록

 

 

  • 참고 : Redis 메소드 소개

Redis에는 다양한 메소드가 존재합니다.

메소드에 대한 실습은 Redis의 콘솔인 redis-cli 에서 진행해주세요.

 

1. String 형식으로 Key 및 Value 생성

 참고1  String 형식의 Key에 Value는 512MB 까지만 담아낼 수 있습니다.

## set "[Key]" "[Value]"

set "animal" "rabbit"
# => OK

 참고2  똑같은 Key 이름에 대해 덮어씌어서 적용할 수 있습니다.

## set "[Key]" "[Value]"

set "animal" "rabbit"
# => OK

set "animal" "cat"
# => OK

redis get "animal"
# => "cat"

 

2. Key를 통한 Value 확인

이전에 set으로 등록한 Key를 조회할 수 있습니다.

## get [Key]

get "animal"
# => "rabbit"

 

3. Key 시간 설정

이전에 등록한 Key와 Value에 대한 유효시간(기간)을 설정할 수 있습니다.

## expire [Key] [seconnd 단위의 integer]

expire "animal" 60
# => (integer) 1

 참고  시간이 말소되면 더이상 Redis에서 Key 호출이 안됩니다.

 

4. expire Time 확인

 참고  초 단위로 Return

 참고  -1 : expire 미설정 / -2 : 없는데이터

## ttl [Key]

ttl "animal"
## expire을 1분 20초로 설정하고 8초 뒤 확인할 경우
# => 72

 

5. Key 제거

사전에 등록된 Key를 제거합니다. 이미 Key가 있는 상태일 시 1을 반환, 없을 시 0을 반환합니다.

## del [Key]

del "posts"
# => 1(Key 있었음) 혹은 0(Key 없었음)

 

6. 모든 Key 제거

redis에 등록되어있는 모든 key를 지웁니다.

 참고  모든 Key가 지워지기 전, key/value 기록이 dump 파일로 백업이 된 후, 지워집니다.

# redis-cli

flushall
# bash 터미널

ec2-user:~/environment (master) $ redis-cli flushall

 

 
  • 참고 : 다른 방법으로 최근 update 시간 얻어내기

 Chapter 6  의 2번 과정에서 최근 기록을 구할 때 저는 직접적으로 SQL문을 사용했었습니다.

sql = "SELECT MAX(updated_at) max_updated_time, COUNT(updated_at) total_count FROM (SELECT posts.updated_at FROM posts) E;"
latestData = ActiveRecord::Base.connection.execute(sql)

 

하지만 SQL문을 초보자가 다루기에는 어려운 방법이 될 수 있습니다.

'나는 하나의 테이블 컬럼(updated_at)에 대한 기록만 필요하다' 라고 한다면, 아래와같은 ORM 방법으로 표현흘 해낼 수가 있습니다.

# [테이블].all.maximum(:updated_at)
Post.all.maximum(:updated_at)

 

하지만 위 ORM 방식에 있어 유의사항이 있습니다.

유의사항을 언급전에 있어, 레일즈에서 ORM 사용에 있어 데이터의 표현방식에 대해서 알아야 할 필요가 있습니다.

 

id값을 기준으로 하여 1번 째 부터 시작해서 데이터를 하나 불러오는 메소드를 기반으로 설명해보겠습니다.

 

1) Active Record::Relation

 

limit 메소드 같은 경우는 Active Record 형식으로 데이터를 반환한다는 특징이 있습니다.

 

2) Array

 

first 메소드 같은 경우는 Array 형식으로 데이터를 반환한다는 특징이 있습니다.

 

해당 본문에서 논의하고자 하는 점은, 위 Active Record와 ORM의 차이점 중 하나인 메소드 지원입니다.

다시 본론으로 돌아와서, 저희는 모든 데이터들 중, 가장 최근에 수정된 날짜의 정보를 가져옴에 있어서 아래와 같은 ORM 코드를 안내했었습니다.

Post.all.maximum(:updated_at)

 

 참고  모든 데이터를 불러오는 all 메소드 같은 경우는 Active Record::Relation 형식입니다.

 

여기서, maximum 메소드를 Array 형식으로 return하는 first 메소드와 함께 써낸다면?..

Post.first(3).maximum(:updated_at)

 

최대값을 찾는 maximum 메소드는 Array 상태에서는 못쓴다는 문제점이 있습니다!

해당 부분을 유의하면서 ORM을 사용하시길 바랍니다.

 

 

  • Redis의 장/단점

장점

1. 빠른 데이터 조회 속도

이 글을 보면서 중간에 많이 언급하긴 했지만, 사전에 미리 데이터를 메모리에 저장을 하고, 이를 불러오는 방식이다보니 정말 빠릅니다.

 

2. DB에 부담이 덜 가는 방식이다 보니 DB가 안정적

 

 

단점

1. 메모리를 2배 이상 사용

Redis 역시 메모리 기반이다보니 메모리를 2배이상 쓰이게 됩니다.

 

2. 효율적인 캐싱 데이터 관리가 요구된다.

캐싱데이터를 제대로 관리를 못할 경우 방금 위에 언급한 문제(실제 DB 데이터와 캐시 데이터가 동기화가 안됨)가 발생된다.

 

 

  • 참고 : Redis Key version 관리

 부록  Recyclable cache keys in Rails

이 글을 집필한 후에 알아낸 사실인데, Redis에서 자동으로 Key 이름 지정에 있어 데이터 갯수와 최근 updated_at에 따라 Key 버전을 관리해주는 메소드가 존재했습니다.

 

해당 방법 역시 Key 저장 시, String 형식으로 저장이 되는 방식입니다.

post = Post.all
Post Load (0.2ms)  SELECT  "posts".* FROM "posts" LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Post id: 1, nickname: "사슴", title: "안녕하세요 9197143724db", created_at: "2020-04-06 08:48:36", updated_at: "2020-04-06 08:48:36">, #<Post id: 2, nickname: "뽀롱이", title: "안녕하세요 d4d26f6f908f", created_at: "2020-04-06 08:48:36", updated_at: "2020-04-06 08:48:36">, #<Post id: 3, nickname: "댕댕이", title: "안녕하세요 3eda0b186360", created_at: "2020-04-06 08:48:36", updated_at: "2020-04-06 08:48:36">]>

post.cache_key
(0.2ms)  SELECT COUNT(*) AS "size", MAX("posts"."updated_at") AS timestamp FROM "posts"
=> "posts/query-6a9e0665e555c7a454e32bd828c16818-3-20200406084836988320"

## Post updated_at을 현재 시간 기준으로 수정
post.find(1).touch
Post Load (0.2ms)  SELECT  "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  (0.1ms)  begin transaction
 Post Update (2.1ms)  UPDATE "posts" SET "updated_at" = ? WHERE "posts"."id" = ?  [["updated_at", "2020-04-07 20:17:29.933028"], ["id", 1]]
  (10.0ms)  commit transaction
=> true 

## 콘솔을 껏다킨 후 재확인 해볼 것.
post.cached_key
(0.2ms)  SELECT COUNT(*) AS "size", MAX("posts"."updated_at") AS timestamp FROM "posts"
=> "posts/query-6a9e0665e555c7a454e32bd828c16818-3-20200407201914142017"

 

사용자 구분 없이 모든 사람들에게 똑같은 정보를 토대로 보여지는 cache 방식이라면 해당 방법이 더 나을 수도 있습니다. 자세한 내용은 Recyclable cache keys in Rails 에서 직접 확인해주세요.

 * 사용자 구분이 있는 예 : 유저에 따라 북마크 체크 여부를 보여주는 것

 

 

  • race_condition_ttl

문서 작성예정

 

 

  • 읽을거리

1. 테이블 인덱싱 VS Redis 캐싱

2. Redis 데이터 스트럭처

3. 우아한 Redis 세미나 후기

4. ZRANK : SKIP LIST Real Time Sorting Algorithm

5. 디스크 기반 Skip list를 사용한 대용량 실시간 랭킹 : WoW 랭킹 서비스 wowz.kr를 사례로

6. 이것이 레디스다 (레디스 기초지식 시리즈)

7. [Redis] 운영에 필요한 최소한의 지식

8. 아주 기초적인 Redis 개념

 

  • 자료 참고

1. How to effectively cache json in API Rails app

2. Redis에 심플한 key-value 로 수 억개의 데이터 저장하기

3. cache.fetch 개념

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
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
글 보관함