Skip to content

Instantly share code, notes, and snippets.

@sneeu
Created August 27, 2019 21:27
Show Gist options
  • Save sneeu/61a16ebe4ba8dc952f0c4deceadd6b6f to your computer and use it in GitHub Desktop.
Save sneeu/61a16ebe4ba8dc952f0c4deceadd6b6f to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Redis\n",
"\n",
"An introduction to using the data-structure store, Redis.\n",
"\n",
"To begin, we need to install `redis` and, optionally, `hiredis`:\n",
"\n",
"```\n",
"pip install redis hiredis\n",
"```\n",
"\n",
"`hiredis` is a parser for the Redis protocol, writen in C to be fast—`redis` includes a protocol parser written in Python.\n",
"\n",
"## The basics\n",
"\n",
"### Import\n",
"\n",
"A simple `import redis` is all we need, `redis` will use `hiredis` automatically if it is installed."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import redis"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Connect\n",
"\n",
"We supply a `host`, `port`, and `db`. `host` and `port` should be fairly straightforward and by default, Redis starts on port `6379`.\n",
"\n",
"Similar to other datastores, Redis has the concept of multiple “databases” (by default up to 16) simply numbered `0`–`15`. We’ll connect to database `0`."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn = redis.Redis(host='localhost', port=6379, db=0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Set a string value\n",
"\n",
"Once we have our connection, we can send the [`SET`](https://redis.io/commands/set) command to set a single key, `a-key` to be some string value, `some-value`. `redis` will return `True` to confirm that the value was stored:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.set('a-key', 'some-value')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Retrieve a string value\n",
"\n",
"Once set, a value can be retrieved with the [`GET`](https://redis.io/commands/get) command, `redis` will return the value:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'some-value'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.get('a-key')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Delete a key\n",
"\n",
"Finally, we can remove a value using the [`DEL`](https://redis.io/commands/del) command. `redis` uses the name `delete` here because `del` is a Python keyword. `redis` will return the number of keys that have been deleted:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.delete('a-key')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Once deleted, `redis` will return `None` when trying to `GET` a key:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"assert rconn.get('a-key') == None"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Simple debugging\n",
"\n",
"Before we go too much further, I’ll introduce some simple debugging to ease with any issue you may come across.\n",
"\n",
"### List all keys\n",
"\n",
"We can check if a key exists with [`EXISTS`](https://redis.io/commands/exists), and list all keys in the current database with [`KEYS`](https://redis.io/commands/keys):"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"rconn.set('another-key', 'some-other-value')\n",
"\n",
"assert rconn.exists('another-key')\n",
"assert not rconn.exists('nowhere-key')\n",
"assert rconn.keys() == [b'another-key']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lists\n",
"\n",
"Redis is a data-structure store and as such can store a number of different data-structures. We’ll start with lists.\n",
"\n",
"List commands usually start with `L`, for left, and in some cases, `R` for (you guessed it) right.\n",
"\n",
"We can push a value on to the left ([`LPUSH`](https://redis.io/commands/lpush)) or right ([`RPUSH`](https://redis.io/commands/rpush)) end of a list. [`LRANGE`](https://redis.io/commands/lrange) allows us to return some “slice” of the list. Like Python, `LRANGE` supports negative indexing, so `0`, `-1` will give us back the whole list:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[b'6', b'95']"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.lpush('a-list', 6)\n",
"rconn.rpush('a-list', 95)\n",
"rconn.lrange('a-list', 0, -1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can insert values before or after a “pivot” value in a list:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[b'6', b'44', b'95']"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.linsert('a-list', 'BEFORE', '95', 44)\n",
"rconn.lrange('a-list', 0, -1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And we can change the value at any index:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[b'9', b'44', b'95']"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.lset('a-list', 0, 9)\n",
"rconn.lrange('a-list', 0, -1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Using a list as a queue or stack\n",
"\n",
"A list can be used as either a [queue](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) or a [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) using a combination of `LPUSH`, `RPUSH`, [`RPOP`](https://redis.io/commands/rpop), and [`LPOP`](https://redis.io/commands/lpop).\n",
"\n",
"The queue:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'81'"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.lpush('a-queue', 81)\n",
"rconn.lpush('a-queue', 82)\n",
"rconn.lpush('a-queue', 83)\n",
"rconn.rpop('a-queue')"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'82'"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.rpop('a-queue')"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'83'"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.rpop('a-queue')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And the stack:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'76'"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.rpush('a-stack', 74)\n",
"rconn.rpush('a-stack', 75)\n",
"rconn.rpush('a-stack', 76)\n",
"rconn.rpop('a-stack')"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'75'"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.rpop('a-stack')"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'74'"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.rpop('a-stack')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In some applications we may want to block while we wait for a value to be added to a queue or stack, in those cases, we can use [`BLPOP`](https://redis.io/commands/blpop) (**b**locking **r**ight **pop**) or [`BRPOP`](https://redis.io/commands/brpop)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sets\n",
"\n",
"Redis has two different options for sets: sorted and unsorted. We’ll describe unsorted sets.\n",
"\n",
"Set commands all start with an `S`, for set.\n",
"\n",
"We can add values to a set, check the size (or **card**inality), and check a value’s membership of a set:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"rconn.sadd('users', 'alice')\n",
"rconn.sadd('users', 'bob')\n",
"rconn.sadd('users', 'carol')\n",
"\n",
"assert rconn.scard('users') == 3\n",
"assert rconn.smembers('users') == {b'alice', b'bob', b'carol'}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also perform common set operations, difference:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"rconn.sadd('admins', 'alice')\n",
"assert rconn.sdiff('users', 'admins') == {b'bob', b'carol'}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And union:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"rconn.sadd('customers', 'carol')\n",
"rconn.sadd('customers', 'dan')\n",
"rconn.sadd('customers', 'frank')\n",
"assert rconn.sunion('users', 'customers') == {b'alice', b'bob', b'carol', b'dan', b'frank'}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Sorted sets\n",
"\n",
"Similarly to sets, a value in a sorted set can only exist once. With a sorted set however, each item has a score which is used to order the members of the set.\n",
"\n",
"The sorted set commands all begin with `Z`.\n",
"\n",
"We use [`ZADD`](https://redis.io/commands/zadd) to add an item to a sorted set and can use [`ZRANGEBYSCORE`](https://redis.io/commands/zrangebyscore) to get the members of the set in order."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[b'baz', b'bar', b'foo']"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.zadd('scores', {'foo': 10, 'bar': 6, 'baz': 1})\n",
"rconn.zrangebyscore('scores', '-inf', '+inf')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can use [`ZINCRBY`](https://redis.io/commands/zincrby) to increase a member’s score:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[b'bar', b'foo', b'baz']"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.zincrby('scores', 20.0, 'baz')\n",
"rconn.zrangebyscore('scores', '-inf', '+inf')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Hashes\n",
"\n",
"A hash is a mapping type, similar to Python’s `dict`. The hash commands begin with an `H`.\n",
"\n",
"We can add some values to a hash, and get them back:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"b'alice@example.net'"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.hset('users:alice', 'email', 'alice@example.net')\n",
"rconn.hset('users:alice', 'password', '6384e2b2184bcbf58eccf10ca7a6563c')\n",
"rconn.hset('users:alice', 'login-count', 0)\n",
"\n",
"rconn.hget('users:alice', 'email')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can also get multiple values back:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[b'alice@example.net', b'6384e2b2184bcbf58eccf10ca7a6563c']"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.hmget('users:alice', ['email', 'password'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And atomically increment a key in the hash:"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"10"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.hincrby('users:alice', 'login-count', 2)\n",
"rconn.hincrby('users:alice', 'login-count', 3)\n",
"rconn.hincrby('users:alice', 'login-count', 5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Scaning\n",
"\n",
"Redis keys (and the database’s “key-space”) can get large over time, to avid having commands block the Redis server on IO, Redis providers [`SCAN`](https://redis.io/commands/scan) commands to iterate over a list, set, hash, or the key-space.\n",
"\n",
"For example to iterate over a hash, we would use `HSCAN` giving it the `cursor` value of 0:"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(0,\n",
" {b'email': b'alice@example.net',\n",
" b'password': b'6384e2b2184bcbf58eccf10ca7a6563c',\n",
" b'login-count': b'10'})"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rconn.hscan('users:alice', 0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The return value is a tuple of the `cursor` value for the next iteration and the contents of the iteration. Python’s `redis` package helps us out with `SCAN`s by providing iterators which manage the `cursor` for us:"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{b'email': b'alice@example.net',\n",
" b'password': b'6384e2b2184bcbf58eccf10ca7a6563c',\n",
" b'login-count': b'10'}"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dict(rconn.hscan_iter('users:alice'))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment