Heroku×Redis×Python で始める NoSQL DB 入門

# はじめに

Heroku では Redis という DB が公式で提供されています。

[キーバリュー型データストア Redis をクラウドで | Heroku](https://jp.heroku.com/redis)

これは SQL やテーブル設計が不要なデータベースで、
例えば Python の場合、以下のように手軽に扱えます。

```python
>>> conn.set('タイトル', 'Redisで始めるDB入門') # データの格納
True
>>> conn.get('タイトル') # データの取得
'Redisで始めるDB入門'
```

この記事では Redis の導入方法から、基本的な使い方までを紹介します。

# データの永続化とデータベースの基本

<details><summary>興味があれば読んでください(クリックで開く)</summary><div>

`消えたら困るデータをどこかに保存しておかなければならない`
というのは、プログラミングを続ける上で直面しやすい問題です。
変数はプログラムが終了すると消えてしまいますからね。

そのようなデータの保存は一般的に `データの永続化` と呼ばれ、主に
JSONなどのファイルにテキストとして保存する方法と、
`データベース管理システム(DBMS)`  を利用する方法があります。

単純なデータの保存であればファイル管理でも良いのですが、
複雑なデータ構造を扱う場合や、複数人が同時に操作するような場面だと難しく、
データ管理に特化した DBMS の方が扱いやすくなります。

また、Herokuでは基本的にファイルを一時的にしか保存できないため、
DB を利用せざるを得ない場面も多いと思います。

この記事では簡単のため、 DBMS もまとめて DB と表記します。

## DBの種類

いくつかありますが、主なカテゴリの `RDB` と `NoSQL` の2種類を紹介します。

### RDB

`関係データベース(Relational DataBase)` の略で、[MySQL](https://www.mysql.com/jp/) や [PostgreSQL](https://www.postgresql.org/) など、
世の中で使われている DB や解説されている DB の多くは RDB です。
初学者にとっては、SQLの学習コストやテーブル設計の難しさが壁になります。

Heroku では PostgreSQL が公式 Add-on で提供されています。
[Postgres - SQL データベース・サービス | Heroku](https://jp.heroku.com/postgres)

### NoSQL

`NoSQL` は（No SQLではなく） `Not only SQL` の略で、
SQL を使わずに、Pythonの文字列やリストなどを扱うような感覚で、
シンプルにデータを管理できるという魅力があります。

`NoSQL` というカテゴリの中にも様々な種類があり、
例えば [MongoDB](https://www.mongodb.com/) は `ドキュメント型` で、[Neo4j](https://neo4j.com/) は `グラフ型` です。
今回使う [Redis](https://redis.io/) は `キーバリュー型(Key-Value-Store : KVS)` です。

## [Redis](https://redis.io/)

オンメモリで読み書きが高速、永続化も可能な KVS です。
主にキャッシュのために使われることが多いですが、
最近ではランキングデータに使われるという話もよく見かけます。

`String` `List` `Set` `Hash` `SortedSet` などの
様々なデータ型をサポートしています。
[An introduction to Redis data types and abstractions – Redis](https://redis.io/topics/data-types-intro)

<details><summary>参考リンク(クリックで開く)</summary><div>

[RedisとElastiCacheを分かりやすくまとめてみた - Qiita](https://qiita.com/gold-kou/items/966d9a0332f4e110c4f8#nosql%E3%81%A8rdb%E3%81%AE%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91)
[webエンジニアのためのはじめてのredis](https://www.slideshare.net/ssusera66c10/webredis)
[Redisの特徴と活用方法について](https://www.slideshare.net/yujiotani16/redis-76504393)
[インメモリKVSのRedisについて – Rest Term](https://rest-term.com/archives/2898/)
[システム開発で得たRedis利用ノウハウ | Future Tech Blog - フューチャーアーキテクト](https://future-architect.github.io/articles/20190821/)
[ソーシャルゲーム開発経験から学んだゲームにRedisを使う際のTips - DEV Community 👩‍💻👨‍💻](https://dev.to/gumi/redistips-59na)
[Redis | AWS](https://aws.amazon.com/jp/redis/)

</div></details>

</div></details>

# [Heroku Redis](https://devcenter.heroku.com/articles/heroku-redis) の初期設定

Heroku のアカウント登録、アプリ作成、クレカ登録は以下から済ませておいてください。
プラン毎の定額課金方式のため、予期せぬ請求の心配はないです。

- [Heroku | Sign up](https://signup.heroku.com/login)
- [Create New App | Heroku](https://dashboard.heroku.com/new-app)
- [Account · Billing | Heroku](https://dashboard.heroku.com/account/billing)

Heroku Redis の Free プランは25MBが上限で、
自動バックアップは対応していないという制限があります。
障害などでデータが失われるリスクがある点は留意しておいてください。

参考：[Heroku Redisについてまとめてみた - Qiita](https://qiita.com/usk/items/f240b781cf0fdbeacdf9)

## [Heroku Redis](https://devcenter.heroku.com/articles/heroku-redis) のインストール

以下から、作成したアプリに Heroku-Redis をインストールします。
[Heroku Redis - Add-ons - Heroku Elements](https://elements.heroku.com/addons/heroku-redis)

右上の `Install Heroku Redis` をクリック。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118830/b7546b79-c796-8b2d-77e7-48e814296094.png">

`Add-on plan` が `Hobby Dev - Free` になっていることを確認してから、
`App to provision to` に作成したアプリ名を入力し、
`Provision add-on` をクリックすると、2~3分程でインストールが完了します。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118830/ebd5bc9d-1c5e-0739-2e5a-66c227ee9987.png">

## REDIS_URLの確認

インストールが完了すると、Dashboard->Settings->Config Vars に
環境変数 `REDIS_URL` が設定されています。Redisに接続する際に使います。
<font color="red">このURLは絶対に外部に公開しないでください。</font>
<img width="1232" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118830/c26c0b52-8010-f8ea-e907-2433383841e0.png">

## 使用状況の確認

Dashboard の Overview または Resources の `Heroku Redis` から、
使用状況を確認することができます。
<img width="80%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118830/cf4c2eaf-e2c3-609c-e7b6-65e0b671c270.png">

# Pythonで操作する

以下の内容は `python3.8.0` と `redis-py 3.3.11` で動作確認しています。

<details><summary>参考リンク(クリックで開く)</summary><div>

[PythonでRedisを操作しよう (基本操作編)](https://weblabo.oscasierra.net/python/python-redis-py-1.html)
[PythonでRedisを扱う(redis-pyの基本) - [Dd]enzow(ill)? with DB and Python](http://www.denzow.me/entry/2017/10/07/233233)
[PythonでRedisを効率的に使う(redis-pyのパフォーマンスをあげるには) - [Dd]enzow(ill)? with DB and Python](http://www.denzow.me/entry/2017/10/08/212059)
[PythonでRedisを参照・更新する - け日記](https://ohke.hateblo.jp/entry/2018/06/09/230000)
[redis-py – Redis Pythonクライアント – GitHubじゃ！Pythonじゃ！](https://githubja.com/andymccurdy/redis-py)

</div></details>

## [redis-py](https://github.com/andymccurdy/redis-py) のインストール

簡単に操作を行うためのライブラリを導入します。

ローカルで試す場合は以下をターミナル(コマンドプロンプト)で実行してください。

```bash
python3 -m pip install --upgrade pip # 念の為実行しておく
python3 -m pip install redis # redis-pyのインストール
```

Heroku 上で実行する場合は `requirements.txt` に `redis` を追加する必要があります。

```requirements.txt
redis==3.3.11
```

## Redisに接続する

ここで環境変数 `REDIS_URL` を使います。
Heroku上で実行する場合はそのままで大丈夫ですが、
ローカルで実行する場合は設定しておくか、`redis://~` を直接書いてください。

まず接続用のモジュールを用意しておきます。

```python:r.py
import redis
import os

def connect():
    return redis.from_url(
        url=os.environ.get('REDIS_URL'), # 環境変数にあるURLを渡す
        decode_responses=True, # 日本語の文字化け対策のため必須
    )
```

以下、これをPythonインタプリタで使っていきます。
※ コマンドプロンプトに `python3` や `py -3` と入力すると立ち上がる実行環境

```bash
$ python3
>>> import r
>>> conn = r.connect() # このconnを通じて操作する
```

## 基本操作

Redis では 1つのKey に 1つのValue が紐付くという形が基本です。
Keyは必ず文字列ですが、Valueは文字列以外の指定も可能です。
ただしデータ型によって使うメソッドが異なります。

### 文字列型データの格納

`set()` メソッドに Key と Value を指定して格納します。
例えば、Key が `タイトル` で Value が `Redisで始めるDB入門` の場合は

```python
>>> result = conn.set('タイトル', 'Redisで始めるDB入門')
>>> print(result)
True # 格納に成功すると True
```

という形になります。

### 文字列型データの取得

`get()` メソッドにKeyを指定して取得します。

```python
>>> result = conn.get('タイトル')
>>> print(result)
'Redisで始めるDB入門' # Keyに紐付くValueが返る
```

また、引数に存在しない Key を指定した場合は

```python
>>> result = conn.get('title')
>>> type(result)
<class 'NoneType'>
```

のように None が返ります。エラーは発生しません。

### Keyの存在確認

Key が登録されているかを確認するには `exists()` メソッドを使います。

```python
>>> conn.exists('タイトル')
1
```

exists メソッドの引数には複数のKeyを指定することが可能で、
そのうち登録されているKeyの数が返ってきます。

```python
>>> conn.exists('title', 'id', 'tag')
0 # 指定したKeyは全て登録されていないので
```

各操作の前にこのメソッドで確認しておくと安全です。

### Keyの一覧を取得

今現在どの Key が格納されているかを確認するには `keys()` メソッドを使います。

```python
>>> conn.keys()
['タイトル']
```

### Keyの変更

Key を別の文字列に変更したい場合は `rename()` か `renamenx()` メソッドを使います。
第1引数に変更前の Key を、第2引数に変更後の Key を指定します。

```python
>>> conn.rename('タイトル', 'title')
True # 変更に成功
>>> conn.renamenx('title', 'タイトル')
True # 変更に成功
```

これらは変更先の文字列が存在する場合に挙動が変わります。

```python
>>> conn.set('title', '変更しないでね') # 変更先のKeyを登録
True
>>> conn.renamenx('タイトル', 'title')
False # 変更できない
>>> conn.rename('タイトル', 'title')
True # 上書きしてしまう
>>> conn.get('title')
'Redisで始めるDB入門' # 'タイトル' に紐付いていた Value
>>> conn.get('タイトル')
None # 変更前のKeyは消滅している
```

### Keyの削除

Key を削除したい場合は `delete()` メソッドを使います。紐付く Value も消えます。
複数の Key を指定可能で、削除に成功した Key の数が返ります。

```python
>>> conn.delete('title')
1 
```

また、全ての Key を削除したい場合は `flushdb()` メソッドを使います。

```python
>>> conn.flushdb()
True
```

## 集合型の場合

Redisで扱えるデータ型には様々ありますが、
特に便利な集合型(Set)について特筆します。

Redis の集合型は Python の集合型と同様の性質で、
要素の重複が許されない順不同な集合データです。

### 集合型データの格納・取得

文字列型では `set()` と `get()` を使いましたが、
集合型では `sadd()` と `smembers()` を使います。

```python
>>> conn.sadd('タグ', 'Heroku', 'Redis')
2 # 追加できた要素の数が返る
>>> conn.smembers('タグ')
{'Redis', 'Heroku'} # 順不同なので追加した順番と違う
```

`sadd()` は第一引数に Key を、第二引数以降に集合型データの要素を指定します。

### 集合型データに要素を追加・削除

要素の追加にも `sadd()` を使います。

```python
>>> conn.sadd('タグ', 'Python')
1
>>> conn.smembers('タグ')
{'Redis', 'Heroku', 'Python'}
```

ただし、既にある要素は追加できません。

```python
>>> conn.sadd('タグ', 'Redis', 'redis-py')
1 # 'redis-py' は追加されるが 'Redis' は重複するので追加されない
>>> conn.smembers('タグ')
{'Redis', 'Heroku', 'redis-py', 'Python'}
```

要素の削除には `srem()` を使います。

```python
>>> conn.srem('タグ', 'Heroku', 'Redis')
2
>>> conn.smembers('タグ')
{'redis-py', 'Python'}
```

Key の削除は文字列型と同様に `delete()` メソッドを使います。

## その他のデータ型や操作について

記事にはまとめきれない多くのデータ型や操作があります。
それらは公式リファレンスに記載されています。
[Welcome to redis-py’s documentation! — redis-py 2.10.5 documentation](https://redis-py.readthedocs.io/en/latest/)

そして redis-py で使えるメソッド名は Redis 公式のコマンド名に準拠しています。
[Command reference – Redis](https://redis.io/commands)
ただし `del` は Python の予約語と衝突するので `delete` になっています。

また、様々な疑問を自力で解決するのは難しいので、
Python や各種技術に関する情報交換ができるコミュニティを紹介しておきます。
[Python.jp Discordサーバ - python.jp](https://www.python.jp/pages/pythonjp_discord.html)

# 最後に

<font color="red">大規模なシステムで全てのデータを NoSQL で管理するのは狂気の沙汰です。
いずれは RDBMS の偉大さを学んでください。</font>

また、上記の内容を活用した例を紹介しているので、
興味があれば読んでみてください。
[NoSQL×PaaSで運用するナレッジベース+WebAPI - Qiita](https://qiita.com/1ntegrale9/items/c4f315f918bad7a0f180)