大きなシステムのトラブルを起こしてみると

転職したのがそれなりに、というかかなり、というかめっちゃでかいシステムを扱う会社なので、トラブルが起きた時も被害がすごいんですよね。一時間システムが止まっただけで被害額がこれだけになる、と上司や同僚から聞かされるにつれ、そういったトラブルを起こしたくはないなあと常々感じていたわけですが…現在担当しているプロジェクトの規模からも相当でかいので、そりゃね、トラブルのタネは色んなところに潜んでいるわけですよね。そして、それを踏むわけですよね…泣

しかしまあ、今回は本当に「やらなきゃやばいな〜」と思ってた内容でもなく、普通に慣れていなくて意識の外にあった内容が原因で、おかげでへこむこともないぐらいの勢いであります。

単純にある機能について忘れていたというのとは少し性格が違っていて、ビジネス規模のおかげで機能要件、非機能要件の優先順位が自分の今までの経験と比較してかなり異なるということ。追加機能が細かい範囲までちゃんと動くようにしっかり実装することよりも、リリースした昨日の大部分が平均的なユーザに対して(ECサイトなので)購買が正しくできることの方が売上規模の上では重要だったりする。それぐらい、基礎となる機能は(ビジネス上)完成してしまっている証左でもあります。

正直なところ、例えば中小企業が自社サービスで辿り着きたいラインなんかはとっくに越えていて、次の段階で勝負している会社なんだな、と改めて感じている。これだけ書くと自社自慢みたいに聞こえるな。正直、ビジネスはうまくいってるけど「成長痛」がすごいので、後遺症が残らないようにケアしていくのが(中途社員としての)これからの課題という感じですかね。今回も、自分の意識から完全に漏れていたので口が裂けても偉そうなことは言えないが、あえて客観的に言うとしたら、単なるシステム機能開発の範囲を超えた、いわゆるマーケティング的な観点をもう少し研修とかで大前提で教える機会があった方がいいと思う。今回ミスした担当者は私自身も含めて、システム開発に関しては相応のキャリアを持っている人間が多かったし、それ故にマーケティング的な観点を見落としたんだと感じている。

転職してまだ半年を超えたところだが、キャリア的には大規模自社Webサービス運営とWebマーケティングという、新しい領域の仕事ができてとても楽しい。とことん、新しいことをやっていたい性格なんだなと思う。そしてこんな世の中の状況なので、幸運だなとも心から思う。本来なら社内の反省文とか始末書とかで書くべき内容なのかもしれないが、別にそういう書類を書く文化でもないのでこうして記録に残しておきます。ポテンシャルと実績が両方伴っている、珍しい会社なので優秀だけどくすぶっている方、いらっしゃれば大歓迎ですよ(突然の勧誘)。情報量がないただの駄文になってしもうた。おやすみなさい。

PythonのWebフレームワーク - Falcon(1)

仕事以外でWebアプリを作るかも、という話になっていて、言語もフレームワークも自由なのでどーしよーかな、と迷っているところです。こういう場合はだいたい、作り慣れている、業務で経験あるものを選ぶのが手堅いというのは分かりつつ、技術者のサガとしては新しいものを求めてやまないわけです。

そんな中、PythonはいいとしてFlask以外にも何かないんかいな、ということで最近流行ってるフレームワークを検索してみました。こういう時には大抵、英語で書かれたブログが出てきますよね。New Python Web Framework in 2020、みたいな。日本語の記事よりも広範囲な内容のことが多い気がする。別にFlaskが嫌とかではないんですが、Micro frameworkの中ではけっこうクセ強いじゃない?と何年か使ってみて感じたので、もう少しプレーンなやつはないんかな、と感じてのことです。

falcon.readthedocs.io

というわけで、まずはFalconを選んでみました。雑に眺めた感じだと、brazing fastを押してる感?Pypy引っ張り出してきて他のフレームワークより早いことを強調しているように見えます。

At a glance

ルーティング

パス文字列とエンドポイント用クラスとの対応という素直な指定方法。クラスの中にHTTPメソッドごとにメソッドを持たせる(def on_getがGETで呼ばれる)。クラスは呼出可能オブジェクトならなんでもいけるんでしょうか。

api.add_route('/images', images)

HookとMiddleware

デコレータで基本処理の前後に処理を付け足せます、というデコレータの使い方の見本みたいな機能。ミドルウェアwsgiと同じ意味なのでこれも素直な機能。Extensibleを謳っているのはこの部分?

