五分後の世界 / 村上龍

f:id:asmsuechan:20210108163905j:plain

読んでいる途中で思った感想を読後ちょっとまとめたもの。

以前の装丁の方が好きだった。

あらすじ

ランニングをしていたはずの小田桐は気付けば霧の中を行進していた。そこは元々いた世界から5分時間がズレた別の世界で、その世界の日本人はアンダーグラウンドと呼ばれる地下に住み、武装しゲリラとして国連軍と戦っていた。日本の国土は終戦アメリカ、イギリス、ロシア、中国により分割統治され、虐殺などによって日本人の人口は26万人にまで減少していた。小田桐は幾度かの戦闘に巻き込まれながらもその日本で生きる人間を観察しながら自身も生き延びる。この小説は、小田桐を通じてアメリカ的文化と価値観の受容を強要されることなく強い日本であり続けようとした日本を書いた小説である。

感想

私がこの本を最初に読んだのは中学二年生の時で、続編のヒュウガ・ウイルスを含めて3周程度読んだように記憶している。それから10年経ち、ふと読みたくなったので再度購入して読んだ。

いわゆる歴史ifもので、2発の原爆を落とされても降伏せず本土決戦が行われて現実よりも酷い状態で終戦を迎えた日本が舞台。時間は恐らく書かれた年の1994年。太平洋戦争後、日本の国土は割譲されて各国の移民が住み始め、混血も進んだ。混血の結果、移民や難民の血が混ざった準国民と純血の日本人である国民の2つの階級が出来た。また、日本に認知されている混血は準国民として国民と差別されることなく扱われるが、調査に合格しない者は地上のスラムなどに住み酷い生活を送ることになる。日本人は文字通りアンダーグラウンドという地下に潜り日本人という種を残すために国連軍などに対してゲリラ活動を続けている。

散々負けて国家としての体を保つのが限界になっているため、一般的な国家同士の戦争という概念を適用できなくなってしまっている。恐らく歳入も少なく継戦能力もほとんど無いのにも関わらず戦っている。ほとんど負けのような状態なのに戦い続けているのは現実よりも精神論を重視しているようにも思える。つまり"日本教"という宗教を信じ、ジハードのように宗教国家が教義に従って戦っているように見える。

文中で日本人は洗練されて尖った印象で書かれており、自分らの人種が優れていることを無根拠に確信しているような描写のされ方をしている。これは恐らく厳重な情報統制の結果そう思い込まされている可能性が高く、 実際、文中で出てきた小学6年生向けの社会の教科書が「欧米と戦うためのプロパガンダ」の役割を兼ねているような文章で、欧米的思考を当たり前のように悪と思わせるよう誘導している。教科書の最後は煽情的で露骨に市民向けのプロパガンダのにおいがする。そしてアンダーグラウンドの電車内で中学生と話すシーンでも分かるように、学生は敵(国連軍)を理解するためという明確な理由の元で学んでいる。

人口も26万人しかいないため情報統制はさほど難しくないはずで、教育 + 疑わしきを罰するだけで十分機能すると思う。しかも94年はまだインターネット普及以前だから一般の人間が海外の"普通"を自由に知り得る機会もほとんどなく教えられた当たり前を何の疑問も持たずに信じていたはず。まさに社会主義国日本という様相で、無駄を削ぎ落として現実的に正しいことが正しいと認識されるような社会として機能している。なおこの正しさの犠牲は倫理と感情。人口も少なく軍事力くらいしかマトモに輸出できないような小国なので一つの正論でうまく社会が回っていて、文中では「残酷だが単純で、曖昧なものがまったくない世界」と書かれている。この正しさに沿うように敬語を無くすなど言語も変化した。これは村上龍の凄いところだと思っていて、言語はそれを話す人間達が作り上げ変化するものだから、日本人の正確がより単純になっていくのであれば言語も単純になっていくのは自然のように思う。これに気付いて小説を書けるのは本当に凄い。

とにかく国際社会に対して日本の存在感をアピールしたがっている様子が伝わってきていて、それがピアノやらバレエやらの文化的な面でも出ている。恐らく世界的に有名な音楽家というキャラクターを出すことで、日本人は感性などの精神性が欧米より優れていることを表しているのだと思う。しかし欧米側からの意見はほとんど書かれておらず、ワカマツのマネージャーが怒りながら言っていた「お前の音楽を聞いているのは第三世界の飢えたガキ共と、下層階級のクズだけだ」くらいしかない。恐らく現実はこっちで、実際には国際的な評価はさほど高くないのではないかと思う。

同じ村上龍が書いた「半島を出よ」の北朝鮮もそうだったけど、やたらと少数先鋭で戦ってる人たちが出てくる。少数先鋭に幻想を抱きすぎている感。戦いは数だよ兄貴!

夢十夜 / 夏目漱石

f:id:asmsuechan:20210106143150p:plain

