失敗をさらけ出す勇気を持つためにはどうしたらいいのか

はじめに

株式会社アンドパッドでプロダクトマネージャー兼デザイナーをしています山田と申します。クラウド型の建築建設プロジェクト管理サービスであるANDPADの施工管理領域のプロダクトロードマップ策定やターゲットクライアントとのヒアリングなどを行っています。

 

私達がよりよいプロダクトを作る過程では不確実性が高い議論が度々発生します。意見の相違は緊張を生み創造性を呼び起こすので悪いものではありません(楽しくは無いかもしれませんが)。ただ緊張を前向な議論に変えるのは簡単ではなく、チームに気兼ねなく発言できる雰囲気が必要です。そのような雰囲気が土台にあると「熱い」トピックスを「冷たく」できます。

 

そして、そのように切り替えられるスキルを持つチームはプロダクトに対して効果的な役割を果たすと考えています。効果的なチームの定義を説明したのちに、最後にアンドパッドで取り組んでいる活動をご紹介します。

 

効果的なチームとなる条件

アリストテレスというプロジェクトをご存知でしょうか?Googleが効果的なチームとなる条件を明らかにするためにGoogleの社内チームを調査したプロジェクトです。チームによって生産性が異なるのはなぜか、高いチームはどのような取り組みをしているのかを調査し、結果として効果的なチームは以下の要素を持っていることが分かりました。

最初に挙げられている心理的安全性というのは「関連のある考えや感情について人々が気兼ねなく発言できる雰囲気」のことです。この心理的安全性がチームの土台となり相互信頼を生み出してきいき、そして効果的なチームと繋がります。

f:id:cafeyamada:20200517195830p:plain

https://rework.withgoogle.com/jp/guides/understanding-team-effectiveness/steps/help-teams-determine-their-needs/

調査の詳細に関してはGoogleが公開している「効果的なチームとは何か」を知るを参照ください。

 

スペースシャトル「コロンビア号」

心理的に安全というのはどういう状況でしょうか。NASAアメリカ航空宇宙局)は、2003年にスペースシャトル「コロンビア号」を打ち上げました。打ち上げ後にビデオチェックしていたエンジニアがシャトルが破損した可能性に気づいて上司に報告しましたが、対策が講じられれず時間が過ぎていきます。居ても経っても居られなくなったエンジニアはミッションリーダーがいる全体会で破損した懸念を取り上げてもらおうと上司ばかりがいる会議の中で発言を決意します。しかし地位の高い人に話すことによって自身のキャリアへの悪影響を気にして結局は発言しませんでした。その8日後、スペースシャトルは燃え上がり7名の宇宙飛行士が死亡します。

f:id:cafeyamada:20200517195957p:plain

もし発言をしていればリーダーがそこまで言うならばと認識を改め、念の為に外壁に耐熱素材を補強しておけば生存の可能性が変わったかも知れません。もちろん事故の要因としては飛行計画チームのリスク判断が甘かったなど他の要因もあり、彼がミーティングで発言しなかったことだけが事故に繋がったわけではありません。ただ彼は発言を躊躇しました。気兼ねなく発言できる雰囲気がなかったのです。

 

職場でのイメージリスク

1965年、組織改革の研究を行っているMITのエドガー教授とウォレン教授が心理的安全を生み出しました。教授らは心理的安全があると希望を否定するデータが提示された際に生じる保守的な姿勢(学習不安)を克服でき保身を考えなくてよくなり、より集中できるようになると結論しています。 

職場で調和を乱すことはイメージリスクの1つです。イメージリストを最小限にするのは簡単です。絶対的な自信がなければ、何もせず、何も発言しなければいいのです。ただ、これでは創造性がなく、イノベーションが犠牲なり、何より本来あるべき人間関係を作るチャンスまでをも失ってしまいます。ちなみにイメージリスクには以下の4つがあり、リスク毎に関連する活動量が低下します。

  • 無知だと思われる不安 => 質問をしなくなる
  • 無能だと思われる不安 => 支援を求めなくなる
  • ネガティブだと思われる不安 => 批評をしなくなる
  • 邪魔をする人だと思われる不安 => フィードバックを求めなくなる

気をつけなければいけないのが、心理的安全というのはプレッシャーがなくメンバー同士が居心地良く仲が良い状態のことではありません。チームの結束性というのはかえって異議を唱えることの積極性を弱める可能性があります。人は意見が一致している調和を乱したくはないのです。これを集団思考と言います。

 

しっぱいCafe

イメージリスクを気にせずに無知や無能をさらけ出すには勇気がいります。その勇気を後押しするためにアンドパッドでは定期的に「しっぱい」を共有する会を開いています。

 

しっぱいCafeではゆるふわに自身の体験談を語り共有します。体験談は業務のことでもプライベートなことでも構いませんが、最初は話しやすい日頃の雑談のようなことから話始めます。雨の予報なのに傘を忘れてきてしまったとか、お弁当があるのにご飯を食べてきてしまったなどです。日常の失敗談が良いところは聞き手はそういう失敗を非難したり代替案を提示したりはしません。クスッと笑うだけです。それに日常の失敗であれば聞き手は質問から行います(例:傘を忘れるなんて何かあったんですか?)。これを利用して失敗を話しても避難されず受け入れる雰囲気を養生します。

f:id:cafeyamada:20200518131611p:plain

2020年3月にデザインチームで開催したしっぱいCafeの様子

ちなみにしっぱいを1つ共有したら好きなお菓子を食べれる権利が与えられます。しっぱいをさらけ出してメンバーが笑って受け入れてくれて、お菓子も食べられる。またさらけ出したい、さらけだすと良いことがあると思ってもらいたいからです。 

 

業務でデザインなどを何かしらアウトプットを聞き手が見ると、人は感情的な反応型フィードバックか指示型フィードバックをしてしまいがちです。そうすると心理的安全性は損なわれ、よりイメージリスクを気にするようになります。そのためにアウトプットに最初に触れたときは非難せず笑って質問して聞いてあげることが大事なのです。しっぱいCafeはその土台となります。

 

幸い私達は共有を躊躇っても誰かの命を奪う仕事ではありませんが、私達がよりよいプロダクトを作ることができなければ、ユーザーの業務は停滞し、幸せを作ることができなくなります。そうならない為にこれからもしっぱいをさらけ出し続けたいと思います。


Rails未経験でANDPADにジョインして半年を振り返る

はじめに

はじめまして。バックエンドエンジニアの小野寺です。
主に Rails でバックエンドの開発をしているANDPADに、Rails 未経験で入社しました。
 
入社前は、バックエンドエンジニアとして、
  • ASP.NET Framework MVC を使った Web サービス開発
  • Node.JS、TypeScript を使った API 開発
といったことをしていました。
 
他にも、React を使ったフロントエンドの開発、ECS、RDS などを使ったインフラの構築なども、少しかじっていました。 
 
この記事では、そんな自分が Rails 未経験で入社してからの約半年を振り返ります。

なぜオクトに入社したのか

転職活動では、使用している技術にはあまり拘らず、幅広く見ていました。技術も大事ですが、「何をやるのか」「どんな価値を提供するのか」を重視していました。いろいろな会社と話をして、最終的にオクトに入社することを決めました。
 
f:id:andpad_onodera:20200430150525p:plain
決め手となった理由をいくつか紹介します。
  • 建築・建設業界を IT で変えていくという挑戦に興味を持った
  • 急成長していくフェーズに携われるチャンスだと思った
  • 面談、面接時の印象が良く、この人達と一緒に働いてみたいと思えた

 