def validate_image_type(req, resp, resource, params):
    if req.content_type not in ALLOWED_IMAGE_TYPES:
        msg = 'Image type not allowed. Must be PNG, JPEG, or GIF'
        raise falcon.HTTPBadRequest('Bad request', msg)

@falcon.before(validate_image_type)
def on_post(self, req, resp):
    # ...

URLのパラメータ化

Flaskであんまり好きじゃない仕様ですが、ここはいい感じの記法っぽい。というか最近のWAFはだいたいそこらへんは改善されているイメージ。

Note:
Falcon also supports more complex parameterized path segments that contain multiple values. For example, a version control API might use the following route template for diffing two code branches:

/repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}

例外でHTTPエラー

ちゃんと見てないけど、結構な分量がチュートリアルでこの話題に対して割かれている。関係ないけどTDDも重視しているっぽい。

テンプレートもORMもなし

まあ、microframeworkを標榜しているのでそりゃそうかと思いつつ、Flaskみたいにjinja使えますみたいな例示もなく、本当にJSONを返すサーバとしての内容がドキュメントのほとんどを占めている。というかテンプレートの説明が薄いことに関しては、フロントでかなりの部分をまかなえる時代なので、そりゃそうかもなという気持ちにもなる。

もうちょっと調べてみる?

というか全部素直なので、学習コスト低い。徹底している。あとはこれだけだと実用に耐えないので、そこをどう補っているのか、というmicroならではの課題をどうするのか、もう少しつっこんで調べてみようと思う。

WebAssemblyのname section

去年WebAssemblyのインタプリタを作って、最近はbinary formatに対応したものを作ろうとしている。特に明確な目標があるわけではない。

前回は最小限で形になる範囲しか対象にしなかったので、仕様書の一部を読めていなかった。具体的には、verificationがその一つである。そこ以外は目を通していたつもりでいたのだが、実は密かに(?)追加された部分を先日見つけて、今回はその話である。

WebAssemblyが扱う単位はモジュールと呼ばれ、その中は複数のセクションに分かれている(図を参照)。

f:id:ironoir:20210113083750p:plain
セクション一覧

このうち、特別なのがcustom sectionで、その名の通り、具体的な内容は決まっておらず、また、仕様上唯一、他のセクションの間のうち、どこでも好きな場所に配置することができる。WebAssemblyのセマンティクスには影響せず、実装に無視されるかもしれないとも書いてある。以前であればその使い道は「デバッグ用など」と簡単に書いてあるだけだったのだが、仕様書に具体的な使い道の例が追加されていることに気がついた(以前からあったのならごめんなさい)。それがname sectionである。

name sectionは、セクション名がnameであるセクションのことで(ややこしいな)、そのモジュールの中で一回だけ、かつdata sectionの直後にだけ出現する。

The purpose of this section is to attach printable names to definitions in a module, which e.g. can be used by a debugger or when parts of the module are to be rendered in text form.

とのことで、name sectionの名の通り、モジュールの中で使用されている名前を持っておくセクションで、やはりデバッグ用途を想定しているらしい。そしてその中はさらに細かくサブセクションに分かれており、モジュール名、関数名、ローカル変数名のどれかが入るとのこと(コンパイルすると消えてしまう情報ということですね)。

こういう情報は仕様書に含まれている含まれていない、どちらにせよ実装側としては何らかの形でほしいものなので、こういった形でしっかり仕様書に定義を書いてくれている方がやりやすいというか、ありがたい。今作っている実装にも付け足せたらいいな。

Slackアプリを作りたい

唐突ですが、そういうことです。本来はあまり特定のプラットフォームに乗ったアプリを書くことは好きではないんですが、そんなもんどんなプログラミング言語でも、多かれ少なかれ巨人の肩に乗って書いているので拡大解釈すればいけます。

www.shoeisha.co.jp

そんなわけで上記の書籍を読みながら入門してみます。ざっくりいうと、Slackアプリはアドオンみたいなもののようです。APIを呼ぶだけであれば別にアプリにする必要もないし、またSlackは元々リマインドの機能があるので、それぐらいの機能であれば特にわざわざプログラミングしてまでSlackアプリを作る必要もないみたいです。

いろいろあってみたいことはあるんですが、特にGoogle Workspace(旧G Suite)と連携したいんですよね。そうなるとデータを読みつつみたいな連携になるのでSlackアプリに興味を持ったという経緯です。