青空文庫Kindle版で読んだ。Wikipedia曰く、「夏目漱石には珍しい幻想文学」だそうな。

読んでる途中に書いた感想等の放流。

感想

薄暗い雰囲気なのだけど色鮮やか。コントラストがあってとても良い。夢でありがちな、時間感覚と空間感覚が欠如した様子が書かれている。

坂の上の雲に書かれていたけど、明治時代は日本語の決まった形がまだなかったらしい。夏目漱石の日本語は現在のものとあまり変わらないように思うので、夏目漱石が今の日本語を作ったのかもしれない。明治時代に書かれた文章でこれだけスラスラ読めるのはすごい。

一、二、三、五夜のみ冒頭に「こんな夢を見た。」と書かれている。これらは実際に見た夢をベースにしている話で、他はこういう夢見たい、みたいな願望だったり?夢のような話って言うし?もしくは現実に根ざしたお気持ちを夢という形で表明しているのか。

前半は夢の雰囲気をそのまま文章に起こしたようなものだったが、後半は書いた当時の時代的な気分が書かれている。前半と後半で雰囲気がかなり違う。

第二夜は和尚から悟りを開いてない事を煽られた侍がキレ散らかしながら悟りを開こうとする話。しまいには自分で自分を殴りまくってる。

第三夜は子泣き爺みたいな話。怪談っぽいテイスト。6歳になる盲目の息子を背負って歩いている。息子は何かおっさんみたいな声と話し方をしていて、まるで目が見えているかのように自分に指示を出している。で、結局杉の木の前まで連れてこられて自分がここで100年前に人を殺したことを思い出させる。罪を背負う事を自覚する話ですかねえ。

第四夜。酔っ払った爺さんが柳の下にいる三、四人の子供の前で手拭いを蛇に変えようする。結局蛇には変わらずに近くの川に入水する。冒頭で爺さんがへその奥が家って言ってるしこれ生まれつつある赤ちゃんの話じゃないかな?蛇はへその緒で笛は産声。川の中に入るのは、お腹から出てくること。知らんけど。

第五夜。戦に負けて敵の大将に捕らえられた男が殺される前に愛する女に会いたいと言って、女が白馬に乗ってそこまで来ようとする話。視点が突然飛ぶし女は男の元に行かねばならないことを知ってるしで、いかにも夢で見ている光景をそのまま書いたという感じ。

第六夜。運慶が仁王を彫るのを眺めている話。野次馬の一人に「木を彫って人の形を作っているのではなく木の中に人の形をした完成品が隠れていて、それを彫り出すだけだから簡単だ」と言われて帰って薪を使って試すけど見つからない。結局明治時代の木に仁王は埋まってないと結論づけた。明治時代の日本には仁王のような大きく強い者はいない事を嘆くような気分を感じる。この時代の人間特有の憂国感なのかもしれない。

第七夜。大きな船に乗っている人がいつ上陸できるかも分からない心細さから海に飛び込んで自殺する話。これも明治時代特有の気持ちのような気がする。他の乗員は欧米の他の国のことを言っていて、開国したてで欧化の流れにうまく乗れるか分からない不安を書いていそう。

第八夜。床屋で椅子に座っている間に床屋の鏡で外の様子を伺うが、音は聞こえるのに鏡には何も映らない。

第九夜。父が死んでいるということを知らずに父の無事を願ってお百度を続ける母と、母の気持ちを知らず激しく泣いたり動いたりする子。冒頭に「世の中が何となくざわつき始めた。今にも戦争が起りそうに見える。」とあるが、これはたぶんそのまま書いた当時の社会の気分。日露戦争開戦前かな。(読んだ後Wikipedia確認したら1908年の連載で、戦後。戦後の動乱だったか。)

第十夜。第八夜で出てきた庄太郎がまた出てきた。女に拉致られてから久しぶりに帰ってきた庄太郎の話。

涼宮ハルヒの直感 / 谷川流

f:id:asmsuechan:20210103230023j:plain

読んでる途中で思った感想の放流

感想

目次の挿絵とかレイアウトがもう懐かしい。

本文中で筒井康隆の名前が出てくるのは面白い。涼宮ハルヒの憂鬱スニーカー文庫じゃなくて角川文庫から出た時に解説書いてた繋がりかな。

なんか現実世界の固有名詞が増えてる?

鶴屋さんの挑戦

ミステリー全然分からんしあまり好んでもないから読むの辛いかもしれん。しかし作者の趣味でも書いてくれたのが嬉しい。

鶴屋さんの語りがこんなに入ったのは初めてだし、ずっとやりたかったんやろなあ。

留学生にキャムと呼ばれてるってことは英語のスペルだとキョンComになるのかな。

文学的教養が多分になければ言ってることのすべてを理解するのは難そう。ある意味ラノベっぽい。ラノベにしては読み応えあるけど。

