parm530
3/13/2018 - 7:46 PM

Redis

What is redis and how to use it in rails ...

REDIS

  • REmote DIctionary Server
  1. key/value store
  2. Built-in support for data structures like hashes
  3. Performs well as a cache store and a full-fledged NoSQL data store

Installing Redis

  • brew install redis on OSX
  • To have launchd start redis now and restart at login: brew services start redis
  • If you don't want/need a background service: redis-server /usr/local/etc/redis.conf (use!)
  • Use in a rails app: gem install redis

How does caching work?

  • Disk access is expensive!
  • Disk access also negatively impacts performance!
    • To counter this, implement a caching layer between your application and database server
  • Initially, a caching layer doesn't contain any data at first!
    • When it receives the request for the data, it calls the database and stores the result in memory
    • Later requests won't need to access the database because the data is already stored in the cache!

Model Caching using Redis

  • Create a simple app using scaffold Snippet content:text

  • In this example, redis will be used to load snippets from Rails rather than using a SQL db.

  • For loading a page that contains many records, it may take a very long time to load, Redis can be used to load the records drastically faster

  • Once the app is created and migrations are ran, install the following gems:

    • gem 'rack-mini-profiler': displays loading time in upper left corner of browser
    • gem 'faker': used to fill in dummy data to create records faster
    #example of using faker in seed.db
    100000.times do 
      Snippet.create(content: Faker::Lorem.paragraph)
    end
    
  • bundle install

  • bundle exec rake db:seed

  • Change root to snippets#index

  • Run rails s

  • Notice the loading time of rack-mini-profiler!


Using Redis as a cache store

  • Open terminal, in one window run redis-server
    • Make an alias in your bash_profile: redis-server /usr/local/etc/redis.conf
  • Add the following gems to Gemfile:
    • gem 'redis'
    • gem 'redis-rails'
  • bundle install
  • In config/development.rb, you can instruct Rails to use redis as a cache store: (or even production.rb)
module nameOfApp
  class Application < Rails::Application
    config.cache_store = :redis_store, 'redis://localhost:6379/0/cache', {expires_in: 90.minutes}
    # redis has 16 databases to choose from (0-15), represents the 0 before cache
    # cache represents the namespace
    # you can configure host, port and other db:
    # Redis.new(:host => '10.0.1.1', :port => 6380, :db => 15)
    # Alternatively: 
    config.cache_store = :redis_store, {
      host: 'localhost',
      port: 6379,
      db: 0,
      namespace: 'cache'
    }
  end
end
  • Restart your app any time you makes changes to these files!

Application Modifications

  • Configuring and setting up redis is now complete!
  • Instead of loading records from the SQL database, head in to a controller that loads records:
#include the controller helper folder, code will be ran inside there
# example SnippetsController.rb
def index
  @data = fetch_snippets
end

#helpers/snippets_helper.rb

module SnippetsHelper
  def fetch_snippets
    snippets = $redis.get("snippets")
    if snippets.nil?
      #IMPORTANT: 
      # the first time around snippets will be nil, redis hasn't put anything in yet
      snippets = Snippet.all.to_json
      
      #Now redis is inserting the array of snippets in a hash with key "snippets"
      # from the database access, so that next time it can just bypass the db and use its store 
      $redis.set("snippets", snippets)
      
      #Expire the cache every 5 hours
      $redis.expire("snippets", 5.hour.to_i)
      end
    JSON.load snippets
  end
end

#Update the views

<% @data.each do |snippet| %>

  <%= snippet["content"] %>

<% end %>

  • Why convert the data received from the db? The fastest way to write objects to redis.
    • Alternative is to iterate over all the objects and then save them as a hash = SLOW
    • Fastest way is to save them as a JSON encoded string, to decode use JSON.load
    • This alters the returned data so we will need to change the views to access attributes as a hash (as above)
  • Restart server and refresh page to notice the change in loading time!
  • If you make a change to a model, redis will wait the amount specified to expire befor getting fresh data. You can create a after_save validation on the model being cached:
#example of categories being cached
class Category

  after_save: :clear_cache
  
  def clear_cache
    $redis.del "categories"
  end
  
end
  • Now, when a model is updated (and saved to the db) Rails is instructed to clear the cache, ensuring that the cache is always upto date.

Redis Rails

  • in gemfile: gem 'redis-rails'
# setting a key/value pair 
redis.set("key", "value")

# retrieving a value
redis.get("key")
#=> "value"


# in your rails app, view template
<% cache 'word-of-the-hour', :expires_in => Time.now.beginning_of_hour + 1.hour do %>
  <%= %w(ruby rails javascript web developer).sample %>
<% end %>

# Every hour, redis will empty the cache and set a new value for the key