入社するまで

いざ入社が決まると、本当に未経験で大丈夫なのか不安になってきました。

f:id:andpad_onodera:20200430150940p:plain

 
少しでも不安を解消するため、RubyRails の勉強をしました。
主に、使用したのは以下の2つです。
 

プロを目指す人のためのRuby入門

何はなくとも Ruby を使えるようにならなくてはと、この本を読みました。
プログラミングの基礎中の基礎の内容がないので、他の言語の経験がある人におすすめです。

Railsチュートリアル

次は Rails を触ってみなくてはと、こちらをやってなんとなくの使い方を学びました。

railstutorial.jp

 

入社してから

入社後は、オンボーディングを受けてから業務に入ります。
正直、ちゃんとアウトプットが出せるか不安な部分もありましたが、比較的早く業務を進める上で必要なことをキャッチアップできたのではないかと思います。
その要因を3つ挙げてみます。
 

入社前の勉強

前職の通勤時間などを利用してコツコツやっていて良かったと心から思いました。
これをやっていなかったら、コードが読めなすぎて入社後数日で発狂していたと思います。

f:id:andpad_onodera:20200430151629p:plain

メンター制度をフル活用

ANDPADでは新入社員にメンターが付いてくれます。
 
ANDPAD は機能が豊富で、会社、ユーザごとに機能や権限など細かく制御できるため、最初は仕様を理解するのが大変です。加えて、Rails の経験がない自分は、コードを読んでも理解が合っているのかわからない部分があったため、メンターが付いてくれるのはとてもありがたかったです。
 
自分の場合は、毎朝 Q&A の時間をとってもらい、前日の作業で発生した疑問点を解消していました。それ以外の時間でも、調べてみてわからないところは、メンターを捕まえて聞きまくりました。
 
入社してしばらくは、メンターがどこにいるか常に監視していました。

f:id:andpad_onodera:20200430151809p:plain

小さなバグ修正からコツコツと

小さな修正でも、アウトプットが出せてリリースもできていると心にゆとりがもてます。修正する際は周辺のコードも合わせて読むよう意識していました。
 
別の修正や機能開発をする際に、だんだんと変更箇所の当たりがつくようになったので、これは意識しておいてよかったなと思います。また、小さな修正でもテストコードを書くことで、仕様理解や、テストデータの作成によるデータ構造の理解につながり役立ちました。
 

おわりに

なんの因果か、最近自分のチームにもう1人 Rails 未経験のエンジニアが入りました。まだまだ、Rails 的にこの書き方はどうなんだろうと迷うこともありますが、2人で一人前の Rails エンジニアに成長できるようにがんばっていけたらと思っています。
 
最後まで読んでいただき、ありがとうございました!

インターンから見たオクト~ 学生インターンのすヽめ ~


はじめに

こんにちは、開発部門インターンの飯沼です。この4月に修士2年になりました。先日まで就活で長いお休みをいただいていたのですが、今月から2ヶ月ぶりに復帰しました。昨年、1度だけこのブログに投稿した時は、Amazon SageMaker の使い方について書きました。ただ今回は職場復帰したばかりで、あまりTech について書けることがないので、この1年インターンとして携わってきたプロジェクトを振り返って、学生から見た Oct について考えてみたいと思います。

 

入社(2019年5月)

もともと2019年1月くらいまでは「俺は Web なんて絶対にやらない!」と公言していたのですが、いざ IT 業界で食べていこうと考えた時に「やっぱり Web は素養としてできるようになった方がいいかな?」と思い始めました。そこで、大学の卒業式の日、当時 Oct で働いていた友人に紹介してくれないか頼んだことが入社のきっかけでした。大学では映像系の研究をしているため、PythonC++ でかなり大規模なプログラムを組んだことはありましたが、入社の時点で Web は全くの未経験でした。入社が決まってから3週間程、研究そっちのけで Vue.js の勉強をして ANDPAD チャットの製作チームに参加することになりました。

 

Web 版 ANDPAD チャット (新チャット)

2019年5月に入社して最初のプロジェクトは、ANDPADチャットのフロントエンド開発でした。そのころは社員の方は今の半分くらいで、私のいた開発チームも3、4人しかいませんでした。

 

しかし、その後わずか1ヶ月でチームの人数は倍増します。そのころになると、同じチームだった、からまげさんの主導で色々なアジャイルな開発手法を試したりして、本格的にチーム開発っぽくなってきました。

 

事前にある程度勉強していたこともあって、Vue.js での開発には比較的すぐに慣れました。しかし、CSS でのUI実装はなかなか慣れず、フロントエンド小泉さんなどCSSスペシャリストに何度も助けていただきました。実はいまだに CSS に苦手意識があります・・・(汗)

こうやってお互いに補い合って開発ができるところが、チーム開発のいいところだと思います。研究は基本的になんでも一人でやらざるを得ないことが多いので・・・

5、6月は旧チャットからの機能移植がメインでしたが、7、8月は新規機能の開発にも取り組みました。その後、9月初旬ごろに新チャットがリリースされました。

 

リリース直後はバグ対応で昼休みが15分しか取れなかった日もあって結構大変だった記憶があります。これも9月半ば過ぎには落ち着いてきたので、このプロジェクトから離れることになりました。 

機械学習プロジェクト

新チャットの開発が落ち着いたことで、「次は何をしようか?」と言う話になりました。ちょうどそのころ、一部サービスに機械学習が導入できないかと言う話が持ち上がってきていました。私は大学や大学院で画像認識や映像処理などの研究をしていたので、このプロジェクトにアサインされました。

 

事情により詳細は伏せますが、セマンティックセグメンテーションや異常検出を使って画像の認識をしようという試みでした。前回書いた SageMaker の記事もこのプロジェクトの一環です。機械学習を行うためには、画像データに一枚一枚正解をつけるアノテーションという作業を行う必要があります。この作業は必要不可欠とは言え、本当に地道でかなりしんどかったです。

 

社内データの分析

諸般の事情で機械学習プロジェクトからは離れることになりました。それに伴って社内のデータ分析をするチームに移籍しました。このチームでは、SQLアクセスログを取ってきて、サービスの利用状況レポートなどの作成に関与していました。私は主にSQLを書いてBigqueryからデータを引っ張ってくるところを担当していました。

SQLは比較的文法が単純で割とすんなり身につきました。Redash をこの時初めて使いましたが、このサービスは非常に便利ですね。

redash.io

SQLで引っ張ってきたローデータから図表やグラフが簡単に作れます。これをコピペすれば、利用状況のレポートがお手軽に作れます。ただ、一つ難点があって、それはAPIが微妙に使いづらいことです。動的クエリという機能をAPIで使おうとすると、3段階に分けて実行する必要があります。動的クエリを使えば、WHERE や GROUP などで条件を指定する部分を変数として扱えるようになるため、各社個別にレポートを作成する時など非常に便利です。

 

就活からこれまで

年明けすぐに、就活のため他社のインターンなどに参加し、2ヶ月以上お休みをいただきました。就活ではいわゆる JTBC (Japanese Tradititonal Big Company) を中心に受けてきました。コロナの影響で思ったよりも早く選考が進み、3月いっぱいで内々定が出たので、今月から Oct に復帰しています。現在はデータ連携チームでRailsを書いています。

 

Oct のいい所・よくない所

ここまで、この1年間を振り返ってきましたが、ここからは私が思う Oct のいい所やよくない所を考えていきたいと思います。

 

