2018年04月27日

最近のPython-dev(2018-04)

はてなブックマークに登録

バックナンバー:

Python 3.7 がベータになり、大きな変更はなく安定期に入りました。 その間、Python の言語自体やエコシステムに関して重要な話題が幾つかありました。


pypi.python.org から pypi.org

長年 Python のエコシステムを支えてくれていた PyPI がリニューアルしました。

Python 3 への移行を始めとしてモダン化され、 Markdown で書いた README をレンダリングできるようになるなどの改善も入っています。

IRC から Zulip chat へ

freenode に python-dev という IRC チャンネルがあるのですが、新しい貢献者がコミュニケーションを取るのに今更IRCを使うのはハードルが高いんじゃないか。Slack や Discord みたいなモダンな環境を試してみないかというメールを投げた所、 Zulip を試用することになりました。

今のところは python-dev とその周辺に用途が限定されていますが、結果が好評なら将来的にもう少し広い用途にも開放されるかもしれません。

個人的には、UIやモバイルアプリの完成度は Slack や Discord にかなわないものの、サブチャンネル的な topic という機能が便利で、複数の話題が並列したときに1つの話題に集中でき、なおかつ Slack のスレッドのように議論が見えにくくなることもないという点にメリットを感じています。

RHEL 7.5 が Python 2 を deprecate

RHEL 7.5 がリリースされ、リリースノートで Python 2 が deprecate されました。つぎのメジャーバージョン (RHEL 8?) で Python 2 が削除され Python 3 だけがサポートされるようです。

Ubuntu 18.04 LTS の方は Python 2 を main リポジトリから外せませんでしたが、 20.04 LTS までには外すでしょうから、(サポート終了後のマシンが一部残るものの) Python 2 を使った環境はだいたい 2025 年ごろにリタイアすることになると思われます。

PEP 394 アップデート

PEP 394 -- The "python" Command on Unix-Like Systems

この PEP は、 Python 2 と3 の非互換による苦痛を緩和するためのコマンド名とshebangについてのガイドラインを提供しています。大雑把に言うと次のようになっています。

  • Python 2 が利用可能なら python2 コマンドを、 Python 3 が利用可能なら python3 コマンドを用意する。
  • python コマンドは python2 コマンドと同じ Python を起動するようにする。
  • Python 2 にしか対応しないスクリプトは shebang で python2 を使う。 Python 3 にしか対応しないスクリプトは shebang で python3 を使う。両対応のスクリプトが shebang に python を使う。

これはあくまでもガイドラインであり、 Gentoo や Arch のように python コマンドがデフォルトで Python 3 になっているディストリビューションもあるし、 macOS は python2 コマンドを用意してくれませんが、それでも Debian, Ubuntu, Fedora, Red Hat の開発者がこのガイドラインにより足並みを揃えてくれるだけでも無いよりはマシです。

さて、このガイドラインは時々更新されることになっていて、 Fedora / RHEL が Python 2 を捨てるのに合わせてどうするかが話題になりました。現在の Guido の考えはだいたいこんな感じです。

  • Python 2.7.10 が出ると共に、「2桁バージョン嫌い」を克服したので、Python 3.9 の次は多分 3.10 になる。 "python3" というコマンド名を使い続けるのに問題はない。 Python 4 は GIL の卒業といった大きな変更が入るときになるだろう。
  • (Dropbox では) まだ Python 2 と 3 の両方を使っているので、 "python" が常に Python 2 であって欲しい。
  • "python" コマンドが存在しないのは問題ない。
  • Python 3 で venv を作ったときに python というコマンドをオーバーライドしてしまうのは間違いだった。

Python 2 を捨てるのを機に "python" コマンドを Python 3 にしたかった Fedora / Red Hat のメンテナにとっては水を差された形です。

一方ですでに "python" コマンドが Python 3 になっているディストリや venv は現存するので、強引に "python" コマンドが Python 3 を指すことを禁止することもしません。結論として、今回のアップデートの大きな変更点は次のような変更になります。

  • "python" コマンドは存在しなくて良い (存在する場合は、今まで通り "python2" コマンドと同一であるべき)
  • (この PEP が有効な期間中に) "python" コマンドが "python3" になることを期待させる段落を削除

ということで、「早くどこでも python というコマンドが Python 3 にならないかなー」という希望は捨てて、 "python3" が正式な推奨されるコマンド名なんだということに慣れたほうが良さそうです。

PEP 572 -- Assignment Expressions

Python はずっと代入を「文 (statement)」としてきましたが、新しく := という演算子で代入式を追加しようという提案です。

例えば次のようなコードが書けるようになります。

# 572 があるとき
if m := re.match(pat, s):
    # do something with m

# ないとき
m = re.match(pat, s)
if m:
    # do something with m

メールの量が膨大すぎるのと娘の誕生日があったので完全には議論を追えてないんですが、最初のモチベーションはリスト内包表記で値の再利用をしたいということだったと思います。

# foo() を2回計算してしまっている
ys = [foo(x) for x in xs if foo(x) is not None]

# 一回でやりたい
ys = [z for x in xs if (z := foo(x)) is not None]

内包表記に限った構文拡張や、新たにブロックスコープを追加するなど、いろんなアイデアが出ましたが、シンプルさと便利さのバランスで今の提案に落ち着きました。

とはいえ、 Python はずっと「代入は文」という制約と共にあった言語なので、この提案は制約がもたらす可読性を失わせるものでもあります。

今までは複雑な式を読み飛ばしても代入を見逃す危険は無かったのが、PEP 572 が承認された場合は、自分が代入を見逃していないか疑いながらコードを読む必要が出てきます。

