Quantcast
Channel: Cybozu Inside Out | サイボウズエンジニアのブログ
Viewing all 690 articles
Browse latest View live

「サイボウズの開発の現場」の発表報告

$
0
0

こんにちは大阪開発部の三苫(@mitomasan)です。

2016/05/23(月)にDevLOVE関西で「サイボウズの開発の現場」というタイトルで私と酒井(@sakay_y)が発表してきましたので報告します。

きっかけ

昨年11月に大阪オフィスが移転し、大阪にもよい感じの開発拠点ができました。そのため大阪開発部も社外勉強会をドンドンやって知名度上げていきたいねという機運が高まっています。

そんな折、DevLOVE関西の中村さん(@yohhatu)よりお誘いがあり「サイボウズの開発の現場」というタイトルでお話しさせていただくことになりました。(ありがとうございました!)

f:id:cybozuinsideout:20160524143658p:plain

勉強会の会場はサイバーエージェントさまをお借りしたのですが、角部屋が一面ガラス張りで凄かったです。(ありがとうございました!)

近畿外の方にはあまりなじみがないかもしれませんが DevLOVE関西 | Doorkeeperは関西で活発な開発者コミュニティです。

devlove-kansai.doorkeeper.jp

kintoneチームのKAIZEN文化

f:id:cybozuinsideout:20160524144800p:plain

私の発表では中途入社してきたWebエンジニアが、kintoneチームに配属されて九ヶ月ほど働いて感じたkintoneチームの文化を紹介しました。(kintone製品ページ)

主に以下の内容をお話ししました。

  • kintoneチームは自分たちの製品をドッグフーディングしている
    • 開発プロセス・コミュニケーションに組み込んでいるのでセルフホスティングと言えるレベル(kintoneが無いとkintoneが開発できない)
    • 分報についても、その概念が広まる前からkintone上で取り組んでいる
  • 改善活動が盛んで、その活動がうまく回るために多くの取り組みを行っている
    • 開発の理想、製品の理想について積極的に話されている
    • リソース確保をプロジェクト計画に組み込んでいる

www.slideshare.net

私自身、kintoneチームに配属されて一緒に働く中でチームのやり方に驚いたり感心したりしたことが多かったです。

自然と「どのようにして彼らはこんな開発文化をつくりあげることができたのだろう?」といったことが気になり、チームを観察して自分なりに出した答えをこのスライドにまとめることができたと思います。

私がサイボウズの開発チームに感じている印象は「教科書的な正攻法をやりきる人達」というものです。

それは最近の バグゼロを実現した話とその後の顛末 - Cybozu Inside Out | サイボウズエンジニアのブログという記事で出てくるような技術的な面もそうですし、今回のスライドにもあるようなチーム外のメンバーとのコミュニケーションの面もあります。

勉強会に参加していただいたみなさまに今回紹介した取り組みを「一回、やってみるか」と思っていただけたら幸いです。

以下の記事にもkintoneチームの取り組みが紹介されていますのでご参照ください。

type.jp

kintoneエンジニアが紹介する品質向上のための取り組み

f:id:cybozuinsideout:20160524154020p:plain

酒井の発表では、kintoneチームのエンジニアが品質向上のためにどのようなことに取り組んでいるのかの紹介しました。

主に以下の内容をお話ししました。

  • 自動テストとビルドパイプライン
  • 大規模データ対応
  • ユーザー価値の探求

www.slideshare.net

私の発表はどちらかというと情緒寄りだったのですがこちらは比較的実践的な内容が多く、参加者のみなさまから多くの同意や共感が寄せられました。

今回の勉強会でも「kintoneを触ったことがある方?」と聞くと大勢の方が手を挙げてくださり、うれしかったとともに身の引き締まる思いでした。

ダイアログ(対話)

私と酒井の発表後、参加者で4~6名ほどで5つほどグループを作り、今回の発表テーマをもとに様々な意見交換を行いました。

私の参加したグループでは全文検索や分報など、情報をチーム内でいかに回すかという事をテーマにお話ししました。

分報を根付かせるには最初から多くを求めず誰もができる小さなルールから初め、ルール以上の使い方をするかどうかは各人にまかせるようにする。 チャットにかじりつくと実作業が進められない人もいるので、必ず共有したいことは共有用のスペースに書き込みをするとよい・・・などの話が出ました。

大阪開発部の紹介

最後に、ついでなのですが大阪開発部の紹介もさせてください。

最初に書いた通り大阪開発部は大阪の開発者コミュニティに参加して社外のエンジニアと交流をしたいという想いがあります。今回のような話をもっと深く聞きたい、あるいはこのテーマをサイボウズと一緒に勉強会をしたいというお話も大歓迎ですので是非お声がけください。

今回紹介したような開発文化・取り組みのあるチームで働いてみたいというお話もお待ちしております。

現在、大阪開発部は5人のメンバー中4人が0~3歳の子供を抱えるイクメンの巣でもあります。多数のメンバーが育休取得や育児のための長期在宅勤務をするなどしています。 サイボウズエンジニアの職場環境 @ 2016 - Cybozu Inside Out | サイボウズエンジニアのブログで書かれていることは大阪も同様です。(カッコいいブーメランデスクはありませんが…)

cybozu.co.jp


機能改善も頑張ります!

$
0
0

こんにちは、東京第2開発部の生江です。サイボウズLiveの開発を担当しています。 新サービスや新機能はとてもワクワクしますが、普段使っているサービスや機能が日々改善されていることも大切ですよね。 というわけで、サイボウズLiveの機能改善について、紹介します。

機能改善を進めるうえでの判断材料

サイボウズLive では、新機能追加を中心に開発を進めてきていましたが、 今後は、既存機能の改善についても力を入れていきたいと考えています。

機能改善を検討するにあたって、サービスを利用してくださってる方の声はとても参考になります。 回数はまだまだ少ないですが、ユーザー会を開催して、直接、お話を伺ったり、 サイボウズLive IDEABOXに投稿いただいたご要望について、 開発チームで議論する機会を増やしています。

「シンプルで判りやすい」サービスを大前提に、課題を設定したあとは、 「定量的」「定性的」の両面から、機能改善を検討していきます。

定量的観点

アクセスログを確認することで、どの画面がよく利用されているか等を確認することができます。 たとえば、画面内にリンク先が同じ導線が複数箇所ある場合、それぞれに異なるパラメータをつけることで、 もっともクリックされている場所を確認しています。

定性的観点

ユーザーヒアリングやユーザビリティテストを行うことで、定量的には表せない状況を知ることができます。 サイボウズLive は、職場やサークル活動など様々な場面で、様々な方にご利用いただいています。 ただ、機能検討を行うにあたって、「様々な方」を対象に検討するのはとても難しく、 機能ごとやアップデートごとに利用者像を想定(ペルソナ)し、検討をすることが多いです。 想定した利用者像に近い方にヒアリングのご協力いただくことで、問題点を明確にすることができます。

どのような機能改善を行っているか

2016年4月に実施した「機能導線の改善」について、どのような検討を行ったかを説明します。

サイボウズLive をあまり使っていない方から「機能が多すぎて判りにくい」という声を聴く回数が増えてきたので、 利用頻度の高い「ホーム」を中心に、機能導線を減らし、シンプルな画面になるように検討しました。

主な変更点は以下の通りです。

  • ホームから、ToDo、アンケート、お気に入りの表示切替機能を削除
  • 共通部分(フッター)から、ToDoとアンケートのTip表示の導線を削除

なお変更箇所の詳細は、サイボウズLive からのお知らせに記載しています

いずれも利用率が低かったので、導線を別の位置に移動する対応を行いました。

ただし、利用頻度が低いといっても、全く利用されていないわけではなく、 その機能を使っていた方にとっては不自由になり、改悪と感じられることもあります。 機能変更を行う際は、反響を想定しつつ、代替方法を紹介するなど、方針を明確にしておくことが大切です。

また、機能とデザインなど、同時に複数の変更を行うと、問題の所在を明確化することが難しくなるため、 別のタイミングでリリースするといったことも必要になります。

おわりに

サイボウズLiveの機能改善は、定性的観点をきっかけに取り組むことが多かったので、 定量的観点を強化して、よりよい改善を継続的に行っていきたいと考えています。 また、短い間隔で機能改善したバージョンをリリースするなど、プロセス改善も行っていきたいと考えてます。

そのためにも、一緒に開発するエンジニアを募集しています。 ガシガシ改善していきたい!と興味を持った方、一緒によりよいサービスを作っていきましょう!

サマーインターン2016を開催します!

$
0
0

皆さんこんにちは!人事部の中江です。 本日はサイボウズのエンジニアサマーインターンシップについてご案内させていただきます!

f:id:cybozuinsideout:20160601152836j:plain

今年は3つのコースを用意しています。

皆さんのエントリーお待ちしています!サイボウズでこの夏熱い5日間を過ごしましょう!!

募集要項

日時

  • 08月01日(月) 〜 05日(金) ※ この回のみ最終日の終了時間がお昼過ぎとなります
  • 08月22日(月) 〜 26日(金)
  • 09月05日(月) 〜 09日(金)

就業時間

 9:00 〜 18:00

 ※ 初日は10時集合、最終日は懇親会を実施するため20時〜21時頃解散となります

会場

コース

対象

 2018年卒業予定の大学生・大学院生

募集人数

 各回、各コース3〜5名程度

選考

 書類選考、面接1回 (Skypeでの面接も可)

給与

 8,000円/日

宿泊費/交通費

 遠方の方には宿泊費と既定の交通費をお支払いします

エントリー方法

 こちらよりエントリーして下さい

締切

  • 08月01日(月) 〜 05日(金) → 07月01日(金)
  • 08月22日(月) 〜 26日(金) → 07月22日(金)
  • 09月05日(月) 〜 09日(金) → 08月05日(金)

お問い合わせ

  • 人事部 中江 麻未
  • メールアドレス:recruit@cybozu.co.jp
  • 電話番号:03-5204-1723

Webアプリケーション開発コース (東京・大阪・松山)

このコースでは、サイボウズが提供するWebアプリケーションの実際のユーザーや社内での要望を元に

  • そのユーザーが本当に困っているのは何なのか
  • それに対してどういった機能を提供するのが良いのか
  • その機能は他のユーザーにも広く使われるものか

といった企画段階から、その機能のプロトタイプを作成するところまでを体験していただきます。

実際にサービスを開発しているメンバーがメンターとなり、課題の設定の仕方や実装の進め方、コードの書き方までを指導しながら開発を進めていきます。

東京と大阪では kintone、松山では メールワイズサイボウズOfficeを題材として扱います。

なお、夏のインターンとは別に、10日間のインターンも募集しています。kintone 開発チームに入って開発してみたいという方は、開発インターンにご応募ください

スケジュール

  • 事前準備:インターン実施の事前準備として、どういった課題に取り組むかについてメンターと相談して決めていきます。
  • 1日目
    • 午前 全コース共通オリエンテーション
    • 全コースで昼食(午後から各部へ)
    • 午後 開発の進め方についての講義
    • 取り組む課題について各自から発表
    • 実装開始
  • 2日目
    • 終日実装
  • 3日目
    • 午前 中間発表 この時点での成果や方向性を発表し、メンター以外の社員からフィードバック
    • 午後 実装
  • 4日目
    • 終日実装
  • 5日目
    • 午前 実装
    • 午後 成果発表(他のコースの参加者の発表も聞けます)
    • 懇親会

必要スキル

  • プログラミングの経験
  • HTML、CSS、JavaScriptの知識

あると望ましい経験/スキル

  • Webアプリケーション開発の経験
  • Git及びGitHubの使用経験
  • サイボウズの製品にこんな機能を追加したい!というアイデア

品質保証/セキュリティコース (東京・大阪)

製品の品質保証業務を通してソフトウエアの品質がどのようなプロセスで確保されているのか、そもそも品質とは何かを学んでいただきます。

品質を確保するだけではなく、製品がリリースされた後、製品の品質に関する情報をどのように顧客に伝達していくのか、顧客の要望をどのように製品にフィードバックしていくのかを実体験を通して学んでいただきます。

スケジュール

  • 1日目
    • 午前 全コース共通オリエンテーション
    • 全コースで昼食(午後から各部へ)
    • 午後 弊社製品について説明
    • テスト作業を通して、品質保証の実業務を体感する
  • 2日目
    • 午前 講義:ソフトウエアの品質について
    • 製品テスト作業
    • 午後 講義:ソフトウエアの脆弱性について
    • サイボウズにおけるセキュリティ対策と体制
  • 3日目
    • 午前 セキュリティテストの実体験
    • 午後 セキュリティテストの実体験
  • 4日目
    • 午前 講義:リリース後の品質保証部の活動
    • 不具合情報の公開作業の実体験
    • 午後 不具合情報の公開作業の実体験
  • 5日目
    • 午前 成果発表資料の作成
    • 午後 成果発表(他のコースの参加者の発表も聞けます)
    • 懇親会

必要スキル

  • コンピュータに興味がある
  • PC操作ができる (ブラウザ、Microsoft Excel)
  • Webサービスを使って情報発信ができる(Facebook、Twitter など)
  • Webサービスが動いている仕組みをある程度理解している(ブラウザの役割、プロトコルなど)

あると望ましい経験/スキル

  • 簡単なプログラムを組んだことがある
  • Webページを作成したことがある

デザイナーコース (東京)

国内外680万人以上のユーザーに利用され、高い顧客満足度を誇るサイボウズ製品・サービスにあなたのデザインアイデアをぶつけてみよう!

サイボウズのデザイングループメンバーとともに、製品・サービスのデザイン業務に取り組んでもらいます。リサーチ、ユーザーシナリオ、レイアウト、プロトタイプ、ビジュアルなどに対して、自由な発想を活かして新しいデザインを提案してください。良質なアイデアが実際の製品・サービスに採用される可能性もあります。

応募者のスキルや希望によって、プログラム詳細を決定します。

例: スケジュールアプリの課題発見とデザイン改善案作成

スケジュール

  • 1日目
    • 午前 全コース共通オリエンテーション
    • 全コースで昼食(午後から各部へ)
    • 午後 プログラムの説明・選択
  • 2〜4日目
    • デザインプログラム実施
  • 5日目
    • 午前 成果発表準備
    • 午後 成果発表(他のコースの参加者の発表も聞けます)
    • 懇親会

必要スキル

  • グローバル市場(US・日本・中国)向けBtoBアプリケーションのデザインへの興味
  • UXD/UI/HCD/デザイン思考/コンセプト設計/ユーザー調査などへの興味
  • Webやアプリのデザインの知識や経験

あると望ましい経験/スキル

  • Webサービスのデザイン実務経験
  • スマートフォンやタブレット用アプリのデザイン実務経験
  • グラフィックツールの利用経験(例:Adobe Photoshop・Adobe Illustratorなど) ※ OS、ツールに関しては応募の際に明記をお願いします。
  • 認知心理学、統計学の知識
  • HCD関連の手法の実施経験
  • 海外市場向けWebアプリやサービスのデザインまたは調査経験
  • 英語スキル

サイボウズの開発を加速させる「ふりかえり」活動

$
0
0

こんにちは、kintoneチームの小林です。

みなさんは、日々行なっている業務を改善するために、どんな活動をしていますか?わたしが所属するkintoneチームでは「ふりかえり」という活動をとても重視しています。ふりかえりとは、今まで行なってきた業務を思い出して、今後取り組む活動を考えることです。

この記事では、kintoneチームのふりかえり活動と、その効果について紹介したいと思います。

ふりかえりとKPT

kintoneチームでは、KPTという手法を用いてふりかえりを行なっています。

  • K(Keep)は、よかったこと
  • P(Problem)は、問題だと感じたこと
  • T(Try)は、KやPを踏まえてこれから取り組みたいこと

を意味しています。

kintoneチームは、自分たちで作ったサービスを自分たちの業務で使っており、ふりかえりもkintone上に作成した「KPTアプリ」を使って行なっています。

以下の画像は、実際にKPTアプリに登録された、Problemの内容です。KeepやProblemの内容、各自が考えたTryの案、チームで決定したTryが書かれています。 f:id:cybozuinsideout:20160628213130p:plain

ふりかえりの進め方

kintoneチームでは、以下の3つのステップでふりかえりを進めています:

1. 各自がKPTアプリに内容を登録する

ふりかえりが始まる前に、各メンバーがK(Keep)やP(Problem)と、それに紐づくT(Try)の案を「KPTアプリ」に登録します。

2. 登録した内容をサブチーム内で議論する

3つのサブチームをつくり、各自が登録したKPTの内容について議論して、Tryを決定します。kintoneチームには現在12人のメンバーがいます。いきなりチーム全体で議論してしまうと、長い時間が必要になってしまいますし、大人数の前では意見が出にくい傾向があります。このため、まずは小規模な単位で議論することにしています。

3. 議論した内容をチーム全体で共有する

サブチーム内で議論した内容をチーム全体で共有します。もちろんこのタイミングでも、意見があれば全員で議論することができます。また、前回のふりかえりで設定されたTryの進捗も、この場で確認します。

ふりかえりは2週間ごとに行なっています。あまりに期間を空けすぎると、その期間にあったことを思い出しにくくなったり、T(Try)が抽象的になってしまったりすることがあるので、頻度を調整しています。

f:id:cybozuinsideout:20160628220753p:plain

ふりかえりの効果

kintoneチームでは、ふりかえりによってチーム内の様々なプロセスを改善してきました。今まで、800個以上のKeep, 1000個以上のProblemが提示され、500個以上のTryを実行してきました。大小さまざまな内容がありますが、代表的な例を挙げてみます:

開発環境の改善

開発効率に影響する問題は頻繁にProblemに挙がります。その度に、IDEの設定の見直しや、ビルドスクリプトの改善、CI環境のメンテナンスなど、様々なTryが実行されています。

勉強会の開催

挙げられたProblemについて、kintoneチーム全体で知見を高める必要がある場合は、勉強会を開催するTryが設定されます。「継続的デリバリー勉強会」「JUnit勉強会」「ユーザストーリー勉強会」など、多くの勉強会がふりかえりに基づいて開催されています。

KAIZEN合宿

もともと、kintoneには、一日かけて技術的負債を返済する「KAIZEN DAY」と呼ばれる日がありました。しかし一日で行える活動には限界があるといったProblemが提示されたため、泊まりがけで集中して活動を行なう「KAIZEN合宿」が行われました。詳しくは以下の記事で解説されています:

KAIZEN合宿のススメ - Cybozu Inside Out | サイボウズエンジニアのブログ

他チームとの情報共有

kintoneは分散開発されており、東京と大阪にプログラマーが、松山に品質保証のメンバーがいます。今までプログラマーと品質保証のメンバー同士は交流が少なく、お互いの現状や問題点があまり共有できていませんでした。この問題がふりかえりで議論され、プログラマーが松山へ出張し、品質保証のメンバーと現状の活動の共有や問題点を議論しました。

ここに挙げたもの以外にも、細かな開発プロセスの改善が多数行われています。

「ふりかえりのふりかえり」でさらなる改善

kintoneチームでは、今までお伝えしたふりかえりの方法を長い間行なってきましたが、特に最近、今までのふりかえり方法に対する課題も感じるようになってきました。そこで、「ふりかえりのふりかえり」と題して、現状のふりかえり方法の問題点を洗い出し、改善策を考える会議を行いました。

以下の写真は、実際に行われたふりかえりのふりかえりの様子です。ふせんに問題点を書き出しながら、東京と大阪のメンバーで改善案を議論しました。 f:id:cybozuinsideout:20160628221318j:plain

洗い出された問題点の一部は以下のようなものでした:

  • Tryが具体的でないことがある
  • Tryに締切がない
  • KPTがマンネリ化している
  • KeepやProblemの登録数が少ないときがある

これらの問題点について深堀し、改善策を実行することにしました。

Tryが具体的でなかったり締切がなかったりする問題については、サブチームでの話し合いの際にTryの期限を設定し、チーム全体のふりかえりで、期限切れになったTryを確認することにしました。Tryを実行するのが難しいようであれば、別のTryを設定したり、Tryを細かく分割するなどの対応を行います。また、今まで登録された、期限が設定されていないTryについては棚卸しを行い、進捗を確認することにしました。

KPTがマンネリ化していたり、KeepやProblemの登録数が少ないなどの問題については「タイムライン*1」と呼ばれる新しいふりかえり手法を併用することにしました。タイムラインは、期間内に起こったことや感じたことをふせんに書きだし、時間順に並べ、チーム内に共有する手法です。サブチームでのふりかえりの冒頭でタイムラインを行なうことで、各自が期間内に起きたことを思い出しやすくなり、その場でKeepやProblemを発見することができるようになりました。

以下の写真は、タイムラインを行なったときのホワイトボードです。個人ごとにふせんを色分けしており、時間軸上に並べています。 f:id:cybozuinsideout:20160629174130j:plain

そのほか、サブチームのメンバーが固定化されていたのでふりかえりごとにシャッフルする、今まで使っていた会議室は広すぎるのでちょうどいい大きさの会議室に変更する、といった細かい改善も行われました。