作る際には、普通にアプリ専用のWebサーバが必要です。というのもアプリを作る際にSlack本体サーバから呼んでもらうURLを登録する必要があって、それがコールバックみたいな役割を果たします。それを真面目にやろうとすると当然自分でクラウドとかでインスタンス立てる必要が出てきます(書籍内ではそれ以外の方法も紹介されているけど)。

意外だったんですが、SDKJavaScript以外にもいくつか用意されてるんですね。別にURL叩ければいいのでなんでもいいはずなんですが、なんとなくJavaScriptしかだめなイメージがありました。Pythonに慣れてるのでそれがいいかなと思いつつ、まだ正式にリリースというわけではないので、今回は大人しくJavaScriptで。というかJSじゃなくて本当はTypeScriptらしいです。

今回は簡単なエコーアプリ(Slackでアプリに向けてメンションするとその内容をそのまま返してくる)を作ったんですが、実際には結構、いい感じにUIを生成できたり、インタラクションも作り込めるみたいで期待が高まります。Slackが人気あるのも頷けるところであります。

しかしまあ、仕事で使ってはいるものの、いまいちまだSlackを使いこなせていない感は否めません。ほぼ完全に在宅勤務になってからはこれがないと話にならないぐらい依存しているわけですが、かなりの流量がある割に検索方法がよくわかっておらず、目grepしていることも多い(むしろ、検索は強くなかったりするんですかね?思った感じで結果が出てくることが体験上少ないので。。。)。そのあたりをSlackアプリで直接改善したりはできないのかもしれませんが、APIを知ることで内部機能なんかにも詳しくなっていきたいですね。開発者に優しいという評判通り、SDKやSlack APIのテスト機能など、かなり親切なUIだな、という印象を持っているので、そこから作る側としても学んでいきたいのお。

Rustのimpl Traitが使えそうで使えない場所

前提の復習

trait objectについて復習している。各書籍にはどう書いてあったんだっけ。

O'ReillyのプログラミングRust

まず、O'ReillyのプログラミングRustだと、11.1.1 トレイトオブジェクトに書いてある。

www.oreilly.co.jp

勝手に今知りたいポイントだけ書くと、 - trait objectはfat pointer(データへのポインタとvtableへのポインタ) - C++とはvtableの持ち方が違う(C++だと構造体側についている) - あるtrait objectが何を指しているのかは、実行時までわからない(仮想メソッドの呼出エラーをチェックするオーバーヘッドが残る)

なんとなくtrait objectを使うと遅い、とは思っていたが、実は今より書き方が少し古いので、dynとかimplとかは出てこない。

実践Rust入門

gihyo.jp

こちらの書籍では、「8-3 静的ディスパッチと動的ディスパッチ」で書かれているので確認してみる。 要するに、trait objectは動的で、ジェネリクスだと静的ということかな。

簡潔なQ

qnighy.hatenablog.com

でも結局、ここを見るのが一番早い(でも何回も見ている)。特にまとめ。

impl Traitが使えそうだけど…

本題。今回だと、parsingを行う関数をつくりたい場合、Readを実装さえしていれば、.bytes()とかで処理をかけるので、具体的な型を関数定義で書く必要はない。シグニチャはこんな感じ。

fn read_version(reader: &mut impl Read)

実装途中で処理の共通化をしようと思って、追加の引数に関数を渡せるようにした。 このfmap()に渡してcollect()する用の関数です。

fn read_vec<R>(reader: &mut impl Read, f: fn(reader: &mut impl Read) -> R) -> Vec<R>

ところが、これだとエラーが出る。

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
  --> src/section.rs:99:59
   |