鶴屋さんの話、何かに似てると思ったらシャーロック・ホームズの話だ。やたらと都合よく疑問を呈するキョンがワトソンくん。シャーロック・ホームズシリーズ読んだことないけど。って思って読んでたら終盤で古泉が同じこと言ってたわ。

英語キャラの登場意図があまり分からないし不自然な英語が混じった不自然な日本語に気を取られてしまう。外国人、こうはならん。

あー、なるほどそういうことか。読み進めたら留学生の登場意図が分かった。すげえ。

「いわゆる一つの萌え要素!」ってなんか死語に近いフレーズだな。

ミステリーあんまり読まない私も楽しめた。ただ、楽しむには涼宮ハルヒシリーズを事前に読んでおく必要がありそうなのでミステリ入門かと言われれば微妙そう。

今回は各キャラの設定を活かしたSF的展開は特に無し。涼宮ハルヒシリーズの登場人物が出てくる普通の小説。

次を予感させるセリフがあって嬉しい。

やはり、あとがきに「1度やってみたかったこと全部やった」と書かれている。

まとめ

小中高に渡って読み続けた鈴宮ハルヒシリーズの新作がやっと9年ぶりに出た。もう、どんな形であれ新刊を出してくれたという事実のみに満足。何気に初詣回は満足度が高い。

今回は日常編だったけど、物語の終盤で結局うやむやになってる第三勢力問題に対して言及されているので次巻(があるとすれば)ストーリーに展開があるはず。楽しみ。

本: 脱資本主義宣言

f:id:asmsuechan:20201231164920j:plain

読みながら書いた感想の放流。

感想

学びが少なかった。

はじめに

衣食住の衣と食の多くを輸入品に頼っている事から分かるように、日本はとにかくお金を右から左に流している。

労働者階級から見た搾取への不満が書かれてる。

国家の維持にはお金が必要。だからもし搾取する側を引きずり下ろすと、国を維持するには今まで上位1パーセントの搾取する側が納めていた多額の税金も等分する必要があるのでは?こうすると結果的に市民は貧しくなるのでは?

「我々の体液が塩辛いのは、海にいた頃の名残りであり、つまり我々の血管を流れているのは海の水だ。」とか書かれててもう怪しい。

カネで失った繋がりもあればカネで得た繋がりもあるのでは?うーん一方向的だなあ。

1章

自分の感情を犠牲にして遠く離れた見知らぬ国の人たちに同情するのは異常。そういう人はたぶん何かに騙されてる。1次、2次ソース以内の情報でものを考えたほうがいい。

全体的に遠く離れた誰かの心配が多い。この本の思想で恩恵を受けるのが自分でも自国でもない印象。

税金がなければ国が立ち行かない事について言及がない。これを前提として国も消費を煽っているわけで。全体的に「その先の現実」に関しての追求がされてない事を見るに、書かれている内容が現実になったら結果として悪いことになりそう。

原発の話も多国に売り込むことへの言及はあるけど石油石炭が取れない代わりのエネルギーとして有用に使うという話は無視されてる。他国からの輸入を減らせるはずなんだが。

労働者階級にとって大事なのは日々の生活で、この生活が脅かされなければ問題ないはずなのに何故か怒っている。

西洋至上主義への違和感というのは、分かる部分が大きい。

筆者は人道主義者(そういう言葉があるかは分からないけど)なのだろうか?

2章

日本とアメリカの車の平均車齢と平均走行距離を比べてどちらも短いから「日本は車も使い捨てている」って言ってる。でもアメリカだだっぴろくて日本は狭いので、日本の車はスタートアンドストップが多いために車齢が短くなっているのではない?数字だけで言い切りが過ぎるのでは?この本全体で、こういう数字の理由が示されていないのに結論を言い切っているのが多くて信用ならなく感じる。

問題点の指摘は分かったから「俺が考える最強の解決策」を知りたい。問題点を述べているばかりで結論が無い。

3章

資本主義とは何か、の説明の部分でも文句が挟まっていて「ちゃんと説明してくれ・・・」って思う。

ようやく結論っぽいことが出てきたけど「カネや経済により依存しない、他の生き物に近い生き方を目指すべきだ」って、つまりどういうことだってばよ。

4章

「この世界の"本来の仕組み"を確認しておきたい」ってあるの怖すぎる。ちゃんとした説明なのかトンデモ説明なのか。

人間界は自然界の一部にすぎない、と書かれているのと同じように、企業もただの人間の群れでしかないと思う。

まとめ

全体的に一方向からのみの意見で説得力が無い気がする。間違ったことは言っていないが正しいことも言っていない、という感想。

この著者、「いかに日本人が"現実"というものを見ていないか」という態度で日本人全体を見ている。周りが全員馬鹿に見えているのかもしれない。

この本、「日本人は知らない不都合な真実」とかのタイトルのほうが良かったのでは?著者の文句本。

文章全体的に「」と""が多く使われていて、論拠じゃなくて印象による結論の誘導をしているように思う。