サイボウズ全社に広がるふりかえり活動

もともと、kintoneチームをはじめ、いくつかの開発チームで行われていたふりかえり活動ですが、現在では、サイボウズの様々なチームが導入するようになってきています。最近では特に、人事の採用チームや面接官のグループが、KPTに基づいたふりかえりを行なっています。また、kintoneチームのふりかえりを見学する人も、開発チーム内外問わず、増えてきています。

kintoneでは既存のアプリを再利用して、新しいアプリを作成することができるので、各チームごとにKPTアプリを簡単に作成することができて、とても便利です(宣伝)

おわりに

kintoneチームで行われているふりかえり活動について紹介しました。

ふりかえりは、さまざまな改善のもととなる有意義な活動です。この記事を読んだみなさんが「ふりかえりをやってみたい!」「今やっているふりかえりをさらに改善したい!」と思っていただけたら、とても嬉しいです。

*1:出典: アジャイルレトロスペクティブズ 強いチームを育てる「ふりかえり」の手引き https://www.amazon.co.jp/dp/4274066983

Elasticsearch 5.0.0のIngest Node用プラグインを書いた話

$
0
0

こんにちは、アプリケーション基盤チームの渡辺です。好きなライブラリはLombokです。

アプリケーション基盤チームでは、Necoプロジェクト(アーキテクチャ刷新プロジェクト)の一環として、 次世代の検索基盤を検討していて、その候補としてElasticsearchを調査しています。

調査をしている中で、Ingest Node用のプラグインを試作しました。 Ingest Nodeとは、Elasticsearch 5.0.0 (2016/7時点ではalpha4までmaven centralにpublishされている)で追加された機能で、 インデクシング時にドキュメントに変換処理を適用できるなんだかすごいやつです。Ingest Node自体の説明はElastic{ON}のスライドが参考になります。


せっかくプラグインの作り方を調べたので、公開して似たようなことをする人の参考になれば良いな、ということでこの記事を書いています。 内容としては、私のようなElasticsearch初心者の方が、初めてプラグイン開発をする際に参考にできる記事を目指します。

なお、この記事はElasticsearch5.0.0-alpha4の動作を元に書いています。 alphaなので今後変更が入る部分もあるかと思いますが、予めご了承ください。

背景

最初に軽く、なぜIngest Nodeを調べることになったのか、という話をします。

まず、次世代検索基盤の要件として、再インデクシングを簡単にしたい、というものがありました。

例えば、ドキュメントのフィールドの型を変えたり、ネストの構造を変えたり、Analyzerを変えたりするには、マッピングを変更してドキュメントを再インデクシングする必要があります。 検索がより良い結果を返すように、継続的にマッピングを改善するためには、再インデクシングは気軽に実施したいです。

再インデクシングの前後で、ドキュメントに変更がない場合(Analyzerの変更など)は、 Elasticsearch 2.3.3から追加されたReindex APIをそのまま実行すれば良いのですが、フィールドの型などが変わる場合はドキュメントの加工が必要になります。

そこで、Elasticsearch 5.0.0で追加されたIngest Nodeの出番です。 Ingest Nodeを使えば、組み込みの機能を組み合わせるだけでドキュメントの加工ができ、より複雑なことをしたい場合はプラグインを書くことで対応できます。

事前準備

事前準備として、Elasticsearchの公式サイトから、5.0.0-alpha4をダウンロードして、起動してください。 https://www.elastic.co/downloads/elasticsearch

curlでlocalhostの9200ポートにGETして起動を確認できます。

# request
$ curl localhost:9200# response
{
  "name" :"Smart Alec",
  "cluster_name" :"elasticsearch",
  "version" : {
    "number" :"5.0.0-alpha4",
    "build_hash" :"3f5b994",
    "build_date" :"2016-06-27T16:23:46.861Z",
    "build_snapshot" :false,
    "lucene_version" :"6.1.0"
  },
  "tagline" :"You Know, for Search"
}

Ingest Node

Ingest Nodeでの変換処理における重要な要素として、ProcessorPipelineがあります。 プラグインの開発に進む前に、まずは組み込みの機能を使って、これらの要素の理解を深めます。

Processor

実際の変換処理を行うのがProcessorです。組み込みでいくつか用意されています。 今回は、フィールドの値を大文字にするUppercase Processorを使ってみます。

Pipeline

インデクシングの際に変換を指定するには、Pipelineの定義が必要です。 Pipelineは複数のProcessorで構成され、各Processorによる変換が順番にドキュメントに適用されます。 Uppercase Processorを要素としてPipelineを定義してみます。

$ curl -XPUT localhost:9200/_ingest/pipeline/upper_sample -d '{"description": "upper processor sample","processors": [    {"uppercase": {"field": "foo"}}  ]}'

これで、ドキュメントのfooフィールドを大文字に変換するPipelineを定義できました。 URIのパスの末尾の「upper_sample」はこのPipelineのIDで、他のAPIからこのPipelineを指定する際に使用されます。

動作確認をするために、まずはSimulate APIを叩いてみます。

Simulate API

Simulate APIでは、Pipelineとドキュメントを指定して、変換の結果を確認することができます。 先ほど定義したupper_sample Pipelineに対して試してみましょう。

# request
$ curl -XPOST localhost:9200/_ingest/pipeline/upper_sample/_simulate -d '{"docs": [    {"_source": {"foo": "bar"}}  ]}'# response
{"docs":[
  {"doc":{
    "_id":"_id",
    "_type":"_type",
    "_index":"_index",
    "_source":{"foo":"BAR"},
    "_ingest":{"timestamp":"2016-07-01T05:55:38.108+0000"}
  }}
]}

ドキュメントのfooフィールドの値が、barからBARに変換されていることが確認できます。

Reindex API

PipelineはReindex APIにも適用できます。まずは、Reindex APIの対象となるインデックスを用意します。

# request
$ curl -XPOST localhost:9200/reindex_src/sample -d '{"foo": "bar baz"}'# response
{
  "_index":"reindex_src",
  "_type":"sample",
  "_id":"AVWlB8IxgIfAWOLZwcdI",
  "_version":1,
  "forced_refresh":false,
  "_shards":{"total":2,"successful":1,"failed":0},
  "created":true
}

reindex_srcをreindex_destに再インデクシングします。 destのpipelineにupper_sample Pipelineを指定します。

# request
$ curl -XPOST localhost:9200/_reindex -d '{"source": {"index": "reindex_src"  },"dest": {"index": "reindex_dest","pipeline": "upper_sample"  }}'# response
{
  "took":100,
  "timed_out":false,
  // ... 長いので省略
}

reindex_destのドキュメントを取得すると、fooフィールドの値が大文字に変換されていることが確認できます。

# request
$ curl localhost:9200/reindex_dest/_search

# respone
{
  "took":2,
  // ... 長いので省略
  "hits":[
    {
      "_index":"reindex_dest",
      "_type":"sample",
      "_id":"AVWgQkdgX1vV7c4s6MzR",
      "_score":1.0,
      "_source":{"foo":"BAR BAZ"}}
  ]
}

プラグインを作る

Ingest Nodeでの変換の仕方をざっくり理解したところで、本題であるプラグインの作成について説明します。

作成したプラグインをElasticsearchに追加することで、独自の変換ロジックを実装したProcessorを追加することができます。 プラグインを作成するには以下の3つが必要です。

  • Pluginクラスの実装
    NodeModuleにProcessorを登録する
  • Processorクラスの実装
    変換ロジックを実装する
  • plugin-descriptor.properties
    プラグインのクラス名の指定などを行う

これらの成果物をelasticsearchというディレクトリにまとめて、zip化したものがプラグインになります。 今回はJava8とgradleで開発します。作成するサンプルの最終的なソースコードはこちらのリポジトリにあります。
https://github.com/mwatanabe/ingest-plugin-sample

変換仕様

変換仕様は何でもよいのですが、単一の組み込みProcessorでは難しいものが良いと思うので、以下の仕様で作ります。

  • 変換対象となるフィールドをPipelineの定義時に指定できる
  • 特定の文字列を別の文字列で置き換える。それぞれの文字列はPipelineの定義時に指定できる
  • 変換した文字列の個数をtypo_countというフィールドに保存する

仕様のイメージが掴みづらい場合は、先にSimulate APIでの動作確認を見ていただくのが良いかと思います。

build.gradle

build.gradleは以下のようになり、gradle buildを実行すると成果物のzipが生成されます。 plugin-descriptor.propertiesはsrc/main/resources/plugin-metadataディレクトリに配置することを想定しています。

apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.elasticsearch:elasticsearch:5.0.0-alpha4'
}

task build(type: Zip, dependsOn: [':jar']) {
    from files(libsDir)
    from 'src/main/resources/plugin-metadata'
    into 'elasticsearch'
}

Processorクラスの実装

変換処理を行うクラスを実装します。

package sample;

import java.util.Map;

import org.elasticsearch.common.Strings;
import org.elasticsearch.ingest.AbstractProcessor;
import org.elasticsearch.ingest.AbstractProcessorFactory;
import org.elasticsearch.ingest.ConfigurationUtils;
import org.elasticsearch.ingest.IngestDocument;

publicclass TypoReplaceProcessor extends AbstractProcessor {
    publicstaticfinal String TYPE = "typo";

    privatefinal String field;
    privatefinal String target;
    privatefinal String replacement;

    public TypoReplaceProcessor(String tag, String field, String target, String replacement) {
        super(tag);
        this.field = field;
        this.target = target;
        this.replacement = replacement;
    }

    @Overridepublicvoid execute(IngestDocument document) throws Exception {
        String value = document.getFieldValue(field, String.class);
        int count = Strings.countOccurrencesOf(value, target);
        String replaced = value.replaceAll(target, replacement);

        document.setFieldValue(field, replaced);
        document.setFieldValue("typo_count", count);
    }

    @Overridepublic String getType() {
        return TYPE;
    }

    publicstaticclass Factory extends AbstractProcessorFactory<TypoReplaceProcessor> {
        @Overridepublic TypoReplaceProcessor doCreate(String processorTag, Map<String, Object> config) throws Exception {
            String field = ConfigurationUtils.readStringProperty(TYPE, processorTag, config, "field");
            String target = ConfigurationUtils.readStringProperty(TYPE, processorTag, config, "target");
            String replacement = ConfigurationUtils.readStringProperty(TYPE, processorTag, config, "replacement");

            returnnew TypoReplaceProcessor(processorTag, field, target, replacement);
        }
    }
}

コードの細かい話は置いておいて、ProcessorとFactoryについて説明すると、以下のようになります。

TypoReplaceProcessor

  • AbstractProcessorを継承してexeuteメソッドとgetTypeメソッド実装する。
  • executeメソッドの引数として変換対象のドキュメント(IngestDocument)が渡ってくる。つまりはここが変換ロジックを書くメソッド。
  • getTypeメソッドはProcessorのtypeを返す。これはPipelineの定義において、Processorを指定するための識別子になる。

Factory

  • Processorのインスタンスを生成する。
  • FactoryはPipeline定義時のProcessorへのconfigを参照できる。

Pluginクラスの実装

PluginではNodeModuleにProcessorのFactoryを登録します。 Pluginクラスを継承して、onModuleという名前のpublicメソッドを定義すると、Elasticsearchがreflectionで良い感じに処理してくれるようです。

package sample;

import org.elasticsearch.node.NodeModule;
import org.elasticsearch.plugins.Plugin;

publicclass SamplePlugin extends Plugin {

    publicvoid onModule(final NodeModule nodeModule) {
        nodeModule.registerProcessor(TypoReplaceProcessor.TYPE, (registry) ->new TypoReplaceProcessor.Factory());
    }
}

plugin-descriptor.properties

プラグインをelasticsearchに取り込むために必要な設定ファイルを書きます。読み込みたいPluginのクラス名を指定しています。

description=sample plugin for Elasticsearchversion=1.0.0name=sampleclassname=sample.SamplePluginjava.version=1.8elasticsearch.version=5.0.0-alpha4

ビルド

gradle buildを実行するとbuild/distributionsディレクトリにプラグインのzipが作成されます。

動作確認

まずは作成したプラグインをElasticsearchにインストールして再起動します。

# プラグインのインストール
bin/elasticsearch-plugin install /<path>/<to>/ingest-plugin-sample.zip

# Elasticsearchの起動
bin/elasticsearch

これでTypoReplaceProcessorが使えるようになるはずです。 getTypeメソッドは"typo"を返すようにしたので、Processorの識別子は"typo"です。 cybouzuをcybozuに修正するProcessorを定義して、Simulate APIで確認します。

# request
curl -XPOST localhost:9200/_ingest/pipeline/_simulate -d '{"pipeline": {"description": "cybozu typo pipeline","processors": [{"typo": {"field": "foo","target": "cybouzu","replacement": "cybozu"}      }    ]  },"docs": [{"_source": {"foo": "cybouzu cybouzu cybouzu"}  }]}'# response
{"docs":[
  {"doc":
    {
      "_id":"_id",
      "_index":"_index",
      "_type":"_type",
      "_source":{"typo_count":3,"foo":"cybozu cybozu cybozu"},
      "_ingest":{"timestamp":"2016-07-01T02:33:01.130+0000"}
    }
  }
]}

responseの_sourceフィールドを見ると、3つのtypoが修正されてcount用のフィールドも追加されているのが確認できました。 これで、プラグインの開発、インストール、動作確認は完了です。

デバッグ

動作確認はできましたが、デバッグのために変換対象のドキュメント(IngestDocument)の中身を見たいこともあると思います。 Elasticsearchの起動時にjdwp用のデバッグポートを指定すれば、IntelliJなどのIDEからattachできます。(よくあるリモートデバッグですね。)

ES_JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" bin/elasticsearch

小ネタ

Reindex APIは内部的にはBulkRequestを利用します。 そうなると、Ingest Nodeが変換対象のドキュメントを複数受け取った場合、変換処理は並列に行われるのか、という点が気になりました。

そこで、Processor内でブロッキング処理(Thread.sleep)を書いて、Reindex APIにかかる時間を計測してみたところ、 ドキュメント数×sleep時間だけ処理時間が増えることが確認できました。

thread_pool APIで再インデクシング中のスレッド数を確認したところ、bulk.activeが1を示していました。 ということで、BulkRequestのドキュメントの変換処理はシングルスレッドで行われているようなので、 少なくともalpha4ではProcessor内でブロッキング処理を書くには注意が必要そうです。(alphaなのでGAになる時には変わってるかも。)

BulkRequestをPipelineが処理するコードはこの辺りでした。

おわりに

ElasticsearchのIngest Nodeについて、簡単な動作確認とプラグインの書き方の説明をしました。 2つのJavaクラスを実装して、設定ファイルを書いて、ビルドしてzipにまとめればプラグインを作成できます。

Ingest Nodeはalpha段階である5.0.0の機能なので、今後も色々変更はあるかと思いますが、 再インデクシングという文脈においてとても有用に感じているので、今後も情報を追っていきたいと思います。

少し長くなりましたが、この記事が読んでくださった方々のプラグイン開発の参考になれば幸いです。

We are hiring!!!

サイボウズでは Elasticsearch 大好きなエンジニアを募集しています!
キャリア採用 募集要項/イベント | サイボウズ株式会社

参考

アーティファクトの管理について、あるいは go-apt-cacher / go-apt-mirror の紹介

$
0
0

誰も本名で呼んでくれないので社内でもハンドルで通そうと ymmt と呼ぶよう社内に本部長命令を出したお願いしている @ymmt2005です。

今回はサイボウズ社内でどのようにアーティファクトを管理しているか紹介します。アーティファクトというのはソフトウェア開発では成果物のことを指します。成果物もいろいろありますが、もっぱら興味の対象になるのはデータセンターにデプロイしたりお客様に配布したりする JAR や実行ファイルやアーカイブです。

忙しい人のために 4 行でまとめておきます。

  • JFrog Artifactory をアーティファクト管理に利用している
  • Debian パッケージ(deb)を作成・管理する仕組みがある
  • 各地のデータセンターで利用するため go-apt-cacher / go-apt-mirrorを作った
  • 既存の apt-mirror や apt-cacher-ng の問題を解消しているので試してみてね

JFrog Artifactory とは

JFrogArtifactory (以下 Artifactory)は JFrog 社が提供しているアーティファクト管理用の Web アプリケーションです。無償のオープンソース版では Mavenリポジトリの機能だけ使えますが、商用版では Debian/Ubuntu で使われる APT, Node.js で使われる NPM, Python で使われる PyPIなど多数のリポジトリがサポートされています。

サイボウズでは Artifactory 商用版の一番安いエディションを一つ導入しています。一番安いものでもリポジトリの機能数に違いはなく、高いエディションは冗長化が主な差異なので、自社技術である冗長化ストレージ square自動障害回復システム 月読を利用して節約というわけです。

Debian/Ubuntu パッケージの利用

サイボウズ社内では非常に多くの Ubuntu サーバーが動作しています。VM・実機合わせると数千台というところでしょうか。Debian/Ubuntu では deb というファイル形式で各種プログラムがパッケージングされています。インストール時やセキュリティパッチなどの際にはこの数千台のサーバーが多数の deb ファイルをダウンロードすることになります。

deb ファイルのリポジトリは APTというツールがサポートする形式で管理されますが、Artifactory を利用すると独自の APT リポジトリを構築して、REST API で JenkinsDroneのような CI サービスから自動的にアップロードすることが簡単に実現できます。CI から Artifactory に自動デプロイすると継続的デリバリーの完成ってなわけです(そこから先の道程もまだありますが)。

サイボウズでは自社製のプログラムを簡易に deb パッケージ化する仕組みを考案して、Artifactory に自動アップロードするようにしています。仕組みについては「社内利用のための deb パッケージング入門」をご覧ください。

Artifactory は Ubuntu の公式 APT リポジトリをキャッシュする仕組みはあるのですが、ミラーする仕組みは持っていません。当社では定期的にセキュリティ更新を適用するようにしているため、キャッシュでは都合が悪いので必要なタイミングで別のツールを使ってミラーするようにしています。

キャッシュとミラー

Artifactory は 1 台しかないため、地理的に離れた各拠点・データセンターではそれぞれのリージョン内で deb ファイルをキャッシュしたりミラーする必要があります。さもないと拠点間のネットワーク帯域の浪費や、インストールやパッチ適用に長時間を要するということになるためです。

頻繁に更新があるリポジトリはキャッシュ、定期的に更新があるリポジトリはミラーするのが適切です。前者にあたるのが社内アーティファクト用 APT リポジトリ、後者にあたるのが Ubuntu 公式リポジトリです。

キャッシュは Squidのような通常の HTTP プロキシも利用できないことはないのですが、APT のインデックスファイルや各種ファイルの更新は独自の仕組みとなっているため、HTTP のキャッシュの仕組みは相性が良くないです。そこで、APT 専用のキャッシュサーバーである apt-cacher-ngを当初は利用していました。

ミラーについては必要なアーキテクチャのサブセットのみミラーできる apt-mirrorを利用していました。

go-apt-cacher / go-apt-mirror

ようやく本題ですが、apt-cacher-ng と apt-mirror にはそれぞれ解消が困難な課題があったため、問題を解消した自社製の APT 専用キャッシュ/ミラーツール go-apt-cacher / go-apt-mirrorを作成しました。

apt-cacher-ng の問題は、HTTPS な上流サーバーに多数の同時接続をするとクラッシュすることでした。デバッグを試みたのですが、内部構造が複雑なため時間がかかり諦めました。自作なら数日で作れそうと踏んで実際に作ったのが go-apt-cacher となります。

go-apt-cacher の特長は以下の通りです。

  • Release や Packages を認識しチェックサム情報を抽出
  • チェックサムに合致しないファイルは破棄してキャッシュしない
  • Release は自動定期更新
  • チェックサムが更新されたら該当ファイルはキャッシュから破棄
  • LRU なディスクキャッシュ
  • 千台単位の同時クライアント接続をサポート
  • 高速(キャッシュ済みファイルなら 1 ms 以下で処理)

apt-mirror の問題はミラー結果が壊れていることがあるというものです。他にもポート番号が付いた URL を適切に扱えない等の問題があるのですが、開発が活発でなく提案されている修正がマージされず放置されている状況でした。そこで go-apt-cacher の成果を流用してこちらも独自に開発したのが go-apt-mirror となります。

go-apt-mirror の特長は以下の通りです。

  • 必要なアーキテクチャ等を指定して部分的なミラーを構築可能
  • rsync より高速な更新処理
  • 不完全なミラーは決して作らない
  • ミラーの更新がアトミックで、更新途中のミラーはクライアントに見せない

詳しくは先日の東京 Debian 勉強会で同僚の湯谷が発表した資料をご覧ください。

まとめ

サイボウズ社内のアーティファクト管理の仕組みを紹介しました。

go-apt-cacher / go-apt-mirror はリリースしたばかりですが、すでにサイボウズ社内の apt-cacher-ng / apt-mirror は置き換え済みです。同様の課題にお悩みのかたは是非お試しください。