個人的にOctに入ってよかったと思うところは、以下の4つです。

  • Web周りの知識が身についたこと 
  • チーム開発の経験
  • 非常にオープンな職場
  • 社員とも平等

一番はやはりWebまわりの知識が身についたことが大きな収穫だったと思います。また「インターンだから」といってやらせてもらえる仕事がショボくなったりはしませんし、機械学習あたりではかなり大きな役割も任せてもらえました。あと、ランチLTなどでの情報共有も盛んに行われています。JTBCでのインターン経験と比較しても、オフィスはとても明るい雰囲気なので、非常に働きやすい職場だと思いました。

 

一方で、「あまり良くないなあ」と思うところもあります。

  • ドキュメント類が未整備
  • 情報へのアクセス

何個も挙げようかと思いましたが、思い浮かんだのはこの2つだけでした(笑)

また、最近ではこのどちらも大幅に改善されてきています。


私が入った頃は旧チャットの仕様書がなくて、機能移植しようにもどうすればいいのか困ってしまったこともありました。また、情報のアクセス権が不明確なのも少し不満でした。かなり機密性の高い情報にアクセスさせてもらえる一方で、Google スプレッドシートなどにアクセス権がなく、最低限必要な情報を入手するのに時間がかかるなんてこともありました。

 

しかし、最近ではConfluenceにドキュメントがまとめられるようになり、情報の集約は進んでいますし、正式に社用アカウントが発行されたことで情報へのアクセス障壁も無くなりました。

 

学生インターンのすヽめ

Octが学生のインターンを積極的に採っているのかはわかりませんが、私はこの会社で働くことができて非常に良かったと思っています。教育制度が未整備であるという欠点はありますが、様々な技術に触れ、実務に携わるという経験はなかなかできるものではないと思います。また技術力の向上だけでななく、学外の人とのつながりを作るという意味でも良い経験になります。ここで言うのも難ですが、こうした経験は就活でも強みになります。是非、大学生、大学院生のうちに長期のインターンをしておくことをお勧めします。

 

最後に

なんだか書いていて退職エントリーみたいだなあとか思えてきましたが、私はまだしばらくOctにいるつもりです。少なくとも今年中は、Railsを書いたりしながら開発に携わっていきたいと思っています。

 

今回は、1年の振り返りがメインになってしまいましたが、次回以降記事を書かせていただく機会があれば、ちゃんとTechな記事を書きたいです。

 

近々、研究の中間報告が済んだら、もっとコミットできるようになるので、どうぞよろしくお願いいたします。

 

追申

MacBook Proのキーボードの調子が悪いです。

塵も積もれば山となる、Vue.js製スプレッドシートのパフォーマンス改善記

はじめに

はじめまして、オクトのフロントエンドエンジニアの小泉です。約1年前に入社し、Vue.js(Nuxt.js)でプロダクトのWebフロント開発に携わっています。

 

初めて会社のブログに寄稿するにあたって、自分がオクトでどんなことをしているかを書こうと思ったのですが、私が担当しているプロダクトは現時点で正式リリース前のため、今回はその中に組み込まれている簡易スプレッドシート機能について、また特にそのパフォーマンスを改善するために行ったことについて書いていきます。

 

といっても、スプレッドシート的なものを自前で実装しようという人はなかなかいないと思いますので、データ量の多いリアクティブなサービスをVue(Nuxt)で開発する際のハマりポイントとして参考程度に流し読みして頂ければ幸いです。

 

スプレッドシート機能とは

現在、オクトで自分が開発に携わっているプロダクトには、商品情報を入力していって、合計金額を計算する明細編集という機能があります。

 

この機能について、チームのデザイナーさんから最初に提案されたデザインイメージは下記のようなものでした。

f:id:ytr0903:20200424122028p:plain

この時点では「こういう画面で編集したい」という程度で、リッチなキーボード操作というものが具体的な要件には含まれていなかったのですが、

 

このExcelライクなデザインを実現すると、ユーザーさんは一般的なスプレッドシートと同じ操作を期待し、「なぜキーボードの矢印キーでセル移動できないのか」「どうして複数選択したセルをDeleteキーで一括消去できないのか」などの要望が上がってくることは想像に難くありません。

 

なのでここは、「このデザインに対して自分が欲しいと思う機能」を実装する必要があると判断し、専用のスプレッドシート機能の開発に取りかかりました。

 

ANDPADのサービス内には他にも明細編集機能が存在しますが、使っているフレームワークが異なるなどの事情から、流用するよりも作り直した方が早いと判断し、一から作っていくことにしました。既存のライブラリを使わなかった理由は

  • 要件に適したものがあまり見つからなかったこと
  • 既存のライブラリを使ってしまうとデザインや機能面での細かいカスタマイズが難しくなること
  • 一般的なスプレッドシートと違って関数機能やセル同士の連動などを汎用的に実装する必要がない

ことから、そこまで複雑な実装にならないだろうと見込んだからです。

 

基本的な機能を1週間ほどで実装し、そこから半年ほど、他の作業の合間に少しずつ機能を追加したりパフォーマンスを改善したりを続けていった結果、現在はこのような形になりました。 

f:id:ytr0903:20200424122235g:plain

当然ながら、機能面でGoogleスプレッドシートなどの有名サービスには及ばないものの、キーボードでの操作、マウスでの範囲選択、切り取り・コピー・ペースト、履歴での元に戻す・やり直すなど、いわゆる一般的なスプレッドシートに期待される機能を一通り搭載しながら、それなりのパフォーマンスと操作性を実現できているのではないかと思います。

 

基本的な実装の仕組み

この項は、パフォーマンス改善の項のための前提知識でしかなく、他のプロダクトに応用できるようなものではないので、興味がない方は適度に読み飛ばして頂いて大丈夫です。

 

スプレッドシートを構成するコンポーネントは大きく分けると

SpreadSheetWrapper.vue > SpreadSheet.vue > SpreadSheetCell.vue

の3つに分かれています。

 

SpreadSheetWrapper は描画ではなく処理のみを行っているコンポーネントで、まとめてしまうとあまりにコンポーネントサイズが大きくなりすぎるために分けています。サーバーから渡されたデータとスプレッドシートで編集するためのデータの相互変換とバリデーションを担っています。

 

SpreadSheet.vue に渡されるデータは、列の配列の中に行の配列があり、どこに表示するかは配列番号で管理しています。

f:id:ytr0903:20200424122318p:plain



いずれかのセルが編集された後、セルの移動やフォーカスが外れるタイミングで、編集後の配列がSpreadSheetWrapperにemitされます。

 

配列からオブジェクトへの変換や金額計算などは、全てスプレッドシートから配列を受け取ったSpreadSheetWrapperで行い、その結果を履歴に保存しつつ、金額計算が反映された状態で配列に再変換したものがスプレッドシートに渡されます。

 

編集完了のタイミングで毎回ラッパーを通して、長すぎる文字数のバリデーションや全角数字の自動半角数字変換を行うので、常に有効なデータのみが保持されます。

 

SpreadSheetではこのデータを、行の数と列の数だけv-forでループしてひたすらセルを並べています。それぞれのセルには表示用のdivタグと編集用のinputタグ(またはtextareaタグ)があり、選択したタイミングでセルのinputタグにフォーカスすることで、そこからキーイベントを取得したり、そのまま文字の編集を行えたりします。

f:id:ytr0903:20200424122405p:plain


シート切り替えや履歴管理、コピー/ペーストや列幅の変更など、個々の機能ごとの実装を見ていくとキリがないので、ざっくり説明するとこのような仕組みで動いています。

 