なので「追加されれば便利に使うだろうけれども今の段階では +1 はしない」という慎重派も多いですし、反対の人もいます。私も慎重派の一人で、メリットを質だけじゃなくて量(頻度)でも説明してほしいなと思っています。

参照実装もあるので興味のある人は試してみてください。(でもMLでの議論に参加するときは冷静にお願いします。)


@methane

songofacandy at 12:40
この記事のURLComments(0)Python 
2018年04月04日

電波法適合の NFC モジュールで安価な NFC タグを利用する

はてなブックマークに登録

はじめに

当ブログの記事「NFC タグで秘密情報を安全に携行する試み」では MIFARE Ultralight 準拠の NXP 製「NTAG21x」シリーズに目を向けました。そこでは市場で現在主流のこの NFC タグ製品の機能面での奥の深さを Android デバイスを使って試してみましたが、値段も安くすっかり気に入った NTAG21x を自作の装置でも利用したいと考えました。携帯端末の可搬性、据え置き型装置の拡張性、どちらも日常生活での NFC の実用性を支えている土台的な要素ですね。

NFC リーダ/ライタモジュール製品を探す

そんなわけでまずは電子工作向きの NFC リーダ/ライタモジュールを探すことから始めました。ネットを見渡してみると様々なものが出回っており、以下の製品などが目にとまりました。いずれもなかなか良さそうです。

国外のメーカーによるこれらのモジュールは個人でも簡単に購入することができます。一方、国内メーカーの製品情報を探したところでは、いくつもの企業が NFC リーダ/ライタモジュールの製造・販売を行ってはいるもののそのほとんどが法人向けの組み込み用であることを知りました。 その中で、私自身が今回調べた範囲では、2018年3月時点で個人が一般のディストリビュータから入手可能な国内製品はソニー株式会社様による「RC-S620/S」のみでした。もし他にも入手しやすい国内製品があればぜひご教示下さい。

さて、ここで気になるのがこれらの製品と電波法との関係です。

電波法と NFC モジュールとの関係について

ご承知のように、日本国内で「技適マーク」のない無線機を使用すると電波法違反となる可能性があり、外国製の機器を使いたい場合などにはこのことが最初に確認すべきポイントとなります。では、やはり電磁波を発生させる NFC リーダ/ライタは電波法においてどのように扱われているのでしょう。技適証明が必要なのか、それとも周囲への影響が微弱であるため特に規定されていないのか。この点について調べてみました。

まとめ

結論として、電波法には NFC リーダ/ライタに関する明確な規定があり、それに適合しない製品の日本国内での使用は NG であることを知りました。要点を整理してみます。

  • いわゆる技適証明は特定無線設備を対象とするものだが、NFC リーダ/ライタは電波法において特定無線設備にはあたらず「高周波利用設備」に該当し、そこに含まれる「誘導式読み書き通信設備」に分類される
    • つまり NFC リーダ/ライタは技適マークとは関係がない
  • 誘導式読み書き通信設備は、その電界強度が所定の条件を満たしている場合、または、総務大臣による型式指定を受けている場合には個別の許可なしに設置することができる
    • 型式指定の申請者は日本人または日本国内の法人でありかつ製造業者または輸入業者でなければならない
  • 電界強度条件との整合性の確認にも型式指定を受けるにも適正な方法での性能試験が必須

詳細

型式指定表示の実例

手元にあるふたつの製品での型式指定表示と公式サイトからの引用を示します。前掲の RC-S620/S にもしっかり印刷されています。  ※クリックで大きく表示

RC-S620/S について

貴重な選択肢として

今回手元で調べた範囲では、日本の電波法に準じた型式指定表示のある国外メーカーの NFC モジュールは見当たりませんでした、また、「3mの距離において電界強度が500μV/m」などという性能情報を目にすることもありません。 そのため、いまの時点で手近な実験や電子工作を適法に行うためには入手が容易で電波法適合の RC-S620/S は貴重な選択肢ということになります。