ソースは https://github.com/cybozu-go/aptutilで公開しています。

開発したシステムの利用者に弟子入りしてみました。

$
0
0

こんにちは。Sales System チームの小林俊久です。最近、少しお腹が出てきました。

Sales System チームでは、お客様がサイボウズのクラウド製品をお試し運用したり、 購入や管理するためのサイトcybozu.com Storeや、 サイボウズの全売上を管理している販売管理システム等を開発しています。

今回は、Sales System チームで行った 「弟子入り」についてご紹介したいと思います。

その要望、真の要望?

開発者のみなさんは、実装したシステムをリリースして利用者からの反応がイマイチだったことはありませんか? または、しばらく経つと使われなくなったことなどありませんか?

私たち開発者は、要望をもとに設計書を作成し、システムを実装します。 要望を満たすために必要な画面や入力項目を用意してめでたく完成。何も文句はないはずです。

にもかかわらず、前述のようなことが起こります。なぜでしょう? 利用者でないと分からない悩みや問題があるのでしょうか?

きっかけ

販売管理システムは、サイボウズ社内の セールスデスク部、マーケティング部、営業部 など多くの社員が利用しています。 しかし、私たち開発の人間が利用する機会はあまりありません。 利用していないのでどのように使われているかイメージすることが難しく、要望として上がってきた要件が何故必要なのか、 どういう状況で、どうやって使われるのかがわからないという問題がありました。 チーム内で問題解決に向けて議論した結果、でてきた案の1つ 「弟子入り」を実践してみることになりました。

弟子入りとは

販売管理システムの メイン利用者を「師匠」として弟子入りし、業務体験を通じて利用者の言葉にならない真の要望を探るという手法です。 この手法は、サイボウズ社内で行っているデザイナーワークショップに講師としてお招きした樽本氏から教わりました。 デザイナーワークショップはサイボウズ社員に限らず、内容に興味のある方は自由に参加いただいています。

弟子入り準備

弟子入りするにあたって、以下の目標を設定しました。

  • 業務体験を通して業務知識を高める
  • 問題点や改善できそうな箇所がないか発見する
  • 利用者と仲良くなる

「業務知識を高める」は、問題と感じていた点なので問答無用に目標です。

つぎに、体験を通して操作につまづいたり、入力しずらいと感じる部分があるかもしれません。 既に業務に慣れている人にとっては あたり前となって要望にも挙がって来なかったりするものです。 その点、弟子である私は新人も同然です。その視点から「問題点がないか発見する」ことも目標としました。

さいごに、「利用者と仲良くなる」です。開発部が他部署と仕事以外でコミュニケーションを取る機会は少なく、 同じ会社の人間であっても関係が希薄になりがちです。今回の弟子入りを機にお互いのひととなりを分かり合うことで 今後の要望や意見交換等がやり易くなるのではと考えました。 他部署との交流を考えた施策は、ほかにも社内部活動や誕生会「社内コミュニケーション活性化のための施策」を参照等もあります。今回はその一環として目標に設定しました。

ちなみに私はボードゲーム部の部長をしています。カタン、カルカソンヌ、トーレスやドミニオンなど、海外アナログゲームをメインに活動しています。 他社交流戦などご希望する方がいらっしゃいましたらご連絡お待ちしております。精鋭がそろってますよ!

目標を建てたところで、利用部門に相談です。相談先は利用頻度が一番高い セールスデスク部 にしました。 セールスデスクは受注/請求/代金回収業務を行う部署で、販売管理システムは必要不可欠です。

  • こちらのやりたいこと(目標)
  • 受け入れ時期調整
  • 弟子入りカリキュラムの作成依頼

などなど。他には業務に必要な権限付与の承認を上長に申請しています。 お互いスケジュールの都合もあり、業務体験と業務観察をそれぞれ 1 日、合計 2 日間の弟子入りとなりました。

弟子入り

1 日目の業務体験では、セールスデスクマスター とも言うべき方に 「師匠」になって頂き、 手取り足取り教えてもらいながら、日常的に行っている以下の業務を体験しました。

  • 受注入力(主にFAXでくる発注書を販売管理システムへ入力)
  • 受注から納品までの手続き処理
  • 入金確認処理
  • 未払い処理

体験中は、どうして付箋を貼るの?どうしてこんなに確認する画面が多いの?など師匠に質問責めです。

2 日目の業務観察では、師匠の後ろに張り付き、日常業務を行ってもらいました。 その過程で、作業中に心の中で喋っていることを口に出してもらいながら、 なぜ今そう思ったのか?なぜその入力順なのか?など、気になった部分を 逐一質問して師匠の今の行動が何に起因しているのかメモを取っていきました。

以上、短い期間でしたが見つけた問題点や改善点は全部で 16 件ありました。 簡単なものから開発業務としてプロジェクトに組み入れる必要があるものまで大小様々です。 最初の弟子入りとしては上出来ではないでしょうか。

f:id:cybozuinsideout:20160802094858p:plain

見つけた問題

特に印象に残った問題を紹介します。

受注入力にも何種類かあり、インターネットにある注文画面からの入力もあれば、CSV一括インポートによる取込みや、FAX での受注もあります。 その中の FAX による発注書を販売管理システムへ入力する業務を行っていた時です。 発注入力はよくある入力画面です。開発時は発注情報を入力するんだなぁ程度にしか考えていませんでした。 発注書を見ながら入力するのですが、何と 発注書のフォーマットがいくつもあるのです! 私は混乱しました。

私 「発注書は 1 種類じゃないのですか?」

師匠 「パートナー契約を結んでる会社毎に発注書は異なります」

私は今までサイボウズが用意した発注書で注文が来るものだと誤解していました。 発注書は各社各様、フォーマットが全然違います。しかも 1 社で 1 つのフォーマットとは限らず、合わせると数十種類あります。 初見では発注書のどの項目をどこに入力したらいいのか全然わかりません。

しかし、そこは師匠。各社の発注書毎にどの項目をどこに入力したらいいのか Wiki にまとめていました。 師匠に感謝しながら Wiki と発注書と入力画面を見比べながら黙々と入力・・・

  1. Wiki から 該当発注書を検索
  2. 検索した発注書マニュアルを参照して、入力画面の項目との紐づけを確認
  3. 発注書の該当項目を入力画面の適切な項目に入力

うーん。非効率です。Wiki をチェックするのが非常に面倒ですが、暗記していない限りは必要です。 さらに暗記したとしても覚え違いや勘違いもあるでしょう。 しかし、現場ではこれが あたり前になっているのか、不便だの改善要望などが開発部にあがってくることはなかったのです。

改善

「発注書毎に入力画面のどこに何を入力したらいいか、入力画面の項目の背景色を変えたり、案内があったら楽なのになぁ」

と感じましたが、これを保守性や汎用性を考慮ながらシステム化するとなるとコストがかかるだろう事は目に見えてます。 とはいっても、入力の不便さは 身をもって体験しているので何とかしたいところですが・・・やりたいことは受注入力画面の入力補助です。要件を分解すると

  • 入力箇所を目立たせる
  • 何を入力したらいいか案内する

ふむふむ。見た目の操作だけなので、割り切ってブラウザの拡張機能で実装しよう!

販売管理システムとは独立した実装にすれば、保守性や汎用性は考えずに済みます。 さらに、プロジェクトではないので品質管理部のチェックや、システムリリース時期の調整も必要ありません。

合間の時間を使ってさっそく作ってみました。 実装は単純で、発注書毎に受注入力画面内の必要な項目の HTML 要素をもってきて、背景色を変えてプレースホルダーに案内を書くだけです。 簡単だったので調子にのってオプション機能を追加して、背景色に好きな色を選択できるようにしました。実装にかかった時間は都合 2 日程度といったところでしょうか。 次は案内文も編集できるようにしてもいいかもしれません。

効果

早速、セールスデスク部 のブラウザを機能拡張してみました。直後の反応がこちらです。

f:id:cybozuinsideout:20160802094859p:plain

機能拡張後、しばらくして効果をインタビューしてみました。

「1 件あたり 30 秒前後短縮した(多い日は 100 件超える受注がある)。短縮もいいけど、なにより 精神的に楽になったのが大きい。 今まではマニュアル(Wiki)見ながら本当にこれでいいよねって確認しながら入力していたが、注文書ごとに入力項目や確認項目が色付けされて分かるようになったので、 自信をもって登録&確認できるようになった!」とのこと。

後日、報酬としてドリップ式のインスタントコーヒーを頂きました(^_^)

まとめ

「エンジニアはもっと利用者の立場に立った目線を持とう!」いま自分が思っている以上にです。 私も設計時は利用者を意識していたつもりでしたが、弟子入り体験を通してそれは上辺に過ぎないのだなと痛感しました。 利用者の声だけでなく、その声があがるに至った 過程を探求することで気づかなかった問題(真の要望?)が見えてきました。

  • 利用者と一緒に働くこと
  • 業務を教えてもらいながら疑問に思ったことは質問し、理由を理解すること
  • 加えて仲良くなり話しやすい関係を築くと尚良し

これが非常に有効だと感じました。もちろん「弟子入り」が解のすべてではありませんが、今後も継続して実践していこうと考えています。

今回、このような貴重な機会を与えてくれた Sales System チームと、忙しいなか快く受け入れてくれた セールスデスク部 の皆さんに感謝します。 ありがとうございました!

おわりに

Sales System チームではいっしょに働くメンバーを募集しています。 今回紹介したように、固定されたルーティンに縛られず、問題に感じた時はチーム皆で考え、施策を出し、成果があるかとりあえずやってみる。チャレンジの日々を送っています。

それでは次回はボードゲーム交流会?でお会いしましょう!

Elasticsearch 5.0.0で再インデクシングの高速化を探求する

$
0
0

こんにちは、アプリケーション基盤チームの渡辺です。IntelliJのコード補完はCtrl+;にバインドしています。

アプリケーション基盤チームでは、Necoプロジェクト(アーキテクチャ刷新プロジェクト)の一環として、 次世代の検索基盤を検討していて、その候補としてElasticsearchを調査しています。

先月の記事で再インデクシングと絡めてingest pluginの話をして、 びっくりするぐらい需要が低く、自分のテーマ選択のセンスのなさを痛感したのですが、 こじらせた感じで今日も再インデクシングの話をしたいと思います。

想定読者は、Elasticsearchにある程度慣れている方として、用語やAPI(インデックス, シャード, ScrollAPI, BulkAPIなど)の説明は最小限にします。

利用したElasticsearchのバージョンは5.0.0-alpha4です。2.X系だと無い機能も使っているので、 すぐに採用できる話ではないかもしれませんが、5.0.0が正式リリースされた後のアップグレードへのモチベーションの一つとなれば幸いです。

TL;DR

  • ReindexAPIの代わりにScrollAPIとBulkAPIを使ったツールを作成して再インデクシングの高速化を探求した
  • ツールをマルチスレッド化してBulkAPIを並列で実行した
  • Sliced Scrollを使ってScrollAPIを並列で実行した
  • preferenceの指定でローカルシャードのみを再インデクシングした
  • NVMeを試してみた
  • ツールでボトルネックになりがちなのはJSONの変換とHTTPの圧縮
  • リソース使いすぎには要注意なので節度を持って取り組みましょう

何をどう速くするのか

今回は再インデクシングを速くすることがテーマです。再インデクシングにも色々(DBの全件スキャンが必要な場合など)あるのですが、今回は以下の条件を前提に高速化を探求していきます。

  • Elasticsearchのクラスタは一つ
  • クラスタ内の特定のインデックスからドキュメントを抜き出して、別のインデックスにそのドキュメントをインデクシングする
  • 抜き出したドキュメントの加工はしない

つまり、Elasticsearch上の特定のインデックスのドキュメントを、まるっとそのまま別のインデックスにコピーするような場合です。 ユースケースとしては、アナライザの変更やシャード数の変更などが考えられます。

この前提を基に、Reindex APIによる再インデクシングから計測を始めます。 それを、Scroll APIBulk APIを使う自作ツール(以降、ReindexToolと呼ぶ)に変えて、 各APIの機能を駆使して並列度を高めたり、お金の力を使ったりして高速化していきます。

elasticsearch.ymlの設定やヒープのチューニング等はまだ探求中のため、この記事では言及しません。 その辺りのパラメータチューニングに関しては、ある程度の情報がまとまったら別途記事にするかもしれません。

クラスタ構成

マシンスペック

以下のスペックを持つ8台のマシン(実際には異なる物理マシン上でlxcのコンテナとしてElasticsearchを動かしている)でElasticsearchクラスタを作成しました。 Elasticsearchのバージョンは5.0.0-alpha4です。

要素性能備考
CPU 2.4GHz,12core,24threads
Memory 188GB Elasticsearchのxmxは8GB
HDD書き込み 800MB/s ddでsequential writeを簡易計測
HDD読み込み 265MB/s hdparmで簡易計測
ネットワーク 10Gbps iperfで計測
OS Ubuntu 14.04

HDDに関してはRAID等が組み合わさった結果、シーケンシャルライトはそこそこ高速に動作しているように見えますが、 実際はランダムライトだったり、リードとライトが同時に実行されたりして、こんな数字はでないので、参考程度に考えてください。

ReindexToolとElasticsearchクラスタの基本構成

ReindexToolを使った場合の再インデクシングの最も単純な構成は以下の図のようになります。

f:id:cybozuinsideout:20160816171730p:plain

基本的には、Elasticsearchクラスタ外のマシン上で、単一プロセスとしてReindexToolを実行し、以下の手順により、再インデクシングを実施します。

  1. 事前に、src-indexと同じマッピングでdest-indexを作成しておく
  2. ScrollAPIを使ってクラスタからsrc-indexのドキュメントを取得する
  3. 取得したドキュメントをBulkAPIでdest-indexに追加する
  4. 2,3の処理をScrollAPIが全てのドキュメントを取得するまで続ける

これは出発地点の基本的な構成ではありますが、ScrollAPIとBulkAPIで再インデクシングをする、という仕組みは最後まで変わりません。

ReindexToolには、「そこそこ速く動いて並行処理も簡単そう」という適当な理由で、Golangを採用しました。 内容的には公開して困るものでもないので、ある程度需要がありそうなら、体裁を整えたうえでそのうち公開するかもしれません。

対象インデックス

再インデクシングの対象となるインデックスについて、保持するデータ(ドキュメント)とマッピングについて説明します。 このインデックスをいかに速くコピーするかが、今回の探求の目的です。

データ

使用するデータは最新の日本語Wikipediaの全記事とします。 https://dumps.wikimedia.org/jawiki/latest/からダウンロードでき、stream2esを使うことで全件インポートすることができます。 ドキュメントの総数は200万件強です。

./stream2es wiki --source /<path>/<to>/jawiki-latest-pages-articles.xml --log debug --target http://localhost:9200/src
# targetのsrc indexは次節のmappingで予め作成しておく

マッピング

wikipediaのドキュメントをインポートするインデックスには、以下の設定のマッピングを使用します。

  • _all: disable
  • レプリカ数: 0
  • シャード数: 16
  • refresh_interval: -1
  • index.merge.scheduler.max_thread_count: 1

_allは今回は使わないので無効にしています。レプリカ数を0にするのは大規模なインデクシングにおける定石です。 シャード数は特に根拠はないのですが、マシンの台数よりは多くしたいのでマシン台数×2の16シャードにしています。 index.refresh_intervalとindex.merge.scheduler.max_thread_countは公式ドキュメントのindexing-performanceのtipsに従って追加しました。

{"settings": {"number_of_replicas": 0,
    "number_of_shards": 16,
    "index.refresh_interval": "-1",
    "index.merge.scheduler.max_thread_count" : 1},
  "mappings": {"_default_": {"_all": {"enabled": false}}}}

インデックスの規模

インポートが完了した後のインデックスの状態をcat indices APIを使って確認します。

$ curl 'es-watanabe-1:9200/_cat/indices?v'
health status index   pri rep docs.count docs.deleted store.size pri.store.size
green  open   src     160205071708.2gb          8.2gb

約200万ドキュメントで合計のサイズは8.2GBほどになりました。

測定方法

基本的には、再インデクシング完了までの実行時間を計測して比較します。この実行時間を短くすることが今回の目的です。

性能測定ということで、いい感じの性能のグラフとかを出せればよかったんですが、それをやるにはSAN値が足りなかったので、 以下のコマンドの結果を1秒毎にターミナルに流して、目視で性能を確認しながら、ボトルネックっぽいところを探して改善していきました。原始的ですね。

  • dstat
$ dstat -tclmdrn
----system---- ----total-cpu-usage---- ---load-avg--- ------memory-usage----- -dsk/total- --io/total- -net/total-
     time|usr sys idl wai hiq siq| 1m   5m  15m | used  buff  cach  free|read  writ|read  writ| recv  send
12-08 01:55:31|1099000|0.090.210.36|38.0G 1139M 24.7G  125G|1797B  279k|0.0523.9|00
12-08 01:55:32|00100000|0.090.210.36|38.0G 1139M 24.7G  125G|0   316k|076.0|  37k   22k
12-08 01:55:33|00100000|0.090.210.36|38.0G 1139M 24.7G  125G|0   308k|072.0|  37k   21k
$ whiletrue; do curl 'es-watanabe-1:9200/_cat/thread_pool?v&h=id,host,bt,ba,bq,bs,bc,st,sa,sq,ss,sc'; sleep 1;done
id   host          bt    ba bq bs    bc st    sa sq ss    sc
vM4E es-watanabe-1 fixed  0024288 fixed  0037584
btge es-watanabe-2 fixed  002413524 fixed  003724143
zPof es-watanabe-3 fixed  002413497 fixed  003724115
3y0A es-watanabe-4 fixed  002413609 fixed  003724226
IyM- es-watanabe-5 fixed  002412979 fixed  003724616
iXmJ es-watanabe-6 fixed  002413538 fixed  003725176
iBLd es-watanabe-7 fixed  002413388 fixed  003725029
cKjt es-watanabe-8 fixed  002413539 fixed  003725173

dstatでリソース使用率を見てシステム全体のボトルネックを探しつつ、 bulk APIとsearch APIのthread_poolでどの程度並列に処理が実行されているかを確認できます。

前準備の説明が長くなりましたが、次節より実際に再インデクシングを始めていきます。

Reindex API

まずは、標準のReindexAPIを使って再インデクシングします。ReindexAPIでは内部でScrollAPIとBulkAPIを実行していて、 ScrollAPIのSize(以降、ScrollSizeと呼ぶ)を指定できるので、ScrollSizeを幾つか変更して実行時間を計測しました。

$ curl -X POST 'es-watanabe-1:9200/_reindex' -d '{"source":{"index":"src", "size": 1000},"dest"  :{"index":"dest"}}'

結果は以下のとおりです。ScrollSizeにより変動はあるものの、最速で大体250秒ぐらいです。

ScrollSize 実行時間
1000 353秒
10000 297秒
100000 261秒
300000 248秒
500000 257秒

dstatを眺めていても、CPU Usageは数%、ディスク書き込みも数MB/sで、idleの時間も見受けられたため、 処理の並列度を高めてリソース使用率を向上させれば、まだまだ速くなる余地はありそうです。

ReindexToolでScrollAPIとBulkAPIを別スレッドで動かす

ReindexAPIは内部的にScrollAPIとBulkAPIを使っていると書きましたが、より詳細には、 ScrollAPIで取得したドキュメントをBulkAPIでインデクシングして、 それが完了したらScrollAPIの続きのドキュメントを取得してBulkAPIで、、、というように、逐次処理を行っています。

タスクの性質的に、ScrollAPIの処理とBulkAPIの処理は別々に進行しても問題ないはずなので、 ScrollAPIとBulkAPIを別のスレッドで実行するようにすれば、お互いの待ち時間が減少して速くなりそうです。

f:id:cybozuinsideout:20160816171717p:plain

そこで、ReindexToolにドキュメントのキューを導入し、ScrollAPIとBulkAPIを別スレッドで実行するようにしました。

f:id:cybozuinsideout:20160816171740p:plain

この構成でScrollSizeを変更した場合の実行時間は以下のようになりました。

ScrollSize 実行時間
5000 409秒
10000 397秒
20000 378秒
50000 399秒

残念なことに、ReindexAPIを用いた場合よりも遅くなってしまいました。 これは、直列部分を並列化した効果よりも、今までElasticsearchクラスタ内部で完了していた処理に対して ReindexToolを導入したことによるオーバヘッドの方が大きいためと考えられます。

しかし、自作のReindexToolを作成したことにより、ScrollAPIとBulkAPIを別々に最適化できるようにはなりました。 次は、これらのAPIにさらなる並列性を導入して、高速化を目指します。

Bulk APIを並列化する

BulkAPIのインデクシング処理は、シャード毎に分割されて並列で実行されます。 つまり、各ノードのBulkAPI用のスレッドが、スレッドプールからノード内のシャード数分払いだされます。 今回のクラスタにおいて、各ノードのBulkAPI用のスレッドプールのサイズは24でした。(cat thread_pool APIのbsの値)