機能を列挙していくと何となくすごいものに見えますが、実際には一つ一つのキーイベントやマウスイベントに対してひたすらロジックを追加していくだけの地味な作業なので、これを作ること自体は時間をかければ誰でもできます。

f:id:ytr0903:20200424163546p:plain

このコードだけでも何となくアナログ感が伝わるでしょうか。

 実際に苦労したのは機能そのものの実装ではなく、それらの機能を次々に入れなが、データ量が増えても破綻しないようにパフォーマンスを両立させることでした。

初期の実装では10行程度のデータでも、表示に数秒かかり、操作をするたびにまた数秒待たせてしまうような状態でした。そこからどのように改善していったかを書いていきます。

 

パフォーマンスを改善するための道のり

当たり前ですが、「これをやれば一気に速くなる」というような銀の弾丸はもちろん存在せず、基本的にはトライアンドエラーで少しずつ改善していきました。

 

本当はきちんと秒数を計測してどの程度改善されたかを数値化すべきなのですが、フロントエンドの速度はブラウザやサーバーなど様々な条件に左右されてしまうため、計測は行えていません。この記事では体感で明らかに速くなった改善をいくつか挙げています。

 

この時、数値ではない体感ベースでの改善をなるべく継続的に行うために、一定のデータ量を基準にして進めるようにしました。今回のケースでは、「100行のデータをストレスなく表示・編集できる」ことを目標に改善していきました。

 

inputタグをできる限り減らす

機能を作り始めた最初期の段階は、全てのセルを、CSSで見た目を整えたinputタグで作り、クリックしてそのinputタグにフォーカスが移るようにしていました。

これは自然な実装ですが、早々に破綻しました。inputタグをmountする描画コストが大きいため、数行ならまだしも、10行~20行のデータでも既に表示するまでに数秒待たされるようになります。

