マーケティングプラットフォーム本部 開発部の高橋です。
連載第3回となる今回のブログは、「Google Cloud Next `19 San Francisco(以下、Cloud Next)」で私が参加したブレイクアウトセッションについて紹介します!
この記事の目次は次のとおりです。
- Keeping up with Serverless : Design Pattern Evolutions for a Scalable Future
- Python 2 to 3: Migration Patterns & Motivation
- 最後に
Cloud Nextでは、キーノートの他に500を越えるブレイクアウトセッションが行われました。
各セッションは扱うサービス(AIやサーバレス、G suiteなど)も、その内容(新サービスの紹介や既存サービスの詳細、実際の応用例など)も幅広く、非常に多岐に渡ります。
私は普段、Google App Engine(GAE)やGoogle Kubernetes Engine(GKE)を利用した製品の開発をしており、その開発業務で役立ちそうなセッションを主に聞いてきました。その中から特に参考になった3つのセッションについて、2回に分けて詳しく紹介していこうと思います。
今回は、サーバレスなアプリケーションのデザインパターンについてのセッションと、GAE上のPython 2アプリケーションのPython 3への移行についてのセッションを紹介します。
Keeping up with Serverless : Design Pattern Evolutions for a Scalable Future
こちらのセッションでは、Google Cloud Function、Google App Engine, Cloud Runなどのサーバレスなサービスを利用して、アプリケーションを開発する際のデザインパターンについて説明していました。
ここでは、サーバレスを以下の性質をもつプラットフォームとして定義しています。
・オートスケーリングがすぐに使える
・ゼロまでスケールダウン可能
・プロバイダによって管理されている
このようなサーバレスなサービス上でのアプリケーション開発におけるデザインパターンとして、以下が紹介されていました。
Busy-wait
これは、時間のかかる外部処理を呼び出す必要がある場合には実行完了まで待ち続けるのではなく、PubSubやCloud Tasks等を利用し呼び出しが完了した時点で処理を終了する、というパターンです。
このパターンに沿っていないコード例は以下のようになります。
id = getUserId() value = slowExternalOp() // 外部の処理を呼び出し、終了まで待つ database.write(id, value)
このような実装を行った場合、以下のような問題点があります。
・slowExternalOp が非常に時間がかかってしまう場合、Google Cloud Function(GCF)やGAEのタイムリミットを超えてしまう
・外部処理の終了を待つだけの何もしていない時間も課金の対象になってしまう
Busy-waitのパターンを利用すると、上記のコードは以下のようになります。
# 外部処理の呼び出し id = getUserId() slowExternalOp(MY_TOPIC, metadata={id: id})
こちらの処理は、slowExternalOpの呼び出しが完了した時点で処理を完了します。
次に、外部処理の完了イベントを受け取って、下記の終了処理を実行します。
database.write(msg.id, msg.value)
このように書き換えることで、タイムアウトを樹にする必要がなくなり、無駄な課金も発生しなくなります。
Session affinity / Machine state
Session affinityでは、‶1回目のリクエストと2回目のリクエストが同じマシンによって処理されるとは限らない"という問題が説明され、Machine Stateでは、"仮に同じマシンによって処理されたとしても、マシンが再起動(あるいは再プロビジョン)されているなどの理由で前回のリクエストと同じ状態であるとは限らない"という問題が説明されていました。
いずれの問題に対しても、"マシンの状態に頼るのではなく、キャッシュやDatastoreなどの外部にデータを保持する"ということが大切になります。
Non-scalable parts
非サーバレスなシステムをサーバレスなシステムへマイグレートしていく際に、システム内にスケーラブルではない部分があると問題になることがあります。
例えば、前述のBusy-waitの例で、slowExternalOp()が同時に10個までしか並列で処理できないようなシステムの呼び出しだった場合、同じタイミングで11個リクエストが来てしまうと、そのうちの1つはエラーになってしまうといった問題が発生します。
このような場合、slowExternalOpの呼び出しをPubSubやCloud Tasksなどで置き換えることで、これらのサービスがバッファとして働いてくれて、開始リクエストの受付と実行できるようになるまでのリトライを自動でしてくれるようになります。
さらに、このようなシステムへの呼び出しの制御やフィルタリングを行いたい場合は、GAEやGCFで実装したレイヤを間に挟むことも有効であると説明していました。
Event processing + automation
"Gmailでメールを受け取ったら、10行程度のコードで書けるような簡単な処理を実行し作業を自動化したい"といった場合でも、サーバレス以前の時代であればハードウェアを準備したりミドルウェアをセットアップしたりと、様々な作業が必要でした。
しかし、現在では単に10行のコードを書いてそれをCloud Functionにデプロイするだけで、このような自動化が実現できるようになりました。
Rapid prototyping
サーバレスなサービスを利用すると、アプリケーションの開発を素早く行うことができます。特に、複数のAPIをまとめあげて何らかの処理を行うような、アプリケーションの開発に適しています。
また、以前は素早いプロトタイプ開発とスケーラブルなシステムの開発は同時に行うことが困難でしたが、サーバレスなサービスを利用するとこれらを同時に行うことが可能となります。
まとめ
Busy-waitのパターンなど既に利用しているパターンもいくつかあり、"確かにそうだよね"と思いながら聞いていました。
非スケーラブルなシステムの扱いについてちょうど悩んでいた所だったので、このセッションで紹介していた内容を参考に、より良い解決法を探していこうと思います。
Python 2 to 3: Migration Patterns & Motivation
Python 2は、2020年にメンテナンスが終了します(https://pythonclock.org/)。こちらのセッションでは、Python 2 から Python 3 へのマイグレーションについて、特に Google App Engine 上のアプリケーションに焦点を当てて説明していました。
Python 2からPython 3へのマイグレーションのステップ
このセッションでは、下記のステップに従ってPython 3へ移行することを推奨していました。
1. 依存しているライブラリがPython3をサポートしているか確認する
2. コードがPython 2とPython 3の両方で動作するように修正していく
3. ランタイムをPython 3に変更する
特に "2. コードがPython 2とPython 3の両方で動作するように修正していく"というテーマについて、セッションでは詳細に説明をしていたので、ブログでも紹介したいと思います。
マイグレーションに便利なツール
このセッションでは、コマンドライン上で実行するとPython 2のコードをPython 3に変換してくれる 2to3, modernize, futurize と、インポートすることでPython 3のコードをPython 2の環境で動作させることが可能になるsix, futureについて説明されていました。
2to3 は Python 2 のコードを Python 3 のコードに変換してくれますが、変換後のコードは Python 2 の環境で動作しないという問題があります。
modernize / six と futurize / future の組み合わせは、どちらもPython 2から3へ変換し、変換後のコードをPython 2環境でも動作させることが可能になるというものです。
futurize / futureの方が少し便利な機能が多いようなので、この記事ではfuturizeとfutureについて紹介します。
futurize / future
# original def greet(name): print "Hello, {0}!".format(name) print "What's your name?" name = raw_input() greet(name)
futurize コマンドでこのPython 2のコードを変換すると、下記のコードが得られます。
from __future__ import print_function from builtins import input def greet(name): print("Hello, {0}!".format(name)) print("What's your name?") nam||<e = input() greet(name)
こちらのコードはPython 2環境でも動作しますが、printやraw_inputの部分がPython 3に適した形に変換されていることが分かると思います。
このサンプルコードはシンプルなので、変換後のPython 3環境でもそのまま動作させることが可能ですが、実際のアプリケーションのコードではツールでの変換だけでは不十分な場合があります。
Integer division
Python 2では整数同士の除算は整数を返しますが、Python 3では浮動小数点数を返すように変更されています。
Python 2で書かれたアプリケーションをPython 3へ移行する場合、このような挙動の変化は思わぬバグの原因となりますが、ツールを利用して変換することで挙動を変えないようにコードを書き換えてくれます。
iterators
次の例は、iterators’(イテレータ)と標準ライブラリのインポートについてです。
Python 2の next() メソッドは、Python 3では __next__() に変更されているので、ツールが自動で変換してくれます。
また、Python 3ではライブラリの命名規則が変更になり、例えばPython 2ではimport ConfigParserとしていたものも、 import configparserと全て小文字で書かなければならなくなりました。
こちらもツールが自動で修正してくれます。
import ConfigParser class Upper(object): def __init__(self, iterable): self._iter = iter(iterable) def next(self): return next(self._iter).upper() def __iter__(self): return self itr = Upper('hello') print next(itr), # 改行を回避するため末尾に','を付与
from __future__ import print_function from future import standard_library standard_library.install_aliases() from builtins import next from builtins import object import configparser # 標準ライブラリが全て小文字になっている class Upper(object): def __init__(self, iterable): self._iter = iter(iterable) def __next__(self): return next(self._iter).upper() def __iter__(self): return self itr = Upper('hello') print(next(itr), end=' ')
GAE向けのマイグレーションステップ
GAE上で動作するPython 2.7アプリケーションをPython 3に移行する際は、下記のようなステップで行うと良いと紹介されていました。
一般的なアプリケーションの移行手順
1. Verify your dependencies support Python3
2. Port the code to a Python 2/3 compatible state
3. Change production runtime to Python3
↓
GAE上のアプリケーションの移行手順
1. Verify your Python and service dependencies support Python 3
2. Migrate your App Engine 2.7 dependencies
3. Port the code to a python 2/3 compatible state
4. Migrate to Python 3 production runtime
GAEのPython 2.7環境で動作するアプリケーションの場合、ライブラリだけでなTask QueueやcronなどのGAE固有のサービスに依存していることがあります。これらはGAEのPython 2.7環境以外で動作しないものが多く、Python 3への移行の障壁となります。
そこでGAE上のPython 2アプリケーションのマイグレーションを考える場合には、ライブラリと同様にどのサービスを利用しているかの確認が重要になります。
どのサービスを利用しているかを確認したら、これらのサービスの代替となるものを探しGAE Python2.7環境上でそれらの代替サービスを利用するように修正します。
この修正が完了したら、あとは先ほどと同様にPython 2環境とPython 3環境の両方で動作するようにコードを修正し、ランタイム環境をPython 3に変更することでマイグレーションの作業が完了します。
ここからは、GAE Python2.7環境のサービスとその代替サービスについて紹介していきます。
Google Cloud Tasks
Google Cloud Tasksは、基本的にGAE 1st genのTask Queueとほぼ同じように利用することができます。
ただし、Google Cloud Tasksではpull queueが利用できないため、注意が必要です。pull queueを利用している場合は、Google Cloud PubSubを利用すると、同様の機能を実現することが可能です。
また、transactional tasksやdeferred tasksもサポートされておらず、現状これらのサービスの代替となるような機能は提供されていないため、これらの機能を利用している場合は自分たちで同等の機能を実装するか、既存の実装を変更するなどかなり大掛かりな修正が必要になりそうです。
また、GAE Python 2.7環境では dev_appserver.py で起動するローカル環境にTask Queueのエミュレータが同梱されていましたが、Cloud Tasksでは対応していないためローカル環境での開発について考慮が必要になります。
Google Cloud Memorystore
GAEのmemcacheを利用している場合、Google Cloud Memorystoreが代替サービスとして利用することが可能です。
Google Cloud Datastore, NDB & Firestore
datastoreとndbを利用してGAE Python 2.7環境上のアプリケーションを開発しているユーザーは多いと思いますが、ndbは現在Python 3をサポートしておらず、現状ではdatastoreのクライアントライブラリ(https://googleapis.github.io/google-cloud-python/latest/datastore/client.html)を利用するように修正する必要があります。
ただ、Python 3に対応するndbの開発は進んでおり(https://github.com/googleapis/python-ndb)、現在はまだアルファ版ですができるだけ早くリリースしたいとのことでした。
また、firestoreのdatastore modeがdatastoreの後継プロダクトとなっており、データの整合性やトランザクション関連の機能が強化されているので、firestoreへ移行できるのであればそちらへ移行するのが良いと説明していました。
GAE Python 2.7環境のサポートについて
PythonコミュニティによるPython 2.7のサポートは 2020年までですが、GAE Python 2.7環境のサポート自体はしばらく続くとのことでした。
まとめ
Python 2から3への、特にGAE環境上のアプリケーションの移行について、かなり実践的なセッションでした。
現在開発中のアプリケーションがGAE Python 2.7環境上で動作していて、Python 3への移行を考えていたこともあり、かなり勉強になりました。
最後に
今回はサーバレスなアプリケーションのデザインパターンについてのセッションと、GAE上のPython 2アプリケーションのPython 3への移行についてのセッションを紹介しました。
次回は、GKE上でのJava/Kotlinアプリケーションの効率的な開発についてのセッションを紹介します。
ブレインパッドでは、エンジニアを募集しています!実際のビジネスで自分の知識・技術を活用してみたいという方、ぜひエントリーください!
www.brainpad.co.jp