本の装丁とタイトルはとても魅力的。

「脱資本主義宣言」というタイトルだからてっきり「現状の資本主義のこういう問題に対して共産主義を取り込むと国家と市民に最大の利益をもたらすだろう」みたいなことが書かれているかと思っていた。違った。

いろいろ合わない部分が多いからと言って批判的に読みすぎたかもしれない。

この人生き辛いやろうなあ、って思いながら読んでたらあとがきで辛いって言ってた。

本: Winny 天才プログラマー金子勇との7年半

f:id:asmsuechan:20201230113003p:plain

読みながら書いた感想の放流

概要

金子勇の弁護士を務めた著者のブログ Attorney@law を書籍化したもの。

金子勇が無罪を勝ち取るまでの闘いが弁護士視点で書かれてる。面白くて一気に読んでしまった。

感想

 人は最初、それは誤っていると言い、
次に、それは無理だと言い、
そして、それは誰でもできたと言う。

しかし、何かを成し遂げるのは実際に何かをした者だけである。

P2Pは少し昔のインターネットを知るオタクからすると憧れの技術という印象がある。

書かれている内容はある程度は正しいのだと思うが警察やマスコミに対する態度などがネットウケしそうな文章になってる。この感想書いた後知ったけどこの本のベースは著者のブログらしい。そりゃネット民っぽい文章にもなる。

幇助が何か、について具体的に書かれていないから解釈問題になる。解釈問題なら都合よくどっちにも倒せる。

ちょうど当時(2000年代)はオタク迫害期2ちゃんねる=犯罪くらいの言われ方をされてた。これも相まってWinnyで情報流出してしまった京都府警のサイバー犯罪対策科に目を付けられた。この時点で「どうしても有罪にする」という結論が決まっていた。そして結論が決まった状態だともう何言っても議論は成立しない。しかし結論が決まっていると「自分は正しい」と強く思い込むことになるので悪意のあった人は恐らくあまりいなかったはず(文中では分かりやすく警察や裁判所が悪意の権化のように書かれていたが)。

技術の世界では「技術そのものが好きなオタク」と「技術によって描く未来が好きなオタク」の2種類は別種であると思っていて、金子氏は前者。前者の作るものは技術的検証であって明確な大義名分がある事は少なそう。

つっても2ちゃんのダウンロードソフト板で作るって発表されてそれでアングラ用途が完全に無いって言うの難しいのでは、とも思う。開発者にとって一番楽しいのは自分がユーザーとして使ってて楽しいものを作ってるときのはずだし何かしら技術以外の意図はありそう。

まとめ

この本は弁護士という極端な立場(絶対に無罪であるとしか答えられない立場)から書かれているのでこの本だけで善悪を0か1かで判別しようとするのは危険。

本: 坂の上の雲

f:id:asmsuechan:20201229224049p:plain

読みながら書いた感想等の放流。

あらすじ

明治維新後の日本が日露戦争ロシア帝国を破るまでを書いた小説。司馬遼太郎秋山好古秋山真之正岡子規の3人を中心にこの時代を書いている。が、この3人思ったより登場しないしなんなら正岡子規は速攻で死ぬ。全8巻。

1巻 3人の生い立ちと近代日本の目覚め
2巻 日清戦争
3巻 日露戦争に向かう日本
5巻 203高地陥落、旅順港陥落
6巻 ロシア革命の兆し
7巻 奉天会戦
8巻 日本海海戦終戦

今度坂の上の雲ミュージアム行きたい。

感想

数年前に1巻だけ読んで挫折したがKindle Unlimitedで無料で読めるので再挑戦してみた。長かったが非常に楽しく読めた。

近代日本を作った人間がどのような生涯を送ってきたかが書かれていて、自分の行動に何か取り込めるものはないかな、とか考えながら読んだ。 これは最近就職活動で「夢とは何か」のような質問をされることがままあり、また就職という節目にあたって自分の夢や将来について朧気ながら考える機会があったたためだと思う。

小学生時代、大正以降の歴史がやたら得意ですごく好きだったのを思い出した。小学生時代の嗜好は大人になっても残るらしい。中学受験時代に習った人物がたくさん出てきて「ああ、この人物はこういう理由でこれをしたんだ」のような納得感があった。過去の偉人をストーリーを抜きにして名前と出来事だけ覚えるのにどれほど意味があるのか今更疑問に思った。

文章中当たり前のように「好古はフランス語が堪能である」みたいに出てくるけどこの時代の人たちはどのようにしてどのくらいの期間で語学を身に着けたのだろうか。かなり気になる。そういえば同じ時代を生きた夏目漱石は「とにかく多読や」って言ってるな。以下の文章がそれ。夏目漱石のものらしいけどこのソースが信用できるかは不明。 http://books.salterrae.net/amizako/html2/sousekigendaidokusho.txt