$ curl 'es-watanabe-1:9200/_cat/thread_pool?v&h=id,host,bt,ba,bq,bs,bc,st,sa,sq,ss,sc'
id   host          bt    ba bq bs    bc st    sa sq ss    sc
vM4E es-watanabe-1 fixed  0024288 fixed  0037584

インデックスのシャード数が16で、ノード数が8であることを考えると、ReindexToolがBulkAPIをシングルスレッドで実行している場合、 各ノードのBulkAPI用のアクティブなスレッド(cat thread_pool APIのbaの値)は、たかだか2になるぐらいです。 bsが24なので、少なくともあと12倍はいけるはず、ということでReindexToolのBulkAPIをマルチスレッドで実行するように修正して、並列度を高めます。

また、ScrollAPIで取得したドキュメントをキューに追加する際の単位(BulkSize)も変更できるようにしました。 例えば、ScrollAPIで10000ドキュメントを取得したとして、BulkSizeを1000とすると、 1000ドキュメント毎の塊を10個、キューにエントリとして追加します。

ReindexToolのBulkAPIを実行するスレッド数(以降Bulk並列度と呼ぶ)を4とした場合、 4つのスレッドが各々、キューから1000ドキュメントの塊を取得し、BulkAPIを実行します。

f:id:cybozuinsideout:20160816171802p:plain

Bulk並列度,BulkSize,ScrollSizeを幾つか変化させて、再インデクシングの実行時間を計測しました。結果は以下のようになります。

Bulk並列度 BulkSize ScrollSize 実行時間
12 1000 1000 86秒
12 2000 2000 84秒
12 2000 5000 92秒
24 1000 1000 86秒
24 2000 2000 83秒
24 2000 5000 92秒

ReindexAPIが250秒程度だったことを考えると、大分速くなりました。 dstatを見ても、ScrollAPIとBulkAPIを受け付けるes-watanabe-1では、30%程度のCPU使用率で、 それ以外のノードも10%程度のCPU使用率なので、1スレッドに比べればリソースを消費できるようになってきました。

# dstat# es-watanabe-1
----system---- ----total-cpu-usage---- ---load-avg--- ------memory-usage----- -dsk/total- --io/total- -net/total-
     time|usr sys idl wai hiq siq| 1m   5m  15m | used  buff  cach  free|read  writ|read  writ| recv  send
15-08 10:03:12|28170001|3.584.393.80|66.0G 1123M 87.8G 33.7G|0    17M|0614| 191M  134M
15-08 10:03:13|30168001|3.584.393.80|66.0G 1123M 87.8G 33.7G|0    19M|0612| 172M  119M
15-08 10:03:14|32166001|3.864.443.82|66.0G 1123M 87.9G 33.6G|0    53M|0778| 171M  128M

# other node
----system---- ----total-cpu-usage---- ---load-avg--- ------memory-usage----- -dsk/total- --io/total- -net/total-
     time|usr sys idl wai hiq siq| 1m   5m  15m | used  buff  cach  free|read  writ|read  writ| recv  send
15-08 10:03:00|10189000|0.721.311.20|70.4G  896M 84.5G 32.8G|0    34M|0571|7057k   16M
15-08 10:03:01|10188000|1.141.381.23|70.4G  896M 84.5G 32.8G|0    24M|0671|5870k   10M
15-08 10:03:02|7192000|1.141.381.23|70.4G  896M 84.5G 32.7G|0    24M|0610|4788k   12M

cat thread_pool APIの出力結果を見ると、Bulk並列度が24の場合も、baの値は10に届かない程度なので、 まだまだElasticsearchの並列処理能力は余力がありそうです。

# cat thread_pool
id   host          bt    ba bq bs    bc st    sa sq ss    sc
vM4E es-watanabe-1 fixed  302430930 fixed  003739803
btge es-watanabe-2 fixed  002444118 fixed  003763329
zPof es-watanabe-3 fixed  102444135 fixed  003763326
3y0A es-watanabe-4 fixed  102444254 fixed  003763444
IyM- es-watanabe-5 fixed  302443603 fixed  003763832
iXmJ es-watanabe-6 fixed  002444175 fixed  003764380
iBLd es-watanabe-7 fixed  302444051 fixed  003764256
cKjt es-watanabe-8 fixed  602444186 fixed  003764391

Scroll APIを並列化する

ここまでくると、ScrollAPIも並列に実行して、ドキュメントの取得も高速化したくなるのが人情というものです。 しかし、通常のScrollAPIは、全ドキュメントのスナップショットをとり、そのスナップショットを先頭からScrollSize毎に逐次的に取得する手段を提供するだけです。(RDBのカーソルのようなもの)

例えば、全ドキュメント数が6000でScrollSizeが1000の場合は、スナップショットは6つに分割され、先頭から順に1000ドキュメントずつ取得することができます。(下図の1,2,3,4,5,6の順で取得する)

f:id:cybozuinsideout:20160816171859p:plain

スナップショットの先頭から順番に取得していかなければならないため、このままでは並列化できません。 そこで、Elasticsearch5.0.0から追加されたSliced Scrollの出番になります。

Sliced Scrollでは、ドキュメント全体のスナップショットを指定した数のSliceに分割し、個々のSliceを別々のScrollとして利用することができます。 例えば、Slice数を2とした場合は、下図のように2つのScrollに分割され、それぞれに0,1という識別子が与えられます。

f:id:cybozuinsideout:20160816171830p:plain

Sliced Scrollを利用するためには、ScrollAPIのパラメータとしてsliceを指定します。 idが各Sliceの識別子でmaxは分割数です。例えば、ScrollAPIを2スレッドで並列に実行するには、 maxを2に指定して、一方のスレッドのidを0に、もう一方のスレッドのidを1にして、ScrollAPIを実行します。

# Thread1
curl -XGET 'localhost:9200/src/_search?scroll=1m' -d '{"slice": {"id": 0,"max": 2    }}'# Thread2
curl -XGET 'localhost:9200/src/_search?scroll=1m' -d '{"slice": {"id": 1,"max": 2    }}'

これで、ScrollAPIも並列で実行できるようになりました。結果として構成は下図のようになります。

f:id:cybozuinsideout:20160816171805p:plain

Slice数を16として16スレッドで並列にScrollAPIを実行した場合、再インデクシングの実行時間は以下のようになります。

Bulk並列度 BulkSize Slice数 ScrollSize 実行時間
24 500 16 1000 66秒
24 1000 16 1000 68秒
24 2000 16 2000 68秒
24 2000 16 5000 71秒

そこそこ速くはなってきたのですが、dstatやcat thread_poolを見ると、まだまだリソースを使い切れていないようです。 もっとリソースを使いきってすっきりしたいところです。

# dstat# es-watanabe-1
----system---- ----total-cpu-usage---- ---load-avg--- ------memory-usage----- -dsk/total- --io/total- -net/total-
     time|usr sys idl wai hiq siq| 1m   5m  15m | used  buff  cach  free|read  writ|read  writ| recv  send
16-08 00:49:09|33165001|2.671.671.24|64.2G  957M 87.5G 36.0G|0    13M|0127| 220M  163M
16-08 00:49:10|41257001|2.671.671.24|64.2G  957M 87.5G 35.9G|0    28M|0182| 214M  132M
16-08 00:49:11|42156001|2.671.671.24|64.2G  958M 87.6G 35.9G|0    40M|0284| 229M  169M

# other node
----system---- ----total-cpu-usage---- ---load-avg--- ------memory-usage----- -dsk/total- --io/total- -net/total-
     time|usr sys idl wai hiq siq| 1m   5m  15m | used  buff  cach  free|read  writ|read  writ| recv  send
16-08 00:49:03|10090000|0.350.340.29|53.1G  750M  101G 33.4G|0    21M|0170|8458k   18M
16-08 00:49:04|13187000|0.350.340.29|53.1G  750M  101G 33.4G|0    29M|0202|6299k   13M
16-08 00:49:05|10190000|0.350.340.29|53.1G  750M  101G 33.4G|0    31M|0257|5250k   17M
# cat thread_pool
id   host          bt    ba bq bs    bc st    sa sq ss    sc
vM4E es-watanabe-1 fixed  402443111 fixed  003762488
btge es-watanabe-2 fixed  102456264 fixed  003785930
zPof es-watanabe-3 fixed  202456315 fixed  003785945
3y0A es-watanabe-4 fixed  502456430 fixed  003786064
IyM- es-watanabe-5 fixed  202455774 fixed  003786454
iXmJ es-watanabe-6 fixed  102456351 fixed  103786993
iBLd es-watanabe-7 fixed  302456244 fixed  003786884
cKjt es-watanabe-8 fixed  202456379 fixed  003787017

ローカルシャードのみを再インデクシングする

ドキュメントの取得とインデクシングを並列化したので、並列度を高めるという方向性はお腹いっぱいな感じです。 しかし、分散システムであるがゆえのブロック(複数のノードからのドキュメントの取得をまとめる処理など)や、 ネットワークを介したドキュメントの転送、ReindexTool自体の負荷分散などはまだまだ手付かずなので、ここに改善の余地はありそうです。

出発地点に立ち返ると、やりたいことはsrcからdestにドキュメントをコピーするだけでした。 また、構成図を見てわかるように、srcのシャードもdestのシャードも同じノード内に存在しています。

となると、各ノードでReindexToolを動かして、ドキュメントの移動はノード内だけに止めておけば、ノード間の通信も発生せず、なんだか幸せになれそうな気がします。

これを実現するためには、まず、自ノード内のシャードのドキュメントのみを取得する方法が必要です。 その方法として、検索APIのpreferenceパラメータが利用できます。 Elasticsearchの検索APIでは、preferenceパラメータによって、実行するシャードを指定できるので、 以下の手順でローカルシャードに対してのみScrollAPIを実行できます。(微妙感あるので他に良いやり方あったら教えてください!)

  1. ローカルノードのIDを取得 (Node Info API)
  2. ローカルノードのプライマリシャードを取得 (Search Shard API)
  3. 2で取得したシャードをpreferenceパラメータに指定してScrollAPIを実行

2の結果、ローカルシャードはshard1, shard2だったとすると、3のScrollAPIは以下のようになります。

curl -XGET 'localhost:9200/src/_search?scroll=1m&preference=_shards:1,2'

また、通信を無くすためには、ノード内のsrcシャードとdestシャードは同じidを持つように揃える必要があります。 今回は単一回の実験だったため、クラスタのRebalanceを無効化した後、 手動でCluster Reroute APIを叩いて、destシャードを移動して揃えました。

f:id:cybozuinsideout:20160816171820p:plain

以上の前準備を行った後に、この構成のノードを図で表すと、下図のようになります。 今までの並列実行の仕組みはそのままで、ローカルシャードのみを再インデクシングの対象にしています。

f:id:cybozuinsideout:20160816171807p:plain

この構成で、再インデクシングの実行時間を計測すると以下のようになりました。

Bulk並列度 BulkSize Slice数 ScrollSize 実行時間
24 500 16 1000 21秒
24 1000 16 1000 21秒
24 2000 16 2000 22秒
# dstat
----system---- ----total-cpu-usage---- ---load-avg--- ------memory-usage----- -dsk/total- --io/total- -net/total-
     time|usr sys idl wai hiq siq| 1m   5m  15m | used  buff  cach  free|read  writ|read  writ| recv  send
16-08 04:08:51|80416000|7.023.801.76|54.1G  755M  102G 31.8G|0   248M|01371|3544B 6761B
16-08 04:08:52|83313000|8.464.151.88|54.1G  755M  102G 31.7G|0   174M|0983|3650B 6769B
16-08 04:08:53|84313000|8.464.151.88|54.1G  755M  102G 31.6G|0   114M|0748|3544B 6773B


# cat thread_pool
id   host          bt    ba bq bs    bc st    sa sq ss    sc
N1Ly es-watanabe-1 fixed 230242588 fixed  203710645
CN8- es-watanabe-2 fixed 2002410878 fixed  103725810
AR56 es-watanabe-3 fixed 2402410943 fixed  103725817
7lai es-watanabe-4 fixed 2302410932 fixed  103725817
CXUS es-watanabe-5 fixed 2402410067 fixed  103725838
p6oN es-watanabe-6 fixed 240249883 fixed  103725819
Q1u2 es-watanabe-7 fixed 2302410052 fixed  103725856
0Ged es-watanabe-8 fixed 2302410808 fixed  103725857

結果を見ると、実行時間も20秒程度でReindexAPIと比べて10倍以上高速になり、 CPUとディスクのリソース使用率も100%には届かないものの、かなり高まってきています。 これは、通信によるオーバーヘッドやブロックを取り除いたことも一因ですが、 ReindexTool自体の負荷を複数ノードに分散したことも大きいと考えられます。

ローカルシャードを他ノードのシャードに再インデクシングする

「ローカルシャードのみを再インデクシングする」の節では、ローカルシャードからローカルシャードへのドキュメントのコピーを想定して、ノード間の通信が発生しないようにしました。 しかし、この方法は、srcとdestのシャード数が同数の場合のみに適用できる方法です。

シャードを分割するなどして、srcとdestのシャード数が異なる場合は、 ローカルシャードへコピーすることもあれば、別ノードのシャードにコピーすることもあり、 BulkAPIを実行する際のノード間の通信は避けられません。

f:id:cybozuinsideout:20160816171810p:plain

そこで、Cluster Reroute APIで、ノード間の通信が必ず発生するようにシャードを移動して、再インデクシングの実行時間を計測しました。 結果は以下です。

Bulk並列度 BulkSize Slice数 ScrollSize 実行時間
24 500 16 1000 24秒
24 1000 16 1000 24秒
24 2000 16 2000 24秒

先ほどの結果が21秒だったことを考えると、若干の劣化があるものの、十分高速に動作するようです。 ということで、シャード数を変更するようなユースケースにおいても、今回の再インデクシングの仕組みは有効と考えられます。

NVMe SSDのマシンで実行する

ディスクアクセスが大量に発生するワークロードにも関わらず、HDDはよく頑張ってくれて、かなり高速に動作するようになりました。 しかし、秒間数百MB程度の書き込みが実行されている状況なので、お金の力でHDDをSSDにしたら、さっくり速くなるのでは、という淡い期待が持てます。 ちょうどいいことに、検証用のNVMeを搭載したマシンがあったので、これを使って実行時間を計測してみます。

CPUとディスクのみですが、マシンスペックは以下です。マシンの関係上、CPUもかなりパワーアップしています。

要素性能備考
CPU 2.6GHz,20core,40threads
SSD書き込み 1.1GB/s ddでsequential writeを簡易計測
SSD読み込み 584MB/s hdparmで簡易計測

HDDの場合と同様に、マシン8台でクラスタを組んで、ローカルシャードの再インデクシングを実施したところ、実行時間は以下のようになりました。 CPUがパワーアップしたので並列度も上げて試しました。また、マッピングのindex.merge.scheduler.max_thread_count:1はHDD用の設定なので、今回はデフォルト値を使っています。

Bulk並列度 BulkSize Slice数 ScrollSize 実行時間
24 500 16 1000 16秒
24 1000 16 1000 16秒
48 500 32 1000 15秒
48 1000 32 1000 16秒

速くはなりましたが、十数秒で終わってしまうので、ツールの立ち上げ処理などの時間比率が高くなり、 再インデクシングの時間が正しく計測できているか怪しいです。

そこで、srcのドキュメントを複製することで、ドキュメント数を増やして再度計測してみます。 srcのドキュメント数を5倍に複製した結果のインデックスの状態は以下のようになりました。 1000万ドキュメント、44GBです。

$ curl 'nvmenode-1:9200/_cat/indices'
green open src   16010253585044.4gb  44.4gb

これを同様の方法で再インデクシングします。データサイズが大きくなったので、シャード数も変化させてみました。 実行時間は以下です。

シャード数 Bulk並列度 BulkSize Slice数 ScrollSize 実行時間
16 24 500 16 1000 71秒
16 24 1000 16 1000 72秒
16 48 500 32 1000 73秒
16 48 1000 32 1000 72秒
32 24 500 16 1000 62秒
32 24 1000 16 1000 62秒

44GBの再インデクシングが約60秒で終わりました。 今回のデータセットに対しては、約700MB/s(16万5千docs/s)程度の速度でインデクシングできています。 秒間1GBも見えてきて、Elasticsearch5.0.0の夢が広がりますね。

参考までに、32シャード、1000万ドキュメントのHDDの場合も計測したところ以下のようになりました。 結果として、112秒かかったので、お金の力によって2倍近く速くなったことになります。

シャード数 Bulk並列度 BulkSize Slice数 ScrollSize 実行時間
32 24 1000 16 1000 112秒(HDD)

ReindexToolのチューニング

最後に、Elasticsearchに関することではないのですが、ReindexToolを作成する中で効果が高かったチューニングが2つほどあったので紹介します。 ReindexToolはGoで書いているので、pprofというプロファイラを使ってチューニングしました。

まず、初期段階のツールでは、ScrollAPIから取得したレスポンスのJSONをパースしてstructとして扱い、 その中から_sourceを抜き出す、という処理を行っていました。 その時のプロファイル結果(開発用のローカル環境で計測)は以下です。

(pprof) top 10
77s of 135.57s total (56.80%)
Dropped 364 nodes (cum <= 0.68s)
Showing top 10 nodes out of 115 (cum >= 10.31s)
      flat  flat%   sum%        cum   cum%
    10.43s  7.69%  7.69%     15.63s 11.53%  encoding/json.(*decodeState).scanWhile
    10.38s  7.66% 15.35%     10.42s  7.69%  encoding/json.stateInString
     8.54s  6.30% 21.65%     16.40s 12.10%  runtime.scanobject
     8.04s  5.93% 27.58%     16.03s 11.82%  compress/flate.(*decompressor).huffSym
     7.95s  5.86% 33.44%     14.20s 10.47%  encoding/json.checkValid
     7.60s  5.61% 39.05%     35.35s 26.08%  compress/flate.(*decompressor).huffmanBlock
     7.12s  5.25% 44.30%      7.12s  5.25%  runtime.memmove
     6.25s  4.61% 48.91%     14.39s 10.61%  encoding/json.unquoteBytes
     5.46s  4.03% 52.94%      5.46s  4.03%  unicode/utf8.DecodeRune
     5.23s  3.86% 56.80%     10.31s  7.60%  compress/flate.(*decompressor).moreBits

ほぼ、JSONの処理です。やりたいことはドキュメントのコピーだけなので、JSON的な処理を頑張らずにどうにかしたいものです。 そこで、jsonparserというライブラリを使い、structへの変換などを省略するようにしました。結果は以下です。

(pprof) top10
53.29s of 58.24s total (91.50%)
Dropped 229 nodes (cum <= 0.29s)
Showing top 10 nodes out of 91 (cum >= 1.29s)
      flat  flat%   sum%        cum   cum%
     9.39s 16.12% 16.12%      9.39s 16.12%  github.com/buger/jsonparser.stringEnd
     8.62s 14.80% 30.92%     17.18s 29.50%  compress/flate.(*decompressor).huffSym
     7.83s 13.44% 44.37%     36.64s 62.91%  compress/flate.(*decompressor).huffmanBlock
     6.77s 11.62% 55.99%      6.77s 11.62%  runtime.memmove
     5.35s  9.19% 65.18%     10.71s 18.39%  compress/flate.(*decompressor).moreBits
     4.75s  8.16% 73.33%      5.36s  9.20%  bufio.(*Reader).ReadByte
     4.09s  7.02% 80.36%      9.61s 16.50%  compress/flate.(*decompressor).copyHist
     2.88s  4.95% 85.30%      5.52s  9.48%  compress/flate.forwardCopy
     2.39s  4.10% 89.41%      2.39s  4.10%  runtime.memclr
     1.22s  2.09% 91.50%      1.29s  2.21%  syscall.Syscall

2倍以上速くなりました。だいぶいい感じです。あとはhttpのgzipの処理も重そうです。 ローカルシャードのコピーだけなら、gzipする必要はなさそうなので、gzipの処理も外しました。結果は以下です。

(pprof) top10
20470ms of 22450ms total (91.18%)
Dropped 213 nodes (cum <= 112.25ms)
Showing top 10 nodes out of 88 (cum >= 4380ms)
      flat  flat%   sum%        cum   cum%
   10410ms 46.37% 46.37%    10410ms 46.37%  github.com/buger/jsonparser.stringEnd
    4250ms 18.93% 65.30%     4250ms 18.93%  runtime.memmove
    2480ms 11.05% 76.35%     2480ms 11.05%  runtime.memclr
    1600ms  7.13% 83.47%     1650ms  7.35%  syscall.Syscall
     780ms  3.47% 86.95%    11150ms 49.67%  github.com/buger/jsonparser.blockEnd
     300ms  1.34% 88.29%      300ms  1.34%  runtime.heapBitsForObject
     180ms   0.8% 89.09%      210ms  0.94%  runtime.greyobject
     170ms  0.76% 89.84%      170ms  0.76%  runtime.procyield
     160ms  0.71% 90.56%     2990ms 13.32%  runtime.mallocgc
     140ms  0.62% 91.18%     4380ms 19.51%  github.com/buger/jsonparser.EachKey

