Top Banner
Advanced Redis data structures by Amir Salihefendic
34

Advanced Redis data structures

Jul 15, 2015

Download

Technology

amix3k
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Advanced Redis data structures

Advanced Redis data structures

by Amir Salihefendic

Page 2: Advanced Redis data structures

About me

Founder

Millions of data items

Co-founder, former CTO

Billions of data items

Page 3: Advanced Redis data structures

Redis: Greatness

Everything is in memory, data is persistent

Amazing Performance

The Hacker’s database

Page 4: Advanced Redis data structures

Redis: Greatness

Great lead dev

Amazing progress (Sentinel, Cluster, …)

Page 5: Advanced Redis data structures

Redis Rich Datatypes• Relational databases

Schemas, tables, columns, rows, indexes etc.

• Column databases (BigTable, hBase etc.) Schemas, columns, column families, rows etc.

• Rediskey-value, sets, lists, hashes, bitmaps, etc.

Page 6: Advanced Redis data structures

Redis datatypes resemble datatypes in programming languages.

They are natural to us!

Page 7: Advanced Redis data structures

redis_wrap

A wrapper for Redis datatypes, so they mimic the datatypes found in

Python

https://github.com/Doist/redis_wrap

Page 8: Advanced Redis data structures

# Mimic of Python listsbears = get_list('bears')bears.append('grizzly')

assert len(bears) == 1assert 'grizzly' in bears

# Mimic of hashes villains = get_hash('villains')assert 'riddler' not in villains

villains['riddler'] = 'Edward Nigma'assert 'riddler' in villainsassert len(villains.keys()) == 1

del villains['riddler']assert len(villains) == 0

# Mimic of Python setsfishes = get_set('fishes')assert 'nemo' not in fishes

fishes.add('nemo')assert 'nemo' in fishes

for item in fishes: assert item == 'nemo'

redis_wrap: usage

Page 9: Advanced Redis data structures

redis_graph

A simple graph database in Python

https://github.com/Doist/redis_graph

Page 10: Advanced Redis data structures

# Adding an edge between nodesadd_edge(from_node='frodo', to_node='gandalf')assert has_edge(from_node='frodo', to_node='gandalf') == True # Getting neighbors of a nodeassert list(neighbors('frodo')) == ['gandalf']

# Deleting edgesdelete_edge(from_node='frodo', to_node='gandalf')

# Setting node valuesset_node_value('frodo', '1')assert get_node_value('frodo') == '1'

# Setting edge valuesset_edge_value('frodo_baggins', '2')assert get_edge_value('frodo_baggins') == '2'

redis_graph: Usage

Page 11: Advanced Redis data structures

redis_graph: Implementationfrom redis_wrap import *

#--- Edges ----------------------------------------------def add_edge(from_node, to_node, system='default'): edges = get_set( from_node, system=system ) edges.add( to_node )

def delete_edge(from_node, to_node, system='default'): edges = get_set( from_node, system=system )

key_node_y = to_node if key_node_y in edges: edges.remove( key_node_y )

def has_edge(from_node, to_node, system='default'): edges = get_set( from_node, system=system ) return to_node in edges

def neighbors(node_x, system='default'): return get_set( node_x, system=system )

#--- Node values ----------------------------def get_node_value(node_x, system='default'): node_key = 'nv:%s' % node_x return get_redis(system).get( node_key )

def set_node_value(node_x, value, system='default'): node_key = 'nv:%s' % node_x return get_redis(system).set( node_key, value )

#--- Edge values -----------------------------def get_edge_value(edge_x, system='default'): edge_key = 'ev:%s' % edge_x return get_redis(system).get( edge_key )

def set_edge_value(edge_x, value, system='default'): edge_key = 'ev:%s' % edge_x return get_redis(system).set( edge_key, value )

Page 12: Advanced Redis data structures

redis_simple_queue

A simple queue in Python using Redis

https://github.com/Doist/redis_simple_queue

Page 13: Advanced Redis data structures

redis_queue: usage

from redis_simple_queue import *

delete_jobs('tasks')

put_job('tasks', '42')

assert 'tasks' in get_all_queues()assert queue_stats('tasks')['queue_size'] == 1

assert reserve_job('tasks') == '42'assert queue_stats('tasks')['queue_size'] == 0

Page 14: Advanced Redis data structures

redis_queue: Implementation

from redis_wrap import *

def put(queue, job_data, system='default'): get_list(queue, system=system).append(job_data)

def reserve(queue, system='default'): return get_list(queue, system=system).pop()

def delete_jobs(queue, system='default'): get_redis(system).delete(queue)

def get_all_queues(system='default'): return get_redis(system).keys('*').split(' ')