今更だけど明治時代に日本が軍艦を作っているということに驚く。教科書では読んで年号と出来事を知っていたがいざ物語として読むと驚く。明治時代と言えば江戸時代のすぐ後で、江戸時代といえば鎖国下で工業のこの字も無いような状態という印象。

普段コンピューターという最近発展したものとばかり接しているから実感がより薄まっていると思うけど、恐らくこの時代は自分が考えるより発展している。

たまに説明のために先の展開を書いてくれるのがとても分かりやすい。

1-3巻までは秋山好古秋山真之正岡子規の3人の登場人物の視点を切り替えながらテンポよく進行していたが、4巻の旅順侵攻あたりになって突然テンポが悪くなった。だらだらと場面の転換も少なく戦況説明を延々としているのは退屈。

203高地争奪戦では組織において現場を見る重要性と専門家が判断を下すことの危険性についてのアンチパターンが書かれていた。 失敗から学ぶことの重要性 5巻途中ではロシア軍のことよりも日本陸軍内部に関する記述が圧倒的に多くなっていることからも状況は内発的な要因に大きく左右されうることが伺える。

日露戦争時代のロシア帝国は官僚化しきっていて、組織のことより自身の将来を案ずる人間が増えてしまったのが問題だった。司馬遼太郎も文中で何度か「目前の日本軍ではなく帝国内の政治を意識していた」のように書いていた。

この小説、同じことを何回も書いてくれて非常にありがたい。登場人物が多いからか「こう書いたのは以前述べた」みたいな記述が多い。

失敗の本質とかでも言われてるけどやっぱ一番重要なのは情報力っぽい。日露戦争の頃はもちろんインターネットはない。でも日本は良い無線機使っててそれも勝利に一役買ったとか。

6-7巻あたり、戦況の描写をダラダラされると結構読むのがしんどい。こういうのは絵があればすぐ分かるだろうと思う。

日本海海戦の描写は(そもそも海戦自体長い時間ではなかったからだと思うが)綺麗にまとまっていて非常に読みやすかった。

まとめ

いかに現実社会が倫理と感情で成り立っているかが分かる。それを踏まえた最適解を取って社会を保とうとするのが政治。知らんけど。

ロシア帝国は皇帝らの気分的な判断により現状把握を間違えた上に、各官僚の目指す最適解も社会ではなく自分を向いていたために自浄作用も働かず腐った結果革命が起きた。知らんけど。

Pythonでジョブキューシステムを作った

Pythonで標準ライブラリのみを使ってシンプルなジョブキューシステムを作りました。キューはRedisを使わずにインメモリに保存します。

capture

この記事ではQueickの特徴と使い方、ジョブキューシステムについての説明、よく使われているシステムでの実装、Queickのアーキテクチャと実装について説明します。

0. きっかけ

以前Raspberry PiNFCリーダーとSlackで研究室の入退室管理(打刻)システムを作ったのですが、Raspberry Piネットが途切れてSlackに打刻されないことが1-2週間に1回ありました。

moriokalab.com

そこでジョブキューシステムを導入して非同期にリトライする仕組みを組み込もうと思いいくつか探したのですが、基本的にキューとしてredisを使っているようでした。

Raspberry Piの中にRedis入れて運用するのが嫌でインメモリで閉じたものが欲しくなったため作りました。

正直、論文終わって少し時間があったのと何となく作れそうな気がして作ってみたので習作の面が大きいです。興が乗ってしまった。

f:id:asmsuechan:20201105153215j:plain
元気に動いている様子

1. できたもの

queickという名前で作りました。queueとquickを足して2で割った名前です。何もquickじゃない気がするけど。強いて言えば私の開発がquickだった。

github.com

1.1 できること

Queickには以下の特徴があります。

  • redisを使わない(データはインメモリ保存)
  • 標準ライブラリだけで作られている
  • リトライ機構を作る
  • 日時指定のスケジュール実行をできるようにする
  • 定期実行(cron)機能
  • ネットワーク復帰時に失敗したジョブを流せるようにする
  • ジョブは新しく生成したスレッドで実行される
  • 永続化無し
  • Raspberry Piなどでの運用、IoTなシステムを想定
  • 大規模システムは想定しない。エッジ端末向け。さほど多くない軽量なタスクを実行する

マシンスペックが高くなく、移動などによりネットワーク接続が保証されていないような環境を意識しています。

1.2 使い方

pipでインストールできて、インストールしたらqueickというコマンドが実行できるようになります。(Python3.6以上必須)

$ pip install queick
$ queick
[INFO] 2020-10-31 14:23:10,105 - Welcome to queick!

まずジョブの本体であるjobfunc.pyを作成します。なおジョブとなる関数は別ファイルでなければなりません。

def function(arg1, arg2):
    print(arg1, "+", arg2, "=", arg1 + arg2)

そしてjobrunner.pyを以下のように作ります。

from queick import JobQueue
from jobfunc import function
import time