初期の6倍程度速くなりました。おそらくまだチューニングできるとは思うのですが、Go力が足りていないのでここまでにしておきます。

ということで、再インデクシングという単純なツールにおいては、以下の処理がボトルネックになりがちなので注意しましょう。

  • HTTPの圧縮展開
  • JSONのEncode/Decode

注意事項

Elasticsearchクラスタの性能を振り絞ることで、再インデクシングを高速化してきましたが、以下の点には注意が必要です。

  • ユーザー操作の検索リクエストなどを停止できないような状況では、再インデクシングでマシンリソースは使いきらないようにする
  • Sliced ScrollのSlice数はシャード数までに押さえておくと高速に動作する。(リファレンスのNoteを参照)
  • Bulk並列度を上げ過ぎると、BulkAPIタスクのキューが溢れてrejectが発生する。rejectが発生するとそのタスクが捨てられるので、ドキュメントのコピーが不完全になる。

reject対策の方法は、processorsの設定をしたり、thread_poolの設定をいじったりなど色々あるのですが、 かなりピーキーな設定になるので、限界まで性能を引き出す必要がなければBulk並列度を抑えるのが無難だと思います。節度のあるリソース利用を心がけましょう!

まとめ

本記事では、特定ユースケースにおける再インデクシングの高速化を探求しました。 結果として、ScrollAPIとBulkAPIを使い倒すことによって、ReindexAPIをそのまま使うよりも10倍以上高速に動作するようになりました。 また、wikipediaの日本語ドキュメントというデータセットにおいて、NVMeを使った8ノードのクラスタで、約700MB/s(16万5千docs/s)のスループットを実現しました。

今回は、ドキュメントを加工しないケースの再インデクシングを対象としましたが、 ユースケースが異なる場合は、個々の高速化テクニックを状況に合わせて部分的に採用して頂ければと思います。 また、SlicedScrollなど、5.0.0からの機能もあるので、バージョンアップを検討する際の要因の一つと考えて頂ければ幸いです。

検索システムを運用していくうえで、再インデクシングは何かと頭を悩ませる問題です。 私達も現状の検索システムでこの問題に悩まされてきたので、本記事がこの種の問題に頭を悩ませている方々の何らかの参考になれば嬉しいです。

We are hiring!!!

サイボウズでは Elasticsearch 大好きなエンジニアを募集しています!
キャリア採用 募集要項/イベント | サイボウズ株式会社

参考

How we reindexed 36 billion documents in 5 days within the same Elasticsearch cluster


お手軽に使える高速なSSE4.2専用文字検索ライブラリ

$
0
0

サイボウズ・ラボの光成です。

今回はC/C++用文字検索ライブラリmie_stringを紹介します。

mie_stringはテキストの中から複数文字のいずれかが存在する場所を高速に検索する関数を提供します。

本文ではその使い方と性能を紹介します。また後半ではSIMD命令を使うときに悩ましい端数処理について詳解します。

準備

mie_stringではCのintrinsic関数(SSE4.2)を使ったものとアセンブリ言語で書いたものの二つを用意しました。

intrinsic関数を使う場合はMIE_STRING_INLINEを定義してからmie_string.hをincludeしてください。 これ以外のファイルは不要です。C/C++のどちらからも使えます。

includeするだけでつかえるので簡単ですね。

なお、コンパイルオプションにはgcc/clangなら-msse42や-mavx、Visual Studioなら/arch:AVXなどの指定を忘れないでください。

asmバージョンを使いたい場合はLinuxなら

nasm -f elf64 mie_string_x64.asm

Visual Studioなら

nasm -f win64 -D_WIN64 mie_string_x64.asm

で作られたオブジェクトファイルmie_string_x64.o(bj)をリンクしてください。NASMはNASMやapt install nasmなどで取得できます。 asm版の方がintrinsic版よりも1~2割ほど高速です。

使い方

提供する関数は二つです。

一つはstd::stringのfind_first_of相当の

const char *mie_findCharAny(const char *text, size_t size, const char *key, size_t keySize);

です。 たとえば[text, text + size)の範囲からスペースかタブあるいは改行を探したいときは

mie_findCharAny(text, size, " \t\r\n", 4);

とします。文字が見つかったときはそのポインタ、見つからなかったときはNULLを返します。 keySizeは16以下という制約があるので注意してください。

もう一つは検索したい文字を範囲で指定できる

const char *mie_findCharRange(const char *text, size_t size, const char *key, size_t keySize);

です。 たとえば16進数の文字0123456789abcdefを探したいときは['0', '9']と['a', 'f']の二つの範囲を指定するため、

mie_findCharRange(text, size, "09af", 4);

とします(注意 : 範囲指定は境界を含みます)。最大8種類の範囲を指定できます。

この場合は文字種が16なので

mie_findCharAny(text, size, "0123456789abcdef", 16);

としてもかまいません。

性能

mie_findCharAny, mie_findCharRangeの比較対象として、次の関数を用意しました。 一つ目は

const char *loop_find_sp(const char *text, size_t size)
{
  for (size_t i = 0; i < size; i++) {
    char c = text[i];
    if (c == '' || c == '\r' || c == '\n' || c == '\t') return text + i;
  }
  return NULL;
}

と検索対象をifの中に列挙したものです。 次に対応する文字だけ1になっている256byteのテーブルspTblを事前に用意して

const char *tbl_find_sp(const char *text, size_t size)
{
  for (size_t i = 0; i < size; i++) {
    unsigned char c = text[i];
    if (spTbl[c]) return text + i;
  }
  return NULL;
}

とテーブルで判定する関数も用意しました。

それからC++のstd::string標準メソッドfind_first_ofとも比べました。

今回は見つける文字の頻度を変えて作成した1MiBのデータに対して該当する場所を全て探すベンチマークをとりました。

実験環境はCore i7-6700 3.4GHz(Skylake)+ gcc-4.8です。

f:id:cybozuinsideout:20160824160438p:plain

横軸は、該当文字が見るかるまでどのぐらいの速度で探せるか、CPUの1cycleあたりの探索byte数です。 値が大きいほど速いことを示します。

縦軸は1MiBのデータの中での該当文字の出現間隔です。値が大きいほど出現頻度は小さいです。 一般的に平均探索長が小さいほど探索byte数/cycleは小さくなります。これは関数呼び出しなどのオーバーヘッドが無視できなくなるためです。

逆に平均探索長が長いとループにおける分岐予測もヒットしやすくなり探索byte数/cycleは小さくなる傾向にあります。

10バイトごとに頻繁に見つかるようなところでもloopやテーブルを用いたものより2倍程度速いです。 出現頻度が少ないところでは5倍以上高速になります。簡単に使えて高速なのですばらしいですね。

またC++のfind_first_ofはとても遅いことがわかります。これは探す文字種が可変なため2重ループになってしまうからです。

同様に16進数探索もベンチマークをとりました。

f:id:cybozuinsideout:20160824155712p:plain

最初のテストと同様の傾向が見られます。 条件をいくつか変えて評価したところ、mie_findCharAnyやmie_findCharRangeはkeyの長さが増えても速度が変わりませんでした。 テーブルを使った場合に似て、これは興味深い性質です。

なお、標準関数のmemchrはSIMD命令を使って64byte単位で探す実装になっています(glibcのmemchr.S)。 出現頻度が少ないところではSSE4.2を使った実装よりも高速なので、一文字だけを探す場合はmemchrを使ってください。 ただVisual Studio 2015のmemchrはglibcほど最適化はされていないのでmie_findCharAnyの方が速いようです。

実装詳解

SSE4.2命令自体の解説や主な使い方はHTTPパーサにおけるSSE4.2最適化の威力と注意点で紹介しているのでそちらをごらんください。

mie_string.hを見るとmie_findCharAnyとmie_findCharRangeはmodeが0か4の違いしかないことに気がつきます。 それならマクロではなく関数呼び出しでよいのではないかと思われるかもしれません。

しかし_mm_cmpestri()の第4引数は数値リテラルを要求し、変数を指定するとエラーになるためこのような形をとっています。

またmie_string_x64.asmではSSEとAVXの両方のコードを生成するために一段階マクロをかませています。

それではtextのsizeが16未満になったときに安全にSSEレジスタに値を読み込む方法について説明します。

メモリからSSEレジスタに値を読ませるには16byte単位で動作するmovdqu命令を使います。 しかしtextのsizeが16未満のときにmovdquを使うとそのsizeの先のメモリにもアクセスしてしまいます。 その領域がOSによってread禁止になっていると例外が発生します。 したがって滅多にないことではありますが、安全のためにはそこを読まないようにしなければなりません。

通常ページ境界は4096byteごとにあります。したがってアドレスaddrの下位12bitが4080(=4096-16)より大きく、 かつそこから読み込む範囲が境界を越えないときのみケアする必要があります。

addr + sizeが4096を超えていると読み込むべきデータがあるはずなので範囲を超えてreadしても問題ありません。

f:id:cybozuinsideout:20160823175910p:plain

それを厳密に式で表すと

addr2 = addr & 0xfff;
「addr2 > 0xff0 && addr2 + size <= 0x1000」がtrueのとき

となります。この判定はコード上の73行目あたりにあります。

f:id:cybozuinsideout:20160823162820p:plain

このとき、4080byte目から16byteをSSEレジスタに読み込んでaddr & 15だけ右シフトすればOKです。

ところが非常に残念なことにSSEにおいて16byteをbyte単位で右シフトする命令はシフト数を定数でしか指定できません。

仕方がないのでIntelの最適化マニュアルには

inline __m128i shr_byte(__m128i v, size_t shift)
{
    switch (shift) {
    case 0: return v;
    case 1: return _mm_srli_si128(v, 1);
    case 2: return _mm_srli_si128(v, 2);
    case 3: return _mm_srli_si128(v, 3);
    case 4: return _mm_srli_si128(v, 4);
    case 5: return _mm_srli_si128(v, 5);
    case 6: return _mm_srli_si128(v, 6);
    case 7: return _mm_srli_si128(v, 7);
    case 8: return _mm_srli_si128(v, 8);
    case 9: return _mm_srli_si128(v, 9);
    case 10: return _mm_srli_si128(v, 10);
    case 11: return _mm_srli_si128(v, 11);
    case 12: return _mm_srli_si128(v, 12);
    case 13: return _mm_srli_si128(v, 13);
    case 14: return _mm_srli_si128(v, 14);
    case 15: return _mm_srli_si128(v, 15);
    default:
    }
}

という方法が紹介されています。しかし分岐予測は殆ど外れるでしょうからこのコードは速くありません。 というか、かっこ悪いです。

どうしたものかと考えて、今回はシャッフル命令pshufbを使ってみました。これはbyte単位で要素をどこに動かすかを指定できます。 s = 0, ..., 15にたいしてs byteずらす方法を記したデータ16byteを16個分テーブルで用意すればよいのです(合計16x16=256byte)。

shift[1] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x80 };
shift[2] = { 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x80, 0x80 };
...

が、このテーブルを見ていると一つ後ろのテーブルの値は一つ前を1ずらしたものであることに気がつきます。

したがって読み込む位置をbyte単位でずらせば32byte用意するだけでよいのでした。 それを元に実装したのが75~77行目です。細かいベンチマークをとったところ上記のshr_byteよりも数倍高速でした。

結局、次の関数で安全に値を読み出せます。

__m128i mie_safe_load(const void *text, size_t size)
{
    static const unsigned char shiftPtn[32] = {
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
        0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
        0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80
    };
    size_t addr = (size_t)text;
    size_t addr2 = addr & 0xfff;
    if (addr2 > 0xff0 && addr2 + size <= 0x1000) {
        addr2 = addr & ~(size_t)15;
        size_t shift = addr & 15;
        __m128i ptn = _mm_loadu_si128((const __m128i*)(shiftPtn + shift));
        return _mm_shuffle_epi8(_mm_load_si128((const __m128i*)addr2), ptn);
    } else {
        return _mm_loadu_si128((const __m128i*)text);
    }
}

実はAVX-512にはこのあたりの処理を考慮した命令が追加されています。が、それはまたの機会にしましょう。

まとめ

今回は複数文字のどれかを高速に探す関数を紹介しました。 ナイーブな実装よりも2~5倍速く、std::string::find_first_ofよりも10倍程度高速でした。 またページ境界をまたがずに安全に値を読むmie_safe_loadも紹介しました。 何かのお役にたてば幸いです。

SRE チームを設立します

$
0
0

運用本部長を務めている山本泰宇です。 運用本部は社内の情報システムを担当する情報システム部と cybozu.comなど自社クラウドサービスを運用するサービス運用部からなる部門です。

本日、サービス運用部にて SRE チームを設立しました。この記事ではチーム設立にいたった経緯と今後の活動計画を紹介いたします。

Site Reliability Engineering (SRE) とは

f:id:cybozuinsideout:20160831023247p:plain:w200

今年の 3 月に O'Reilly から出版された "Site Reliability Engineering"で有名になりましたが、Google のプロダクトやサイトを安定運用するための活動やその活動に従事する人・チームを指します。特徴としては基本的にソフトウェアエンジニアからなる集まりで、自律的な仕組みや自動化を日常的に行っていることです。

サイボウズでも 5 月から社内で SRE 本の輪講を開催し、理解を深めてきました。中でも盛り上がったのは Chapter 5 "Eliminating Toil"です。簡単にいうと「通常の運用でオペレーターの操作を必要とするケースは、それそのものがバグ(不具合)である」とみなして「オペレーション」という綺麗な言葉ではなく Toil (骨折りとか苦労の意)と呼んで極力減らしていこうという内容です。

他にも Error Budget (障害があるたびに減っていき、枯渇すると新規リリースを停止するような取り決め)やパーセンタイルでの指標定義など実践的なノウハウに富んでおり、クラウドサービスの運用に関わる人は必読の一書です。

SRE チーム設立にいたる経緯

サイボウズは元々オンプレミスのパッケージ製品開発の会社でしたので、クラウドサービスのオペレーターはいませんでした。クラウドサービスの開発にあたっては、私をはじめ製品を開発していたプログラマがインフラの仕組みを構築しました。

cybozu.com のサービスイン前後までは、今でいう SRE チームのようにソフトウェアエンジニアが自律的な仕組みや自動化をがんがん整備して、日常のオペレーションは完全に自動化するような体制でした。

しかしサービスインからしばらくして、順調に顧客が増え、システムの規模も増加するにつれて簡単には自動化できないような対応が増えてきました。オンコール対応(障害発生時の緊急呼び出しのこと)をはじめ、各種の調査依頼や特殊な変更手順などです。多くは優先度の高い割り込み業務にあたります。

ソフトウェアエンジニアの生産性は割り込みがあると激しく落ち込むことが知られています。実際夜中の障害対応にあたったエンジニアがしばらく調子を崩す事例などが積み重なった結果、開発専門のチームとオペレーションを主務とするチームに分割しました。

しばらくは開発チームの生産性も改善しそれなりにうまくまわっていたのですが、最近では以下のように深刻な問題が出てきてしまいました。

  • 開発したシステムの運用コストが甚大な事例が複数あった

    稼働中のシステムの変更は、現システムからの移行手順が良く検討され自動化されていないと非常に困難なものになります。開発専門チームがそういったオペレーションまで想定できず、移行作業の人的負荷が多大になったりしました。

  • オペレーションチームのソフトウェア開発スキルが向上しない

    開発専門チームがあるとはいえ、当初はオペレーションチームも時間があれば自動化等の開発をしていました。しかし時間が経ちオペレーションチームに新規配属されるメンバーが増えてくると、次第にオペレーション専門のチームに様相が変わってしまいました。

  • Toil が爆発しているのに減らせない

    チームがわかれると、お互いの情報を知る機会も減ってしまいます。オペレーションチームが開発ではなく手順書を増やしていく(Toil が増えていく)状況であるにも関わらず、開発チームがそれに気付かず改善できないということが積み重なってしまいました。気付いたときには長大な手順書が営々と受け継がれている状況でした。

これらの問題が見過ごせない状況になったため、チームを再び統合し、SRE チーム設立の運びとあいなったわけです。

SRE チームの業務と今後の取り組み

これまでやってきたことも含め、サイボウズの SRE チームの業務内容を紹介します。

サイトの信頼性を保証するという定義の通り、サイボウズの SRE チームはサービスレベル目標(SLO)を踏まえた指標管理セキュリティ対策や緊急パッチの適用各種モニタリングシステムの開発・運用ロードバランサ自社製 KVSの開発、データセンター間ネットワークの設計や各種ハードウェアの調達、更新など様々な業務をこなしています。現在は 1 万 5 千社以上の有償契約を抱え、一日のアクセス量は 1.5 億程度の規模です。

cybozu.com では稼働率目標 99.99% を掲げていますが、概ね達成できている状況ではあります。

f:id:cybozuinsideout:20160831112725p:plain

とはいえ色々問題を内在しているので、新生 SRE チームは以下の方針を掲げて活動していきます。

役割を固定しすぎない

何年も開発専門とか、何年もオペレーションばかりといった役割の固定化は避けます。 Google のようにインシデント対応や問い合わせ対応は仕事の 50% に収まるように調整する、かどうかは分かりませんが、やり方を検討していきます。

ソフトウェア開発スキルを磨く

オペレーションチームの若手を中心に、ソフトウェア開発スキルを向上させる機会が少なかった点を改善します。既に私や他のプログラマが教師役として、トレーニングの時間を設けています。

Toil はなくしていく

長大な手順書も。絶対にやるという気持ちが重要だと思うので、明記しておきます。

We are hiring!

最後にお約束で済みませんが、SRE チームで活躍できる人材を大募集中です! 職場環境や開発環境については以下の記事で紹介しています。

SRE の募集要項はこちらです。まずは話を聞いてみたいという方も大歓迎ですので、Twitterやお知り合いの社員を通して遠慮なくお声かけください。

それでは。

Go でいい感じのコマンドを作れるツールキットの紹介

$
0
0

SRE@ymmt2005です。最近は systemd が好物です。

今回は GitHub でサイボウズが公開している Go 言語のプロジェクト群、特にいい感じのコマンドを作れる github.com/cybozu-go/cmdについて紹介します。

SRE チームでは最近 Go でツールを開発する機会が多くなっています。最初のうちは決まった作り方をしていなかったため、コマンドごとに仕様がばらばらで、以下のような問題がでてきました。

  • REST API サーバーのアクセスログを記録しないコマンドがある
  • 外部コマンド実行時のログを記録しないコマンドがある
  • SIGTERM 等シグナル処理の方法がばらばら
  • ログファイルのリオープンができずローテートしにくい
  • ログメッセージの形式がばらばら

大雑把にいうと、ログとシグナル処理がきちんとしてないと扱いにくいわけです。 具体的にどうなっていると「いい感じ」か書き連ねてみると:

  • SIGINT/SIGTERM で実行中の処理を終えてから止まる(graceful stop)
  • SIGHUP で設定再読み込みないし実行中の処理を終えてから再起動(graceful restart)
  • SIGUSR1 でログファイルをリオープン
  • 統一された形式の機械処理可能なログ出力
  • HTTP アクセスの結果はログに出力
  • 外部コマンド実行結果はログに出力
  • ログレベルを調節可能
  • 一連の処理を追える識別子がログに含まれる

な感じと思うのですがいかがでしょう。

最後の識別子については分かりにくいので補足すると、Go は goroutine というプロセスでもスレッドでもない仕組みで並列に処理が走るので、goroutine ごとに識別子がないと一連の処理が追えないのです。が、実は goroutine には ID というものがないので、どうするかという問題です。

結論から言うと、github.com/cybozu-go/cmd (以後 cmd)を使うと上に挙げた仕様を満たすコマンドがさくっと作れます。サイボウズが公開している Go 言語製プロダクトは全て cmdに対応済みです。

cmdの仕組み

cmdは Go 1.7 で標準パッケージとなった contextを中心に組み立てられたツールキットです。contextを使うと、以下のような問題にうまく対処できます。

  • SIGTERM とタイムアウト処理のどちらか先に来たほうで処理を中断
  • リクエストごとに異なる値を関数コール間で引き継ぎ

うまく使えば graceful stop や goroutine の識別子問題を解消できるわけです。

cmdcontextを利用して任意のタイミングで中断を指示できるバリア同期の仕組みを提供します。cmd.Environmentがそれで、Cancel(err error)メソッドを呼び出すことで任意のタイミングでコンテキストをキャンセルし、実行中の goroutine が戻ってくるのを待機するようになっています。

import (
    "context""github.com/cybozu-go/cmd""github.com/cybozu-go/log"
)