そこで、セルコンポーネントをさらに、表示用のセル(SpreadSheetPreviewCell.vue)と、編集用のリアクティブなセル(`SpreadSheetTextCell.vue)に分け、

基本的にはPreviewCellしか表示せず、選択中のセルのみinputタグ(またはselectタグ)と切り替える形でその都度マウントし、mountedのイベントでフォーカスを移すように変えたところ、描画が約10倍速くなりました。

その後、プルダウンメニューを使うセルだけ、デザインの都合でそのままにしていたのですが、これもデータが多くなるにつれて無視できない遅延になっていったので、

編集中以外はPreviewCellに統一して、矢印マークをCSSで同じ場所に配置するようにしてさらに高速化させました。

 

背景とセルを分離し、背景はCSSのみで描画する

TextCellとPreviewCellを分離したものの、PreviewCellの状態でも行うべき処理がいくつか残っていました。例えば、選択中かどうか、切り取り中のセルかどうか、バリデーションエラーかどうか、などは、アクティブでないセルであっても表示に反映させる必要があります。

f:id:ytr0903:20200424123119p:plain

ところが、この判定とデザインの切り替えをPreviewCellで行ってしまうと、選択範囲が変わるたびに個々のセルで「いま自分が選択対象になったかどうか」の判定が行われ、全てのセルの更新が走ってしまいました。

 

これを避けるために、「PreviewCellは単純に自身が持っている値だけを表示する」ようにして、選択中かどうかといった見た目の変化は、セルと同じサイズの背景を描画する単一のコンポーネントで処理する方式に変更しました。

 

SpreadSheetCellsBackground.vue という長ったらしい名前のコンポーネントですが、やっていることは配列をそのままdivタグのループにしてCSSで装飾をしているだけです。

f:id:ytr0903:20200424123237p:plain

この変更の結果、選択されているかどうかなどのフラグをpropsで渡す必要がなくなり、選択中のセルのフォーカスを切り替える際のラグが4秒から1秒以下に改善されました。最初の改善と併せて、「見た目に関することはできる限りCSSで行う」というのがシンプルながら有効であることを再確認しました。

 

見えている範囲以外は描画しない

こちらも基本的ながら効果の高い改善でした。

Vue.jsにはIntersection Observerを利用したvue-observe-visibilityというプラグインがあるので、これを利用すれば表示範囲に出入りするごとにイベントをトリガーさせることができます。

f:id:ytr0903:20200424135743p:plain初めは行番号を管理するVisibleRowsという変数を用意して、表示領域に入るたびに行番号を追加し、visibleRows.includes表示を切り替える実装を行っていました。

 

ところが、確かに初期描画は速くなったものの、今度はスクロール時にかなりの待ち時間が発生するようになりました。

f:id:ytr0903:20200424160738g:plain

これでは初期描画が速くなっても意味がありません。

 

一定時間の経過後に、全ての行が一気に表示されていることから、処理後のDOM更新がボトルネックになっていると推測し、Vueの秘密のパフォーマンステク9選紹介 - Qiitaでも紹介されていた、子コンポーネントへの処理の分離を導入することにしました。

 

既に実装が進んでいるものを子コンポーネントに移すのはかなり困難だったため、自身のdataとしてisVisibleのみを持ち、渡されたslotの表示を切り替えるだけの単純なラッパーコンポーネントを作りました。

f:id:ytr0903:20200424161612p:plain

正直、これだけで改善されるかは確証がなかったのですが、これが予想以上の効果を生みました。

f:id:ytr0903:20200424161445g:plain

改善前のgifと比べると一目瞭然。すぐに表示されるのでわかりにくいですが、今までは同じタイミングで一気に表示されていたものが、行ごとに別々のタイミングで表示されており、処理が分散されていることがわかります。

 

中身は親コンポーネントに残したままなのでpropsのバケツリレーも起こらず、実装が複雑にならずに処理速度が明確に向上しました。

 

コンポーネントのupdatedの回数を減らす

表示自体は速くなったものの、セルの編集が完了してから別のセルに移動するまでの処理が遅いという問題が依然として残っています。

 

これを改善するために、Chrome拡張機能Vue.js devtoolsのPerformanceタブを見ながら原因を探っていると、updatedの回数が明らかにおかしいことに気づきました。

f:id:ytr0903:20200424161854p:plain

一度セルを移動しただけでPreviewCellのupdateイベントが6500回走っています。

 

100行×13列で1300個のセルがあるので、1回の編集で全てのPreviewCellが5回更新されているのではと推測できます。(誤差があるのは選択中のセルの切り替えに伴うものと考えられます)

 

Total TimeやAverage TimeはブラウザやPCの性能によって毎回バラつきがあるのですが、Countは信用して良いはずです。

 

対処法は大きく分けて2つ、「更新されていない箇所でupdatedが走らないようにする」「1回の処理でupdatedが2回以上走らないようにする」。

具体的には

  • 不要なpropsを渡さない

  • 1度の変更で同じ値が複数回更新されないようにする

  • propsをオブジェクトでまとめて渡さない(差分を検知できずに毎回updateが走ってしまうため。面倒でもStringやNumberに分解して渡す)

 

どれも当たり前ですが、根本的なロジックを見直す必要があるうえ、後から要らなくなったpropsを消さなくても困らないため、開発しているとつい後回しにしてしまいがちな部分でした。

f:id:ytr0903:20200424162102p:plain

渡されているpropsの中身や、その値を操作する親コンポーネントのイベントに無駄がないかを1つ1つ見直していった結果、

 

期待通りにPreviewCellの更新回数を1回(編集が完了したことで値が更新されたもののみ)に抑えることができました。

 

データのバリデーションをセルでは行わない

これも当たり前といえば当たり前なのですが、最初は「文字数を超えていたらアラートを出す」「全角数字が入力されたら半角に直す」みたいな簡単な処理はセルで行っていました。

 

しかし、値の修正が複数個所で行われていると、「wrapperから間違った値が渡されたら、cellで修正して再度emitする」というような、遠回りな処理が発生してしまいます。

 

さすがに無限ループにはならないように気を遣っていましたが、それでも親子間でのデータのやり取りが増えると、必然的にコンポーネントの更新回数も無駄に増えてしまいます。

 

全てのデータ修正をwrapperに寄せたことで、1度の操作で2回以上更新が行われなくなり、パフォーマンスを最適化することができました。加えて、処理の流れを一元化したことで履歴機能もスムーズに実装できるようになりました。

 

おわりに

スプレッドシートの改善について、まとまりなくつらつらと書いてきましたが、この記事の内容を一言で表すと「塵も積もれば山となる」です。

 

例えばこれが10行程度のデータを編集するためのものであれば、全てのセルで毎回更新が走ったとしてもそんなに重くなりませんし、そこの改善に力を入れても得られるものはそんなにありません。

f:id:ytr0903:20200424161854p:plain


updatedの項で貼ったこの画像も、セルごとの平均処理時間は230ms。並行処理の数が少なければもっと少なくなるので、本来であればそこまで致命的な数字ではありません。そもそも、細かく気を遣わなくても必要十分なパフォーマンスを提供してくれるのがVue.jsやReact.jsのようなフレームワークであるとも考えています。

 

しかし、それが100件、セルの個数にすると1300個という単位のデータになると、一つ一つの処理が数ミリ秒増えるだけでも一気に重くなってしまいます。

 

記事に書いてきたことの1つ1つは非常に単純ですが、だからこそ普段は軽視しがちな領域の処理でもあります。

 

このような大規模なデータを扱う場合には、Vueのライフサイクルの正しい理解と、ほんの少しの高速化の積み重ねが必要になっていくことを学びました。

 

ちなみに、上記の改善をすべて行ったことで、最終的にどの程度の変化があったかというと、 

f:id:ytr0903:20200424213222p:plain

初回描画が9秒から3秒、セル選択が5秒から1秒未満、セル編集が3秒から1秒未満に短縮されました。

……これでもまだ遅い部分はありますが、データ量に依存することも踏まえれば、ある程度実用的な範囲には収めることができたかなと思っています。

まだまだ最適化の余地はあると思うので、今後もリリースに向けて改善を続けていく予定です。

 

ここまで読んでくださってありがとうございました。何らかの参考になれば幸いです。

 

また、このようなプロダクトの改善や新規開発に興味を持った方は、ぜひ一度弊社の採用サイトを覗いてみて頂ければと思います。Webブラウザ上でこのような複雑な機能開発をするのは、困難を伴いますが刺激的でとても楽しいです。一緒にお仕事できる方をお待ちしております。

hrmos.co

 

Kubernetesがコンテナイメージのダウンロードに失敗する原因を探る

はじめに

バックエンドエンジニアの須恵です。 過去の記事ではSREを名乗っていましたが、最近はGoでAPIサーバーを開発するのが主業務になっています。

依然SREチームの末席にも名を連ねさせていただいており、自分の開発しているAPIサーバーが乗っているKubernetesクラスターのメンテナンスや、クラスターに同居している他のサービスのCI/CD関連に取り組んだりしています。

会社のブログに寄稿するのは久しぶりです。

起きた問題

いつからか、そこそこ頻繁に、Podの更新が失敗する症状が発生するようになりました。 その際PodのステータスとしてErrImagePull または ImagePullBackOffが確認できました(ErrImagePullからImagePullBackOffに推移する)。

kubectl describeすると、このようになっています。

f:id:tsue88oct:20200407204958p:plain
拡大してご覧ください

ErrImagePullの発生原因としてまず候補に挙がるのは「存在しないイメージをダウンロードしようとしている」というものですが、イメージはECRにpush済みであり、docker pullすることもできます。

Kubernetesが調整ループを根気よく回し続けた結果、数分後~数時間後には解消していることがほとんどだったのですが、解消までの時間にムラがありすぎるのと、CircleCIのジョブやHelmのrelease statusが失敗扱いになってしまい、開発体験に悪影響を与えていました。

(特にHelmについては、保存されるhistoryの上限を超えて失敗するとデプロイ可能な状態に復旧するのに手間がかかります)

先に結論から

原因

イメージのダウンロードは(実際には)進行中であるが、 kubeletのタイムアウト期間(デフォルトの1分)の間にDockerから進捗報告ができず、ダウンロードを打ち切られている。

対策

対症療法

今動いているノードのkubeletのオプション

--image-pull-progress-deadline=10m

を直接変更する。

根本対応

CloudFormationで作られたノードであるため
テンプレートでkubeletのBootstrapArguments

--kubelet-extra-args "--image-pull-progress-deadline 10m"

を追加する。


ここからは、時系列で解決への道のりを書いていきます。

検索してIssueを発見

KubernetesのIssueを検索してみると、今回の困りごとと一致していそうなタイトルを見つけました。

github.com

このIssueのコメントに、

「kubeletに --image-pull-progress-deadline を設定しましたか?」

とあります。

(kubeletは、Kubernetesの多数あるコンポーネントのうち、ワーカーノードに常駐し、ノードのリソースやPodの状態を監視してAPIに送信したり、コンテナランタイムにコンテナ起動などの指示出しをするコンポーネントです)

続くこちらのコメント(および引用されたソースコード)によると、

「kubeletは、一定周期ごとにDockerからのイメージプル進捗報告を期待しており、進捗報告が得られない場合プルを打ち切る」こと、

「一定周期」は--image-pull-progress-deadlineで設定可能(デフォルトは1分)であることがわかります。

サポートに問い合わせ

本当にこのIssueと同件なのか、なかなか確信が持てず、確信に至るための打ち手も見えなかったので、AWSサポートを頼ることにしました。

(ところで、問い合わせの極意について、もうお読みになりましたか?)

サポートケースを開いて相談したところ、担当の方から、 「EKSログコレクターのログ提供」を手順を添えて依頼されました。

EKSログコレクターとは

こちらです↓↓

github.com

これは何

READMEを眺めてみると、

  • OSのログ
  • kubeletのログ
  • Dockerデーモンのログ

を収集してアーカイブするシェルスクリプトのようです。

ログコレクターの実行まで

一刻も早くサポートへログを提供したいところですが、ここでまた壁にぶつかりました。

ノードに入る手段の確立

ログコレクターを実行するにはワーカーノードの中に入らなければなりませんが、問題のノードにはSSM エージェントはインストールしておらず、セキュリティグループでポート開放もしていませんでした。

一時的に開放して作業するか思案していたところで、このようなツールを発見しました。

github.com

これは、SSMエージェントがインストールされたコンテナをDaemonSetとしてデプロイし、かつノードのディレクトリをマウントすることで、後付けでセッションマネージャーを利用可能とするものです。

必要十分な取り扱い方がREADMEに記載されていますが、都合により一部異なる手順で利用します。

このクラスターではkube2iamを動かしているので、ノードに紐付いているロールを改変する必要はなく、DaemonSetにannotationを書き足すだけで十分です(同様に、公式のIAM Role for Service Accountsを利用しているクラスターであれば、そちらの仕組みに乗ることができます)。

  template:
    metadata:
      annotations:
        # ポリシー AmazonEC2RoleforSSM を持つロール
        iam.amazonaws.com/role: arn:aws:iam::xxxxxxxxxxxx:role/ALLOW_SSM

また、eksctlで作ったクラスターではないので、下記のコメントアウトも必要でした。

        # For debugging .env files produced by eksctl
        # - name: etc-eksctl
        #   mountPath: /etc/eksct
      # For debugging .env files produced by eksctl
      # - name: etc-eksctl
      #   hostPath:
      #     path: /etc/eksctl
      #     type: Director

ログコレクターが動かない(パッケージ不足)

無事、kube-ssm-agentを利用してワーカーノードに入れるようになりました。 満を持してログコレクターを実行しますが、必要なパッケージが不足しておりエラーに阻まれます。

sh-4.2$ sudo bash eks-log-collector.sh

    Fatal Error! Application "iptables" is missing, please install "iptables" as this script requires it, and will not function without it. Exiting!

それならばと、iptablesをインストールします。

sudo yum install iptables
備考

※本当にノードにパッケージが存在しないのではなく、コンテナ内部ではそのように認識されているだけだと思います(別件でkube-proxyの起動設定を見たときに--proxy-mode=iptablesだったため)。


今度こそ…

sh-4.2$ sudo bash eks-log-collector.sh

    Fatal Error! Application "sysctl" is missing, please install "sysctl" as this script requires it, and will not function without it. Exiting!

なるほどそうですかと、procpsをインストールします。

sudo yum install procps

これでログコレクターが実行できるようになりました。

sh-4.2$ sudo bash eks-log-collector.sh
(略)
    Done... your bundled logs are located in /opt/log-collector/eks_i-xxxxxxxxxxxxxxxxx_2020-03-18_0619-UTC_0.6.0.tar.gz

kube-ssm-agentの内部でログコレクターを動かしたため、生成物はkubectl cpを使ってローカルにダウンロードできます。

kubectl cp ssm-agent-24lnj:/opt/log-collector/eks_i-xxxxxxxxxxxxxxxxx_2020-03-18_0619-UTC_0.6.0.tar.gz ./eks_i-xxxxxxxxxxxxxxxxx_2020-03-18_0619-UTC_0.6.0.tar.gz

サポートからの回答

ようやくログをサポートに提出できました。 いただいた回答によると、やはりkubeletのタイムアウトが発生しているとのことでした。 根拠として、下記のログをピックアップして提示いただきました。

# 多少編集を加えています
Cancel pulling image "略" because of no progress for 1m0s, latest progress: "d25013272171: Extracting [==================================================>]  88.27MB/88.27MB"
level=error msg="Not continuing with pull after error: context canceled"
PullImage "略" from image service failed: rpc error: code = Canceled desc = context canceled
container start failed: ErrImagePull: rpc error: code = Canceled desc = context canceled
Error syncing pod, skipping: failed to "StartContainer" for "略" with ErrImagePull: "rpc error: code = Canceled desc = context canceled"

「1分間進捗報告がなかった」ことにより、ErrImagePullへつながっています。

kubeletの設定変更を推奨されたので、実行します。

kubeletの設定変更

ユニットファイルを変更したあと、サービスを再起動します。 systemdのサービスとして動いていることを初めて知りました。

引き続きkube-ssm-agentが活躍します。

sudo vi /etc/systemd/system/kubelet.service

--image-pull-progress-deadline 10m \を追加します。

ExecStart=/usr/bin/kubelet --cloud-provider aws \
    --config /etc/kubernetes/kubelet/kubelet-config.json \
    --allow-privileged=true \
    --kubeconfig /var/lib/kubelet/kubeconfig \
    --container-runtime docker \
    --image-pull-progress-deadline 10m \
    --network-plugin cni $KUBELET_ARGS $KUBELET_EXTRA_ARGS

サービスを再起動します。

sudo systemctl daemon-reload
sudo systemctl restart kubelet

今回問題が起きていたのは開発環境のクラスターであったので、実際はそれほど気を揉むことなくエイヤと行いましたが、ノードとPodが定常状態(=リソース状況、Podの状態に急激な変化なし、かつコンテナランタイムに指示出し中ではない)であれば、 特に我々が運用しているサービスの側にダウンタイムが発生する懸念はありません。

処置後の経過観察

デプロイを何度も試行してみないことには効果が現れているか確認ができないので、気長に様子を見続けます。

すると、明らかに症状が改善していることがわかりました。

f:id:tsue88oct:20200408125807p:plain
🎉

kube-ssm-agentの除去

便利な反面、稼働させたまま放置しておくのはセキュリティ的によろしくありません。 用が済んだら削除しておきましょう。

kubectl delete ds ssm-agent

こうして簡単に削除できるのも利点ですね。

おわりに

自分の運用しているクラスターで開発体験が悪化することは慙愧の念に堪えませんが(というか、自分も開発しているから困る)、今まで知らなかったことを学ぶ良い機会になりました。

もっとKubernetesに詳しくなっていきたいです。

AWSサポートの一歩進んだ使い方 ~問い合わせの極意編~

はじめに

はじめまして、オクトSREチームの@DanKadoiです。 github.com

普段は、

  • AWS上でのインフラ構築
  • Docker・Kubernetes環境整備
  • CI / CD サイクルの改善や効率化
  • プロダクト初期フェーズにおける様々な運用課題の解決支援
  • インフラやアプリケーションのモニタリングの仕組みを整備
  • OS、ボリューム、ネットワーク、AWSアカウントのセキュリティ対策

etc.. などの技術領域で、日々少しずつ会社貢献しています。

Terraformを用いたIaC(Infrastructure as Code)や、運用負荷の低減/耐障害性の強化などを目的としたインフラ構成の改善、サーバレスアーキテクチャ(過去記事参照)への移行、開発量・成果・費用対効果・処理効率などを可視化するためのメトリクス取得法の確立、より高度で高効率なKubernetesによるコンテナ運用、GitHub Actionなど、手につけたい技術は他にもまだまだたくさんあります。 tech.88oct.co.jp

知らない技術や新しい仕組みをインフラ(あるいはプロダクト)に導入して運用を軌道に乗せるには、苦難がつきものです。

雑多な差し込み業務を捌きながら、唐突に発生する障害の事象確認をしつつ、日々の業務に平行して新しい仕組みを導入するための検証・調査を実施するのは一定のハードルがあります。

しかし、障害 は嫌でも発生してしまいますし、説明を求められれば事実確認が必須となります。

なので私は、事象の詳しい確認・調査の難航が容易に見越せて、尚且つ、早急な解決を求められている場合は、AWSの技術的なサポートを積極的に利用させて頂いております。

今回は、私がAWSサポートへ技術的な問い合わせをする際に気をつけているポイントについて、事例紹介と併せて解説させて頂きたいと思います。

AWSサポートとは

f:id:d_hack0928:20200323121523p:plain AWS 使用料の月額のうち数%(サポートプランAWS使用料で変動)を支払って、AWS Trusted AdvisorAWS Personal Health Dashboardなどを含め、特別なサポートをAWSから受けられるサービスのことです。

aws.amazon.com

「技術的な問い合わせ」には、事象や問題をスムーズに確認・解決するために培われた コミュニケーションナレッジ があります。
それは、だれが,どこに,どのような 問い合わせをする場合においても、変わらず在ると、私は考えています。
分かる人がこの記事読めば そんなの当たり前だ と思うかもしれませんが、基本に立ち返る良い機会だ くらいに受け止めて閲覧頂けますと幸いです。

サポートプランに加入する方法

ご利用中のAWSアカウントにて、rootユーザでログインして頂き、以下のドキュメントに沿ってプラン変更の操作をしてください。 aws.amazon.com

スムーズに解決したAWSサポートへの問い合わせ事例

本番環境で起こったこと

2020/02/16 午後
ANDPADの社内環境にて、AWS CognitoでGoogle認証(過去記事参照)をかけているページをChromeで開こうとすると、ERR_TOO_MANY_REDIRECTSのエラーを出して認証ページに到達しない現象が発生し、環境利用者から問い合わせを受けました。 tech.88oct.co.jp

取り急ぎ、上長およびセキュリティチームから、「他のブラウザをからアクセスすれば発生しないので一旦それで回避してほしい」といった旨のアナウンスを社内向けに出してもらい、私の手元(macOS Catalina 10.15.3, Chrome80系)で事象の再現を確認し以下のように整理しました。

発生条件:
■(OS問わず) Chromeのバージョン80.xxx以上を利用している端末で発生する
■■ERR_TOO_MANY_REDIRECTSが出て、Cognito認証後のページに全くアクセスできず画像1のエラーがブラウザに表示されているケースがある
■■シークレットブラウザを使ったり、googleアカウントの再ログインなどを行ってアクセスを試みたりして、Cognito認証後のページに到達できるが、画像2のエラーが開発者モードで確認できるケースがある

発生しない条件:
■Chromeを使っていても、バージョン79.xx以下のままアップデートしていない環境では、発生しない
■Chrome以外のブラウザを利用すれば、Cognito認証(googleアカウントログイン)通過後のリダイレクトが正常に行える

f:id:d_hack0928:20200322200209p:plain f:id:d_hack0928:20200322200213p:plain

本件の場合、本番環境で発生したためALBの再作成といったサービスダウンタイムが発生する作業を実施しづらく、頻繁に利用される社内環境であったため、AWSサポートに技術的な問い合わせをして早急な解決を図りました。

f:id:d_hack0928:20200322205408p:plain

AWSサポートに問い合わせを送る際に私が気をつけたポイント

本番用のAWSアカウントにて本件に該当するリソースを確認し、以下のようなテンプレートでAWSサポートに問い合わせのメッセージを送信しました。

${いつもの冒頭挨拶}

発生事象:
${本番環境で起こったこと に記載の通り}

■該当するリソース
■■ALB ${ARN}
■■リスナー ${ARN}
■■リスナールール ${ARN}
■■インターネット経由でアクセスする際のURL ${エンドポイントのURL}
■■Cognito認証を以下の通り利用している
ユーザープール ID:  ${ARN}
( プール arn:  ${ARN} )
クライアント ID:  ${ID}
セッション Cookie:  ${Cookie種別}
セッションタイムアウト:  ${Time}
認証されていない場合:  ${アクション種別}
スコープ:  ${スコープ}
■■転送先
ターゲットグループ: ${ARN}
( ${ターゲットグループに紐付くリソースにデプロイされているアプリケーションの説明} )

発生条件:
${本番環境で起こったこと に記載の通り}

発生しない条件:
${本番環境で起こったこと に記載の通り}

問い合わせ:
本件を早急に解決したい と考えいて、解決手段を模索しております。
本件は、Chrome 80におけるSameSite Cookie仕様変更の影響で発生している と考えて良いでしょうか。
また、上述しております該当もしくは関連するawsインフラリソースにおいて、本件の原因となりうる問題が発生していないかご確認いただけますでしょうか。

気をつけたポイントは以下の通りです。

  • 何が、どのリソースで、どのような条件で発生し、何に困っているかを、正確に言語化する
  • 読み手の調査に手間や時間がかからないように、リソースのユニークIDはすべて記載する
  • 読み手が「何に回答して欲しいのか」に悩まないように、問う内容その補足情報を明確に区別する
  • 添付ファイルは、タイトルで中身が推察できるようにしつつ、文章内で参照できるようにする (画像1など) f:id:d_hack0928:20200322215316p:plain

実は、開発環境でも数日前に発生していた

本件に極めて類似する事象が、ANDPADの開発用のAWSアカウントでも、2020/02/13 午後 に確認されていました。

その際は、Cookie headerの文字数が4,096を超えているところまで確認し、最終的に関連するALBリソースを再作成することで事象の復旧を確認しました。

( ※開発用のAWSアカウントは、サポートプランに加入していなかったため、当時AWSへの問い合わせは断念していた)

開発用のAWSアカウントのその後...

開発部の執行役員と交渉して、開発用のAWSアカウントもサポートプランに加入して頂く運びとなりました。
加入の意図としては、以下のような説明をしました。

  • これまで運用してこなかったAWSのマネージドな仕組みや新しい技術をプロダクトで使おうと思った際に、稀に検証段階でテクニカルサポートが欲しいケースがある
    • 本番適用前なので、本番用のAWSアカウントにリソースがなく、テクニカルサポートを受けられない
    • 真新しい技術で日本語記事がインターネット上に少ない段階だと、調査が難航したりする
  • 複数のAWSアカウント間でリソースを共有して連携するようなケースが近年増えてきて、共有先のAWSアカウントでマネージドな機能を利用する際にIAM権限回りの問題が発生して調査が難航しやすい
    • アカウントをまたいでサポートに状況確認してほしい場合、共有先のアカウントがサポートプラン未加入の場合、対応してくれるかわからない
  • 開発用のAWSアカウントで数日前に発生した障害が、サポートプラン未加入のためAWSに問い合わせできず原因調査も難航したため、リソースを再作成することで強引に復旧させた。しかしその後、本番用のAWSアカウントでも同様の問題が発生してしまった(まさに本件のケース)

などを解決したい。(未然に防ぎたい)

これからステークホルダーに対してAWSサポートプランの加入交渉をしようと考えている運用チーム/SREチームの方は、加入によって増えるコスト加入したら享受できる恩恵 を天秤にかけた結果が、先方に伝わるように説明できれば良いと思います。
意思決定者と交渉するのもSREの仕事です、その参考になれば幸いです。

問い合わせ後のAWSサポートによる素早い対応

f:id:d_hack0928:20200322221217p:plain 先方の担当者の方に、問い合わせ内容のホスピタリティを褒めて頂けました。
回答を要約すると、以下のような内容でした。

  • 該当リソース(ALB)に対し、「Cross-Origin Resource Sharing (CORS) リクエストの際に SameSite 属性が必要となるブラウザのために、Application Load Balancer の認証機能において ‘SameSite=None’ を付与する変更」が加えられた
  • この変更によって、クライアントがバージョン 80 以降の Chromium ベースブラウザを使用する場合において、Load Balancer が Authentication Cookie にこの属性を含める形となった
  • これによって、Cookie のサイズが許可されるサイズよりも大きくなるという問題が見つかった(文字数が上限を超える)
  • 御社のALB に対して、この変更のロールバックを実施した
  • 現在、全ての Load Balancer に対してロールバックを実施している段階である

本事象に関連する情報:
https://forums.aws.amazon.com/ann.jspa?annID=7413
Chromium (web browser) - Wikipedia

まとめ

スムーズな事象解決を目指した問い合わせのナレッジとは

これまでの私の業務経験を元に、問い合わせの文章がどの程度の情報を持っていればその後のやり取りがスムーズになるか、箇条書きでまとめてみました。

今後、サーバやアプリケーションで発生した何らかの事象について問い合わせをしたい方は、問い合わせ先がどこであっても、以下に気をつけて頂ければ良いかと思います。もちろん、下記をすべからく満たしている必要はありません。

  • 自分の状態を具体的に説明する
    • どのような事象・問題が発生しているか
    • どのような環境で発生しているか
    • (AWSの)どのインフラ・リソースで発生しているか
    • 誰が・何に・どのように困っているか
    • (解決した場合)どのような状態であることが、正であるか
    • いつまでに、どうなっていたいか(希望・期待)
  • 自分で収集できた、関連し得る情報を共有する
    • 事前に確認することができた事実や調査結果
    • 関連が疑われる挙動・異常・操作
    • 関連するログファイルを添付
    • 事象を手元で再現したときの様子をgifやスクリーンショットなどで添付
  • 誰が読んでも概ね同じような解釈ができる文章を書く
    • 主語を省略しない
    • 技術やリソースの固有名詞は正確に記載する
    • 隠語・造語・略語・社内用語はなるべく使わない
    • あなたの問い合わせを受けとる人間は、あなたの親友でも家族でもないことを念頭に入れておく

おわりに

問い合わせの多くは、2~5個の質問をし返さないと、調査に着手することすら難しい程度の情報量 しか持っていません。この記事は、読んだすべての方の業務が、今までより少しでもスムーズに回ることを願って書きました。読んで振り返ったナレッジを、些細な質問からでも、是非実践してみてください。

engineer.88oct.co.jp

NuxtのSSRモードでメモリリーク?原因はaxios?

はじめに

最近金髪から黒髪に戻して更生しました藤井(フロントエンドエンジニア)でございます。久々にテックブログに貢献させて頂きます!

 

今はメインの施工管理機能の改修の傍ら、新機能開発にも携わっておりまして、フロント側はSSRモードのNuxtを採用しております。さて、そんな中SREチームから「フロントエンドがメモリリークしていませんか?」との通報が入りました。

 

いやいや、そんなハズは・・と思いながらもDatadogを見てみると見事にメモリ消費量が右肩上がりに増えていってます。なぜじゃ・・・。

f:id:yohei-fujii:20200314183203p:plain

調査開始

メモリリークになるような処理を入れた覚えはないぞ!と思いつつも調査開始です。ひとまずChromeのDevtoolでPerformanceやMemoryタブを見ながら確認しますが、これと言って原因が見つかりません。

f:id:yohei-fujii:20200314201317p:plain

 

おかしい・・。なんでだろう。

あれこれ試しましたが解決せず、ほぼ1日消化しました(やってしまった・・)

 

SSRのデバック

そして他の人にも相談しながらやはり気になったのはSSRモードということです。考えてるだけでは何も進まないので、ひとまず調査してみることに。以下の記事を参考にさせていただきました。

qiita.com

ターミナルで以下のコマンドを叩いてNuxtを立ち上げます。

node --inspect node_modules/.bin/nuxt-ts

次に、以下のページをブラウザで開きます。

chrome://inspect/#devices

 

このような画面が開きますので、Inspectを押してChrome Dev Toolsを起動させます

f:id:yohei-fujii:20200314184607p:plain


そして立ち上がったNuxtアプリケーションを確認します。最初にスナップショットを撮って置きます。あとは何度もページ再読み込みしたりなどしてアクセスをかけました。そしてもう一度スナップショットを取得(HEAP SNAPSHOTはChrome DevToolsのMemoryタブから取得できます)

 

カメラで撮影をしている人のイラスト

 

いよいよ取得したスナップショットを確認です。

さてさて、確かにSnapshot 1 から2にかけてメモリ消費量が増えてます。ひとまずShallow Size(オブジェクト自体の大きさを表す)の大きなものからパーッと目を通しました。

 

おぉー、いっぱいあるなぁ。どれどれ。

根気よく中身を確認していきます。

 

f:id:yohei-fujii:20200314185115p:plain

そうしているとArrayの中に気になる部分を見つけました! 

InterceptorManager?はて、こんなものがなぜ出てくるのか・・。

f:id:yohei-fujii:20200314185429p:plain

そしてそれはArrayだけではなく、system / Contextにも同じようなものが出ていることを確認できました。

 

InterceptorManagerと見て、真っ先に思いつくのはHTTPクライアントであるaxiosです。やはりSSRモードだと何か悪さしているのか?と気になりググってみると似たような記事がヒットしました。

 

techblog.timers-inc.com

 

これは、確かに気になるぞということで早速consoleなどを仕込んで確認します。

f:id:yohei-fujii:20200314190007p:plain

 

まず初回アクセス時点でのInterceptorsの中身を確認。

f:id:yohei-fujii:20200314190104p:plain

ターミナルの方でhandlersの中身が空であること、そしてブラウザの方でも初期値はまだセットされておらず空であることを確認できました。

f:id:yohei-fujii:20200314190215p:plain


それでは、何回かアクセスを繰り返してみて確認します。

すると・・

f:id:yohei-fujii:20200314190449p:plain

 

あら、増えている・・。

 

f:id:yohei-fujii:20200314190500p:plain

 

なぜだー!!!お前はなんだ!

どうやらpluginsはSSRモードだとリクエストの度に動くので、そのことが起因して、axiosインスタンスが肥大化して行っているようです。

 

後々気付いたのですが、公式ページにちょこっとだけ書いてありました。

私たちは isomorphic な HTTP リクエストを作るために axios を使っています。私たちはあなたの Nuxt プロジェクトに、私たちの axios module を使うことを強くオススメします。

node_modules 内の axios を直接使用しており、axios.interceptors を使用してデータを処理する場合、interceptors を追加する前にインスタンスを作成してください。そうしなければ、サーバレンダリングされたページをリフレッシュする際に、interceptor が複数追加され、データエラーが発生します。 

ja.nuxtjs.org

いよいよ対応処理

どうするかというと、Interceptorsのhandlerが存在する場合(=2回目以降のplugins呼び出しの場合)に登録実行させなければ、インスタンスの肥大化を防ぐことができるのではないかと考え、判定処理をいれました。

f:id:yohei-fujii:20200314191536p:plain

一度変数interceptorManagerに代入しanyを付与しているのは、型の問題があったからです。AxiosRequestConfigにはhandlersがないので、どうしようかとも考えましたが、ひとまずanyを使ってしのぎました。(どなたかより良い方法あったら教えてくださいませ)

f:id:yohei-fujii:20200314191617p:plain

idがすでに1以上であれば、つまりinterceptorsの登録が既にされていたらreturnするようにしました。

 

そして運命の確認!もう一度ブラウザを開き、何度もアクセスをして見てみました。

f:id:yohei-fujii:20200314191938p:plain

 よし!何度ページを読み直しても増えていません!

 

それでは、念の為今回の対応を取り込み、検証環境で確認してみます。再度Datadogを確認します。

 

f:id:yohei-fujii:20200314200259p:plain

見事メモリ消費量が安定しました!めでたしめでたし!

 

さいごに

思わぬSSRの罠にハマってしまった感があります。日頃から気をつけていても、今回はまさかお前か!みたいな感じでメモリリークを起こしてしまっていました。SSRモードの場合は、pluginsだけではありませんがライフサイクルにはかなり気をつけなければなりませんね。

 

さて、今回の作業のようにパフォーマンス改善調査というのは根気のいる作業ですが、品質を考える上では気をつけて対応していかなければならないところです。まだまだ課題も多いですが、一つ一つ解決していけたらなと思います。興味ある人、ぜひ一緒にやりましょう!