q = JobQueue()
q.enqueue(function, args=(1, 2,))
q.enqueue_at(time.time() + 5, function, args=(3, 4,)) # 5秒後に1回実行

st = SchedulingTime()
st.every(minutes=1).starting_from(time.time() + 10)
q.cron(st, function, args=(1, 2,)) # 10秒後から1分おきに実行

そしてjobrunner.pyを実行するとジョブがqueickのキューに追加されスレッドで実行されます。

$ python jobrunner.py

f:id:asmsuechan:20201105154758p:plain

実行できていますね。

2. ジョブキューシステム

ジョブキューシステムについて簡単に説明します。ジョブキューシステムの基礎の部分は非常に簡単です。基本的に(1) アプリケーションからキューにジョブを投入するモジュール、(2) キュー、(3) ワーカーの3つの要素で構成されています。

まずアプリケーションがメソッドを呼び出してキューにジョブを追加します。そしてジョブキューシステムがキューに追加されたジョブを取り出してそれをスレッドや別プロセスにて実行します。基本はこれだけです。

ですのでジョブキューシステムを(標準ライブラリのみで)作ろうと思ったら基本的には並行プログラミングをしていくことになります。

そして一般的なジョブキューシステムは非同期に実行する必要がある処理を安全に行うことを目的として作られています。ここで非同期に実行する必要がある処理とは、大量の処理や時間がかかる処理、メール送信など別のサーバーで実行するような処理などバックグラウンドで動作しておいた方がいい処理のことです。

3. 既存システムのアーキテクチャ

さて、実装を始めるにあたりまずは調査です。主に以前少し使ったことがあるSidekiqと、Python用ジョブキューシステムのRQのコードを読みながらアーキテクチャを理解していきました。

3.1 RQのアーキテクチャ

Python用ジョブキューシステムのRQは以下のようなアーキテクチャでした。

RQ本体はワーカープロセスとスケジューラープロセスの2つのプロセスが動作しており、それぞれがキューから取り出したジョブをforkして実行します。スケジューラーは1秒ごとにキューの中身を見て開始予定時間を過ぎたジョブを実行します。また、リトライはスケジューラーを活用しており、失敗したプロセスはN秒後にスケジュールされます。

RQがschedではなくスケジューラーを自前実装なのはPython2系とのコンパチのためでしょうかね。

3.2 Sidekiq

Pythonではないのですが以前少し使ったことがあるSidekiqについても調べました。Sidekiqは図にまとめるのが面倒になったので箇条書きします。

  • スレッドでジョブを実行
  • リトライ処理できる

4. アーキテクチャ

そして既存システムのアーキテクチャと要件を考慮し、以下のようなアーキテクチャで作ることにしました。構成に他との違いはさほどありません。

アプリケーションがTCPサーバーを介してジョブキューにジョブをenqueueし、ワーカープロセスがこれを受け取って開始時間がセットされたジョブならスケジューラーにジョブを移動します。そして即時開始できるジョブはスレッドを作ってそこで実行します。ジョブ失敗時はN秒後に実行するよう指定してスケジューラーに直接ジョブを投げます。

4.1 ネットワーク復帰時リトライのアーキテクチャ

ネットワーク復帰時に失敗ジョブを実行する仕組みも作っています。図がゴチャつくのを防ぐために別の図にしていますが基本のアーキテクチャは上のものと同じです。

この機能を使うにはqueick起動時に--ping-hostオプションを指定しなければなりません。このオプションにはネットワーク疎通確認用のサーバーがあるIPアドレスやホスト名を指定します。

$ queick --ping-host asmsuechan.com

そしてジョブのenqueue時にq.enqueue(function, args=(1, 2,), retry_on_network_available=True)のようにretry_on_network_availableをTrueにセットしたらそのジョブでネットワーク復帰時リトライが使えます。

ネットワークチェッカーは1秒1回ネットワークの疎通確認をして切断状態->接続状態への変化を検知した時に失敗キューを全てdequeueしてジョブを再実行するものです。ワーカープロセスとは別のサイクルで動作するので別プロセスで起動します。ですのでqueickに--ping-hostオプションが指定されていないときはネットワークチェッカーは起動しません。

5. 実装

ではようやく実装の話です。上のアーキテクチャを実装に落とし込んでいきます。(実際の順序は逆で、まずとりあえず最小限動くものを作ってそれから他システムのソースコード読みながらアーキテクチャを改善していったのですが)

こんなインターフェイスでアプリケーションから使うことを想定し実装しました。

# job.py
from queick import JobQueue, RETRY_TYPE
from jobfunc import function
q = JobQueue()
q.enqueue(function, args=(1, 2,))

# jobfunc.py (別ファイル)
def function():
    print(arg1, "+", arg2, "=", arg1 + arg2)

これでjob.pyを実行するとキューにジョブがenqueueされてワーカープロセスが生成したスレッドで処理が実行されます。

