Table of Contents [expand]
Memcache は、Web アプリとモバイルアプリバックエンドのパフォーマンスとスケーラビリティを改善する技術です。ページの読み込みが遅すぎる場合や、アプリにスケーラビリティの問題がある場合は、Memcache の使用を検討してください。小規模なサイトであっても、Memcache の導入によってページの読み込みを高速化し、将来の変化にアプリを対応させることができます。
このガイドでは、単純な Flask 1.0 アプリケーションを 作成して Heroku にデプロイし、Memcache を追加してパフォーマンスのボトルネックを軽減する方法を示します。
前提条件
このガイドの手順を完了する前に、以下のすべての条件を満たしていることを確認してください。
- Python の知識がある (Flask についても一定の知識があることが理想的です)
- Heroku ユーザーアカウント (無料ですぐにサインアップ)
- 「Heroku スターターガイド (Python)」の手順を理解している
- Python と Heroku CLI がコンピュータにインストールされている
Heroku 用 Flask アプリケーションの作成
Flask は、最小限を指向し、アプリケーションスケルトンを必要としないフレームワークです。 次のように、Python 仮想環境を作成して Flask をインストールするだけです。
$ mkdir flask_memcache
$ cd flask_memcache
$ python -m venv venv
$ source venv/bin/activate
(venv) $ pip install Flask
Flask フレームワークをインストールしたので、アプリのコードを追加できます。タスクを追加および削除できるタスクリストを作成しましょう。
Flask では、アプリケーションを構造化する方法にかなりの柔軟性があります。始めるために、最小限のスケルトンを追加しましょう。まず、task_list/__init__.py
でアプリを作成します。
import os
from flask import Flask
def create_app():
app = Flask(__name__)
app.config.from_mapping(
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev_key'
)
return app
この小さなサンプルアプリでは SECRET_KEY
は使用しませんが、
常に設定することをお勧めします。より大きなプロジェクトではほとんど常に使用し、
多くの Flask アドオンでも使用されます。
アプリケーションを探す場所を Flask に指示するために、FLASK_APP
環境変数も設定する必要があります。ローカル開発の場合、すべての必要な環境変数を .env
ファイルで設定します。
FLASK_APP=task_list
FLASK_ENV=development
.env
ファイルで定義された変数を Flask が確実に選択するよう、python-dotenv
をインストールします。
(env) $ pip install python-dotenv
flask run
でアプリを実行して http://127.0.0.1:5000/
でアプリにアクセスできるようになりましたが、アプリはまだ何も動作しません。
Heroku アプリの作成
以下の手順で、Flask スケルトンを新しい Heroku アプリに関連付けます。
Git リポジトリを初期化してスケルトンをコミットします。コミットしたくないファイルを絶対にコミットしないよう、
.gitignore
ファイルを追加することから始めます。次の内容をこのファイルに貼り付けます。venv/ .env *.pyc __pycache__/ instance/
Now commit all files to the Git repository:
$ git init $ git add . $ git commit -m 'Flask skeleton'
Heroku アプリを作成します。
$ heroku create
In addition to creating the actual Heroku application, this command adds the corresponding remote to your local Git repository.
Heroku アプリは用意できましたが、Flask アプリはまだ Heroku にデプロイする準備ができていません。いくつかの必要な変更を後で行いますが、まずは、タスクリスト機能の一部を実装しましょう。
タスクリスト機能の追加
ユーザーがタスクを表示、追加、削除できるタスクリストをアプリに追加しましょう。そのためには、次の手順に従う必要があります。
- データベースをセットアップする
Task
モデルを作成する- ビューとコントローラーロジックを作成する
PostgreSQL データベースのセットアップ
Flask でデータベースを設定する前に、データベースを作成する必要があります。Heroku では、次のようにして、無料の開発用データベースをアプリに追加できます。
$ heroku addons:create heroku-postgresql:hobby-dev
アプリ用の PostgreSQL データベースが作成され、その URL を含む DATABASE_URL
環境変数が追加されます。データベースを使用するには、データベース接続、モデル、移行を管理するためのライブラリがいくつか必要です。
(env) $ pip install flask-sqlalchemy flask-migrate psycopg2
ここで、task_list/__init__.py
でデータベースを設定できます。
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
def create_app():
app = Flask(__name__)
app.config.from_mapping(
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev_key',
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(app.instance_path, 'task_list.sqlite'),
SQLALCHEMY_TRACK_MODIFICATIONS = False
)
db.init_app(app)
migrate.init_app(app, db)
from . import models
return app
db
オブジェクトが作成され、Flask アプリ全体からアクセスできるようになります。データベースは SQLALCHEMY_DATABASE_URI
を介して設定され、このとき、使用可能であれば DATABASE_URL
が使用されます。それ以外の場合、ローカルの SQLite データベースにフォールバックします。SQLite データベースを使用してローカルでアプリケーションを実行する場合は、instance
フォルダーを作成する必要があります。
$ mkdir instance
データベースの使用準備ができました。次のようにして変更内容を保存します。
$ git commit -am 'Database setup'
前出のスニペットでは、from . import models
を使用してデータベースモデルをインポートします。しかし、アプリにはまだモデルがありません。これを変更しましょう。
Task モデルの作成
タスクを作成して保存するには、2 つのことを行う必要があります。
task_list/models.py
でTask
モデルを作成します。from task_list import db class Task(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text(), nullable=False) def __repr__(self): return '<Task: {}>'.format(self.name)
This gives us a task table with two columns:
id
andname
.データベースを初期化し、移行を作成します。
(venv) $ flask db init Creating directory .../flask_memcache/migrations ... done Creating directory .../flask_memcache/migrations/versions ... done Generating .../flask_memcache/migrations/env.py ... done Generating .../flask_memcache/migrations/README ... done Generating .../flask_memcache/migrations/alembic.ini ... done Generating .../flask_memcache/migrations/script.py.mako ... done (venv) $ flask db migrate -m "task table" INFO [alembic.runtime.migration] Context impl SQLiteImpl. INFO [alembic.runtime.migration] Will assume non-transactional DDL. INFO [alembic.autogenerate.compare] Detected added table 'task' Generating .../flask_memcache/migrations/versions/c90b05ec9bd6_task_table.py ... done
The new migration can be found in
migrations/versions/c90b05ec9bd6_task_table.py
(your filename’s prefix will differ).
ここまでの変更内容を保存します。
$ git add .
$ git commit -m 'Task table setup'
タスクリストアプリケーションの作成
実際のアプリケーションを構成するのは、フロントエンドに表示されるビューと、バックエンドの機能を実装するコントローラーです。Flask では、メインアプリケーションに登録されるブループリントにより、バックエンドコントローラーの編成が容易です。
task_list/task_list.py
でコントローラーのブループリントを作成します。from flask import ( Blueprint, flash, redirect, render_template, request, url_for ) from task_list import db from task_list.models import Task bp = Blueprint('task_list', __name__) @bp.route('/', methods=('GET', 'POST')) def index(): if request.method == 'POST': name = request.form['name'] if not name: flash('Task name is required.') else: db.session.add(Task(name=name)) db.session.commit() tasks = Task.query.all() return render_template('task_list/index.html', tasks=tasks) @bp.route('/<int:id>/delete', methods=('POST',)) def delete(id): task = Task.query.get(id) if task != None: db.session.delete(task) db.session.commit() return redirect(url_for('task_list.index'))
This controller contains all functionality to:
GET
all tasks and render thetask_list
viewPOST
a new task that will then be saved to the database- Delete existing tasks
task_list/__init__.py
でブループリントを登録します。# ... def create_app(): app = Flask(__name__) # ... from . import task_list app.register_blueprint(task_list.bp) return app
コントローラーがセットアップされたので、フロントエンドを追加できます。Flask では Jinja テンプレート言語を使用します。この言語では、区切り記号 {% %}
の内側に、Python のような制御フローステートメントを追加できます。今回のタスクリストビューのために、まず、すべてのビュー用のボイラープレートコードを含むベースレイアウトを作成します。次に、タスクリストに固有のテンプレートを作成します。
ベースレイアウトを
task_list/templates/base.html
に作成します。<!DOCTYPE HTML> <title>{% block title %}{% endblock %} - MemCachier Flask Tutorial</title> <!-- Fonts --> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css" rel='stylesheet' type='text/css' /> <!-- Bootstrap CSS --> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" /> <section class="content"> <div class="container"> <header> {% block header %}{% endblock %} </header> {% for message in get_flashed_messages() %} <div class="alert alert-danger"> <p class="lead">{{ message }}</p> </div> {% endfor %} {% block content %}{% endblock %} </div> </section> <!-- Bootstrap related JavaScript --> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
ベースレイアウトを拡張するビューを
task_list/templates/task_list/index.html
に作成します。{% extends 'base.html' %} {% block header %} <h1 style="text-align:center">{% block title %}Task List{% endblock %}</h1> {% endblock %} {% block content %} <!-- New Task Card --> <div class="card"> <div class="card-body"> <h5 class="card-title">New Task</h5> <form method="POST"> <div class="form-group"> <input type="text" class="form-control" placeholder="Task Name" name="name" required> </div> <button type="submit" class="btn btn-default"> <i class="fa fa-plus"></i> Add Task </button> </form> </div> </div> <!-- Current Tasks --> {% if tasks %} <div class="card"> <div class="card-body"> <h5 class="card-title">Current Tasks</h5> <table class="table table-striped"> {% for task in tasks %} <tr> <!-- Task Name --> <td class="table-text">{{ task['name'] }}</td> <!-- Delete Button --> <td> <form action="{{ url_for('task_list.delete', id=task['id']) }}" method="POST"> <button type="submit" class="btn btn-danger"> <i class="fa fa-trash"></i> Delete </button> </form> </td> </tr> {% endfor %} </table> </div> </div> {% endif %} {% endblock %}
The view consists of two cards: one that contains a form to create new tasks, and another that contains a table with existing tasks and a delete button associated with each task.
タスクリストが機能するようになりました。これまでの変更内容を保存します。
$ git add .
$ git commit -m 'Add task list controller and views'
Heroku にデプロイするためにアプリを設定する準備ができました。
タスクリストアプリを Heroku にデプロイする
Heroku への Flask アプリケーションのデプロイは、以下の手順で簡単に実行できます。
gunicorn
サーバーをインストールし、依存関係をrequirements.txt
にフリーズします。(venv) $ pip install gunicorn (venv) $ pip freeze > requirements.txt
アプリの起動方法を Heroku に指示するために、
Procfile
をそのルートディレクトリに追加する必要があります。$ echo "web: flask db upgrade; gunicorn task_list:'create_app()'" > Procfile
The above command always runs any outstanding database migrations before starting up the application.
Heroku アプリの必要な環境設定を行います。
$ heroku config:set FLASK_APP=task_list $ heroku config:set SECRET_KEY="`< /dev/urandom tr -dc 'a-zA-Z0-9' | head -c16`"
アプリを Heroku にデプロイします。
$ git add . $ git commit -m 'Add Heroku related config' $ git push heroku master $ heroku open
タスクをいくつか追加して、アプリケーションをテストします。タスクリストが Heroku 上で動作するようになりました。ここまで完了したら、Memcache を使用してタスクリストのパフォーマンスを向上させる方法を学ぶことができます。
キャッシングを Flask に追加する
Memcache はインメモリの分散キャッシュです。そのプライマリ API は、SET(key, value)
と GET(key)
の 2 つの操作で構成されます。
Memcache は、複数のサーバーに分散していますが、操作は一定の時間に実行されるハッシュマップ (または辞書) のようなものです。
Memcache の最も一般的な用途は、コストの高いデータベースクエリや HTML レンダリングの結果をキャッシュし、これらの高コスト操作を繰り返す必要をなくすことです。
Memcache のセットアップ
Flask で Memcache を使用するには、まず実際の Memcached キャッシュをプロビジョニングする必要があります。これは、MemCachier アドオンから無料で簡単に入手できます。
$ heroku addons:create memcachier:dev
次に、適切な依存関係を設定する必要があります。Flask の内部で Memcache を使用するために、Flask-Caching
を使用します。
(venv) $ pip install Flask-Caching pylibmc==1.5.2
(venv) $ pip freeze > requirements.txt
task_list/__init__.py
で、Flask 用に Memcache を設定できるようになりました。
# ...
from flask_caching import Cache
cache = Cache()
# ...
def create_app():
app = Flask(__name__)
# ...
cache_servers = os.environ.get('MEMCACHIER_SERVERS')
if cache_servers == None:
cache.init_app(app, config={'CACHE_TYPE': 'simple'})
else:
cache_user = os.environ.get('MEMCACHIER_USERNAME') or ''
cache_pass = os.environ.get('MEMCACHIER_PASSWORD') or ''
cache.init_app(app,
config={'CACHE_TYPE': 'saslmemcached',
'CACHE_MEMCACHED_SERVERS': cache_servers.split(','),
'CACHE_MEMCACHED_USERNAME': cache_user,
'CACHE_MEMCACHED_PASSWORD': cache_pass,
'CACHE_OPTIONS': { 'behaviors': {
# Faster IO
'tcp_nodelay': True,
# Keep connection alive
'tcp_keepalive': True,
# Timeout for set/get requests
'connect_timeout': 2000, # ms
'send_timeout': 750 * 1000, # us
'receive_timeout': 750 * 1000, # us
'_poll_timeout': 2000, # ms
# Better failover
'ketama': True,
'remove_failed': 1,
'retry_timeout': 2,
'dead_timeout': 30}}})
# ...
return app
これにより、MemCachier で Flask-Caching
が設定され、何通りかの方法で Memcache を使用できるようになります。
get
、set
、delete
などでキャッシュに直接アクセスするmemoize
デコレーターを使用して関数の結果をキャッシュするcached
デコレーターを使用してビュー全体をキャッシュする- Jinja2 スニペットをキャッシュする
コストの高いデータベースクエリをキャッシュする
Memcache を使用して、コストの高いデータベースクエリをキャッシュすることはよくあります。この単純な例にはコストの高いクエリは含まれませんが、学習のために、すべてのタスクをデータベースから取得するのはコストの高い操作であると仮定します。
Task クエリ (tasks = Task.query.all()
) をキャッシュするために、task_list/task_list.py
でコントローラーロジックを次のように変更します。
# ...
from task_list import db, cache
#...
@bp.route('/', methods=('GET', 'POST'))
def index():
# ...
tasks = cache.get('all_tasks')
if tasks == None:
tasks = Task.query.all()
cache.set('all_tasks', tasks)
return render_template('task_list/index.html', tasks=tasks)
# ...
この新しい機能をデプロイしてテストします。
$ git commit -am 'Add caching with MemCachier'
$ git push heroku master
$ heroku open
キャッシュ内で何が起きているかを見るために、MemCachier のダッシュボードを開きます。
$ heroku addons:open memcachier
タスクリストを最初に読み込むと、get miss
と set
コマンドが増加しているはずです。それ以降は、タスクリストを再読み込みするたびに get hit
が増加するはずです (ダッシュボードの統計を更新してください)。
キャッシュは機能していますが、まだ大きな問題があります。新しいタスクを追加して結果を確認します。新しいタスクが現在のタスクリストに反映されていません。新しいタスクがデータベースに作成されましたが、アプリで表示されているのはキャッシュから取得した古いタスクリストです。
古いデータのクリア
データをキャッシュする場合、キャッシュが古くなったらそのデータを無効化することが重要です。この例では、新しいタスクが追加されるか既存のタスクが削除されるたびに、キャッシュされたタスクリストは古くなります。この 2 つの操作のどちらかが実行されるたびに、キャッシュを確実に無効化する必要があります。
task_list/task_list.py
で新しいタスクを作成または削除するたびに all_tasks
キーを削除することによって、これを実現します。
# ...
@bp.route('/', methods=('GET', 'POST'))
def index():
if request.method == 'POST':
name = request.form['name']
if not name:
flash('Task name is required.')
else:
db.session.add(Task(name=name))
db.session.commit()
cache.delete('all_tasks')
# ...
@bp.route('/<int:id>/delete', methods=('POST',))
def delete(id):
task = Task.query.get(id)
if task != None:
db.session.delete(task)
db.session.commit()
cache.delete('all_tasks')
return redirect(url_for('task_list.index'))
修正されたタスクリストをデプロイします。
$ git commit -am 'Clear stale data from cache'
$ git push heroku master
新しいタスクを追加すると、キャッシングの実装以降に追加したすべてのタスクが表示されるようになりました。
記憶デコレーターの使用
前述のキャッシング戦略 (キャッシュされた値の取得を試み、ない場合は新しい値をキャッシュに追加する) はごく一般的であるため、Flask-Caching
にはそのための memoize
という名前のデコレーターがあります。memoize
デコレーターを使用するために、データベースクエリのキャッシングコードを変更しましょう。
まず、get_all_tasks
という名前の専用関数にタスククエリを入れ、memoize
デコレーターで修飾します。すべてのタスクを取得するには、常にこの関数を呼び出します。
次に、古くなったデータの削除を cache.delete_memoized(get_all_tasks)
に置き換えます。
これらの変更を行った後、task_list/task_list.py
は次のようになります。
# ...
@bp.route('/', methods=('GET', 'POST'))
def index():
if request.method == 'POST':
name = request.form['name']
if not name:
flash('Task name is required.')
else:
db.session.add(Task(name=name))
db.session.commit()
cache.delete_memoized(get_all_tasks)
tasks = get_all_tasks()
return render_template('task_list/index.html', tasks=tasks)
@bp.route('/<int:id>/delete', methods=('POST',))
def delete(id):
task = Task.query.get(id)
if task != None:
db.session.delete(task)
db.session.commit()
cache.delete_memoized(get_all_tasks)
return redirect(url_for('task_list.index'))
@cache.memoize()
def get_all_tasks():
return Task.query.all()
記憶されたキャッシュリストをデプロイし、機能性が変わっていないことを確認します。
$ git commit -am 'Cache data using memoize decorator'
$ git push heroku master
get_all_tasks
関数は引数を取らないので、
@cache.cached(key_prefix='get_all_tasks')
で修飾する
(@cache.memoize()
の代わりに) こともできます。この方が少し効率的です。
Jinja2 スニペットをキャッシュする
Flask-Caching
を利用して、Flask で Jinja スニペットをキャッシュすることができます。これは、Ruby on Rails でのフラグメントキャッシング、あるいは Laravel でのレンダリングされたパーシャルのキャッシングに似ています。HTML のレンダリングは CPU 使用率の高いタスクになることがあるため、アプリケーションに複雑な Jinja スニペットがある場合にそれらをキャッシュするのは良い考えです。
CSRF トークンを使用するフォームが含まれたスニペットはキャッシュしないでください。
タスクエントリのレンダリングされたセットをキャッシュするために、task_list/templates/task_list/index.html
で {% cache timeout key %}
ステートメントを使用します。
<!-- ... -->
<table class="table table-striped">
{% for task in tasks %}
{% cache None, 'task-fragment', task['id']|string %}
<tr>
<!-- ... -->
</tr>
{% endcache %}
{% endfor %}
</table>
<!-- ... -->
ここで、タイムアウトは None
であり、キーは連結される文字列のリストです。タスク ID が再利用されない限り、これが、レンダリングされたスニペットのキャッシングのすべてです。Heroku で使用する PostgreSQL データベースでは ID を再利用しないため、条件は整っています。
(SQLite のように) ID を再利用するデータベースを使用する場合は、それぞれのタスクが削除された時点でフラグメントを削除する必要があります。次のコードをタスク削除ロジックに追加することで、これを実現できます。
from flask_caching import make_template_fragment_key
key = make_template_fragment_key("task-fragment", vary_on=[str(task.id)])
cache.delete(key)
アプリケーションで Jinja スニペットをキャッシュした効果を見てみましょう。
$ git commit -am 'Cache task entry fragment'
$ git push heroku master
(最初のリロードを除き) ページをリロードするたびに、リスト内の各タスクで get hit
の増加が確認できるはずです。
ビュー全体をキャッシュする
さらに一歩進んで、スニペットの代わりにビュー全体をキャッシュすることができます。ビューが頻繁に変更される場合やユーザー入力用のフォームがビューに含まれている場合、意図しない副作用が発生することがあるため、これは慎重に行ってください。今回のタスクリストの例では、タスクが追加または削除されるたびにタスクリストが変更され、ビューにはタスクを追加および削除するためのフォームが含まれているため、この条件の両方が当てはまります。
task_list/task_list.py
で @cache.cached()
デコレーターを使用してタスクリストビューをキャッシュできます。
# ...
def is_post():
return (request.method == 'POST')
@bp.route('/', methods=('GET', 'POST'))
@cache.cached(unless=is_post)
def index():
# ...
# ...
@cache.cached()
デコレーターの位置は、
index()
関数の定義の直前 (つまり、@bp.route()
デコレーターよりも後) にする必要があります。
ビューを GET
したときに index()
関数の結果をキャッシュしたいだけなので、
unless
パラメータを使用して POST
リクエストを除外します。GET
ルートと POST
ルートを 2 つの異なる関数に分離することもできます。
タスクを追加または削除するたびにビューが変更されるため、これが起きるたびにキャッシュされたビューを削除する必要があります。デフォルトでは、@cache.cached()
デコレーターは 'view/' + request.path
形式のキー (今回のケースでは 'view//'
) を使用します。
キャッシュされたクエリを削除した直後に、task_list/task_list.py
の作成および削除ロジックでこのキーを削除します。
# ...
cache.delete_memoized(get_all_tasks)
cache.delete('view//')
ビューのキャッシングの効果を確認するには、アプリケーションをデプロイします。
$ git commit -am 'Cache task list view'
$ git push heroku master
最初の更新で、存在しているタスクの数に応じて get hit
カウンターが増加するだけでなく、現在キャッシュされているビューに対応する形で get miss
と set
が増加するはずです。ビュー全体が単一の get
コマンドで取得されるため、それ以降のリロードでは get hit
カウンターは 1 しか増加しません。
ビューのキャッシングは、コストの高い操作または Jinja スニペットのキャッシングを陳腐化しないことに注意してください。キャッシュされる大きな操作内で小さな操作をキャッシュしたり、大きな Jinja スニペット内で小さな Jinja スニペットをキャッシュしたりするのは良いやり方です。(ロシア人形方式キャッシングと呼ばれる) この手法では、構築単位をゼロから作り直す必要がないため、大きい方の操作、スニペット、またはビューがキャッシュから削除された場合のパフォーマンスに寄与します。
セッションストレージでの Memcache の使用
Heroku では、再起動時に内容が失われる一時的なファイルシステムが dyno に備わっているため、セッション情報をディスクに保存することは推奨されていません。
Memcache は、タイムアウトがある短命セッションの情報を保存するのには適しています。しかし、Memcache はあくまでキャッシュであって永続的ではないため、寿命の長いセッションについては、データベースなどの永続的なストレージオプションの方が適しています。
Memcache でセッションを保存するには、Flask-Session が必要です。
(env) $ pip install Flask-Session
(venv) $ pip freeze > requirements.txt
次に、task_list/__init__.py
で Flask-Session
を設定します。
import pylibmc
from flask_session import Session
# ...
def create_app():
# ...
if cache_servers == None:
# ...
else:
# ...
app.config.update(
SESSION_TYPE = 'memcached',
SESSION_MEMCACHED =
pylibmc.Client(cache_servers.split(','), binary=True,
username=cache_user, password=cache_pass,
behaviors={
# Faster IO
'tcp_nodelay': True,
# Keep connection alive
'tcp_keepalive': True,
# Timeout for set/get requests
'connect_timeout': 2000, # ms
'send_timeout': 750 * 1000, # us
'receive_timeout': 750 * 1000, # us
'_poll_timeout': 2000, # ms
# Better failover
'ketama': True,
'remove_failed': 1,
'retry_timeout': 2,
'dead_timeout': 30,
})
)
Session(app)
# ...
今回のタスクリストアプリではセッションを使用しませんが、別のアプリで次のようにセッションを使用できるようになりました。
from flask import session
session['key'] = 'value'
session.get('key', 'not set')