def queue_stats(queue, system='default'): return { 'queue_size': len(get_list(queue)) }

Page 15: Advanced Redis data structures

Cohort/Retention Tracking

How bitmapist was born

Page 16: Advanced Redis data structures

bitmapist: The idea

MixPanel looks great!

Page 17: Advanced Redis data structures

bitmapist: Problem with MixPanel

MixPanel would cost $2000/USD++/month

Page 18: Advanced Redis data structures

bitmapist + bitmapist.cohort• Implements an advanced analytics library on top of Redis bitmaps

• https://github.com/Doist/bitmapist

•Hundreds of millions of events for Todoist

•O(1) execution

Page 19: Advanced Redis data structures

bitmapist: Features

•Has user 123 been online today? This week? •Has user 123 performed action "X"? •How many users have been active have this month? •How many unique users have performed action "X" this week? •How many % of users that were active last week are still active? •How many % of users that were active last month are still active this month?

•O(1)! Using very small amounts of memory.

Page 20: Advanced Redis data structures

bitmapist: Bitmaps?

• SETBIT, GETBIT, BITCOUNT, BITOP

• SETBIT somekey 8 1

•GETBIT somekey 8

•BITOP AND destkey somekey1 somekey2

• http://en.wikipedia.org/wiki/Bit_array

Page 21: Advanced Redis data structures

bitmapist: Usage# Mark user 123 as active and has played a songmark_event('active', 123)mark_event('song:played', 123)

# Answer if user 123 has been active this monthassert 123 in MonthEvents('active', now.year, now.month)assert 123 in MonthEvents('song:played', now.year, now.month)

# How many users have been active this week?print len(WeekEvents('active', now.year, now.isocalendar()[1]))

# Perform bit operations. How many users that# have been active last month are still active this month?active_2_months = BitOpAnd( MonthEvents('active', last_month.year, last_month.month), MonthEvents('active', now.year, now.month))print len(active_2_months)

Page 22: Advanced Redis data structures

bitmapist.cohort: Visualization

Read more http://amix.dk/blog/post/19718

Page 23: Advanced Redis data structures

fixedlist

How fixedlist was born

Page 24: Advanced Redis data structures

fixedlist: Problem

Timelines: Exponential data growth

Page 25: Advanced Redis data structures

fixedlist: The Easy Solution

Throw money at the problem

Page 26: Advanced Redis data structures

fixedlist: Cheating!

• Fixed timeline size •O(1) insertion •O(1) update •O(1) get • Cacheable

Solution that Facebook and Twitter use

Page 27: Advanced Redis data structures

fixedlist

2.5x faster than pure Redis solution

1.4x less memory than pure Redis solution

https://github.com/Doist/fixedlist

Page 28: Advanced Redis data structures

fixedlist: Usage# Add a value to a list fixedlist.add('hello', 'world')

# Add mutliple values to multiple keys at once fixedlist.add(['hello1', 'hello2'], ['world1', 'world2'])

# Get valuesfrom a list assert fixedlist.get('hello') == ['world', 'world1', 'world2']

# Remove a value fixedlist.remove('hello', 'world1')

Saved Plurk tens of thousands of $

Page 29: Advanced Redis data structures

Redis+Lua+Python

When you want:

More complex data types

Better performance

Page 30: Advanced Redis data structures

Redis+Python: Incr implementation

def incr_python(key, delta=1, system='default'): client, scripts = get_redis(system)

with client.pipeline() as p: p.watch(key) value = delta old = p.get(key) if old: value = int(old) + delta p.set(key, value) p.unwatch() return value

Page 31: Advanced Redis data structures

Redis+Lua: Incr implementation

scripts = { 'incr': client.register_script(_load_lua_script('incr.lua'))

} ...

def incr_lua(key, delta=1, system='default'): client, scripts = get_redis(system) return scripts['incr'](keys=['key', 'delta'], args=[key, delta])

local delta = tonumber(ARGV[2]) local value = delta local old = tonumber(redis.call('get', ARGV[1])) if old then value = value + old end if not redis.call('set', ARGV[1], value) then return nil end return value

Page 32: Advanced Redis data structures

Perfomance: Lua 3x fasterPythontime python test_incr_python.py 300000 python test_incr_python.py 300000 37.77s user 12.00s system 73% cpu 1:07.73 total

Luatime python test_incr_lua.py 300000 python test_incr_lua.py 300000 10.76s user 2.85s system 66% cpu 20.513 total

https://github.com/amix/demo-redis-python-lua

Page 33: Advanced Redis data structures

fixedlist in Lua

Proof of conceptTokyo Tyrant example

https://gist.github.com/amix/f15508ac6a8b534c3290

Page 34: Advanced Redis data structures

Q & A

More questions:

[email protected]

@amix3k