なおインターフェイスはrqに影響を受けています。Queue.enqueue(func)って直感的で分かりやすいですね。

5.1 ワーカー

ワーカーの役割は「ジョブをキューから受け取って別スレッドで実行する」です。この実装にはconcurrent.futuresを使いました。ジョブの受け取りはpollingではなくてイベント通知によって行っています。

また、ワーカーは「実行するジョブの関数を見つける」ことも必要です。ここはimportlibを使っておりimportlib.import_module(module_name)のようにして関数名から動的に関数を探し出してきています。

なおキューには辞書型として以下のような形式でジョブが入っています。

f:id:asmsuechan:20201105161014p:plain

スレッドを使用するとPythonGILによってマルチコアを活用できないという問題があります。しかしこのシステムの理想的なユースケースがたまにネットワークが切断される環境でのジョブ実行だと想定しています。つまりジョブ自体は軽くて別ホストのサーバーと通信するものレスポンス待ちの時間が長くなるはずなのでマルチプロセスのオーバーヘッドを避けてスレッドで実行するようにしました。

ここの検証は下の性能評価で行っています。

5.2 TCPサーバー

アプリケーションからワーカーへのメッセージの送信部分はTCPサーバーにしました。HTTPサーバーでも良かったのですが大したことはしないのでTCPソケット使うようにしました。ポートは9999に開いてジョブを待ち受けます。

ちなみにここは実装サボってて、スレッドやforkを使ったコネクションの多重化をしていないので1コネクションしか同時に受けることができません。興が乗るか必要に迫られたら作り込もうかなと思ってます。

5.3 スケジューリング、リトライ、定期実行

スケジューリングとリトライは基本的に同じ仕組みで作っていて、schedモジュールを使っています。

キューは1本で、一度ワーカープロセスがジョブを受け取って開始時間がセットされていたらスケジューラーにセットするようにしています。

リトライはジョブが失敗した時スケジューリングキューに未来の時間を設定したジョブを新しく投入します。デフォルトはExponential Backoffを採用しています(最大リトライ時間は3600秒後)。

定期実行もスケジューリング機能に乗っています。定期実行ジョブの場合はジョブ実行時に次のジョブをスケジュールする実装です。

スケジュール時間はジョブ投入時に指定できて、上のExponential Backoff、10秒後に固定のもの、1秒後, 2秒後, 3秒後, ...と失敗回数が増えるに連れて1秒ずつ増えていくもの、5秒後, 10秒後, 15秒後, ...のようにN秒ずつ増えていくものを準備しました。

5.4 自動テスト

Pythonのテストフレームワークはpytestがデファクトっぽいのですが標準ライブラリ縛りでやっているのでユニットテストにはunittestモジュールを使います。別にアプリケーションコードじゃないので気にする必要が無いと言えばそうなのですがどうせなら貫きたかったので。つまり趣味です。

結合テストはDockerで環境作ってその中でファイル書き込みを行うジョブを実行してファイルの行数をテストするようにしました。苦肉の策な風もありますがないより100倍マシです。イベント駆動でマルチプロセスなシステム、自動テスト非常に難しいですね。

6. 性能評価? とりあえずジョブ100万件投げる

とても雑で性能評価とは言いにくいですが簡単なジョブを100万件投げてみて不具合が出ないか確認します。

マシンスペックはCPU: Intel® Core™ i7-8565U CPU @ 1.80GHz × 8, RAM: 32GB, OS: Pop!_OS 20.04 LTSです。なおThreadPoolExecutorのmax_workersは8に設定しています。

実際のジョブは以下です。100万回ループするだけの簡単なCPUバウンドの処理です。これを100万回実行します。

def function(arg1, arg2):
    start_time = time.time()
    for i in range(0, 1000000):
        i = i
    end_time = time.time()

    print("Time:", end_time - start_time)

実際に計測を実行してみたところ、まずジョブの投入時間は70sでした。

$ time python testclient.py
python testclient.py  70.11s user 68.13s system 44% cpu 5:14.06 total

なおジョブは1つあたり約30msで終了し、合計でかかった時間は8.5時間程度でした。

f:id:asmsuechan:20201105145845p:plain

そしてジョブ実行中の最大メモリ使用量は1.1GBでした。

f:id:asmsuechan:20201105150342p:plain

30msくらいかかる処理を100万回実行するくらいでは特に問題なく実行できました。concurrent.futuresさんが偉いです。本当に落ちるまでやろうと思ったらハードリミットまでやれそうです。

7. CPUバウンドな処理とI/Oバウンドな処理の並列性比較

上でGILの話をしましたが、ここではその実際の性能を計測します。といってもマルチスレッド部分で込み入ったことはしていないので結果は自明っちゃ自明なんですけどね。

7.1 100000000回ループ(CPUバウンドな処理)

まずは単純に100000000回ループするだけのジョブを実行します。プログラムは以下です。