func main() {
    env := cmd.NewEnvironment(context.Background())

    // goroutine で関数を呼び出し// non-nil な error を返すとすぐ Cancel が呼び出される
    env.Go(func(ctx context.Context) error {
        for {
            select {
            case<-ctx.Done():
                // キャンセルされたら中断returnnildefault:
            }
            doSomething()
        }
    })

    //env.Go()...
    env.Stop()

    // バリア同期
    err := env.Wait()
    if err != nil {
        log.ErrorExit(err)
    }
}

cmdフレームワークは大域的な cmd.Environmentを用意して、SIGINT/SIGTERM が飛んでくると大域 Environment をキャンセルするシグナルハンドラを設定します。大域 Environment が提供する context にぶら下がることで、プログラム全体の goroutine が graceful に制御できるというわけです。

さらに、cmdは graceful stop が可能なネットワークサーバーを作る機能を提供しています。例えば HTTPServerは標準パッケージの http.Serverをラップして、アクセスログの出力と graceful stop する機能を追加します。

func main() {
    s := &cmd.HTTPServer{
        // 標準の http.Server をくるむだけ
        Server: &http.Server{
            Handler: http.FileServer(http.Dir("/path/to/files")),
        },
    }

    // 標準の ListenAndServe と違い内部で Go() を使いすぐ戻ってくる
    err := s.ListenAndServe()
    if err != nil {
        log.ErrorExit(err)
    }

    // SIGINT/SIGTERM がきたら graceful stop する
    err = cmd.Wait()

    // cmd.IsSignaled でシグナル受信したか判別できるif err != nil&& !cmd.IsSignaled(err) {
        log.ErrorExit(err)
    }
}

cmd.HTTPServerはそれ以外に、ハンドラに渡す http.Requestに一意な識別子(リクエストID)を持たせた context を付けています。

ほかにも一杯あるのですが、詳しい話はチュートリアルを用意したのでそちらでご確認ください。あと cmd.HTTPServerの実装テクニックは個人ブログ(英語)で解説しているので、興味があればご覧ください。

logの仕組み

上記の例でログについては説明を省いていましたが、ログは github.com/cybozu-go/log (以後 log)という別パッケージで実装した機能を使っています。

logは俗にいう属性ログという機能を提供しています。属性ログは、key-value 形式で任意の属性を構造化して記録できる仕組みのものです。こんな感じ:

log.Error("message", map[string]interface{}{
    "field1": []int{1, 2, 3},
    "field2": time.Now(),
})

出力形式として、人間にも読み易い syslog に似た形式(plain)と、logfmt という形式に加え、JSON Linesをサポートしています。cmdフレームワークを使うと、出力形式やログレベルは共通のコマンドラインオプションで変更可能になっています。

その他のパッケージ

  • github.com/cybozu-go/netutil

    ネットワーク関係のツール群です。 IsNoRouteToHostのようなネットワークエラーの判別関数などがあります。

  • github.com/cybozu-go/goma

    シンプルなモニタリングツールです。 なにかあれば、なにかするというルールを設定ファイルや REST API やコマンドラインでさくさくと定義できるようになっています。

  • github.com/cybozu-go/transocks

    redsocksのような透過 SOCKS プロキシを実現するツールです。 IPv4/v6 両対応で、SOCKS 以外に HTTP プロキシ(CONNECT) にも対応しています。

  • github.com/cybozu-go/usocksd

    SOCKS4/4a/5 対応の SOCKS サーバーです。 外向け通信に使う IP アドレスを動的に制御する機能を備えているのが特長です。 アクセスログもばっちり出るので、セキュリティ監査にはもってこいです。

  • github.com/cybozu-go/aptutil

    apt-mirror や apt-cacher-ng といったツールを置き換えるものです。 以前の紹介記事をご覧ください。

まとめ

いい感じの Go プログラムを作れる github.com/cybozu-go/cmdと仲間たちを紹介しました。使ってみて良かったらぜひブログなどでご感想をお寄せください!

C/C++プログラマのための開発ツール

$
0
0

サイボウズ・ラボの光成です。

先日、社内で主にLinux上でC/C++を用いている開発者向けの講義をしました。 「こんなことができる」と知ってもらい、興味を持てば各自で勉強してもらおうと広く浅くツールを紹介しました。

gtags, ASan, Valgrind, addr2line, cppcheck, SystemTap, perfなどです。 興味があれば講義資料「C/C++プログラマのための開発ツール」をごらんください。

コンパイラオプション

受講者には新人やサイボウズ・ラボユースの学生もいたので基本的なところから紹介しました。

C/C++コンパイラを使うときはできるだけ警告オプションをつけるのが望ましいです。 警告が出るのは自分のコードの書き方に不備があることが多いからです。

gccやclangでは-Wall -Wextraは基本としてそれ以外にも有用なオプションがあります(C++用gccのオプション)。 一般にコンパイラによって出す警告は異なるので、可能なら複数のコンパイラでチェックした方がよいでしょう。

ソース読みの補助ツール

あるプロジェクトのコードを読むとき、関数名や変数名でひたすらgrepしている人がいます。 効率が悪いのであまり感心しません。

複数のディレクトリ内でまとめてgrep的なことをするにはag(The Silver Seacher)が高速で便利です。

また関数が定義されている場所や、呼び出されている場所を調べるにはGNU GLOBALを使うとよいです。自分が使っているエディタで使えるように設定しておきましょう。

クラス階層を視覚的に見たいときはDoxygenもよいでしょう。 関数の呼び出しグラフを可視化してくれるKcachegrindも便利です。

f:id:cybozuinsideout:20160914120634p:plain

このように目的に応じて適切なツールを使うと効率よくコードを把握できます。

vimやemacsしか触ったことがないという人もたまには統合環境を使ってみるとよいでしょう。 何も設定しなくてもそういった機能を使えることが多いので、世界が広がるかもしれません。

デバッグ

gdbの最低限の使い方やcoreファイルを扱ったデモをしました。 それからlibSegFault.soをpreloadする方法、/proc/<プロセスid>/mapsやobjdump, addr2lineを使ってエラー箇所から関数を調べる方法などを紹介しました。

メモリ関係のチェックツールとしてAddressSanitizer(ASan), Valgrind, TCMallocなども紹介しました。 使い勝手とオーバーヘッドの少なさを考慮するとASanがよいと思います。

静的解析ツール

プログラムを実行せずにおかしなところを指摘してくれます。 コンパイラの警告の延長上にある機能です。

Visual Studioの/analyzeオプションやフリーソフトのcppcheck, clang付属でXcodeの静的解析ツールとしても使われているscan-buildなどがあります。 それぞれ得意、不得意があるのでできれば複数のツールで調べるとよいでしょう。

たとえばうっかり書いてしまいそうな次のコードがあります。 どこがおかしいか分かりますか?

int f(int a) {
    if (a != 3 || a != 5) return 4;
    return 2;
}

このコードに対してVisual Studioは

不適切な演算子です:
|| を使用した相互排除は常に 0 でない定数となります。
&& を使用しようとしましたか?

と警告してくれます。面白いですね。

静的解析ツールにはCoverityといった優れた商用ツールもあります。

SystemTap

Linux kernelの中身を動的に調べられるツールです。 awk/Cに似たスクリプトを書いて実行すると、裏でkernelモジュールに変換されて動きます。

特定のネットワーク処理やdisk IO処理がkernelのどこでどのように実行されているかを調べられます。

ユーザコードのstacktraceなども表示でき、ltraceやstraceみたいな使い方もできます。 kernel内部で使われている構造体のメンバ変数を表示できるなど非常に強力なツールです。

SystemTap/Examplesにはたくさんのサンプルスクリプトが掲載されているので最初はそれらをコピーして使うだけでも有用でしょう。

今回は紹介しませんでしたが「半年かかったバグ調査の顛末は」ではblktraceが活躍したり, 「ファイルキャッシュクリアの謎」ではFtraceを使ったりしました。

perf

CPUが持つ詳細な情報を用いてサンプリング形式でプロファイルするツールです。

kernelシンボルを入れておけばperf topで今システムのどこが重たいのかをリアルタイムで見ながら探せます。 システム開発中に、たまにperf topして、予想していない関数が上位に来ていると原因を調べて性能改善できたことが何度かあります。

f:id:cybozuinsideout:20160913154429p:plain

図はわざと無限ループするPythonスクリプトを実行したときに上位に来た関数のうち、PyDict_GetItemの内部に入ったところです。 逆アセンブルされて、左側のCPU負荷が高いところを示す数値は常時変わっています。 初めて見るとなかなか感動すると思います。

まとめ

以上、駆け足で紹介しました。もちろん今回の資料で紹介してなくて有用なツールもたくさんあります。 また新しいツールもどんどんでてくるでしょう。 アンテナを張って、自分の中で常時情報を更新できるようにしておきたいものです。

サイボウズサマーインターン2016 報告その1〜Webアプリケーション開発コース

$
0
0

こんにちは kintone開発チームの小林です。

サイボウズでは、8月から9月にかけて、5日間のサマーインターンを3回開催しました。サマーインターンは以下の3つのコースに分かれています。

  • Webアプリケーション開発コース
  • 品質保証・セキュリティコース
  • デザインコース

今回は、Webアプリケーション開発コースについて、1回目のサマーインターンの内容を中心にお伝えします。

f:id:cybozuinsideout:20160902225132j:plain

インターンの概要

第1回のサマーインターンは、8月1日〜8月5日に行われました。Webアプリケーション開発コースには、東京で4名、大阪で1名の学生が参加しました。

Webアプリケーション開発コースでは、サイボウズが提供しているクラウドサービス「kintone」を題材に、新機能のプロトタイプを作ってもらいました。今回のプロトタイプは、もともと、開発チーム内で要望を受けたり改善したいと考えていた機能のプロトタイプでした。

プロトタイプの開発にあたっては、ただ機能を実装するだけではなくkintoneの実際の開発のプロセスに則って、利用シナリオを想定した仕様検討やメンターによる実装レビュー、自動試験など、一連の流れを体験してもらいました。

プロトタイプ

インターン生5名に作ってもらったプロトタイプは以下のとおりです。

フォームの「元に戻す」「やり直し」機能

f:id:cybozuinsideout:20160901150707p:plain

kintoneは、ユーザがフォームをドラッグアンドドロップで設計することができます。ただし、設定変更したフィールド元に戻したり、逆にやり直したりすることはできません。

この問題を解決するため、UndoボタンとRedoボタンをつくり、ボタンを押すと、変更した内容を元に戻したり、やり直したりすることができるようにしました。

アプリの誤削除を防止するダイアログ

f:id:cybozuinsideout:20160831135311p:plain

kintoneには、自分が管理権限のあるアプリを一覧でみることができる画面があります。この画面でアプリを削除しようとしたときに、誤って別のアプリを削除してしまった、というケースがあります。

意図しないアプリの削除を防止するための仕組みとして、アプリを削除しようとしたときに、ユーザにアプリ名を入力することを求め、アプリ名をチェックしてから削除するダイアログを実装してもらいました。

ポータル画面に表示するコンテンツを整理する機能

f:id:cybozuinsideout:20160902160428p:plain

kintoneのポータル画面には、アプリウィジェット、スペースウィジェットなど、5つのウィジェットがあります。kintoneの使用用途によっては、特定のウィジェットは使わないこともあるため、kintoneの管理者がどのウィジェットを表示するか選択できるようにしてもらいました。

全てのピープルの投稿をいいね順に取得するAPI

ピープルとは、kintoneのユーザーごとに存在するページのことで、ユーザ同士の直接の連絡や、アイデアなどの共有に使用する機能です。インターン生には、一日のうちでどのユーザの投稿が盛り上がっているのかがわかるように、投稿についたいいねの数を基準に、いいね順に投稿を取得するAPIを作成してもらいました。また、ただAPIをつくるだけではなく、デモ用のクライアントアプリも作成してもらいました。

ピープルに投稿するAPI

最近は、Slackなどに代表されるようにBotの使用が一般的になってきています。kintoneでも、業務の状況を自動的に投稿したり、自分にとって必要な情報(天気予報や株価情報など)を自動的に投稿するような使い方が考えられます。そこで、Botによる自動投稿ができるよう、kintoneのピープルへ投稿できるAPIを作成してもらいました。

スケジュール

1日目

1日目は、お互いに簡単に自己紹介をしたあと、インターンの概要についてオリエンテーションを行いました。その後、kintoneの開発をする上で必要なJavaScriptやJavaの技術を学ぶために、簡単な機能を練習として実装してもらいました。

2日目

2日目は、課題として与えたユーザの利用シナリオから具体的な仕様を検討し、仕様書にまとめてもらいました。仕様書を書くにあたって、まず簡単な座学を行い、どんな内容を書けばいいのかを勉強してもらいました。仕様書を書き終えたあとは、インターン生同士で、仕様書のレビューを行い、仕様の考慮漏れや問題点などを探してもらいました。その後は、レビューで指摘を受けた点を各自修正しつつ、実装をはじめました。

3日目〜4日目

3日目と4日目は、実装の続きを行なってもらいました。インターン期間中は、メンターがインターン生のとなりに座るので、こまめに相談したり、コードレビューを行なったりしました。また、ずっと実装するだけではなく、CIやgit、インフラなどのトピックに関しては、講義の時間を設けて集中的に解説を行いました。さらに、途中で社長の青野とのランチ会の時間を設けたり、社員面談をしたりするなど技術的な側面だけではなくサイボウズ全体のことを知ってもらえる機会をいくつか設けました。

5日目

5日目は、成果発表会を行いました。ひとりひとり、実装したプロトタイプの紹介やデモ、インターンを通じて学んだことなどを紹介してもらいました。30名以上の社員が参加し、積極的な意見交換や質疑応答が行われました。

f:id:cybozuinsideout:20160902225423j:plain

成果発表会が終わった後は、懇親会と「インターン完走証」の表彰を行いました。 f:id:cybozuinsideout:20160902225800j:plainf:id:cybozuinsideout:20160902225908j:plain

インターン生の感想

インターン生の感想を紹介します。全体を通じて好意的に受け止めてもらえたようです!

  • 今回のインターンで、何か機能を実装するにあたって、さまざまな背景やシナリオ、ストーリーがあることを再認識することができました。そして、仕様に基づいて開発を進めていくうえでの大変さやチームワークの不可欠さを感じることができたました。とても有意義な5日間でした。ありがとうございます。

  • 今回のインターンではkintoneの機能のプロトタイプを作成し、初めて企業での開発を体験しました。これまで個人で開発してきたものとは異なり、大規模なシステムだったのでコードの理解は大変でしたが、1週間という短い期間で想定していた機能を実装できて安心しました。メンターの方々には何度質問しても丁寧に説明をして頂けて感謝しています。

  • 5日間でkintoneの機能を1つ実装するという課題で課題を見たときは案外行けそうだと思ったが、実際のコードを見たときに行けなさそうだと思ってしまった。しかし、メンターさんの丁寧な説明もあり、表面部分だけは理解し、無事に課題を達成することができた。また、わがままにも丁寧に付き合っていただき課題のクオリティを向上することができたのでメンターさんには感謝しきれないと思った。

  • 実際の製品に触れて機能追加や改善を行う機会はなかなか得ることができないので、仕様策定・設計・実装・レビューのサイクルを体験できてタメになった。社員の皆さんが実際に「kintone」を使って「チームワークあふれる会社」を実現しており、企業理念と製品にズレが無いと感じた。リモート会議を実際に体験したり、社員の方々が様々な働き方をしているのを見たりして、本当に多様な働き方が可能なんだと驚いた。

  • 実務でのプログラム開発のプロセスを体験できて、とても勉強になりました。今回使用したJavaは、基本を勉強した程度でほとんど経験が無かったので、課題に取り組んでいるときには詰まることもたくさんありましたが、メンターの方が丁寧に説明してくれたので課題をこなすことができました。仕事の環境もとても良かったので、集中して課題の実装に取り組むことができました。また、技術面だけではなく、サイボウズの雰囲気を知ることができたり、社員の方がやさしく接してくれ、充実した5日間を過ごすことができました。

まとめ

今回のインターンで作ってもらったプロトタイプは実装難度の高いものが多く、インターン生は苦労していましたが、5日間という短い期間の中で素晴らしい内容を実装してくれました。また、制作物だけでなく、サイボウズという企業風土や社員に触れて学んでもらえたことも多いようでした。

これからも、学んだ経験を活かして是非活躍してほしいです!

10日間インターンのお知らせ

サイボウズでは、通年で10日間の開発インターンも募集しています。予定が合わずサマーインターンは参加できなかったという方、今からでもインターンをしてみたいという方、ご応募お待ちしております!

Bashタブ補完自作入門

$
0
0

ドーモ、SREチームの湯谷(@yutannihilation)です。最近気になるモジュラーシンセはIntellijelです。

上司がいい感じのコマンドをつくるという記事を書いていましたが、いい感じのコマンドにはいい感じのタブ補完を付けたくなります。この記事ではBashのタブ補完を自作する方法を紹介します。

タブ補完の仕組み

Bashのタブ補完自体はBashに組み込まれている仕組みです(参考:Bash Reference Manual - 8.6 Programmable Completion)。completeというBashの組み込み関数によって補完方法(compspec(completion specification)と言うらしいです)が規定されていて、これがタブなどによって起動されます。

タブ補完は、lsならファイル名、cdならディレクトリ名、というようにコマンドに応じたものが設定されています。最近のLinuxディストリビューションでは多くのコマンドで快適にタブ補完ができますが、それはbash-completionというパッケージのおかげです。

このパッケージが用意してくれている数々の補完関数は、/usr/share/bash-completion/completions以下に配置されていることが多いです(このパスはディストリビューションやbash-completionのバージョンによっても異なります)。たとえば、manの補完方法は/usr/share/bash-completion/completions/manに定義されています。見てみましょう。

$ cat /usr/share/bash-completion/completions/man
# man(1) completion                                        -*- shell-script -*-[[$OSTYPE== *@(darwin|freebsd|solaris|cygwin|openbsd)* ]] || _userland GNU \
    || return1_man(){local cur prev words cword split
    _init_completion -s-n : ||return

...略...

    __ltrim_colon_completions "$cur"return0}&&
complete-F _man man apropos whatis

# ex: ts=4 sw=4 et filetype=sh

_man()というシェル関数が定義され、それに続いてcompleteコマンドが実行されています。-Fは、補完するための関数を指定するオプションです。これによって、manコマンドとaproposコマンドとwhatisコマンドは_man()という関数によって補完が行われるように設定されています。

ユーザがタブを押すと、_man()COMP_CWORDとかCOMP_WORDSといった変数(後述)で今のコマンドラインに入力されている文字が渡されます。_man()はそこから候補を絞り込んだ結果を、COMPREPLYという変数で返します。それがタブ補完の候補として表示される、という流れです。

はじめの一歩

まず、候補としてonetwoを表示するシンプルな関数_dummy()をつくってみます。候補のリストをCOMPREPLYという変数に入れると、補完候補として表示されるようになります。

$ _dummy(){COMPREPLY=(one two);}

これをmanコマンドの補完方法に指定します。

$ complete-F _dummy man

これでmanコマンドは_dummy関数でタブ補完されるようになりました。ここで、

$ man <TAB>

と打つと、以下のように候補が表示されるはずです。

$ man
one  two

でも上の_dummy()だと、コマンドラインの状態はお構いなしに同じ候補しか表示しません。

$ man o<TAB>
one  two
$ man one<TAB>
one  two

oまで打ったら候補はoneだけになってほしいですよね。そのために_dummy()は、今のコマンドラインの状態を受け取って、候補の絞り込みをする必要があります。

補完候補の絞り込み

これには、COMP_CWORDCOMP_WORDSという変数とcompgenというコマンドを使います。

COMP_CWORDCOMP_WORDS

この変数には、それぞれ以下の値が入っています。

  • COMP_CWORD: 今カーソルがあるワードは何語目か
  • COMP_WORDS: コマンドラインのワードのリスト

たとえば、以下のようにCOMP_CWORDCOMP_WORDSechoする関数を設定してみます。

$ _dummy(){echoecho COMP_CWORD: ${COMP_CWORD}echo COMP_WORDS: ${COMP_WORDS[@]}}

これでタブを押すと、以下のような結果になります。COMP_CWORDCOMP_WORDSにどのような変数が渡っているかイメージが付くでしょうか。カーソルの直前がワードのときと空白のときで結果が異なる点には注意が必要です。

$ man <TAB>
COMP_CWORD: 1
COMP_WORDS: man
$ man test test2<TAB>
COMP_CWORD: 2
COMP_WORDS: man test test2
$ man test test2 <TAB>
COMP_CWORD: 3
COMP_WORDS: man test test2

_get_comp_words_by_ref()

現在カーソルがあるワードは${COMP_WORDS[${COMP_CWORD}]}、そのひとつ前は${COMP_WORDS[${COMP_CWORD}-1]}といったかたちでアクセスすることができます。でも、ちょっと長くて見づらいです。_get_comp_words_by_ref()という便利関数を使うと、現在カーソルがあるワードは$cur、ひとつ前のワードは$prev、ワード数は$cwordという変数に入ります。