個人的には、電波法の必然性と重要性を十分に理解・納得しながらも、一方で、とりわけ IoT 方面での国外メーカーによる国際的にメジャーな製品を公然と試すことのできないケースが発生することをもどかしく思うこともあります。手近な実験や試作のためにわざわざ電波暗室を借りたり渡航したりするわけにもいきませんしね。。 (^^;
さっそくスイッチサイエンス様のサイトから RC-S620/S を購入しました。 本来組み込み用途のこのモジュールの入出力端子は FFC コネクタで一般的な電子工作では何かと取り回しにくいのですが、同社オリジナルのブレークアウト基板と専用ケーブルのセットがあわせて販売されているためとても助かります。

利用者向けの公式リソース

利用者向けに以下のリソースが公式サイトで公開されています。各ダウンロードページに記載された規約・使用許諾契約の内容を十分に確認した上でこれらを利用します。

  • Sony Japan | FeliCa | 法人のお客様 | ダウンロード
    • RC-S620/S製品仕様書<簡易版>
    • RC-S620/Sコマンドリファレンスマニュアル<簡易版>
      規約
             :
      第3条(使用権)

      1. ソニーは、利用者に対して、利用者が対象文書を使用目的のために本ウェブサイトから複製することができる非独占的な権利を無償にて利用者に許諾します。
      2. 利用者は、対象文書の全部または一部を、さらに複製したり、これに対する修正、追加等の改変をすることはできません。また、対象文書およびその複製物を、第三者に頒布したり、ウェブサイトにアップロードするなどして第三者が取得可能な状態にしないものとします。
             :
  • Arduino向けRC-S620/S制御ライブラリの提供
    • 「Arduino向けRC-S620/S制御ライブラリ」ダウンロードページ
      RC-S620/S制御ライブラリ 使用許諾契約
             :
      第1条(総則)
       1.ソニーは、本規約に定める各条項に従い、本ソフトウェアおよび関連資料に関する非独占的かつ譲渡不能な次の権利(但し特許権は除く。以下「使用権」とします)を、お客様に許諾します。
        (1)お客様が、ソフトウェア使用者をして、本ソフトウェアおよび関連資料の全部もしくは一部を使用、複製、複写または改変してアプリケーションソフトウェアを開発する権利。 なお、本規約において、ソフトウェア使用者およびアプリケーションソフトウェアとは、それぞれ、次の意味を有します。
         .愁侫肇ΕД∋藩兌圈本ソフトウェアを入手頂いたお客様1名の方。
         ▲▲廛螢院璽轡腑鵐愁侫肇ΕД◆Д愁法疾夙鸚椰ICカードリーダ/ライター製品「RC-S620/S」を制御するためのソフトウェアプログラム。
        (2)お客様が、本項第1号に基づき開発されたアプリケーションソフトウェアを複製したうえ、ソースコード形式またはバイナリ形式にて、第三者に頒布する権利。
       2.お客様は、前項で定める場合を除き、本ソフトウェアおよび関連資料の全部もしくは一部を使用、複製、複写または改変してはならないものとします。
             :

公式リソースのカバーする範囲

このようにドキュメントに加えサンプルコードが公式に提供されていることは利用者にとってとても嬉しい配慮ですが、これらの参照・使用に際しては以下の事情をあらかじめ理解しておく必要があるでしょう。

  • RC-S620/S はあくまでも組み込み用製品であり本来の顧客は法人である
  • 各仕様書は冒頭のページの注釈のとおり「調査、試作および評価」を目的とする「簡易版」であり、「商用向け」の版は顧客が「正式に RC-S620/S を導入」する場合に提供される
  • 「製品仕様書<簡易版>」 1, 2 項に記載のあるように RC-S620/S は FeliCa に加え ISO/IEC 14443 (Type A, Type B) にも対応しているが、主眼はあくまでも前者である
  • 各仕様書およびサンプルコードは FeliCa まわりの一部の機能に特化した内容である

思いがけない援軍の存在

さて、冒頭で触れたように手元で実現したいのは MIFARE Ultralight NTAG21x へのアクセスです。RC-S620/S が ISO/IEC 14443 Type A(MIFARE)に対応していることは上記の通り仕様書に明記されているものの、公式のリソースではそのための具体的な方法や手順がわかりません。

まずそもそも MIFARE の近接を検知するにはどうすればいいのか。「Arduino向けRC-S620/S制御ライブラリ」の polling() 関数は FeliCa を検知するために「コマンドリファレンスマニュアル<簡易版>」に記載されている「InListPassiveTarget」というコマンドを呼び出しています。
ためしに「InListPassiveTarget ISO/IEC 14443 Type A」のキーワードで情報を検索したところ次の興味深い資料が目にとまりました。

PN532」は NXP による NFC モジュールです。この資料の中の InListPassiveTarget の記述箇所を探すと Page 115 (上図)にそのものずばりのコマンド説明がありました。

「RC-S620/Sコマンドリファレンスマニュアル<簡易版> Version 2.11」 Page 80 の InListPassiveTarget の説明と読み合わせてみると、ターゲットが FeliCa の場合のパラメータ説明は一致しており、さらに「簡易版」には記載されていない ISO/IEC 14443 Type A, B への対応方法を含め 7 ページに渡り詳しい説明が掲載されています。その他のコマンドについても同様で、CommunicateThruEx など RC-S620/S 側のみに存在するいくつかのコマンドはあるものの、全体としてはいわばスーパーセットのマニュアルのように見受けられました。実際にこのマニュアルに添って「Arduino向けRC-S620/S制御ライブラリ」の polling() 関数を以下の要領で書き換えてみると ISO/IEC 14443 Type A の近接を検知することができました。

FeliCa 対応のオリジナルコード

/* InListPassiveTarget */
memcpy(buf, "\xd4\x4a\x01\x01\x00\xff\xff\x00\x00", 9);
buf[5] = (uint8_t)((systemCode >> 8) & 0xff);
buf[6] = (uint8_t)((systemCode >> 0) & 0xff);

ret = rwCommand(buf, 9, response, &responseLen);

ISO/IEC 14443 Type A 用

ret = rwCommand((const uint8_t *)"\xd4\x4a\x01\x00", 4,
                 response, &responseLen);

このように、メーカーの異なる RC-S620/S と PN532 との間にコマンドレベルでの共通性がある理由は NFC フォーラム仕様のどこかにあるのかも知れませんが手元では今のところ該当する情報の特定に至っていません。 あるいは、両社がともに「デバイスやサービス間の新機能と互換性を実現するための規格策定」をミッションのひとつとする NFC フォーラムのコアメンバーであることに関係があるのかも知れません。いずれにせよ PN532 のマニュアルのおかげで先が見えてきました。

NTAG21x ネイティブコマンドの発行について

先般の記事では、NTAG21x のメモリアクセスに必要な次のよっつのネイティブコマンドを挙げました。

  • GET_VERSION
    NTAG 製品のバージョン,ストレージサイズ等を得る
  • READ
    所定のページアドレスから始まる 4 ページ分(16 バイト)のデータを得る
  • WRITE
    所定のページアドレスへ 1 ページ分(4 バイト)のデータを書き込む
  • PWD_AUTH
    パスワードで保護された領域へのアクセス要求に先行してパスワード認証を要求する

これらを RC-S620/S 経由でターゲットへ送出してやれば NTAG21x のメモリを自由に操作できるはずです。

RC-S620/S にはこのようにターゲットとする PICC 固有のコマンドを発行するための CommunicateThruEX コマンドが用意されています。ただし、「コマンドリファレンスマニュアル<簡易版> Version 2.11」 Page 71 の「本コマンドは 212/424 kbps の RF パケットの送受信を行います」の記述のとおりこのコマンドは FeliCa 専用で、通信速度 106 kbps の ISO/IEC 14443 向けには使えません。

一方、「PN532 User Manual Rev.02」にはそれと同様の機能で汎用性のある InDataExchange, InCommunicateThru のふたつのコマンドの情報が掲載されています。同マニュアルの InListPassiveTarget コマンドのページには MaxTg フィールドについて「The PN532 is capable of handling 2 targets maximum at once, so this field should not exceed 0x02.」と記述されており、InDataExchange においては target (Tg) を明示的に指定するのに対し、InCommunicateThruでは「it is assumed that a target has been first activated.」という違いがあります。

  • PN532 User Manual Rev.02 - 7.3.5 InListPassiveTarget
  • PN532 User Manual Rev.02 - 7.3.8 InDataExchange
  • PN532 User Manual Rev.02 - 7.3.9 InCommunicateThru

ふたたび RC-S620/S 側に目を向けると、「コマンドリファレンスマニュアル<簡易版> Version 2.11」 Page 50 の InListPassiveTarget コマンド説明で MaxTg フィールドについては「Target ID を取得する Target の最大数. 01h を指定してください」と記述されています。つまりこの部分の仕様は PN532 と異なる様子であり、この要素の影響を受ける InDataExchange, InCommunicateThru の挙動は、両コマンドの存在有無の確認を含め RC-S620/S 上で実際に試してみる必要がありそうです。

前掲のよっつの NTAG21x ネイティブコマンドの発行を RC-S620/S 経由で試みたところ、InListPassiveTarget によるターゲットの捕捉後に以下の組み合わせが有効であることを手元で確認しました。また、この結果から RC-S620/S においても InDataExchange, InCommunicateThru の両コマンドを利用可能であることが明らかになりました。

  • NTAG: GET_VERSION コマンド (0x60)
    • InCommunicateThru コマンド(0x42)経由での発行のみ有効
      • rwCommand((const uint8_t*)"\xd4\x42\x60", 3, response, &responseLen); の要領
  • NTAG: READ コマンド (0x30)
    • InDataExchange コマンド(0x40)、InCommunicateThru コマンド(0x42)いずれを経由しても有効
      • rwCommand((const uint8_t*)"\xd4\x40\x01\x30[開始ページ番号 1byte]", 5, response, &responseLen); の要領
      • rwCommand((const uint8_t*)"\xd4\x42\x30[開始ページ番号 1byte]", 4, response, &responseLen); の要領
  • NTAG: WRITE コマンド (0xA2)
    • InDataExchange コマンド(0x40)経由での発行のみ有効
      • rwCommand((const uint8_t*)"\xd4\x40\x01\xA2[対象ページ番号 1byte][書き込みデータ 4byte]", 9, response, &responseLen); の要領
  • NTAG: PWD_AUTH コマンド (0x1B)
    • InCommunicateThru コマンド(0x42)経由での発行のみ有効
      • rwCommand((const uint8_t*)"\xd4\x42\x1b[パスワード 4byte]", 7, response, &responseLen); の要領

ライブラリへの加筆とテストプログラムの作成

ここまでの経緯でおおむね材料が揃ったところで、「Arduino向けRC-S620/S制御ライブラリ」へ自分のほしかった機能を追加し、あわせて動作確認用にテストプログラムの作成を行います。

想定した内容

以下の内容を想定しました。NDEF メッセージの解釈や ISO/IEC 14443 Type B の ID 取得など若干の要素を追加しています。さしあたり手元で用途のなかった NTAG21x ネイティブの WRITE, PWD_AUTH コマンドは今回は利用していません。今後必要になったときに実装を追加しようと思います。

  • ライブラリへ追加する関数のイメージ
    • ISO/IEC 14443 Type A の近接検知と ID の取得
    • ISO/IEC 14443 Type B の近接検知と ID の取得
    • NTAG21x 専用: メモリの総ページ数を取得
    • MIFARE Ultralight 用: 所定のメモリページの内容を読み出す
  • テストプログラムのイメージ
    • ターゲットが ISO/IEC 14443 Type A (MIFARE) の場合
      • ID を取得しシリアルへ出力
        • MIFARE Ultralight の場合
          • メモリ内容のダンプをシリアルへ出力
            • NDEF メッセージが記録されている場合
              • NDEF レコード内容を順次取得しシリアルへ出力
    • ターゲットが ISO/IEC 14443 Type B の場合
      • 擬似 ID である PUPI (Pseudo-Unique PICC Identifier) を取得しシリアルへ出力
    • ターゲットが FeliCa (Standard / Lite / Lite-S) の場合
      • IDm を取得しシリアルへ出力

機器の接続

手元ではこれまで Arduino IDE を利用する機会はありましたが Arduino 実機を扱うのは今回が初めてでした。

RC-S620/S と Arduino UNO をシリアル接続して制御を行い、上記のように処理状況を UNO から USB 経由で PC 上のシリアルターミナルへ随時出力することを想定していたのですが、UNO のハードウェアシリアルが一系統のみであることを知り、別立ての USB シリアル変換モジュール(FT231X)と Arduino 標準の SoftwareSerial ライブラリを併用することにしました。

同じ理由で、Arduino IDE から UNO 本体の USB シリアル経由でプログラムを書き込む際には [RC-S620/S 側 TXD] <--> [RX ピン] の結線を外して排他する必要があります。このあたりの事情は Arduino ユーザには当然のことなのでしょうがビギナーにはちょっと新鮮でした。

  

ソースコード

動作の様子

動画:3分19秒

  


(tanabe)
klab_gijutsu2 at 08:38
この記事のURLComments(0)NFC | IoT
2018年03月20日

LVSの高負荷対策 その3 〜drop entryのサービスへの影響について〜

はてなブックマークに登録

こんにちは。インフラ担当の岡村です。
LVSの高負荷対策 その2 〜障害の再現とその原因〜」の記事で、LVSに備わっている高負荷対策機能であるdrop entry機能について紹介しました。今回は、drop entry機能が有効になった時のサービスへの影響を調査したので紹介します。

前回のおさらい

詳しくは前回の記事を見ていただきたいのですが、簡単にまとめると次のようになります。

  • drop entryは、IPVSのエントリをランダムに削除する機能。
    • SYN_RECV状態とSYNACK状態のエントリについては、1秒おきにIPVSのコネクションハッシュテーブルからランダムに選んだ範囲(全体の32分の1)をスキャンし、その中のエントリを削除する。
    • ESTABLISHED状態のエントリについては、「最後のパケットが届いてから60秒以上経過」かつ「最初のパケットが届いてからの受信パケットの合計数が8以下」のエントリのみ削除の対象とする。
  • 空きメモリのサイズが設定した閾値を下回ったときに、自動でdrop entryを有効にすることが可能。
    • 閾値を大きくすれば、少ないエントリ数でもdrop entryが有効になるようになりLVSの耐性は増すが、その分正規のサービス利用者の通信にも影響が出やすくなる。

前回、『drop entry有効時には、IPVSを経由するどの接続もエントリ削除の影響を受ける可能性がありますが、ロードバランサが落ちてサービス停止してしまうよりはずっと良いのではないか』と締めくくりましたが、実際にどれだけ正規の通信に影響を与えるのか気になります。
drop entryが有効になった時のサービスへの影響の大きさも、閾値を決めるための判断材料として知っておきたかったため、検証環境で調査してみました。

調査

まず、ESTABLISHED状態のエントリについてですが、一般的なWebアプリの通信では、「最後のパケットが届いてから60秒以上経過」かつ「最初のパケットが届いてからの受信パケットの合計数が8以下」となるような(正常な)通信は無いと判断し、調査の対象にしていません。
サービスへの影響が考えられるのは、SYN_RECV状態のエントリが削除されるときのみ(※)なので、その割合を測定しました。
(※KLabではDSR構成にしており、SYNACK状態にはならないためです。以降、DSR構成での調査の紹介になります。)

LVS経由でWebサーバと通信する場合、SYN_RECV状態のエントリが削除されると、WebサーバからのSYN,ACKは返ってきますが、クライアントからのACKがWebサーバに届かなくなり、お互いに再送を繰り返し、最終的にtimeoutします。 なので、指定した回数のhttpリクエストを行ってtimeoutになった回数をカウントするスクリプトを作成して、エントリ削除の回数を測りました。
LVSでは、/proc/sys/net/ipv4/vs/drop_entry に3を設定し、drop entry機能を常に有効にしています。検証環境内で、次の図の構成で測定を行いました。


packet_flow2

この場合、何度スクリプトを実行してもエントリは削除されず、timeoutは発生しませんでした。
これは、測定用サーバとLVSがネットワーク的に非常に近く、図の◆銑Δ隆屬僚衢彁間が0.3ms程度だったため、drop entryの処理に引っかからなかったためと考えられます。

SYN_RECV状態の時間が短い方が、drop entry機能で削除されにくくなることを説明するため、イメージ図1,2を作成しました。

drop_entry_image3

図内の矢印はIPVSのコネクションハッシュテーブル上に存在するSYN_RECV状態のエントリを表し、矢印の長さがSYN_RECV状態である時間です。 また、1秒おきに実行されるdrop entry機能のスキャンと削除の処理を、青の長方形で表しました。 すると、この長方形と重なった矢印(エントリ)が、drop entryで削除されると考えることができます。

drop_entry_image4

図2は、矢印の長さだけを変更したもので、図1の半分にしています。SYN_RECV状態が短いほど、drop entryの処理に引っかかりにくく、削除されにくいことがイメージできると思います。
それでは次に、実際に測定してみます。

遅延を考慮した測定

実際の通信では、パケットは外部の回線を流れるのでその分遅延します。遅延が大きければエントリがSYN_RECV状態である時間が長くなるため、削除される割合が多くなると予想されます。
そこで、測定用サーバ上でtcコマンドを使用して、外部と通信する際に発生する遅延を検証環境内で再現した上で、再度測定してみました。

packet_flow1

手元のスマホからLVSへpingすると、往復に100msほどかかっていたので、 50ms,100ms,200msの3通りの遅延時間で、削除されるエントリ数を確認しました。 その結果が次のようになります。

tcで指定した遅延時間50ms100ms200ms
エントリ削除回数 / 全リクエスト数37回 / 32000回79回 / 32000回201回 / 32000回
エントリ削除された割合0.115625 %0.246875 %0.628125 %

どの遅延の場合もおよそ、80 Requests per Second (計測時間400秒)となるように測定しています。
予想通り、遅延時間が大きいほど削除されるエントリ数が多くなりました。

念のため前回の記事と同様にhpingを使用して、エントリ数が異常に増えた場合のtimeoutの割合も調べましたが、結果はほぼ変わりませんでした。例えば、hpingでSYNパケットを350kppsの頻度で送りつけると、LVSの総エントリ数が1300万程度になりました。その状態で100ms遅延させた測定用サーバから上と同じ測定を行った結果、timeout数は70回(およそ0.22%)でした。
ただし、ppsが大きく、途中経路やLVS,Webサーバでパケロスが発生してしまうほどの場合は、パケットの再送により遅延が大きくなり、削除されるエントリ数が上の結果より多くなると思いますのでご注意ください。

まとめ

調査の結果、通信の遅延時間がエントリ削除の割合に関係していることがわかりました。
遅延はクライアントの通信環境やネットワーク的な距離などにも依るので一概には言えません。 しかし、200ms以下の遅延なら削除される割合は1%に満たなかったので、SYNパケットが大量に到来し始めたら早い段階でdrop entryを有効にし、LVSの耐性を上げることを優先して良いのではないでしょうか。

okamura_h at 17:10
この記事のURLComments(0)lvs 
2018年02月08日

Re: Configuring sql.DB for Better Performance

はてなブックマークに登録

Configuring sql.DB for Better Performance という記事を知りました。 コネクションプールの大きさを制御する3つの設定を丁寧に解説されたとても良い記事です。

しかし、この記事で推奨されている設定については同意することができません。私が推奨する設定とその理由を解説していきたいと思います。

Limit ConnMaxLifetime instead of MaxIdleConns

Allowing just 1 idle connection to be retained and reused makes a massive difference to this particular benchmark — it cuts the average runtime by about 8 times and reduces memory usage by about 20 times. Going on to increase the size of the idle connection pool makes the performance even better, although the improvements are less pronounced.

この、 "to this particular benchmark" というのが問題です。このベンチマークでは、8並列で常にDBにクエリを投げ続けています。1つのクエリが終了するとすぐに次のクエリを投げるので、 DB.SetMaxIdleConns(1) で大きな効果が現れました。

このベンチマークの動作は、例えばDBに大量のデータを挿入するバッチ処理などに当てはまりますが、Web アプリケーションなどには当てはまりません。

1秒間に1000回クエリを実行するアプリケーションを想定した簡単なシミュレータを書いてみました。クエリは一様分布でランダムなタイミングで実行され、各クエリと新規接続には10msかかるとします。 (このシミュレータのgist)

MaxOpenConns(20) の時、 MaxIdleConns(4) と MaxIdleConns(10) の動作を比べてみましょう。オレンジの線は総接続数、青い線は使用中の接続数、緑の線は接続が利用可能になるのを待っている時間の最大値をミリ秒で表しています。

maxidle-4-vs-10

1000回のクエリを実行するのに、 MaxIdleConns(4) だと 285 回接続していますが、 MaxIdleConns(10) だとそれを 69 回まで減らすことができています。一方で、負荷が止まった後もずっと維持し続ける接続も増えてしまっています。

今度は SetMaxIdleConns(100); SetConnMaxLifetime(time.MilliSecond * 300) のシミュレーション結果を見てください。

maxlifetime-300

20x4=80 回の接続をしています。 MaxIdleConns(10) のときの 69 回よりも多いですが、これは動作をわかりやすくするために lifetime を短く設定しているためです。シミュレーション時間を100秒に伸ばしたら、MaxIdleConns(10) の場合では接続回数はおよそ 690 回になり、 SetConnMaxLifetime(time.Second * 30) の場合の接続回数は 80 回になるでしょう。

このグラフで、再接続が特定のタイミングに集中し、そのタイミングでレイテンシが伸びてしまっているのが気になるかもしれません。これはシミュレーションが完全に一様分布になっていて、最初に全ての接続がほぼ同時に作られてしまっているからです。時間によって負荷が変動するアプリケーションでは、接続が作られるタイミングがもっと分散するので、このスパイクは発生しにくいはずです。次のグラフは、200msかけて段階的に負荷が増えた後に、上のグラフと同じ1000msの負荷がかかったときのものです。

maxlifetime-300-2

SetConnMaxLifetime を使う他の理由

DB.SetConnMaxLifetime() を提案し実装したのは私です。このAPIはアイドルな接続を減らす SetMaxIdleConns() よりも良い方法ですが、それだけではありません。

"Configuring sql.DB for Better Performance" で紹介されたとおり、 MySQL では wait_timeout という設定で接続がサーバーから切られる恐れがあります。また、OSやルーターが長時間利用されていないTCP接続を切断することもあります。どのケースでも、 go-sql-driver/mysql はクエリを送信した後、レスポンスを受信しようとして初めてTCPが切断されたことを知ります。切断を検知するのに何十秒もかかるかもしれませんし、送信したクエリが実行されたかどうかを知ることもできないので安全なリトライもできません。

こういった危険をなるべく避けるためには、長時間使われていなかった接続を再利用せずに切断し、新しい接続を使うべきです。 SetConnMaxLifetime() は接続の寿命を設定するAPIですが、寿命を10秒に設定しておけば、10秒使われていなかった接続を再利用することもありません。

接続の寿命を設定することで、他にも幾つかの問題に対処することができます。

  • DBサーバーがロードバランスされているとき、サーバーの増減をしやすくする
  • DBサーバーのフェイルオーバーをしやすくする
  • MySQL でオンラインで設定変更したとき、古い設定で動作するコネクションが残り続けないようにする

接続のアイドル時間を制限するAPIを別に追加しなかったのは、現実的な環境における性能への影響と、 sql.DB の実装の複雑さを天秤にかけた結果です。

推奨する sql.DB の設定

  • SetMaxOpenConns() は必ず設定する。負荷が高くなってDBの応答が遅くなったとき、新規接続してさらにクエリを投げないようにするため。できれば負荷試験をして最大のスループットを発揮する最低限のコネクション数を設定するのが良いが、負荷試験をできない場合も max_connection やコア数からある程度妥当な値を判断するべき。
  • SetMaxIdleConns() は SetMaxOpenConns() 以上に設定する。アイドルな接続の解放は SetConnMaxLifetime に任せる。
  • SetConnMaxLifetime() は最大接続数 × 1秒 程度に設定する。多くの環境で1秒に1回接続する程度の負荷は問題にならない。1時間以上に設定したい場合はインフラ/ネットワークエンジニアによく相談すること。

@methane
songofacandy at 19:35
この記事のURLComments(0)golang 
2018年01月30日

最近のPython-dev(2018-01)

はてなブックマークに登録

バックナンバー:

Python 3.7b1 が29日の予定です。問題なければ日本時間の今日中に出るはずですが、buildbotやTravisが不安定なので少し遅れるかもしれません。これで feature freeze なので新機能追加やパフォーマンス向上などは基本的に終わりです。

beta 前の駆け込みで、先月号で紹介したAcceptされたPEPを実装する大型コミットがたくさんマージされました。(間に合わなかったものは無いはず。たぶん。)

今回は私が関係していたところを中心に紹介していきます。


dict の順序の言語仕様化

Python 3.6 から dict が挿入順序を保存するようになりましたが、この挙動は実装詳細であり、まだ言語仕様ではありませんでした。そのため、他の Python 実装で同じ挙動になることは保証されません。具体的には PyPy は同じ挙動(というかCPythonがPyPy実装を真似した)ですが、 MicroPython は違います。

この順序に依存しないように、内部では維持している順序をイテレート時にランダム化するような改造をするか、便利だしいっその事言語仕様にしてしまうかという話題があって、結果として 3.7 からは言語仕様になることになりました。

これに関連して、僕が collections.OrderedDict からリンクリストを削ってメモリ使用量を半減する実装を試していたのですが、挿入順を維持するためだけなら dict を使えるのでこの実装はディスコンになりました。

hasattr(), getattr() の高速化

https://github.com/python/cpython/pull/5173

hasattr(), getattr() は探索する属性が見つからない場合、内部で一度 AttributeError を発生してからそれを握りつぶして、 hasattr なら False, getattr なら第三引数に渡されたデフォルト値を返すという挙動をしていました。

この例外を発生させる+潰すの組み合わせが若干のオーバーヘッドになっているのでなんとかしたいのですが、すべてのケースで回避することはできません。 Python で __getattr__ を定義しているときにも AttributeError を投げるのが決まり(プロトコル)ですし、Cレベルでこれに相当する tp_getattro というスロットも同じプロトコルにもとづいています。

しかし、属性アクセスについて何らかのカスタマイズが不要な場合、 tp_getattro にはデフォルトで PyObject_GenericGetAttr() という関数ポインタが設定されています。 tp_getattro を呼び出す前に、それが PyObject_GenericGetAttr であれば、属性が見つからなかったことを例外を使わずに返す特殊化された関数を呼ぶようにしました。

これで、 getattr をカスタマイズしていない多くの型で getattr() や hasattr() が高速になりました。 例えば Django のテンプレートで hasattr(s, '__html__') としているところがあり、かなりの回数で False を返すので、少し高速化できたことを確認しています。

私は hasattr(), getattr() だけを高速化していたのですが、さらに Serhiy さんが _PyObject_Lookup という名前の新プライベートAPIとして整備して、 Python 内部で PyObject_GetAttr() を呼んでから AttributeError を消していたすべての呼び出し元を新APIを使うように書き換えてくれました。

定数畳み込み

例えば 1 + 2 というコードがあったとき、1 と 2 という定数をスタックに積んでから BINARY_ADD するというバイトコードが一旦作られます。

その後、 peephole と呼ばれているバイトコードレベルの最適化で、連続する LOAD_CONST のあとに BINARY_ADD が来たときは、1 + 2 を計算してしまって結果の 3 を定数テーブルに登録し、1つの LOAD_CONST に置き換えるという最適化をしていました。これを定数畳み込みといいます。

しかし従来の方法には欠点がありました。畳み込み前にテーブルに入っていた定数が不要になるかもしれないのに、そのまま残ってしまっていたのです。次のサンプルコードでは、定数3しか利用していないのに、1と2がテーブルに残っている例です。 (なお、co_consts の最初にある None は docstring なので消せません)

>>> import dis
>>> def x():
...     return 1+2
...
>>> x.__code__.co_consts
(None, 1, 2, 3)
>>> dis.dis(x)
  2           0 LOAD_CONST               3 (3)
              2 RETURN_VALUE

定数畳み込みのあとに定数テーブルを再構築するという最適化が提案されたこともありますが、定数テーブルを2回作り直すよりも定数畳み込みをバイトコード生成前のASTの段階でやってしまう方が筋が良いだろうということで却下されていました。

そしてASTレベルの定数畳み込みは、Eugene Toderさんが実装されていたのですが、それも何年も放置されていました。 それを見つけて今のバージョンで動くように手直して、 Python 3.7 に入りました。 Python 3.7 では上の例はこうなります。

>>> x.__code__.co_consts
(None, 3)
>>> dis.dis(x)
  2           0 LOAD_CONST               1 (3)
              2 RETURN_VALUE

TextIOWrapper の encoding を変更可能に

「Python 3.7 でテキストファイルのエンコーディングを初期化後に変更可能になります」 という記事で紹介したのでそちらを参照してください。

lru_cache の省メモリ化

functools.lru_cache は dict と双方向リンクリストを使って LRU を実装しています。

dict と双方向リンクリストという組み合わせは OrderedDict と同じです。同じ実装が2種類あるのは無駄だなぁと思って一度 OrderedDict を使う形で書き直してみたのですが、パフォーマンスが落ちてしまいました。 OrderedDict は dict を継承しているしがらみが強くて、要素を削除する時などに内部で探索をなんどかやり直していることがあり、 dict + 双方向リンクリストとしての実装は lru_cache の内部実装の方が圧倒的に素直だったのです。

ということでこの試みは失敗したのですが、その際に lru_cache の双方向リストの各エントリがGC可能オブジェクトになっていることを発見しました。これにより、一要素ごとに3ワード (64bit マシンでは 24byte) のオーバーヘッドがかかり、GCの処理時間も長くなります。

Python の巡回参照GCを動作させるには、各エントリを個別に走査してもらわなくても lru_cache 本体が走査されるときにリンクリストを舐めて行けばすむということを思いつき、各エントリのGCヘッダを削除することができました。これでキャッシュされている要素数 * 24バイトの削減と、lru_cacheをヘビーに使っているアプリでのGC速度の向上が見込めます。

ABCのC実装

https://github.com/python/cpython/pull/5273

Python の起動時間を遅くする3大要因が ABC と Enum と正規表現です。(独断と偏見)

起動時にほぼ確実に import されるモジュールに _collections_abc があるのですが、これを見ると、たくさんの ABC が定義され、たくさんの型がそこに register されているのがわかります。

以前に紹介した importtime オプションで確認してみます。

$ local/py37/bin/python3 -Ximporttime -c 42
import time: self [us] | cumulative | imported package
...
import time:        74 |         74 |       _stat
import time:       202 |        275 |     stat
import time:       166 |        166 |       genericpath
import time:       244 |        409 |     posixpath
import time:      1696 |       1696 |     _collections_abc
import time:       725 |       3105 |   os
import time:       218 |        218 |   _sitebuiltins
import time:       160 |        160 |     _locale
import time:       155 |        315 |   _bootlocale
import time:       500 |        500 |   sitecustomize
import time:       220 |        220 |   usercustomize
import time:       692 |       5047 | site

ABCの定義とregisterの両方を根本的に高速化するために、Ivan Levkivskyi さん (つづりが難しい) がC実装を作ってくれたのですが、色々問題があって協力して改善中です。

量としては多くないんだけどメタクラスと weakref を扱うので正しく実装するのが面倒なんですよね。。。

なんとか 3.7b1 までにマージしても問題なく動きそうなところまでは持っていったのですが、 3.7 のリリースマネージャーが b1 後にしても良いよと例外を認めてくれたので、一旦落ち着いて実装を磨き直す予定です。

これがマージされると、 Python の起動時間 (python -c 42) はこんな感じになります。

バージョン起動時間(ms)
3.616.4
3.7b114.4
3.7b1 + C-ABC13.9

また、 python -c 'import asyncio' の時間も、 Python 3.6, 3.7b1, C-ABC で 88.2ms -> 58.7ms -> 57.6ms と早くなって来ています。

(追記) str, bytes, bytearray に isascii() メソッドを追加

Python の str の isdigit() などのメソッドは Unicode 対応しているために非ASCII文字に対しても True を返してしまいます。また int() も Unicode 対応しています。

>>> s = "123"
>>> print(ascii(s))
'\uff11\uff12\uff13'
>>> s.isdigit()
True
>>> int(s)
123

しかし、ASCII文字だけを受け付けたいというケースもあるでしょう。その時に .encode('ascii') が例外を投げないかチェックするのは手間です。

そこで str.isascii() メソッドの追加を提案しました。これを使えば s.isascii() and s.isdigit() で ASCII の整数だけが使われているかをチェックできます。

いろいろな意見はあったものの基本的に反対意見はなくて、Guidoがbytesとbytearrayにも同じメソッドを追加しておいてというメッセージで決まりました。

実装の話として、 str は内部で ASCII フラグを持っている (このフラグで UTF-8 へのエンコードがスキップできる) ので O(1) ですが、 bytes と bytearray はバイト列を舐めて最上位ビットが立っていないかチェックしているので文字列長nに対して O(n) になります。

(追記) PEP 567 -- Context Variables

引数を使わずに関数呼び出しを超えて状態を持ち回りたい事があります。

例えばWebアプリケーションだとしたら、ログを書く時に現在のリクエストのIDとかユーザーIDを残したいけれども、ログを書く全ての関数の引数にそれらを追加したくないなどです。

Python の標準ライブラリで言えば、 decimal モジュールが演算精度などをコンテキストとして管理しています。

こういった状態は、マルチスレッドではスレッドごとにコンテキストが異なるので、従来はスレッドローカル変数に格納していました。

しかし、 asyncio などの非同期フレームワークでは、複数のコンテキストが1つのスレッドで動くので、スレッドローカルでのコンテキスト管理がうまく行きません。そこでスレッドと独立したコンテキスト管理のためのライブラリが PEP 567 で追加されました。

これを使えば、各スレッドが「現在のコンテキスト」を持っているものの、スレッド内でコンテキストの切り替えが明示的にできるので、 asyncio だとイベントループが関数呼出しする毎にそのコンテキストを復元してやることができます。

MLでの議論は長すぎて私は見てなかったのですが、そろそろ Accept されそうだというところで依頼されて実装の方のレビューをしていました。

内部実装としては HAMT と呼ばれるアルゴリズムを利用していて、名前は聞いたことがあったもの詳細は知らなかったので、それを理解しながらC実装をレビューするのは楽しかったです。

songofacandy at 16:01
この記事のURLComments(0)Python 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント
Syndicate this site