Redis (Overview)

  • Install Redis link
  • After installation run redis server
  • In a new tab, run redis-cli
    • You can see the redis url: 127.0.0.1:6379, default port is 6379
  • Simple commands:
# set key value
set name parm

# get value from key
get name 
# => parm

Redis Basics

Datatypes

  • key/value pair
  • string
  • list (list of ORDERED strings)
  • hashes
  • set (list of UNORDERED and UNIQUE strings)

Redis Persistence Options

  • Data is saved in memory for fast access!
  • Two options:
    • AOF (Append-only File): logs every operation in the system file. If the log gets too large, Redis will be able to shrink it down to only the latest version of the file.
    • RDB (Redis Database File): creates a snapshot, creating copies of moments in time. (Default)
  • Configuring which option to use:
    • cd in to the directory where you downloaded redis
    • Open redis.config, find section for Snapshotting, read the info on how to configure the snapshot after x sec with atleast x amount of changes
    • Find section for appendonly and change no to yes
    • Save & close, copy the path to the file
    • Restart the server and this time append the path to it: redis-server /usr/local/etc/redis.conf

Replication

  • Copy and rename the above file as redis2.conf
    • change the port number to 3780
    • find slaveof and replace with: slaveof 127.0.0.1 6379, slave of redis.config
    • Open a new tab and start another server with the redis2.conf file
    • Should notice master-slave relation has started

Security

  • Open both .conf files
  • find requirepass enterpass, replace enterpass with a password, make sure slave is updated with the same password in the location of masterauth
  • restart both servers for each to take effect!
  • In order to use the cli, enter the following: redis-cli -a password, entering the password that was set in the .conf file.

Datasets using Keys

set address 435

incr address #increment by 1
# => 436

incrby address 100 #increment by x

#decrement
decr address
decrby address

# For multiple words for a value, USE DOUBLE QUOTES!!
set country "North America"

#getset is used to get a key and then set it, returns original key
getset firstName "Parm"

#set multiple values:
mset street blah city blah2 state blah3 zip "blah4"

#retrieve those values:
mget street city state zip

#check if something exists:
exists street
# returns 1 for yes it exists, 0 else

#delete a key
del street

#expire a key in x sec
exp city 5

#set and expires
set zip "989-899989" ex 9

Hash

#creating a hash:
hmset nameofhash key1 value1 key2 value2 ...

#retrieve the hash:
hgetall nameofhash

#retrieve a certain value in the hash:
hget nameofhash key1

#retrieve multiple values
hmget nameofhash key1 key2

#exist?
hexist nameofhash  key1

#set another key
hmset nameofhash keyx valuex

#increment
hincrby nameofhash key numtoincrementby

Lists

#creates a new list (right push appends to bottom of list)
rpush nameoflist value1 value2 value3 ...

#list the list
lrange nameoflist startindex stopindex
#startindex is usually 0!
# -1 all 
# -2 all except the last one
# -3 all except the last 2

#append to the BEGINNING of a list (left push)
lpush nameoflist valuex

#remove (left pop)
lpop nameoflist

#right pop (removes at the end of the list)
rpop nameoflist

#trimming a list
ltrim nameoflist starti stopi
#ex ltrim grocery 0 2
# lrange 0 -1 to see the new list

Sets

#creating a set
sadd nameofset value1 value2 value3 value4

#retrieve set
smembers name

#add more values
sadd valuex valauey

#is there?
sismember nameofset value
#returns 0 no, 1 yes

#creating a subset
sadd nameofset:value value_1 value_2 value_3 ...

#retrieve subset
smembers nameofset:value

#creating a new value that has the same sublist as another value
sunionstore nameofset:newvalue nameofset:existingvalue

#remove the last value from the subset
spop nameofset:value

#count of how many values
scard nameofset

Sorted Sets

#creating a sorted set, helps if keys are integers
zadd nameofset key1 value1 key2 value2 key3 value3 ...

#listing the set
zrange nameofset starti endi
# 0 start, -1 end
# return just the values, to return with key AND values:

zrange nameofset 0 -1 withscores #again 0 is starti and -1 is endi

#reverse the set
zrevrange nameofset starti endi withscores

#get upto a certain range of key
zrangebyscore nameofset -inf integer withscores
#inf = information that will be obtained until the integer value

#retrieve the position of a value
zrank nameofset value
#returns the index or ranking

# IF THEY KEYS ARE NOT INTEGERS, REDIS WOULD SORT THE KEYS ALPHABETICALLY


Security with Redis

  • open the config file and search for security, requirepass password is the main way to secure redis
  • Next, ensure that redis port and address is firewalled from unrestriced access!
    • Binding redis to a single interface is the standard config and best practice
  • Search for bind and notice that redis is binded to 127.0.0.1 = 1 interface, which means we can only access the redis server through that address!
  • It is advised to stray away from adding more than one interface to a single redis server!