こんな感じです。

$ _dummy(){local cur prev cword
  _get_comp_words_by_ref -n : cur prev cword
  echoecho cur: ${cur}echo prev: ${prev}echo cword: ${cword}}

$ man arg1 arg2<TAB>
cur: arg2
prev: arg1
cword: 2

こうした便利関数はやはりbash-completionパッケージによって提供されているもので、/usr/share/bash-completion/bash_completionに定義されています。定義が気になる場合はこのファイルを覗いてみましょう。(例えば_get_comp_words_by_refの定義はこのあたりです)

compgen

compgenは、オプションによって出てくる補完候補から、引数とマッチ(基本的に前方一致)するものだけを絞り込むコマンドです。各オプションはman 7 bash-builtinsに詳しく書かれています。ここでは主要なオプションだけ紹介します。

  • -W

-Wは、変換候補となる文字列のリストを指定するオプションです。以下のように、指定した文字列リストから、引数と前方一致するものだけに絞り込んでリストアップしてくれます。

$ compgen-W"one two once twice"--
one
two
once
twice
$ compgen-W"one two once twice"-- o
one
once
$ compgen-W"one two once twice"-- onc
once
  • -c/-f/-d

自前で文字列のリストを与えなくても、コマンドのリストとか、ファイルのリストとかを補完候補にすることができるオプションです。-cはコマンド(と実行ファイル、ディレクトリ)、-fはファイル、-dはディレクトリを補完するようになります。たとえば、以下のように-cオプションにmanという引数を与えると、manから始まるコマンドがリストアップされます。

$ compgen-c-- man
mandb
manpage-alert
man
manpath

補完してみる

compgenを使うと、dummy()は現在のコマンドラインの文字を使って候補を絞り込んだ補完ができるようになります。

$ _dummy(){local cur prev opts
  _get_comp_words_by_ref -n : cur prev
  opts="one two once twice"COMPREPLY=( $(compgen-W"${opts}"--"${cur}"))}

こんな感じの動きです。

$ man <TAB>
once   one    twice  two
$ man on<TAB>
once  one

ようやく補完できました。めでたしめでたし!(たったこれだけしか補完できない_dummy()に飽きたら、complete -r manでデフォルトの補完に戻すことができます)

例:go-apt-cacher の補完

具体例として、APT専用のキャッシュプロキシgo-apt-cacherのタブ補完をつくってみましょう。

go-apt-cacherには以下のオプションがあります(バージョン1.2.2時点)。オプションによって補完すべきものが違います。-f-logfileはファイル名をとり、-logformat-loglevelはそれぞれ固有の選択肢をとります。

$ go-apt-cacher -h
Usage of go-apt-cacher:
  -f string
        configuration file name (default "/etc/go-apt-cacher.toml")-logfile string
        Log filename
  -logformat string
        Log format [plain,logfmt,json]-loglevel string
        Log level [critical,error,warning,info,debug]

まず、go-apt-cacher -まで入力してタブを押すとオプションが補完されるようにしてみましょう。オプションはコマンドの直後、つまり$cwordが1の位置にきます。このときはオプションのリストをcompgen-Wに指定して補完するようにします。

$ _go_apt_cacher(){local cur prev cword
  _get_comp_words_by_ref -n : cur prev cword

  if ["${cword}"-eq1]; thenCOMPREPLY=( $(compgen-W"-f -logfile -logformat -loglevel"--"${cur}"))fi}&&
complete-F _go_apt_cacher go-apt-cacher

次に、オプションが-logformatの場合はplainlogfmtjsonのいずれかが、-loglevelの場合はcriticalerrorwarninginfodebugが補完されるようにしてみましょう。カーソルがオプションの次に来ているときは、$prevにオプションが入るのでこれを条件分岐に使うといいでしょう。

$ _go_apt_cacher(){local cur prev cword
  _get_comp_words_by_ref -n : cur prev cword
  
  if ["${cword}"-eq1]; thenCOMPREPLY=( $(compgen-W"-f -logfile -logformat -loglevel"--"${cur}"))elif["${cword}"-eq2]; thenif ["${prev}"="-logformat"]; thenCOMPREPLY=( $(compgen-W"plain logfmt json"--"${cur}"))elif["${prev}"="-loglevel"]; thenCOMPREPLY=( $(compgen-W"critical error warning info debug"--"${cur}"))fifi}&&
complete-F _go_apt_cacher go-apt-cacher

さらに、オプションが-logfile-fのときにはファイルへのパスが補完されるようにしてみます。ファイルの補完にはcompgen-fオプションを使います。compopt -o filenamesは、ディレクトリ名には/を付けてくれるようにするおまじないです(詳しいことが気になる方はman 7 bash-builtinsを読んでください)。

$ _go_apt_cacher(){local cur prev cword
  _get_comp_words_by_ref -n : cur prev cword
  
  if ["${cword}"-eq1]; thenCOMPREPLY=( $(compgen-W"-f -logfile -logformat -loglevel"--"${cur}"))elif["${cword}"-eq2]; thenif ["${prev}"="-logformat"]; thenCOMPREPLY=( $(compgen-W"plain logfmt json"--"${cur}"))elif["${prev}"="-loglevel"]; thenCOMPREPLY=( $(compgen-W"critical error warning info debug"--"${cur}"))elif["${prev}"="-logfile"-o"${prev}"="-f"]; then
      compopt -o filenames
      COMPREPLY=( $(compgen-f--"${cur}"))fifi}&&
complete-F _go_apt_cacher go-apt-cacher

これで完成です。

この例はcompgen-W-fしか使いませんでしたが、最終的にCOMPREPLYに候補のリストを渡せば何をしてもかまいません。やり方は様々です。/usr/share/bash-completion/completions/下に良い例がたくさんあるので、探求心の強い方は参考にしてみてください。

補完関数の置き場所

個人的に使うものであれば~/.bash_completionというファイルをつくって書いておけば、ログイン時に自動で読み込まれます。

パッケージなどに含める場合であれば、bash-completionパッケージが使っている/usr/share/bash-completion/completionsなどに置くのが良いでしょう。このパスは、pkg-config --variable=completionsdir bash-completionというコマンドで調べることができます。詳しくはbash-completionのFAQを参照してください。

まとめ

みなさまの快適なBash生活の一助となれば幸いです。

サイボウズではBashやビアバッシュが好きなエンジニアを募集しています。SREの募集要項はこちらです。

サイボウズサマーインターン2016 報告その2〜品質保証・セキュリティコース

$
0
0

こんにちは!東京品質保証部の中園です。

サイボウズではサマーインターンを8月から9月にかけて3回開催しました。今回は、サイボウズ サマーインターン報告その2と題して、2回目のサマーインターン 品質保証・セキュリティコースの内容をお届けします!

f:id:cybozuinsideout:20160923111224j:plain

前回の記事: blog.cybozu.io

インターンの概要

2回目のサマーインターンは、8月22日~26日に開催しました。品質保証・セキュリティコースには、5名の学生が参加してくれました!

本コースは、サイボウズで行っている実際の品質保証・セキュリティの業務を体験し、品質保証業務を知ることを目標としました。5日間で業務の体験と、それに先立つ体系的な講義を用意しました。

スケジュール: f:id:cybozuinsideout:20161003133821j:plain

ほぼ日替わりで、各業務の体験を行ってもらいました。5日間という短い期間ということもあり、かなりタイトなスケジュールでしたが、インターン生の皆さんは頑張ってついてきてくれました。

製品テスト

製品テストのターンでは、サイボウズ製品のテストとしてkintoneのテストを体験してもらいました。

kintoneのアプリには、レコードの一覧画面で先頭行を固定表示する機能があります。この機能を題材とし、kintoneのQAがやっているテストプロセスの一部、テスト設計→レビュー→テスト実施を体験してもらいました。 f:id:cybozuinsideout:20160923101116j:plain

実際の機能仕様書を見ながら、kintoneのQAが利用しているExcelのテスト仕様書テンプレートに沿ってテスト設計を行いました。条件の洗い出しや、設計のコツをメンターが教えながら、テスト設計を進めてもらいました。どのくらいの粒度で設計すればよいかなど、悩みながらもなんとかテスト仕様書を完成させてくれました!

f:id:cybozuinsideout:20160930100811j:plain

出来上がったテスト仕様書をインターン生同士で交換し、テスト設計レビューを実施しました。テスト設計レビューでは、抜けている観点が無いか確認したり、分かりにくい手順があれば指摘をします。レビュー完了後、テスト仕様書をもとにテストを実施してもらいました。

f:id:cybozuinsideout:20160923110829j:plain

また、不具合発見時の報告を体験するため、kintoneのアドホックテストも実施しました。機能はアプリのフォーム設定画面に絞り、不具合を見つけ、報告してもらいました。いくつかのkintoneの不具合を見つけることができ、品質の向上に貢献してくれました!

セキュリティテスト

サイボウズ製品のガルーンを対象として、セキュリティテストを体験してもらいました。今回は、次期バージョンで新しく追加される機能の一部の検証を実施しました。

まず初めに、セキュリティテストに必要な知識の講義を聴講します。講義が終わり、実際のセキュリティテストに入っていきます。最初に行うのはテスト設計です。製品テストと同様に機能仕様書を見ながら、ガルーン の新機能がどのようなものかを把握していきます。

テスト仕様書を読んだだけでは、追加された画面でどのようなリクエストが発生するかはわかりません。メンターからアドバイスを受けつつ、実際の環境を触ってリクエストを洗い出します。慣れない作業に苦戦しながらも、みなさん無事にテスト仕様書を完成させました。 f:id:cybozuinsideout:20160930093259p:plain

次に、実際の検証作業を実施してもらいました。どのような手順で確認すればよいか・脆弱性の場合どのような挙動になるか・脆弱性ではない場合どのような挙動になるかをkintoneアプリにまとめています。アプリを参考に検証を進めてもらいました。 f:id:cybozuinsideout:20160930093303p:plain

リクエスト・パラメーターそれぞれに対して網羅的に検証し、まだ社内の検証で検出していなかった脆弱性を見つけてくれたインターン生もいました。

見つけてくれた脆弱性:新機能でユーザー設定を追加する処理にSQLインジェクションの脆弱性(CVSS v3 基本値:2.7)

※ 開発中バージョンの新機能です。すでに改修済のため、お客様がご利用の製品版には影響ありません。

脆弱性を見つけるのは地道な作業の繰り返しである分、脆弱性を見つけられた方はとても嬉しそうでした。2回目のインターンでは、以下のような脆弱性を探してもらいました。

  • クロスサイト・スクリプティング(XSS)
  • SQLインジェクション
  • CRLF Injection
  • 情報漏洩
  • 不適切な認証
  • クロスサイトリクエストフォージェリ(CSRF)
  • Session Fixation
  • ログインの不備

「自身の管理外のネットワーク / コンピュータに今回学ぶ技術を使った行為をしないこと」は、インターンで学んでもらったことの中で最も重要です。

不具合情報公開サイトの運営

サイボウズでは、製品を利用しているお客様や、サイボウズ製品を取り扱っている営業パートナー様に向けて、製品の不具合/脆弱性/制限事項に関する情報を不具合情報公開サイトで公開しています。

今回は「不具合」と「脆弱性」の2種類の記事を、ガルーンを対象として作成してもらいました。

不具合記事では、まず、社内の検出情報をもとに不具合を再現させ、再現手順と発生する現象を把握しました。次に、その不具合が再現するバージョンや影響範囲を動作確認で洗い出しました。記事作成に必要な情報が集まったら、読者が読みやすく、検索しやすい言葉で記事を書くための執筆ルールに沿って記事を書いてもらいました。

f:id:cybozuinsideout:20160929134855j:plain

脆弱性記事では、不具合記事と同様に動作確認を行ったうえで、脆弱性用の記事フォーマットに沿って記事を作成してもらいました。脆弱性記事の場合は、脆弱性の脅威をお客様やパートナー様へ正しくお伝えすることも大切ですが、記事が脆弱性攻撃に利用されることのないよう情報の公開粒度にも配慮する必要があります。この点に注意しつつ、記載する文言を推敲してもらいました。

不具合も脆弱性も、インターン生一人ひとりが違う現象の記事を担当し、再現バージョンの洗い出しのコツや、記事構成の仕方等についてメンターからアドバイスを受けながら進めてもらいました。

最後に仕上がった記事をメンターとインターン生全員で確認し、良かった点やもう少し工夫した方がよい点をメンターがフィードバックしました。

インターン生の感想

インターン生の感想を一部ご紹介します。満足度の高いインターンだったようです!

  • 5日間本当に楽しく過ごせました。職場の雰囲気も良く、初日からあまり緊張せずに行うことができました。仕事でも、本当にやっている仕事の一部を体験させてもらえたようで良い経験になりました。昼ごはんの時や懇親会では、社員の方や他のインターンシップ生と交流を深めることができて良かったです。もう一度行きたいと思えるインターンシップでした。

  • 全てのプログラムが実際のQA業務とほとんど同様の事を行うため、普段では味わうことのない体験をすることができました。実際の製品を用いて、脆弱性や不具合を見つけるなどの業務を行えるインターンは他にはなかなか無いので、本当に参加して良かったと思います。

  • 品質保証とは何かについて講義や業務を通して知ることができ、いい経験になりました。また品質保証のなかでも製品テスト、脆弱性検査、不具合情報公開サイトの運営など様々な種類の体験が行え、とても充実した5日間でした。

  • 5日間のインターンシップの間では、サイボウズさんの文化や理念に触れながら、実務をすることができました。実務では、業務の大変さや難しさ、責任の重さを体感することができ、会社の方を通してサイボウズという会社についてもより知ることができたので、大変貴重な体験になりました。

  • 最高の5日間でした。何より、同じ目標を持てる人たちに出会えたことは、大きいと思います。この5日間の経験を今後活かしていきたいです。

まとめ

5日間という短い期間でしたが、インターン生の皆さんはサイボウズの品質保証について沢山学んでくれました!また、サイボウズの会社の雰囲気も知ってもらえたようで嬉しく思います。

優秀な学生さんたちに出会えて、メンターとしても大変良い刺激をもらいました。インターン生の皆さんの今後の活躍を期待しています。


デブサミ関西「企業向けクラウドサービスの開発・運用」の発表報告

$
0
0

こんにちは大阪開発部の三苫(@mitomasan)です。