# jobfunc.py
import time
def function():
    start_time = time.time()
    for i in range(0, 100000000):
        i = i
    end_time = time.time()

    print("Time:", end_time - start_time)

# testclient.py
from queick import JobQueue
from jobfunc import function

q = JobQueue()
print(q.enqueue(function, args=(,)))
[INFO] 2020-11-05 11:24:06,648 - Job received -> data: {'func_name': 'jobfunc.function', 'args': (1, 2), 'retry': False, 'retry_interval': 10, 'retry_type': 'constant', 'max_retry_interval': 600, 'retry_on_network_available': True, 'max_workers': 10}, addr: ('127.0.0.1', 49808)
Time: 1.9901025295257568

単体ジョブの実行時間は1.99sです。

次に、上のジョブを2つenqueueした時の実行時間です。

[INFO] 2020-11-05 11:24:29,554 - Job received -> data: {'func_name': 'jobfunc.function', 'args': (1, 2), 'retry': False, 'retry_interval': 10, 'retry_type': 'constant', 'max_retry_interval': 600, 'retry_on_network_available': True, 'max_workers': 10}, addr: ('127.0.0.1', 49816)
[INFO] 2020-11-05 11:24:29,555 - Job received -> data: {'func_name': 'jobfunc.function', 'args': (1, 2), 'retry': False, 'retry_interval': 10, 'retry_type': 'constant', 'max_retry_interval': 600, 'retry_on_network_available': True, 'max_workers': 10}, addr: ('127.0.0.1', 49818)
Time: 4.395071744918823
Time: 4.393498420715332

2つのジョブが終了する時間は4.39sとなっています。これは単体のジョブの2倍以上の時間がかかっています。よってCPUバウンドな処理は向いていません。

7.2 外部への通信を行う(I/Oバウンドな処理)

次に通信のレスポンス待ちが発生するケースです。計測用コードは以下になります。https://moriokalab.com (弊研究室)にGETリクエストを投げるだけです。

# jobfunc.py
import time
import urllib.request

def function():
    start_time = time.time()
    with urllib.request.urlopen('https://moriokalab.com') as f:
        pass
    end_time = time.time()

    print("Time:", end_time - start_time)

# testclient.py
from queick import JobQueue
from jobfunc import function

q = JobQueue()
print(q.enqueue(function, args=(,)))

まず単体の実行時間を計測します。

[INFO] 2020-11-05 10:49:43,941 - Job received -> data: {'func_name': 'jobfunc.function2', 'args': (1,), 'retry': False, 'retry_interval': 10, 'retry_type': 'constant', 'max_retry_interval': 600, 'retry_on_network_available': False, 'max_workers': 10}, addr: ('127.0.0.1', 49002)
Time: 1.1877124309539795

単体での実行時間は1.19sであることが分かりました。

次に2つに増やした場合の実行時間を計測します。testclient.pyのprint(q.enqueue(function, args=(,)))を2行に増やして実行します。

[INFO] 2020-11-05 10:47:13,975 - Job received -> data: {'func_name': 'jobfunc.function', 'args': (,), 'retry': False, 'retry_interval': 10, 'retry_type': 'constant', 'max_retry_interval': 600, 'retry_on_network_available': False, 'max_workers': 10}, addr: ('127.0.0.1', 48968)
[INFO] 2020-11-05 10:47:13,976 - Job received -> data: {'func_name': 'jobfunc.function2, 'args': (,), 'retry': False, 'retry_interval': 10, 'retry_type': 'constant', 'max_retry_interval': 600, 'retry_on_network_available': False, 'max_workers': 10}, addr: ('127.0.0.1', 48970)
Time: 1.3101766109466553
Time: 1.8106589317321777

2つ目のジョブの終了時間が1.81sということで、単体の実行時間の約1.5倍です。2つのジョブを実行しているのにも関わらず実行時間は明らかに2倍より少ないのでI/Oバウンドな処理での有用性が分かりました。

8. まとめ

私は自律移動ロボット関係の研究をしているのですが、最近の自律移動ロボットのソフトウェア部分はPythonで書かれる場合が多いしロボットはセンサーから生成され続けるデータを扱うのでクラウド連携等でこちらの方にも応用できたら良いかなと考えながら作りました。

とにかく非同期ジョブ実行+スケジューリング+リトライを手軽に行えるものが欲しくて勢いで作ってしまいましたが作ってる間とても楽しかったです。標準ライブラリ縛りプログラミングは楽しい(競プロは苦手だけど)。

難しくない割に動くものができるので新しい言語を使い始めるときの練習としても良いテーマかもしれません。

機能的には他にもワーカープロセス多重化したりキューを複数作ったりとかいろいろ実装のしがいはありそうだと思ったのですがひとまずいらないので作ってないです。

dev.toに英語記事を書いてRedditに投稿してみたところ少しだけ反響があってOSSやってる感じがして楽しいです。

9. 参考