99 | fn read_vec<R>(reader: &mut impl Read, f: fn(reader: &mut impl Read) -> R) -> Vec<R> {
   |   

前提で確認したとおり、impl Traitが使えるのは以下の2つの場合。

  • 引数で使われた場合 (RFC 1591)
  • 戻り値で使われた場合 (RFC 1522)

で、今回のエラーが出ているのは「引数として渡す関数内の引数」ということで、使える範囲に該当するのでは、と思って書いたのだが、だめだった。

ただ解決は難しくない。 引数でimpl Traitが使われた場合、以下のように翻訳される。

// before
fn give_42_to_callback(callback: impl Fn(i32))
// after
fn give_42_to_callback<F: Fn(i32)>(callback: F)

というわけなので、最初から翻訳後の状態で(impl Traitを使わずに)書けば問題なくビルドが通るようになる。

// before NG
fn read_vec<R>(reader: &mut impl Read, f: fn(reader: &mut impl Read) -> R) -> Vec<R>
// after OK
fn read_vec<T: Read, R>(reader: &mut T, f: fn(reader: &mut T) -> R) -> Vec<R>

むしろ、こっちの方が少し短くなってスッキリするしいい感じかも(それがエラーの原因ではないだろうけど)。

LEB128形式の数値をRustで読み取る

WebAssemblyのbinary formatでは、数値の読み込み時にLEB128形式と呼ばれる可変長のフォーマットを使う。以下を参考にして、その関数をRustで書いてみた。ちなみに書籍の中ではRubyで書かれているので、比較のためその部分だけ抜粋する。

booth.pm

Ruby実装(上記書籍より引用)

def read_unsigned_leb128(max_bits)
  value = 0
  shift = 0

  loop do
    b = readbyte
    value |=  ((b & 0x7F) << shift)

    shift += 7
    break if b[7] == 0
    raise "Invalid LEB128 encoding" if shift >= max_bits
  end

  value
end

Rust実装

fn read_u32_from_leb128<T: Read>(reader: &mut BufReader<T>) -> u32 {
    let mut acc: u32 = 0;
    let mut count: u8 = 0;
    for byte in reader.bytes() {
        if let Ok(b) = byte {
            let val: u32 = (b & 0b01111111) as u32;
            let shifted_val = val << (7 * count);
            acc += shifted_val as u32;
            count += 1;
            if b < 0b10000000 { break; }
        } else {
            break;
        }
    }
    acc
}

実装していくつか気づく点があった。まず、やはりというか型の厳密さである。まず、Rubyの場合は戻り値Integerの大きさは言語上制限がないので、呼び出し時に最大ビット幅を指定してチェックしている。一方、わたしの実装はu32を返している。厳密なWebAssemblyの仕様上は扱える整数のビット幅は自由に指定可能なので、Ruby実装の方がそれをうまく表現できている。ただし、同時に仕様書に実際出てくるのは、以下のようにキリのいいビット幅のみということを考えると、個別の型ごとに関数を分けた方がパフォーマンスが出るかもしれない、と考えてこのようにした。

Note: The main integer types occurring in this specification are u32, u64, s32, s64, i8, i16, i32, i64. However, other sizes occur as auxiliary constructions, e.g., in the definition of floating-point numbers.

とはいうものの、ここを何かジェネリックな書き方ができればうれしいな、とも思う。単純にジェネリックにしても、そこにつけるトレイトをどのようにすればいいのか困る。基本数値型をまとめて指定できる方法があればいいのにな、とも少しだけ感じた(不便はほとんどない)。

Rustのrotate shift

超小ネタ。Rustには標準でrotate shiftが用意されている。例えば左に1つずらすrotate_leftだと、調べてみると基本的な型にはそれぞれ実装されている。ちなみに専用のtraitがあるというわけではないらしい。

doc.rust-lang.org

挙動に関しては特に驚くようなことはない。もちろん、これとは別に<<演算子が用意されていて、挙動が異なるとアナウンスされている。

Please note this isn't the same operation as the << shifting operator!

    let o = 0b11110000u8;
    assert_eq!(o.rotate_left(3), 0b10000111);
    assert_eq!(o << 3, 0b10000000);

ここまではいいのだが、試しにsignedな数値に同じことをしようとしたら、怒られた。

fn test_rotate_shift() {
    let p = 0b10000000i8;
    assert_eq!(p.rotate_right(1), 0b01000000);
    assert_eq!(p >> 1, 0b11000000);
}
error: literal out of range for i8
   --> src/main.rs:158:13
    |
158 |     let p = 0b11110000i8;
    |             ^^^^^^^^^^^^ help: consider using `u8` instead: `0b11110000u8`
    |
    = note: `#[deny(overflowing_literals)]` on by default
    = note: the literal `0b11110000i8` (decimal `240`) does not fit into the type `i8` and will become `-16i8`

どうやら、overflowing_literals属性というのをつけないといけないらしい。

#[allow(overflowing_literals)]
fn test_rotate_shift() {
    let p = 0b10000000i8;
    assert_eq!(p.rotate_right(1), 0b01000000);
    assert_eq!(p >> 1, 0b11000000);
}

これで無事に、signedな数値を、マイナスの符号がついていても2進リテラル表記できるようになった(符号がプラスならば属性がなくとも表記可能)。当然の話だが、rotateの結果は符号を保存しないがshift演算子は符号を保存することにご注意。