2016/09/16 デブサミ関西(Developers Summit 2016 KANSAI #devsumi)にて「企業向けクラウドサービスの開発・運用 ~悩みどころのパターンと対策~」というタイトルで発表してきました。少し間が空いてしまいましたが報告します。

きっかけ

私はサイボウズ大阪開発部ではまだ一年目のエンジニアなのですが、入社以前より企業向けのクラウドサービスをいくつか開発・運用したことがあります。ですので、世のクラウドサービスが運営会社がどのような事に悩み、解決しているのか興味がありました。

入社後もクラウドサービスを開発・運用する上で起こる問題はどんなものがあるだろうと観察しながら(もちろん当事者として対応もしながら)過ごしてきました。

そこから出た結論としては、どこの会社でも同じようなことが課題としてもちあがり、それを解決する方法に銀の弾丸はなく、問題が出てきたコンテキストも様々で、解決策はコレと単純に言えるものではないものが多い、という当たり前のものでした。

とはいえ「似たようなことは、形は違えどどこでも起きうるんだな」と感じる話もあり、何らかのパターンをとして伝えられそうな気がしたのでこの機会に整理しておきたいと思ったのがきっかけです。

発表内容について

この発表ではクラウドサービスの開発・運用するうえで起きる問題をそれぞれ経験・課題・理想・対策という項目をもったパターン形式でまとめて発表しました。

最初は発表時間分のコンテンツを用意できるかなと心配していたのですが、いざふたを開けてみると当日の45分ではしゃべり切れない分量を用意してしまいました。

  • 性能系
    • 見積れない性能要件
    • 事前チューニング困難
    • 想定外のヘビーユーザー
  • リリース・運用系
    • 継続的デリバリー vs 定期更新希望
    • 無停止リリース
    • 障害調査とセキュリティ
  • サービスのありかた
    • どこからお金をいただこう?
    • 個別カスタマイズ
    • 新機能 vs 利便性

当日は上記の9パターンのうち5個程度をお話したのですが、残りについては話せずじまいでしたので当日参加いただいた方も資料をお読みいただければ幸いです。

www.slideshare.net

感想

ここからは私の個人的な感想になるのですが、この資料を書いたときにすでに「このトピックの内容はすでに古いな」と思うこともありました。

技術の進歩は早く、昔とても難しいと感じていたことが今の世の中ではとても簡単になっているかと思うこともあれば、やっぱり今でも変わらず難しいなと思うことも多いです。「ここは仕方がないよ」と思うことが意外と解決されていたり「こんなの誰もがハマるからいい加減解決されてないとおかしい」みたいなことが今も解決されていなかったり。

過去の結論にとらわれず、現状に合わせて物事を評価しなおす、課題解決にもう一度取り組んで見ることが大事なのかなと感じました。

また、アウトプットの方法として今回パターン形式でまとめるという事を初めてやってみました。命名センスなどが問われますが、なかなか作るのも楽しく自分の経験を整理・カタログ化できるのでおすすめのアウトプット方法だと感じました。

宣伝

クラウドサービスの開発・運用をすることに興味をお持ちの方はぜひサイボウズもご検討くださいまし。

開発であればロケーションの制約は少ないため大阪オフィスや在宅勤務などでも仕事に取り組めます。

cybozu.co.jp

サイボウズサマーインターン2016 報告その3〜デザイナーコース

$
0
0

こんにちは。サイボウズ デザイングループの河内山です。

サイボウズではサマーインターンを8月から9月にかけて3回開催しました。今回は「サイボウズ サマーインターン報告その3」と題して、サマーインターン3回目のデザイナーコースの報告をします。

f:id:cybozuinsideout:20161025164910j:plain

過去の記事: blog.cybozu.ioblog.cybozu.io

インターンの概要

3回目のサマーインターンは、9月5日~9日に開催しました。デザイナーコース参加者は1人でした(寂しい…)。 デザイナーコースではサイボウズで実施しているデザイン業務を体験してもらい、同時に会社・部署紹介などを織り交ぜながらインターン生と企業がお互いを深く知ることを大切にしています。

採用面接だけでは時間が限られていますが、インターンでは長い時間お互いを知ることができるため、インターン経由の入社は理想的な流れの1つだと思います。実際、2017年入社予定組にインターン出身者の学生も多く、デザイナーコース出身者もいます!

主なメニュー:

  • デザイングループの紹介
  • 課題:製品のデザイン提案
  • ペルソナ ワークショップ
  • サイボウズのデザインプロセスの紹介

スケジュール: f:id:cybozuinsideout:20161025171732p:plain

課題について

主力製品であるサイボウズOfficeモバイルUXに関するデザイン提案を、リサーチ→プランニング→プロトタイピングというフローで実施しました。適切なアドバイスができるよう、対象製品はメンターが担当している製品になります。

f:id:cybozuinsideout:20161025172241p:plain

リサーチ

事前に用意したペルソナとジャーニーマップをもとに、製品でモバイルを利用するユーザーの行動や心理を理解しました。自分とは違う人のためのデザインを考えるのに、ペルソナが役に立ったと思います。

f:id:cybozuinsideout:20161026125235p:plainf:id:cybozuinsideout:20161025182849p:plain

期間中は、インターン生とデザイングループメンバーによるペルソナワークショップも実施しました。 f:id:cybozuinsideout:20161025172756j:plain

プランニング

ペルソナとジャーニーマップからUXの問題点を見つけ、どういう状態が解決になるかのゴールを設定しました。複数出た問題点から今回フォーカスするものを絞りました。

問題点:

  • 外出中にモバイルから対応しにくいメールを、会社に帰ってPCから返信しようとしても、該当のメールを探す手段がない

ゴール:

  • 後からから対応が必要なメールを探しやすくする

プロトタイピング

ここからいよいよ問題解決のデザイン案作成です。インターン生が使い慣れているということで、今回はSketchProttを使いました。ここはインターン生が得意な作業だったようで、デザイン案がどんどん出てきました。実装を考慮した手堅い案から、未来的な大胆な案まで幅広い案が出ました。

作成したデザイン案をもとにデザイングループメンバーと一緒にレビュー会を実施して、実際の現場と同じように率直な意見交換が行われました。 f:id:cybozuinsideout:20161025173257p:plainf:id:cybozuinsideout:20161025173336j:plain

成果発表

他コースのインターン生と一緒に課題の成果とインターン全体の感想を大勢の前で発表します。課題発表中は実況スレッドが立ち上がり、インターン生の考えたデザイン案にポジティブなコメントがたくさん流れていました。

f:id:cybozuinsideout:20161025173429j:plain

インターン生の感想

インターン生の感想を一部ご紹介します。

  • デザインの現場に入って製品のデザイン提案をするというのは他ではできない経験なのではないかと思います。かつデザインプロセスに則って改善するというのは、最も実践的なスキルだと思うので、5日間でそれが体験できたのはとても良い経験になりました。オフィスもチームも働き方も、すべてチームワークを前提に考えられていて、働いてみてとても心地よかったです。準備、運営本当にありがとうございました! 良いデザイナーになります。

まとめ

メンターと一緒に課題に取り組む時間が多いですが、ランチや打ち上げでインターン生といろいろな社員がカジュアルに話しをする機会もあり、全てのコースでインターンの趣旨どおり会社と人を知ってもらえた5日間だったと思います。 f:id:cybozuinsideout:20161025173602j:plainf:id:cybozuinsideout:20161025173613j:plain

来年もデザイナーコースのインターンは実施予定です。デザイングループが運営しているFacebook(Cybozu UX Cafe)で告知しますので、興味がある方はぜひフォローをお願いします!

また、デザイングループの新卒採用サイトもありますので、ぜひチェックしてください。

cybozu.co.jp

そのコードに価値はあるか?kintone開発チームでスクラムトレーニングを受講しました

$
0
0

こんにちは。kintone開発チームの天野(@ama_ch)です。3ヶ月ほど前からスクラムマスターを始めました。

10/18(金)に@ryuzeeさんにサイボウズ東京オフィスにお越しいただき、kintone開発チームのメンバーでスクラムトレーニングを受講しました。

きっかけ

私が開発しているkintoneはクラウドサービスですが、サイボウズはパッケージ製品の開発でスタートした歴史があり、開発プロセスにもパッケージ時代の習慣が強く残っていました。

  • 長大な要件リスト
  • 実装と試験工程の分断
  • 開発後期での手戻り
  • etc...

もちろんこれまで手をこまねいていた訳ではなく、開発チーム内では次のような活動に取り組んできました。

  • タイムボックス(イテレーション)を区切った開発
  • テスト自動化の推進
  • ビルドパイプラインの構築
  • 開発者環境への継続的デリバリー
  • ふりかえり
  • KAIZEN DAY, KAIZEN合宿

しかしこうした活動の対象範囲は工程全体から見ると一部でしかなく、なかなか工程全体を改善することができていませんでした。最近はNecoプロジェクトといった開発主導の興味深いプロジェクトも進行する一方で、プロダクト開発チームはいわゆるウォータースクラムフォールの状態が続いていました。

f:id:cybozuinsideout:20161107132316p:plain
https://www.scrumalliance.org/community/articles/2015/june/water-scrum-fal

自分がプロダクト開発チームの一員として、どうすれば開発プロセスを改善してユーザーに価値を届けることができるのか。開発プロセスやチーム文化について学んだ結果、スクラムをきちんと学んで実践することで実現できるのではないかという考えに至りました。

トレーニングプログラムの選定

当初は私が認定スクラムマスター(CSM)研修を受けようと考えていたのですが、

  • 費用が高い(1名で約30万円)
  • 普及のためにもチーム全員で受講したい

という理由で見送りました。チーム全員で受講可能なトレーニングプログラムとして、ryuzeeさんにお願いすることになりました。

実施したトレーニング内容

当日はryuzeeさんにサイボウズ東京オフィスにお越しいただき、1日かけて次のような内容でトレーニングを実施しました。

  • アジャイル基礎
  • 紙飛行機ワークショップ
  • スクラム解説
  • マルチタスクワークショップ
  • 要求分析ワークショップ
  • 質疑応答

f:id:cybozuinsideout:20161107132746j:plain設営中

当日は社内のテレビ会議システムでトレーニングの様子を中継し、大阪オフィスのメンバー数名がリモートで聴講しました。

座学

座学は午前と午後で大きく2つに分かれていました。

  • アジャイル開発の基礎
  • スクラム解説

座学で使用したスライドと似たものがryuzee.comのトレーニング概要ページにあります。

参加者のアジャイルやスクラムの理解度にはかなりバラつきがありましたが、基礎的なところから丁寧に解説してもらうことで1日で共通の理解を作ることができました。気軽に質問ができる空気だったので、基礎が理解できているメンバーは積極的に現場への適用方法を質問して理解を深めることができたのも良かったです。

後半のスクラム解説時に、スクラムマスターとは何かを説明するために下記の動画を上映しました。


Super Scrum Master - the power of scrum- 日本語字幕版

これが非常に分かりやすく、参加者に好評でした。その後も社内で他チームのメンバーに説明するために使われるなど、意外な広がりを見せております。

紙飛行機ワークショップ

トレーニングの中では複数回のワークショップを実施しました。中でも最初に行った紙飛行機ワークショップが印象的だったのでご紹介します。

  • 5人のチームを作成
  • 「1人で連続して折ってはいけない」などのルールに従いチームで紙飛行機を制作
  • 3m飛んだ紙飛行機の数を競う
  • 1回の制作をスプリントに見立て、見積もり→計画→制作→ふりかえりを実施する
  • スプリントを4回繰り返す

f:id:cybozuinsideout:20161107132951j:plain製作中

最初はなかなか紙飛行機が飛ばず苦労したチームが多かったのですが、スプリントを繰り返すうちにだんだんと飛ぶようになり、見積もりの精度も改善したことが実感できました。

f:id:cybozuinsideout:20161107133026j:plain結果

またこのワークショップの教訓として、「明示されていないルールは破って構わない」というものがありました。例えば飛行機の発射場から遠いチームは不利なので近くに発射場を作るといった工夫です(残念ながらワークショップ中は気付きませんでしたが)。

感想

全体を通して非常に濃いトレーニングで、参加したメンバーの満足度も高かったです。

スクラムは理解は容易で習得は困難と言われますが、私自身も例に漏れず実践にあたり疑問に持っていたことが沢山ありました。ryuzeeさんに直接相談することで、チームのコンテキストに合わせて「こういう場合はどうするか」というケーススタディが深掘りできたのがとても良かったです。

ワークショップでは計画・ふりかえりのサイクルを回す重要性や、マルチタスクが与える悪影響など、アジャイルの前提となる重要な原則を学ぶことができました。

トレーニング後の変化

トレーニングを受けて終了ではなく、トレーニングがどのような行動の変化に繋がったかが重要です。現在も改善の途中ですが、その後起きた変化にも触れておきます。

色々なところで仕事の進め方について議論が生じるようになった

一番大きい変化として感じたのはこれでした。今までもそれぞれが頑張っていましたが、スクラムというお手本がインプットされたことで、色々なところでスクラムと照らし合わせて仕事の進め方について議論が生じるようになりました。

  • バックログの書き方はこれでいいか
  • スプリントプランニングのやり方は問題ないか
  • 調査タスクはどう扱うべきか
  • 不具合改修のあるべき姿はどんなものか

Readyの定義を作った

トレーニング前に「プランニング期間に何をすべきかが分かりにくい」という問題について議論しており、ちょうど良かったのでトレーニングで学んだReadyの定義を作ってみることにしました。

作ったばかりでまだ運用できていませんが、共通認識を得ることができたため今後取り掛かるアイテムの曖昧さはだいぶ軽減できるのではないかと期待しています。

Readyの定義についてはryuzeeさんご本人の解説記事が参考になります。
プロダクトバックログ項目はReadyなものだけスプリントに投入するべきという話 | Ryuzee.com

プロダクトバックログ改善プロジェクトが始まった

数年前から使っているkintoneで作ったバックログアプリが使いにくく、スクラムで期待するプロダクトバックログの役割が果たせていないことが分かりました。トレーニング後の議論でもここを問題視した意見が多かったため、プロダクトバックログを改善することが決まりました。

こちらも実際に改善するのはこれからですが、トレーニングによって共通の問題意識を作ることができ、改善のための一歩を踏み出すことができたのは非常に大きな成果だと考えています。

他にもたくさん

他にも多くの変化が起きています。この流れを維持して、変化を促進していきたいですね。

  • バックログリファインメントをやることになった
  • PMがスプリントレビューを主催するようになった
  • スプリントプランニングで作業時間の見積もりをした
  • 全体的に時間を守る意識が高まった
  • スクラムマスター紹介ビデオが流行った

まとめ

開発プロセスを改善するためにryuzeeさんにスクラムトレーニングを実施していただきました。トレーニングをきっかけに良い変化が起きつつあります。スクラムマスターとして、この変化を後押しして価値のあるプロダクトを世に届けるチーム文化を作っていきたいと思います。

サイボウズは、開発プロセスも自分たちで議論して改善していくことができる開発文化を作ることができる環境です。 価値あるコードを書きたい方はぜひサイボウズへ!
cybozu.co.jp

みんなで理想を共有! "動く"プロトタイプを導入しました。

$
0
0

こんにちは。サイボウズ デザイングループの樋田(といだ)です。
今年の6月にデザイナーとしてサイボウズへ中途入社しました。
今は社内でプロトタイピングおじさんやってます。

プロトタイピングってもう開発のプロセスには欠かせないものになってますよね。
サイボウズの開発でもいままでプロトタイピングをやってきたのですが、プロトタイピングをより開発に活かすべく新しいツールを導入することにしました。

理想のツールを探して

これまではInVIsionだけでプロトタイプを作っていたのですが、InVisionのプロトタイプだけでは実装とのずれがあって詳細な検討ができない…という不満がありました。
そこで実装に近い"動く"プロトタイプが作れるツールを導入することにしました。

主な目的はこの3点

  • 早く出来る
  • 簡単にできる(学習コストが低い)
  • 再現度が高い

世の中にはたくさんのプロトタイピングツールがあるので 理想のツールを探して片っ端から試してみました。

各ツールそれぞれ特徴があるのですが、大きくまとめると

  • 再現度:Webアプリケーション < ネイティブアプリ
  • 共有: Webアプリケーション >ネイティブアプリ
  • 簡単:Webアプリケーション < ネイティブアプリ

あとはGUI志向のツールよりプログラミングに近いツール(条件分岐などを使う)のほうが プロトタイプで再現できることが多くなります。

上記のような事情を考えつつ最終的に候補に残したのが以下の2つです。

Flinto for Mac

f:id:cybozuinsideout:20161121145804p:plain

https://www.flinto.com/mac

Principle

f:id:cybozuinsideout:20161121145857p:plain

http://principleformac.com/

(なんかどちらもWebサイトが似てる…)

どちらもMacのCore Animationを利用したGUIベースのプロトタイピングツールです。
プログラミングレスでアニメーションも含めたモックアップを手軽に作ることができます。

f:id:cybozuinsideout:20161121173955g:plain
こんな感じのプロトタイプがすぐできる(※デモなので開発中のプロダクトとは関係ありません)


どちらのツールにするかものすごーく悩んだのですが、最終的にはFlinto for Macを選びました。
Flintoは日本語にローカライズされており、日本語のチュートリアルも充実しています。
社内への普及も考えると、学習コストは低いにこしたことはありません。

さっそく使ってみた

さっそく担当のプロジェクトで使ってみました。
Flintoのようなアニメーションを再現できるプロトタイピングツールは、マイクロインタラクションのような細部の検討に使うイメージがあったのですが、 今回は完成イメージの共有のために、要件が決まった段階ですぐにFlintoで動かせるプロトタイプを作ってみました。

みんなの反応

Sketchでざーっとデザインをつくり、Flintoでアニメーションをつけました。大体2〜3時間ぐらいの作業です。 できたものを持ってみんなに見せてみたところ…

  • あれ、もう出来てるの?
  • 動いてるものを見るとテンション上がるね!
  • 作ろうとしているのがイメージしやすくて良いね
  • これそのままリリースできればいいのに…

おお、良い反応です。そのままリリースはできないですけどね…

細部の検討というより、あえて事前に動くものをつくってしまうことで完成イメージを全員で共有することができました。
さらに、動くものを触りながら「これちょっと違和感あるな〜」とか「この実装はちょっと厳しいかも〜」 といったフィードバックも得られやすくなりました!

ついでに社内ヒアリングもしてみた

Flintoで作ったプロトタイプを使って社内ヒアリングもやってみました。 これも絵だけ見せるよりも格段にフィードバックが得られやすくなりました。
ものにもよりますが、言わなければプロトタイプだとバレないレベルでヒアリングができます。

やってみてわかったFlintoプロトタイピングのコツ

実際にプロジェクトで活用してみていくつかポイントがわかりました。

  1. 早くつくる
  2. 小さくつくる
  3. 継続的に改善する

早くつくる

プロトタイプ全般にいえることですがプロトタイプを作るのに時間をかけてしまうと、作った頃には役に立たない…。なんてことになりがちです。
Flintoは細部までこだわることができるツールですが、できるだけ早く形にして共有するのが重要そうです。

小さくつくる

実装に近いプロトタイピングができるFlintoですが、できるのはあくまでプロトタイプです。 実際の製品を再現しようと、本物みたいな巨大なプロトタイプを作ってしまうと検証ポイントがぶれてしまう危険があります。
できるだけ検証したいポイントやストーリーをしぼって小さくつくるとブレずに検証が進められます。

継続的に改善する

プロトタイピングを検証して見つけた改善ポイントはすぐにプロトタイプに反映してみるのが良かったです。 すぐに反映→検証→反映...を繰り返すと実装前にプロトタイプで詰められます。
すぐに改善を反映できるようSketchとFlintoで連携がとれるようにしておくとスムーズです。
Flinto for Mac Sketch Plugin

まとめ

以上サイボウズでのFlintoを使ったプロトタイピングの話でした。
今は

  • 全体の共有 → InVision
  • ストーリーの検証 → Flinto for Mac

という使い分けを自分はしています。 Flinto以外にも色々な手段でプロトタイピングをしているのですが、それはまた別の機会にお話できればと思います。

今後もデザインを開発プロセスで活かすべく改善を続けていきます。 サイボウズで一緒にプロトタイピングしたい方はこちらでデザイングループの紹介をしてます!

cybozu.co.jp

UX Cafe Facebookページでも情報発信中です!

Rでkintoneのデータを取得するパッケージをCRANで公開しました

$
0
0

ドーモ、SREチームの湯谷(@yutannihilation)です。

表題のとおり、kintoneアプリのデータをRのdata.frameとして取得するパッケージ「kntnr」をCRANに公開しました。これで、Rでデータ分析もグラフ作成*1もやり放題です。

CRAN - Package kntnr

※あくまで個人の趣味として業務の合間につくったものです。自己責任でお使いください。

インストール

install.packages()でインストールしてください。

install.packages("kntnr")

使い方

認証

kntnrパッケージは、以下の情報を環境変数として受け取ります。

  • KNTN_URL: cybozu.com の URL を指定します(サブドメイン.cybozu.com
  • KNTN_AUTH_TYPE: 認証の方式を指定します。password(ユーザ名とパスワードを使った認証)またはtokenAPIトークンを使った認証)
  • KNTN_AUTH: ユーザ名:パスワードをBase64エンコードした文字列、またはAPIトークン

Rのセッションに環境変数をセットする方法は色々ありますが、kntn_set_auth()関数を使うと、インタラクティブにこれらの環境変数を設定できます。認証方式はauth_type引数で指定できます。

library(kntnr)# デフォルトはユーザ名とパスワードを使った認証
kntn_set_auth()# トークンを使った認証
kntn_set_auth(auth_type ="token")

間違った情報を入力してしまったときは、overwrite引数にTRUEを指定すると上書きできます。

kntn_set_auth(overwrite =TRUE)

レコードの取得

kntn_records()は、指定したkintoneアプリからレコードを取得します(レコードの一括取得API)。appにはアプリ番号(例:アプリのURLがhttps://サブドメイン.cybozu.com/k/123/なら123の部分)を指定します。

kntn_records(app =123)

レコード一括取得APIで一度に取得できるレコード数は、最大500レコード、デフォルトでは100レコードとなっています(本記事執筆時点)。kntnrパッケージでは、取得したいレコード数が一度に取得したいレコード数を超えている場合は、自動でリクエストを分割してくれます。

取得する最大レコード数はmax_records引数で指定します。デフォルトは1000レコードです。一度に取得するレコード数はrecords_per_requestで指定できます。デフォルトは100レコードです。たとえば以下のように指定すれば、最大200レコードを取得するリクエストを2回まで行います。

kntn_records(app =123, max_records =400, records_per_request =200)

具体的に、こういうアプリがあるとすると、

f:id:cybozuinsideout:20161122133827p:plain

以下のようなdata.frame(正確にはtibble)としてデータをRに取ってくることができます。

d <- kntn_records(app =123)#> Getting 100 records from 0

d
#> # A tibble: 5 × 15#>   レコード番号    作業者 更新者 作成者  文字列__1行_       ステータス ドロップダウン#>          <chr>    <list>  <chr>  <chr>         <chr>            <chr>          <chr>#> 1            5 <chr [1]> cybozu cybozu        懇親会 未申請(下書き)         営業部#> 2            4 <chr [0]> cybozu cybozu          商談             承認         営業部#> 3            3 <chr [1]> cybozu cybozu          商談 未申請(下書き)         営業部#> 4            2 <chr [1]> cybozu cybozu      イベント 未申請(下書き)         営業部#> 5            1 <chr [1]> cybozu cybozu 会社Aへの往訪       上長確認中         営業部#> # ... with 8 more variables: `$revision` <int>, 数値_0 <dbl>, ユーザー選択 <list>,#> #   更新日時 <dttm>, 文字列__複数行__ <chr>, 作成日時 <dttm>, 計算 <chr>, `$id` <int>

ここで、作業者という列に注目してください、tibbleは列名の下にその列の型が表示されますが、<list>となっています。これは、作業者フィールドは複数の値が入る可能性があるためです。kntnrは、こうしたフィールドはlistでラップするようにしています。

ネストしている列を展開する

こうしたネストしたフィールドは、tidyrパッケージのunnest()で展開することができます。やってみましょう。

d %>%
  tidyr::unnest(d,作業者)%>%
  select(レコード番号,作業者,文字列__1行_)#> # A tibble: 4 × 3#>   レコード番号 作業者  文字列__1行_#>          <chr>  <chr>         <chr>#> 1            5 cybozu        懇親会#> 2            3 cybozu          商談#> 3            2 cybozu      イベント#> 4            1 cybozu 会社Aへの往訪

<list>だった作業者の列がしっかり<chr>(character)に展開されています。

ただし、レコード数がひとつ減っていることに気付くでしょうか。これは、レコード番号4のレコードは作業者が空だったためです。unnest()では、指定したカラムにデータが存在しないレコードは消えてしまいます。なるほどこれはこれで正しい挙動でしょう。しかし、dplyrパッケージのleft_join()のようにレコードがなければNAで埋めてほしいときもあります。

そんなときのためにkntn_unnest()という関数をつくりました。kntn_unnest()は、データが存在しないレコードはNAで埋めてくれます。unnest()と違って列名を指定することはできません。すべてのネストした列を展開します。(kntn_unnest()の挙動はまだ模索中なので、今後変更するかもしれません)

d %>%
  kntn_unnest()%>%
  select(レコード番号,作業者,文字列__1行_)#> # A tibble: 5 × 3#>   レコード番号 作業者  文字列__1行_#>          <chr>  <chr>         <chr>#> 1            5 cybozu        懇親会#> 2            4   <NA>          商談#> 3            3 cybozu          商談#> 4            2 cybozu      イベント#> 5            1 cybozu 会社Aへの往訪

ファイルのダウンロード

kntn_file()は、指定したキーのファイルを取得します(ファイルダウンロードAPI)。その際、httrパッケージのcontent()関数を使って読み取ることができるファイル形式であれば、自動で適切な形式に変換してくれます。変換したくない場合は、as="text"またはas="raw"を指定してください。

d <- kntn_records(app =234)

d$添付ファイル[[1]]#> # A tibble: 1 × 4#>                                             fileKey      name#>                                               <chr>     <chr>#> 1 12345678901234567890asdfghjklasdfghjklasdfghjklas  data.csv#> # ... with 2 more variables: contentType <chr>, size <chr># CSVファイルはdata.frameとして読み込まれる
x <- kntn_file(app =234, fileKey = d$添付ファイル[[1]]$fileKey[1])# CSVファイルはテキストとして読み込まれる
x <- kntn_file(app =234, fileKey = d$添付ファイル[[1]]$fileKey[1], as ="text")

その他のAPI

kintoneには他にも様々なAPIがありますが、kntnrパッケージはkintoneアプリのデータ取得に特化しています。今のところレコード取得とファイルダウンロードにしか対応していません。

最後に

需要があるのかまったく分からないまま公開したパッケージですが、よければ使ってみてください。何かあれば、GitHubレポジトリのIssuesTwitterあたりでお知らせいただけると助かります。

(この記事はSREチームの業務とはまったく関係ないですが、)SREチームでは、Rが好きなエンジニアや、周囲でまったく使っている人がいないRの話でも会社ブログに書かせてくれるようなあたたかい職場で働きたいエンジニアを募集しています。SREの募集要項はこちらです。

*1:kintoneではグラフ作成も思いのままにできますが、ggplot2に慣れてしまうとちょっと物足りないときがあります。

Viewing all 690 articles
Browse latest View live