KARAKURI chatbotのオンラインセミナーにpixivのコミュニティマネージャー2名が登壇しました

こんにちは。以前【先輩と後輩 コミュニティマネージャー編】を投稿した、pixiv事業本部コミュニティマネージャー(以下CM)のmonbebeです。皆さんお元気ですか?

今回は、先日KARAKURI chatbotのオンラインセミナーにチーム長のtattsunさんと登壇したことをお伝えしたいと思います。

チャットボットとは

「チャット」と「ロボット」を組み合わせた造語で、「チャット」はテキストを使いネット上でやりとりをすること、「ボット」は人がコンピューターを使って行っている作業をロボットが自動的に実行するプログラムのことを指します。簡単な質問と回答は人力ではなく、ネット上プログラムが会話形式で対応できます。業務の効率化やサービスの向上に、チャットボットは大きく貢献しています。

KARAKURI chatbotとは

KARAKURI chatbotとは、カラクリ株式会社が運営するカスタマーサポート(CS)領域に特化した、大手企業満足度No.1のAIチャットボットです。 自動応答により、顧客のお問い合わせに対して自己解決(セルフ式)できるように促せます。そのため増えるお問い合わせに対し、対応品質を落とすことなく効率的に対応できます。

KARAKURI chatbotを導入した経緯

KARAKURI chatbotを導入する前はお問い合わせフォームからお問い合わせいただくことが必須で、回答までに時間を要する状況でした。また、お問い合わせ件数がサービスの成長とともに増加しており、今後も増えていく傾向にあるため、ユーザー自身で24時間365日疑問を解消できる環境作りが求められており、かつ先行して関連サービスである「BOOTH」がKARAKURI chatbotを導入して、そこで成果が出ているためpixivにKARAKURI chatbotの導入を決めました。

登壇の経緯

pixivは2020年の12月中旬にKARAKURI chatbotを導入しました。導入初期はチャットボットの業界水準の回答率やユーザー満足度を上回ることを目標とし、pixivのCMチームが一丸となってトレーニング(AI学習)に取り組みました。その結果、2021年の5月から現在に至るまで業界水準の回答率を維持するとともに、ユーザー満足度も水準を大幅に上回った形で運用することがでました。

この渦中、カラクリさんから運用方法に関して、セミナーに登壇のお話をいただきました。チャットボットを導入したことで得られたことやユーザー満足度を上げることができた活用のコツをシェアしたく、セミナーに登壇させていただきました!

チャットボットのユーザー満足度を向上させるためにやってきたこと

ユーザー目線での工夫:

まずはじめに行なったこととしては、ページごとに異なる起動メッセージを表示させるようにしました。 また、それぞれの起動メッセージによくある質問と回答をカテゴリ分けした選択肢を表示して、ユーザーが質問したい単語を入力しなくてもクリック・タップだけで目的の回答に辿りつけるようにしました。

「ログインページ」 「イラスト投稿ページ」

加えて、ユーザーが求める回答を出せるようにチャットボットをトレーニング(AI学習)しながら、回答カード設計の精査を行なってきました。

例えば、ユーザーが「イラストが投稿できない」と入力すると、「イラストが投稿できない」という回答カードがヒットします。 しかし、単に「投稿できない」と入力すると、「イラスト」・「うごイラ」・「マンガ」「小説」全てを網羅した「投稿できない」という回答カードがヒットするように組み合わせました。 このように、複数の回答が想定される質問にも、ご希望の回答に繋がる選択肢を提示するように設計しました。

さらに、「ブックマークといいねの違いは?」といった、機能の異なるキーワードが同じ質問内に並列された場合でも、それぞれの回答カードに選択肢として関連・競合する他の回答カードを紐づけることで、どちらの回答カードがヒットしたとしても最終的に求められる回答へたどり着けるように設計しました。

また、選択肢を進んでいっても結果的に解決することのできないものについては、結論(下図赤枠部分)を文頭に配置して、ユーザーの質問時間を極力削減するよう工夫しました。

トレーニング(AI学習)体制の工夫:

各回答カードを種類分けし、その種類に詳しい担当者をアサインして、回答カードの設計の精度を高め、業務の平準化を図りつつ、業務負荷を分散して限られた時間の中で回答カードの設計を精査できる体制にしました。そしてユーザー満足度も種類ごとに目標設定をし、各担当者は自身の担当する種類の目標を達成すべく、責任を持ってトレーニング(AI学習)を行なってきました。

以上の改善を積み重ねることによって、現在では業界水準を上回ったユーザー満足度で運用することができております。 それでもなお、さらなるユーザー満足度の向上を目指し、pixivCM全員でトレーニング(AI学習)に励んでいきます。

当日のセミナーの様子は?

前説が長くなりましたが、ここからは登壇したセミナーについてお話していきたいと思います!

参加者は約30名で、チャットボットを検討しているが何から始めたらよいか迷っている方や、チャットボットを使っているがうまく活用できていないとお悩みの方、他社の活用方法やコツを聞いてさらなるユーザー満足度向上の参考にしたい方などが集まったセミナーでした。

セミナーの構成は、主に我々がチャットボットの活用のコツについて説明し、参加者からいただいた質問へ回答後、交流会で参加者のカスタマーサポートの業務改善の課題感を共有しながら解決の糸口やヒントを話し合いました。

【登壇中の画像】

セミナーの発表で工夫したこと

私もtattsunさんもセミナーの登壇が初めてだったのですごく緊張したのですが、tattsunさんのリードの元で万全な準備ができたかと思います。

カラクリさんとどんな内容を発表するか、当日の流れや発表時間の調整などをこまめにSlackやONLINEミーティングで擦り合わせをしました。カラクリさんの手厚いサポートに感謝しております!

セミナー発表資料を作成する際は、公開してはいけない情報が入っていないかを注意して、参加者にわかりやすく伝えるようテキストは少なめにして、伝えたいことを図式化し、説明が足りない分は口頭で補うようにしました。また、グラフを使う際は、必要な部分だけ抜粋して大きく示すなど「見やすいデザイン」になるよう工夫していました。

資料には管理者目線と実務担当者目線での内容を盛り込み、どの立場の参加者にも興味を持っていただけるものにしました。

最後に

カラクリさんから登壇についてご連絡いただいた際には、「え?緊張する!」という思いでいっぱいでした。セミナー終了後も参加者の皆さんがどう思われたか不安でしたが、参加者から以下のような嬉しいご感想をいただきました。

  • A社から 「チャットボット導入の参考になりました。チャットボットの利用方法について具体的なケースを聞けて良かったです。まだ導入自体検討中ですが、利用時のイメージを持つことができました。」
  • B社から 「ユーザー満足度の向上に悩んでいたので、良いヒントが得られました。弊社も起動カードを設置していますが、よくある質問に、前月の表示数TOP3を設置しているというのを参考にしたいです。」
  • C社から 「他社のチューニング方法や運用体制が知れて、今後運用する参考になった。分析方法や、長文の時の対応等の悩みが解消できた。」

今後も他社様へ有益な情報を発信しつつ、ユーザーの皆様のお悩みをよりスムーズに、正確に解決できるよう、トレーニングや設計の見直しを都度行い、快適にpixivのサービスをご利用いただける環境作りに努めてまいります。

みんなでプロダクトをつくる「おもちゃ箱」の取り組み

こんにちは、pixivのリクエスト機能を開発するチーム(以下、リクエストチーム)で22新卒として内定者アルバイトをしているsaitoです。

リクエストチームでは、コミッションという新しい領域に挑戦しています。リクエスト機能をリリースして1年が過ぎた今でも、ユーザーにより価値を届けるためには何をするべきかをチームみんなで考えて取り組みを続けています。

そんなリクエストチームで、昨年から「おもちゃ箱」という新しい取り組みをはじめたので、紹介します。

背景

リクエストチームでは、2021年に「再送依頼」「ファンレター」「プランの複数設定」などのリクエストの体験を向上させるための本質的な改善を繰り返し行ってきました。

それらのリリースの合間では、プロダクトマネージャーが次のリリースの準備を進めます。この間は、プロダクト開発の凪とも呼べる状態になっていて、エンジニアは不具合の修正や運用の改善、データ分析などに時間を使うようになりました。

このやり方には、1つ1つのリリースに力を入れられるというメリットがありますが、一方でリリースの頻度が落ちてしまうというデメリットもあります。プロダクトがいつも同じだと、ユーザーは新鮮さを感じなくなってしまうのです。

私はプロダクトをつくる人として、「ユーザーに新しい価値を届ける頻度が落ちること」に次第に課題を感じるようになりました。

課題の原因と解決策

今回の課題が発生したのは、リリースにおけるプロダクトマネージャーへの依存が大きいからであると考えました。例えば、「アイデアの提案」と「アイデアに責任を持って進めること」は、ほぼ1人に任せっきりの状態でした。

単純に考えると、これらを誰でもできるようにすれば課題を解決できます。しかし、プロダクトマネージャーの役割をすぐに誰でもこなせるわけではありません。

そのため、まずは「誰でもアイデアを提案できる場所」をつくることにしました。これがおもちゃ箱です。

おもちゃ箱とは

とりあえず何かやりたいと思ったときにアイデアを入れておくNotionのページです。pixivではドキュメント管理にNotionを使っています。詳しい使い方は次の記事で紹介しています。

inside.pixiv.blog

おもちゃ箱という名前には、アイデアの提案を気軽で楽しいものにしたいという思いが込められています。また、アイデアをおもちゃとして例えたからこそ、チームで共通理解を作りやすいというメリットもありました。

例えば、おもちゃを乱暴に扱ってしまうと簡単に壊れてしまいます。同様にアイデアに対して心無い指摘をしたとします。勇気を出した提案者はきっと萎縮してしまうでしょう。したがって、アイデアについて話し合うときは、大切なおもちゃを扱うように丁寧に扱うことにしました。

他にも「業務とは切り離して自由な場所にする」や「おもちゃをそのまま本番環境に導入してはいけない」などのコンセプトが自然と受け入れられました。

また、このようなプロダクトでやりたいことのリストはバックログと呼ばれることがありますが、バックログという言葉よりも「おもちゃ箱」の方が私はテンションが上がります。

f:id:pxv:20220322122744p:plain
おもちゃ箱のNotionページ

おもちゃ箱の運用

おもちゃ箱には、いくつかやり方が決まっています。

まず、おもちゃ箱にアイデアを入れた人は、そのアイデアの実現に責任を持つ必要がありません。もし丁寧にものごとを進めるのであれば、pixivにおいてはインセプションデッキ作りやステークホルダーとの調整をする必要があります。それらをアイデアの提案者に強要すると、アイデアの提案に障壁ができるため、「誰でもアイデアを提案できる場所」とは程遠くなってしまうでしょう。

やることになったアイデアの責任はプロダクトマネージャーが引き受けますが、一部例外もあります。もしおもちゃ箱のアイデアに強い関心があれば、(アイデアの規模によっては) その人の裁量で挑戦しても良いことにしました。これで、プロダクトマネージャーがリソースを割けなくても、アイデアを次に進められます。

次に、溜まったアイデアについてみんなで意見交換をするために、おもちゃを整理する会を開くことになっています。第1回はFigJamで行いました。

f:id:pxvpxv:20220315142316p:plain
おもちゃ箱を整理する会の様子

参加者が順番にアイデアの説明をして、気になるアイデアがあれば意見交換をします。今回はアイデアの対象とプロダクト利用のステップごとに分類して整理しました。FigJamを使うことで、途中にチームメンバー同士でハイタッチをしたり、絵文字やアイコンをぶちまけたりと好き放題やれたのが盛り上がりに繋がって良かったです。

取り組みの成果

ボトムアップでアイデアを提案できる仕組みができた

おもちゃ箱は誰でも使うことができるので、アルバイトの私でも安心してチームにアイデアを提案できます。これは、私に限らず他のアルバイトやインターン、新卒、他にも様々な役割の人が該当します。

「自分は◯◯だから」と引け目を感じてアイデアの提案を諦めるのは非常に勿体ないです。プロダクトを良くするために何ができるのかを考えることはとても尊いので、おもちゃ箱をチームの大切な文化として継続させたいです。

おもちゃ箱から機能をリリースすることができた

先日、おすすめのタグを表示する機能がリクエスト送信ページに追加されました。

この機能では、「クリエイターとファンの好み」がおすすめされるのでリクエスト内容を考える手助けになります。さらに「リクエスト内容」に書いた文章からタグとして適切な単語を取り出しておすすめするので、タグ入力の手間を省くことができます。

チームメンバーの手を借りながらも、アイデアの提案から具体化、開発からリリースまでを私が担当しました。「リリースにおけるプロダクトマネージャーへの依存が大きい」状態を解消する事例の1つになって良かったです。

他のチームにも導入された

おもちゃ箱の取り組みを社内で話していると、複数のチームから「うちでも試してみたい」という声をもらいました。リクエストチームのおもちゃ箱はリクエストチームの課題を解決するために誕生しましたが、ネーミングや考え方は他の課題にも当てはめることができます。おもちゃ箱のやり方は誰でも閲覧できる場所で言語化していたので、他のチームで運用を検討するときの助けになったはずです。

今後、それぞれのおもちゃ箱を運用して得られた知見をお互いに共有することで、より良い運用を目指すことができるでしょう。

最後に

リクエストチームに異動して半年経った頃に、当時のチームリーダーから「saitoさんがやりたいなら1日中プロダクトのことを考える日を作ってもいいよ」と言われたことがありました。しかし、当時は自分の裁量でどこまでできるのかを判断できず、中途半端な状態で終わってしまいました。

しかし、今回のおもちゃ箱の取り組みでは、当時できなかった仕組み作りや裁量の言語化を行って実践するところまでやり遂げられました。チームの成長に合わせて私自身も成長できたのは大きな収穫です。

ピクシブ株式会社では、一緒に最高のプロダクトをつくりたい仲間を募集しています。この記事を読んで興味を持っていただいた方は、以下の求人サイトからエントリーをお願いします。学生アルバイトも大歓迎です!

recruit.jobcan.jp

pixiv Android アプリでのデザインシステムを活用したダークテーマ対応

フリーランスのソフトウェアエンジニアとして活動している djyugg です。ピクシブ株式会社では、業務委託として主に pixiv Android アプリの開発に携わっています。

play.google.com

多くの要望を頂いていた pixiv Android アプリのダークテーマ対応を2021年7月末にリリースしました。

このダークテーマ対応は、ピクシブのデザインシステムの思想を実装して実現しています。今回は、Android アプリでいかにデザインシステムの実装を実現し、ダークテーマに対応したかを紹介します。

なお、今回は主に Android View 向けの内容となっており、Jetpack Compose には触れていないです。

デザインシステムを利用した色の設定

ピクシブでは、デザインシステムを運用しています。

デザインシステムを実現する実装は、ダークテーマ対応開始時点では Android アプリ向けには存在していなかったため、対応開始と同時に実装をはじめました。

Web 向けの内容になりますが、以前公開された記事はこちらです。

前編:

inside.pixiv.blog

後編:

inside.pixiv.blog

デザインシステム内で定義している内容はいくつかありますが、ダークテーマ対応時に主に利用したのは、デザインシステム内でデザイントークンとして定義している色です。

後編の記事にも記載されていますが、用途ごとに利用するセマンティクスを最低限にした色の名前を用意しており、それらを利用することでライト・ダークテーマ毎に適切な色が当たるように設計されています。

この色のデザイントークンを各画面・コンポーネントで利用することでダークテーマ対応を実現しています。

色の実装について

具体的な実装としては、デザイントークンを独自の属性として定義し、ライト・ダークテーマそれぞれで利用する Theme に属性に対応する色を設定する形になります。

また、デザイントークンとは別に、デザインシステム内で利用する色をまとめているカラーパレットが存在しており、属性の内容はカラーパレットの色を設定しています。

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Brand -->
    <attr name="colorPixivBrand" format="color" />
    <attr name="colorPixivBrandDark" format="color" />

    <!-- Semantic -->
    <attr name="colorPixivSuccess" format="color" />
    <attr name="colorPixivAssertive" format="color" />
    ...

    <!-- Surface -->
    <attr name="colorPixivSurface1" format="color" />
    <attr name="colorPixivSurface2" format="color" />
    ...

    <!-- On Surface -->
    <attr name="colorPixivText1" format="color" />
    <attr name="colorPixivText2" format="color" />
    ...
</resources>

themes.xml

<resources>

    <style name="Theme.Pixiv.Light" parent="Theme.MaterialComponents.Light.Bridge">
        <item name="colorPixivBrand">@color/color_palette_xxx</item>
        <item name="colorPixivBrandDark">@color/color_palette_xxx</item>
        <item name="colorPixivSuccess">@color/color_palette_xxx</item>
        <item name="colorPixivAssertive">@color/color_palette_xxx</item>
        <item name="colorPixivSurface1">@color/color_palette_xxx</item>
        <item name="colorPixivSurface2">@color/color_palette_xxx</item>
        <item name="colorPixivText1">@color/color_palette_xxx</item>
        <item name="colorPixivText2">@color/color_palette_xxx</item>
        ...
    </style>

    <style name="Theme.Pixiv.Dark" parent="Theme.MaterialComponents.Bridge">
        <item name="colorPixivBrand">@color/color_palette_xxx</item>
        <item name="colorPixivBrandDark">@color/color_palette_xxx</item>
        <item name="colorPixivSuccess">@color/color_palette_xxx</item>
        <item name="colorPixivAssertive">@color/color_palette_xxx</item>
        <item name="colorPixivSurface1">@color/color_palette_xxx</item>
        <item name="colorPixivSurface2">@color/color_palette_xxx</item>
        <item name="colorPixivText1">@color/color_palette_xxx</item>
        <item name="colorPixivText2">@color/color_palette_xxx</item>
        ...
    </style>
</resources>

Theme切り替え

DayNight Theme を用意することでライト・ダークテーマ毎に参照する Theme を切り替えるようにします。

values/themes_daynight.xml

<resources>
    <style name="Theme.Pixiv.DayNight" parent="Theme.Pixiv.Light" />
</resources>

values-night/themes_daynight.xml

<resources>
    <style name="Theme.Pixiv.DayNight" parent="Theme.Pixiv.Dark" />
</resources>

依存関係

Theme の依存関係を図にすると以下のような形になります。

values-night ディレクトリは、アプリ内でのナイトモードが有効になっているときに利用される代替リソースのディレクトリです。ダークテーマが有効となっている場合は、このディレクトリのリソースが利用されるようになります。

上記のように定義した Theme.Pixiv.DayNight を利用すると、ダークテーマ無効時は Theme.Pixiv.Light 、ダークテーマ有効時は Theme.Pixiv.Dark を参照するようになります。 この代替リソースの仕組みを利用して Theme を切り替えることで、 属性に紐づける色リソースの切り替えを実現します。

AppCompat や Material Components にて用意されている DayNight Theme も同様の実装となっており、それらを参考に実装しました。

この DayNight Theme を各 Activity で利用するよう設定し、デザイントークンに紐づく属性をレイアウトやコード上で参照することで色を適用していきます。

デザインシステムと Material Components for Android

ここからは、Theme やコンポーネントの設定について書きます。

ピクシブのデザインシステムにて、現時点で Android 向けに定義されているコンポーネントのほとんどは Material Design に定義されているものをベースに配色を変えたものとなっております。そのため、 Material Components for Android を活用して実装しています。

現時点では、バージョン 1.4.0 を利用しているため、その前提で続きを書きます。

Color Theming

Material Components for Android に定義されているすべてのコンポーネントを利用するためには、上記ライブラリに定義されている Theme を利用する必要があります。

pixiv Android アプリでは、Theme 適用時のアプリへの影響を最小限に抑えるために Bridge Theme を採用しました。

また、Material Components for Android には、Material Theming というアプリの外観を体系的に表現する仕組みがあります。その中でも、ダークテーマに大きく関わる部分として Color Theming が挙げられます。

今回、Material Components for Android を利用するにあたって、Color Theming の属性には類似する役割のデザイントークンを割り当てています。以下、設定の一部です。

themes.xml

<item name="colorPrimary">?attr/colorPixivBrand</item>
<item name="colorPrimaryVariant">?attr/colorPixivBrandDark</item>
<item name="colorOnPrimary">?attr/colorPixivText5</item>
<item name="android:colorBackground">?attr/colorPixivBackground1</item>
<item name="colorOnBackground">?attr/colorPixivText2</item>
<item name="colorSurface">?attr/colorPixivSurface1</item>
<item name="colorOnSurface">?attr/colorPixivText2</item>
<item name="colorError">?attr/colorPixivAssertive</item>
<item name="colorOnError">?attr/colorPixivText5</item>

ピクシブのデザインシステムでは、デザイントークンが代替の役割を果たすため直接参照することはありませんが、Color Theminig を活用する場合はこれらの属性をレイアウトやコード上で適宜参照する形になります。

コンポーネント

ピクシブのデザインシステムで定義しているコンポーネントは、Material Components for Android のものをベースに定義されてはいますが、配色のルールはデザインシステム向けに変更しています。

上記の実現方法としては、Widget.MaterialComponents. から始まる Style をベースに、デザインシステム向けに変更した Style を定義し、各所でその Style を利用する形にしています。

Widget.MaterialComponents. から始まる Style では、Color Theminig の属性を参照して色の設定を行なっているため、その Style を利用するだけでも多少はデザインシステムの色が反映されます。

しかし、Material Design とピクシブのデザインシステムでは色の利用方法・種類が異なるため、Color Theminig の設定のみではピクシブのデザインシステムで定義されたコンポーネントを再現することができません。そのままでは意図した配色にならないため手を加える必要があります。

そのため、Color Theming 自体はコンポーネントに色を反映するために補助的に利用し、Color Theming のみで意図した配色にならない箇所については別途カスタマイズしていくことになります。

簡単な一例ですが、MaterialRadioButton をデザインシステム向けにカスタマイズする場合は以下のように Style を設定しています。

styles.xml

<style name="Widget.Pixiv.CompoundButton.RadioButton" parent="Widget.MaterialComponents.CompoundButton.RadioButton">
    <item name="useMaterialThemeColors">false</item>
    <item name="buttonTint">@color/pixiv_compound_button_button_tint_color_selector</item>
</style>

この例では、RadioButton が disabled かつ checked の状態が Material Components 標準の Style と異なります。 適用したときの UI の差分は以下の通りです。枠で囲っている箇所が変更点になります。

MaterialRadioButton などのコンポーネントは、Color Theminig で設定した色を元に色を生成・適用する実装が入っており、Widget.MaterialComponents. から始まる Style ではその実装を利用する設定である useMaterialThemeColors が有効になっています。

デザインシステムの実装を利用する限り、 useMaterialThemeColors の実装を利用する必要がないため無効化しています。RadioButton の例では、この設定を無効化した上で RadioButton に割り当てる色を独自に設定している形になります。

この Style を以下のようにデフォルトの Style として Theme に設定することで、必ず Style が当たるようにしています。

themes.xml

<item name="radioButtonStyle">@style/Widget.Pixiv.CompoundButton.RadioButton</item>

これをコンポーネント毎に実装していくことで、コンポーネント単位でデザインシステムの意図に沿ったダークテーマ対応を行いました。

また、Material Components for Android のダークテーマ向け Theme で有効となっている Elevation Overlay は、ピクシブのデザインシステムでは利用しないため設定を無効化しています。 Elevation Overlay の詳細については公式ドキュメント の Elevation を参照してください。

themes.xml

<item name="elevationOverlayEnabled">false</item>

ここまで書いた内容を、アプリ全体に適用していくことで、ダークテーマ対応を実現しました。

まとめと今後

pixiv Android アプリで、ピクシブのデザインシステムを用いたダークテーマ対応方法について書かせていただきました。

Android アプリ向けのデザインシステムの実装は、実質的に Material Componets for Android のラッパーのような形になったと感じています。 このライブラリに含まれるコンポーネントは、柔軟にカスタマイズすることが可能でかつドキュメンテーションもしっかりしていることもあり、今回書いた内容も苦戦することなく実装でき非常に助かりました。

ちなみに、開発期間としては2019年の9月頃から着手し、2021年の7月末にリリースしました。この期間内に、ダークテーマ対応のみ開発していたわけではないですが、おおよそ2年近く作業していた形になりました。 レビューを除き、設計・実装はほぼ私1人で対応しましたが、なかなかに大変でした。。。

開発が始まった当初から、長期的な開発タスクとなることはわかっていたため、少しずつ開発を進めていくやり方も試行錯誤して進めました。 その内容についても今回書く予定でしたが、思っていたよりも文量が多くなってしまったため、この記事内には載せないことにしました。今回の記事が好評であれば、そちらも公開することがあるかもしれないです。

また、今回は Android View 中心の話でしたが、現在 Jetpack Compose での実装も進めておりますので、機会があればそちらについても何かしら記事を書ければと思っています。

最後に、ピクシブさんのほうでエンジニアを募集しています。この記事を見て気になった方は、以下のリンクも是非ご参照ください。

https://hrmos.co/pages/pixiv/jobs/003hrmos.co

pixivのブックマークに関する負荷対策をしました

10/22(金) 追記

この記事で解説している内容について解説する勉強会を開催することとなりました。以下のconnpassよりお申し込みください。

pixiv.connpass.com

pixivではサービスの成長に伴い、気に入った作品に対して付けることができるブックマークの総数が急速に増加しており、ユーザーの皆様に滞りなくサービスを提供し続けるためブックマークに関するデータベース(以後DB)の負荷対策が必要になりました。 2021年2月より対策を行うプロジェクトを発足し、先日ユーザーの皆様にこれまでお掛けしたいくつかのご不便の解消についてお知らせすることができました。

今回はこのブックマークDBに関する負荷対策プロジェクトについて技術的な観点からご紹介いたします。

pixivのブックマークについて

pixivでは気に入った作品に対して♡をすることで利用できるブックマーク機能があります。 ブックマークすることでお気に入りの作品を見返したり、クリエイターへ好きの気持ちを伝えることが可能です。 ブックマークはpixivに投稿できるイラスト*1・小説作品に対して行うことができますが、最も量が多いのがイラストブックマークです。近年サービスの成長やレコメンド機能の改善によって伴って急速に利用数が増加しています。

イラストのブックマークされた回数*2は現在135億を突破し、月間およそ3億のペースで増加しています。 f:id:pxvpxv:20211021115301p:plain

pixivではブックマークデータをMySQL(InnoDB)で管理しています。 pixivは用途ごとにDBを分割しており、イラスト関連のテーブルを収めたDB・小説関連のテーブルを収めたDBといった多数のDB系統を運用しています。その一つの系統としてブックマークDBと呼称している系統があります。

ブックマークDBでは

  • イラストブックマーク
  • ユーザーブックマーク(フォロー・フォロワー)
  • マイピク

を主に扱っており、現在(2021年10月)ピーク時で秒間200~300query/secのINSERT、(mainと3台のreplicaの計4台)30,000~35,000query/secのSELECTを捌いており、以下のスペックです。

  • CPU: Intel Xeon Silver 4110 * 2
  • メモリ: 1TB
  • ディスク: Intel SSD D3-S4510 7.68TB
  • MySQLバージョン: 5.7
  • DBファイルサイズ: 3.4TB

pixivはブックマークをサービス上非常に重要な機能と考えており、ブックマーク数やフォロー数についてユーザー毎の上限を設けるといった制限を極力しないよう運営してきました。 サービス提供開始からの歴史もあり、ヘビーユーザーの中にブックマーク数が100万を超えるユーザーも存在しています。

ブックマークDBの問題について

pixivは2007年9月にサービスインし今年14周年を迎えました。

www.pixiv.co.jp

ブックマーク機能はサービスの最初期から存在しているため、現在のサービス規模を考慮に含めていないテーブルスキーマがありました。そのため近年の急なブックマークの増加に伴い、特に多数のブックマークを持っているケースで非常に重いクエリが発行されることがあり問題になりました。

具体的な例を上げると、非公開・公開のイラストブックマークを持っているか正確に判定するクエリや特定のタグがついたブックマークを抽出するクエリがあります。

改善前のイラストブックマークテーブルのスキーマ ※本番スキーマを一部簡略化しています

CREATE TABLE `pix_bookmark_illust` (
  `bookmark_id` bigint AUTO_INCREMENT COMMENT 'ブックマークID',
  `bookmark_illust_user_id` int COMMENT 'ブックマークしたユーザID',
  `bookmark_illust_illust_id` int COMMENT 'ブックマークされたイラストID',
  `bookmark_illust_comment` varchar COMMENT 'ブックマークコメント',
  `bookmark_illust_restrict` tinyint COMMENT 'ブックマーク公開設定(0=公開, 1=非公開)',
  `bookmark_illust_add_date` timestamp COMMENT 'ブックマーク時刻',
  `bookmark_tag01` varchar,
  `bookmark_tag02` varchar,
  `bookmark_tag03` varchar,
  `bookmark_tag04` varchar,
  `bookmark_tag05` varchar,
  `bookmark_tag06` varchar,
  `bookmark_tag07` varchar,
  `bookmark_tag08` varchar,
  `bookmark_tag09` varchar,
  `bookmark_tag10` varchar,
  `bookmark_illust_delflg` tinyint COMMENT 'ブックマーク削除フラグ',
  PRIMARY KEY (`bookmark_id`),
  KEY `bookmark_illust_illust_id` (`bookmark_illust_illust_id`),
  KEY `bookmark_illust_user_id` (`bookmark_illust_user_id`,`bookmark_id`),
  KEY `user_id_illust_id` (`bookmark_illust_user_id`,`bookmark_illust_illust_id`),
) ENGINE=InnoDB;

改善前の非公開判定クエリ

SELECT 1
FROM `pix_bookmark_illust`
FORCE INDEX (`bookmark_illust_user_id`)
WHERE
  `bookmark_illust_user_id` = :user_id
  AND `bookmark_illust_restrict` = :bookmark_illust_restrict_private
LIMIT 1

改善前のタグ絞り込みクエリ

SELECT
  `bookmark_id`,
  `bookmark_illust_illust_id`
FROM `pix_bookmark_illust`
FORCE INDEX (`bookmark_illust_user_id`)
WHERE
  `bookmark_illust_user_id` = :user_id
  AND `bookmark_illust_restrict` = :bookmark_illust_restrict
  AND (
    bookmark_tag01 = :bookmark_tag 
    OR bookmark_tag02 = :bookmark_tag
    OR bookmark_tag03 = :bookmark_tag
    …(tag10まで続く)
    )
ORDER BY `bookmark_id` DESC
OFFSET :offset
LIMIT :limit

上記クエリは利用しているindexからわかるように、ユーザーに紐づくブックマーク数が増加することによってrowsが増え、それにより大きなDiskReadを発生させる問題があります。 またブックマークに付けることができる10個のタグがそれぞれのカラムに存在するため、検索のために効果的なindexを貼ることが困難ですし、将来的にタグを10個以上増やすといったこともできません。

ブックマークの増加に伴ってこれら効率の悪いクエリの発行によって発生するSlowQuery*3量は増加しており対策が必要でした。

f:id:pxvpxv:20211021115220p:plain
ブックマークDBのSlowQuery量の変化

ブックマークDBはこれまでこの状況を緩和するためにDBの物理メモリ量を増やしてきました、しかし1TB搭載の現在からさらに増加させることは調達の費用・時間コストさらに長期的に見てこのコストが指数的に増加していく点から現実的ではなく、根本的な対処を行うと決定しました。

対処するにあたっては

  • 過去につくって表層だけ整えてきたサーバサイド実装の掘り返し
  • 追加するindexの内容やそれについての検証
  • 100億レコードを超えるテーブルの構造変更

等が必要です。

ピクシブ株式会社ではインフラチームと開発チームは体制上分かれていますが、今回は双方の密な連携が必要であったため、チームに準ずる特定課題への対処の組織としてピクシブで運用している「タスクフォース」を作りインフラメンバーと開発メンバーで対処にあたりました。

具体的な対策内容

複数の手段のあわせ技で対策しました。

論理削除廃止・index追加・ブックマークタグのテーブル分割

SlowQuery増加の問題はindexで効果的な絞り込みができていなかったことに大半が起因します。そのため効果的にindexを利用できるようにすることが目標になりました。

これを実現するため、主に以下3点を行いました。

  • 論理削除フラグを廃止し物理削除化
  • 公開設定を扱う絞り込みクエリに適用できるindexの追加
  • イラストブックマークタグ絞り込みをindexを用いて行うため、タグデータのテーブル分割

当初ブックマークについてはデータ量の多さなどから中長期を見据えMySQL以外への移行(pixivで検索に用いているSolrの利用や分散DBの採用等)も案に出ましたが、不確実性が大きいため、まずは正攻法でイラストブックマークタグを別テーブルとして分離し、タグの絞り込みはそのテーブルから行うことを検討しました。

初めにブックマークで行っていた論理削除を物理削除に変更する対応を、以下理由から行いました。

  • 論理削除用のカラムは今後テーブル設計をする際に余計な要素になる
  • ディスク性能が上がった昨今においては物理削除のコストは許容出来る

その上で以下を考慮し新しいテーブル設計をしました。

  • クエリ発行量が非常に多いため、単一テーブルで完結する構造にする (テーブルJOINを避ける)
  • イラストブックマークタグが設定されていないレコードについても最小コストで抽出出来るようにする

その結果以下のテーブル構造が完成しました。

変更後のイラストブックマークテーブルのスキーマ ※本番スキーマを一部簡略化しています

CREATE TABLE `pix_bookmark_illust` (
  `bookmark_id` bigint AUTO_INCREMENT COMMENT 'ブックマークID',
  `bookmark_illust_user_id` int NOT NULL COMMENT 'ブックマークしたユーザID',
  `bookmark_illust_illust_id` int COMMENT 'ブックマークされたイラストID',
  `bookmark_illust_comment` varchar COMMENT 'ブックマークコメント',
  `bookmark_illust_restrict` tinyint COMMENT 'ブックマーク公開設定(0=公開, 1=非公開)',
  `bookmark_illust_add_date` timestamp COMMENT 'ブックマーク時刻',
  `bookmark_illust_untagged_flg` tinyint COMMENT 'ブックマーク未分類フラグ(0=タグがついている, 1=タグがない)',
  PRIMARY KEY (`bookmark_id`),
  KEY `bookmark_illust_illust_id` (`bookmark_illust_illust_id`),
  KEY `bookmark_illust_user_id_restrict_id_illust_id` (`bookmark_illust_user_id`,`bookmark_illust_restrict`,`bookmark_id`,`bookmark_illust_illust_id`),
  KEY `bookmark_illust_user_id` (`bookmark_illust_user_id`,`bookmark_id`),
  KEY `user_id_illust_id` (`bookmark_illust_user_id`,`bookmark_illust_illust_id`),
  KEY `user_id_restrict_untagged_flg_id_illust_id` (`bookmark_illust_user_id`,`bookmark_illust_restrict`,`bookmark_illust_untagged_flg`,`bookmark_id`,`bookmark_illust_illust_id`),
  KEY `user_id_restrict_untagged_flg_illust_id_id` (`bookmark_illust_user_id`,`bookmark_illust_restrict`,`bookmark_illust_untagged_flg`,`bookmark_illust_illust_id`,`bookmark_id`)
) ENGINE=InnoDB;

変更点

  • 論理削除フラグ、ブックマークタグカラムを削除
  • ブックマークしたユーザーについてさらに公開設定で絞り込めるようにするindexを追加
  • ブックマークタグがついているかどうかを示すカラムを追加し、indexを追加

新しく作成したイラストブックマークタグテーブルのスキーマ

CREATE TABLE `pix_bookmark_illust_tag` (
  `bookmark_id` bigint COMMENT 'ブックマークID',
  `bookmark_illust_user_id` int COMMENT 'ブックマークしたユーザーID',
  `bookmark_illust_illust_id` int COMMENT 'ブックマークされたイラストID',
  `bookmark_illust_restrict` tinyint COMMENT 'ブックマーク公開設定(0=公開, 1=非公開)',
  `bookmark_illust_tag_name` varchar COMMENT 'ブックマークタグ',
  PRIMARY KEY (`bookmark_id`,`bookmark_illust_tag_name`),
  KEY `user_id_restrict_tag_name_id_illust_id` (`bookmark_illust_user_id`,`bookmark_illust_restrict`,`bookmark_illust_tag_name`,`bookmark_id`,`bookmark_illust_illust_id`),
  KEY `user_id_restrict_tag_name_illust_id_id` (`bookmark_illust_user_id`,`bookmark_illust_restrict`,`bookmark_illust_tag_name`,`bookmark_illust_illust_id`,`bookmark_id`)
) ENGINE=InnoDB;

ポイント

  • 元々のブックマークテーブルにあったtag01~tag10についてをこのテーブルで再現
  • JOINが不要になるようにクエリで必要なカラム(ユーザID等)をこのテーブルにも配置

テーブル構造が固まった後は、この構造で本当に問題ないのかを確認するため、サービス環境で実際に発行されているクエリを取得し、テストクエリを作成・検証環境で実行し並列で高速に捌くことができることを検証しました。 その結果、テーブル構造の変更によってこれからの運用が可能であることが分かりテーブル構造の変更を行うことにしました。

テーブル構造を変更することにより、諸問題を解消することができることが分かりましたが、100億以上のレコードを持つブックマーク関連テーブルに対する1スレッドでのALTER TABLEは現実時間で完了しないため、特別な対応が必要でした。

ALTERは大まかに以下の手順で擬似的に実現できます。

  1. ユーザーリクエストを受けていないDBを用意しレプリケーションを追従させておく
  2. レプリケーションを停止
  3. 移行したい構造を持ったテーブルを ${table_name}_newのような名前で新規に作成
  4. 一定区間に切りながらデータを旧テーブルから並列にSELECTし新テーブルにINSERTするスクリプトを実行
  5. 旧テーブルをdropし、新テーブルをrenameして置き換え
  6. レプリケーションを再開させ追従
  7. 新しいテーブル構造を持ったDBファイルができるのでこれを本番のreplicaに配布
  8. replicaの1台をmainに昇格(旧mainはパージ)

区間を切りながら並列にSELECTしINSERTして新しいデータを作成することはALTER文より高速なので現実時間で構造変更ができます。 イラストブックマークの場合、新しいテーブルのデータを作成するスクリプトに1週間。作業の中で新旧テーブルの内容がズレていないかといった確認や本番DBのデータ置き換え作業等を含めると一回約1ヶ月を要しました。

この擬似的なALTERをベースとし、必要に応じてトリガーやデータを補正するためのバッチスクリプトを組み合わせ、pixivを無停止でテーブル構造の変更を行いました。

テーブル構造の変更を行ったことで、様々なクエリをindexを効かせて効果的に捌く事が出来るようになりました。先に例示した一部ケースで非常に重くなってしまうクエリなどは下記のように変化しました。 先のケースでは一部クエリで本番実行すると非常に重く障害に至る可能性がありましたが、改善後のクエリはユーザーのブックマークが増加しても高速に応答出来るようになりました。

改善後の非公開判定クエリ

SELECT 1
FROM `pix_bookmark_illust`
FORCE INDEX (`user_id_restrict_untagged_flg_id_illust_id`)
WHERE
  `bookmark_illust_user_id` = :user_id
  AND `bookmark_illust_restrict` = :bookmark_illust_restrict_private
LIMIT 1

改善後のタグ絞り込みクエリ

SELECT
  `bookmark_id`,
  `bookmark_illust_illust_id`
FROM `pix_bookmark_illust_tag`
FORCE INDEX (`user_id_restrict_tag_name_id_illust_id`)
WHERE
  `bookmark_illust_user_id` = :user_id
  AND `bookmark_illust_restrict` = :bookmark_illust_restrict
  AND `bookmark_illust_tag_name` = :bookmark_illust_tag_name
ORDER BY `bookmark_id` DESC
OFFSET :offset
LIMIT :limit

適応ハッシュインデックスの無効化

今回新しいタグテーブルの作成や、既存のイラストブックマークテーブルにindexを新たに追加しまた。それによりDBのファイルサイズ増加と、indexの更新コストが増えたことで、index更新時のロック待ちによる競合が悪化する懸念がありました。

事前にindexあり・なしで更新の負荷が問題ないか検証を行い、問題ないことを確認した上で一部本番サーバに適用を行いましたが、本番環境では定期的にデータ削除を行うバッチ処理が走っており、その直後からロック待ち状況を表しているSemaphoresの値が悪化していることに気づきました。

このままではロック待ちが大量に発生することで、実行クエリが滞留し障害になる可能性もあるため改善に向けた調査・対応を行うことにしました。 詳細については紆余曲折ありここに記載しきれないので割愛しますが、以下の対応を実施しました。

  • “SHOW ENGINE INNODB STATUS” コマンドを実行し ”btr0sea.cc” 内の処理で大量にロック待ちが発生していることを確認
  • MySQLのソースコードから該当処理を調査し、適応ハッシュインデックスにおけるindex更新処理でロックを取っていることが判明
  • 適応ハッシュインデックスにおけるヒット率を確認し、ヒット率が低いことを確認した上で、適応ハッシュインデックス(innodb_adaptive_hash_index)の無効化を実施

適応ハッシュインデックスを無効化した後、どのぐらいロック待ちが改善したかは以下グラフを見ていただければ歴然だと思います。 今回のブックマークDBにおいては適応ハッシュインデックスはヒット率が低く、逆に更新コストにおけるロック待ちの悪化というデメリットの方が大きかったため無効化を行いました。

しかし環境によっては有効化していたほうがクエリの高速化やディスクIOを抑えられるというメリットもあります。適応ハッシュインデックスはデフォルト有効なので、ヒット率やロック待ち状況などを総合的に判断して有効・無効の判断を行うことになるかと思います。

f:id:pxvpxv:20211021120943p:plain
ブックマークDBのSemaphoresの変化

アプリケーションコードのリファクタリング・全発行クエリの列挙と見直し

DB負荷に対処する上でアプリケーションでどのようなクエリが何を目的として発行されているのかを把握することは本質的な改善を行う上で重要です。重いクエリでもプロダクト側としてみると重要ではないクエリで、軽いクエリに置き換えられたり、そもそもの機能の要件に対してアプローチできたりするからです。

しかしpixivにおいてブックマーク関連実装は最初期に作られ、ほとんど手をいれずに必要になった段階で外側を整えてきた結果、内部実装は混沌とした状態になっていました。全く同じようなクエリがあったり、必要ないカラムを取得していたり等です。

ただ幸いにも、pixivはモノレポジトリ構成を採用しておりコードは1レポジトリだけにあり、DBへのアクセス方法は1つ(内製のPDOラッパーを利用)で、またpixiv以外からブックマークDBへの直接アクセスを行うサービスはありませんでした。

実装を整理する銀の弾丸はないので、地道にリファクタリングを繰り返し、不要なSELECT *を廃止し必要なカラムだけを取得するようにし、適切に関数分割と統合を行い、最終的にブックマークDBへの全クエリをスプレッドシートにまとめ整理しました。取得するカラム・絞り込む条件・ソート等の観点で類似のものをまとめたクエリ数はイラストブックマークで約30種類でした。

これらクエリをインフラチームと開発チームで確認しindexを検討し対応しました。

大きな更新処理の非同期化

pixivはブックマークタグを書き換える機能を持っています。 これは既存のブックマークに付けたタグを一括して別のタグ名に変更する(必要であればマージ)処理ですが、ブックマークに多数同じタグを付けている場合大量レコードのUPDATEを発生させる問題が従来からありました。

今回のイラストブックマークタグの書き換え機能では最大で50万行程度の更新が見込まれました。 ユーザー起因処理で一度に50万のレコードをアップデートすることは現実的ではないので、今回pixivではこれらのユーザーリクエストをキュー化し非同期に毎秒一定数を更新できるようにしました。

pixivではこれまでこの手の処理に対して都度DBに作ったキューテーブルとJenkinsを用いたバッチ実行で実装していましたが、リトライ等まで考えると都度実装の手間が大きいという問題がありました。今回GoogleCloudPlatformのCloudTasksをタスクコントローラーとして用い、pixiv側のDBと連携して一つの大きな処理を複数に分割して実行できる処理基盤をpixivに実装しました。

これによりブックマークタグの書き換えだけでなく、pixivで大量のデータ更新を免れない要件を持つ機能に対して容易にDB更新量を一定に抑えたうえで実現することができるようになりました。

結果

諸々の対応の結果、6月のindexの追加、9月のタグ絞り込みに関するテーブル分割の完了により、ブックマークDBのSlowQueryの数を最終的にほぼ0にすることができました。

また対応以前は出ていたSlowQueryの大半について処理時間が数秒~数十秒かかる物だったのですが、対応後にわずかに出ているSlowとなるクエリは1秒未満のものとなっています。 クエリの応答時間はpixivのブックマークページ等の表示時間に直結するので、場合によっては表示に時間を要していたページも高速に表示されるようになりました。

f:id:pxvpxv:20211021121208p:plain
ブックマークDBのSlowQuery量の変化

そしてブックマーク数の増加が特段の懸念にならない状態になったので、サービス上で多数のブックマークを持っている場合にやむなく行っていた以下の制限を解消しました。

  • イラストブックマークタグ絞り込み
  • イラストブックマークタグ書き換え

今回の取り組みによって過去困難であったブックマークの根本的な問題に対処し、ユーザーの皆様にこれまでお掛けしたいくつかのご不便の解消ができました。 pixivはこれからも機能の改善に努めてまいります。

あわせてよみたい

*1:マンガ・うごくイラストを含む

*2:ブックマーク後解除したかどうかを問わない・非公開のブックマークを含む

*3:実行に0.1秒以上かかるクエリ

先輩と後輩 コミュニティマネージャー編

こんにちは、pixiv運営本部でコミュニティマネージャー(以下CM)業務を担当しているnakotchとmonbebeです!

まずは先輩のnakotchの自己紹介から始めます

ピクシブ株式会社(以下ピクシブ)へは、2019年2月に中途入社しました。 入社時はpixivFACTORYでコミュニティマネージャーを経験し、2020年7月にpixiv運営本部へ異動してきました。 前職はとある金融会社で働いておりましたが、「好きなことの知見を生かして働きたい、創作をもっと身近に感じたい」と思い、転職を決意しピクシブへ入社しました。趣味は絵を描くこととサバゲーで汗を流すことです。最近、愛銃を買って毎日磨いて戦いに備えています。

現在の仕事内容は、主にpixivに関するお問い合わせや違反報告対応、ヘルプページの整備、企画の立案から運営、デザインなどを行っております。

つぎに後輩のmonbebeの自己紹介をさせていただきます

私は、中国の吉林省出身です。もっと日本のことを知りたい、実際に日本をこの目で見てみたいと強く感じ、留学することを決意し、2017年に日本の大学院を卒業後、日本で働き始めました。前職はとあるホテル予約サイトのインバウンドチームにて顧客対応を経験し、2020年9月にピクシブへ中途入社しました。 キャリアアップやスキルアップのため、また、ピクシブのフラットで風通しのよい社風に惹かれ、転職を決意し入社しました。 現在の仕事内容はnakotchさんと同じく、主にpixivに関するお問い合わせや違反報告対応、ヘルプページの整備、他プロジェクトのコミュニケーション進行、チャットボットの運営などを行っております。

お互いの出会った時の印象

nakotch: 日本語だけではなく、英語や中国語、韓国語が堪能な人が入社してくると聞いたときには驚きました。pixivのCMに異動してきて、初めての後輩女性社員だったので嬉しかったです。monbebeさんとは日本と外国の文化の違いを話したりします。日本では当たり前のように感じていたものが実は日本独自の文化だったことに気づいたりと日々勉強になっています。

monbebe: 日本語を使うことにあまり自信がなかったため、外国人社員として業務をこなせるかどうか、不安なまま入社したのですが、先輩がおすすめのマンガやアニメ、そしてサバゲーなどを紹介してくれて、緊張をほぐしてくれました。私が理解しやすいようにと工夫しながら、優しく説明してくださったり、こういうやり方が効率が良いです、と付箋に書いてくださってりして、より早くチームに溶け込むことができました。

チームで価値を最大化

pixivには週に1,500件以上のお問い合わせが届きます。 対応者によって返答が異なってしまっては、カスタマーサポート失格です。 誰が対応をしても同じ品質の返答ができるよう、アルバイトさんも含め日々サービスの質を向上させる環境作りを行っております。

ピクシブでは「チームで価値を最大化」という考えがあります。これはチームで仕事をすることで、個人では成し遂げられないスケールとスピードで価値を生み出すということです。

pixivのCMではイラスト、マンガ、小説など様々な分野に特化したメンバーがいます。1人で全てを把握することは難しいですが、それぞれのスペシャリストがいることで、スピーディーに幅広いユーザーをサポートすることができます。 気軽に相談できる環境、この人ならわかるだろうという信頼、チームへの尊敬の気持ちが最高の価値を生み出すことができると考えています。 これからも向上心を持ち、様々な業務に励んでいきます。

お昼すぎに紅茶タイムをする

「そろそろお茶の時間にしない?」の声から、私たちは各々おすすめスイーツを持ち寄り、紅茶を飲みながら雑談をし、休憩をしてます。

仕事のことやプライベートのこと、最近食べたもの、面白かった映画の話など話題は様々です。また、CMの仕事だけではなくCM以外の業務を担当することもあるため、個人のタスクを把握することはなかなかできません。他部署のタスクと合わせて、いっぱいいっぱいになっていないか、一人で抱え込んでないかも話します。 急ぎのタスクがあった場合、事前にスケジュールを調整できたりするので、この紅茶タイムが実は重要だったりします。「〇〇のタスクが結構時間がかかりそう」と相談すると「私も手伝いますよ!」と手伝ってくれるmonbebeさんには頭が上がりませんね。

今まで食べたスイーツで一番好評だったのは外苑前のお店のクレープです。

このご時世で今はなかなか対面で紅茶タイムを設けることができないので、落ち着いたら二人で旅行とかいけたらいいですね。

最後に

nakotch: 入社当初、別業界からの転職ということもあり、右も左もわからない私に先輩が文章の作り方や考え方、CMとはどうあるかを丁寧に教えてくれました。先輩から教わったことをこれからも後輩にも受け継いでいきたいなと思います。 ピクシブでは役職の壁を越えて様々な業務にチャレンジすることができます。現在はCMでありながら企画を立て、運営する機会をいただいているので、CMだからこそできる、ユーザーに寄り添った楽しくてワクワクする企画を作っていきたいです。後輩のフレッシュさとやる気にいつもエネルギーをもらっているので、私も負けないよう頑張ろうと思います!

monbebe: CMの業務はチームメンバー同士で助け合いながら、難題を一つ一つみんなで一緒に解決しています。温かい雰囲気に感謝して、更に意欲的に仕事に取り組み、もっとチームの力になりたいです。CMとしての業務以外でも、任せてもらえる仕事が増えたことで、責任も感じますが、段々と仕事が面白くなってきました。いつかnakotch先輩のように自分で考えて動けるようになり、他部署の仕事内容にも目を向け、自分の仕事を多角的に見ることができればと思います。現在はとにかくチームのみんなと一緒に働くこと自体が楽しいので、日々の時間を大切にしています。

これからも、みなさまに快適にサービスをご利用いただけるよう、チーム一同頑張ってサポートしてまいります。

現在、福岡CMでは新たな仲間を募集しています。 本記事をご覧いただき興味をお持ちになられた方は、以下応募サイトよりエントリーをお願いいたします。

https://recruit.jobcan.jp/pixiv/show/b002/278185recruit.jobcan.jp

コミュニティマネージャーがリクエスト機能の立ち上げで感じたこと

こんにちは。ピクシブでコミュニティマネージャー(以下CM)をしているtsuboです。
今回は、2020年9月にpixiv上に追加された「リクエスト機能」立ち上げの話を、CM目線でお伝えします。

立ち上げの経緯

2020年5月ごろ、その日の仕事を終え、自席でまったりインターネットを楽しんでいる時に、リクエスト機能のプロダクトマネージャー(以下PM)から「ねえねえ、リクエスト機能のCMが今誰もいないんだけどさ、tsuboさんやらない?」と話を受けました。

実は、こういった流れで新しいお仕事が振ってくるのは弊社あるある。普段から社員同士が気軽に声を掛け合えるコミュニケーションができるからこそです。

リクエスト機能とは?

リクエスト機能」とは、pixiv上でファンがクリエイターに対し、対価を払って自分好みの作品をリクエストできる機能です。
2021年3月現在、リクエストされた件数はなんと20,000件!今もなお加速し続けている新機能です。

とにかくキャッチアップ

リリース半年前、PMやエンジニア、デザイナーがすでにキックオフを終えているなか、突如リクエストチームに飛び込んだ私。
リクエスト機能上でできること・できないことの数々から、どんなリスクが予想され、それにどう対応していくかなど、毎日ひたすら情報をキャッチアップしました。

エンジニアと協力し「管理画面にはこんな機能があると嬉しい」「このリンクはどこに置いたらいいかな」と、毎日相談しては設計していく、そんな日々が続きました。

その甲斐あって、リリース時には皆様にご満足いただける体制はとれたかと思います。

また、リクエスト機能では画面上にヘルプページへのリンクを多く置いています。このリンクの数は弊社他サービスと比べてもかなり多く、利用するユーザーさんが画面上で問題を解決できるよう促しています。
ヘルプページには簡潔かつ必要な情報があってほしい。そんな想いから、PMとデザイナーと相談しながら記事をつくっていきました。

リクエストCMの普段の業務

リクエスト機能のCMは、お問い合わせの対応はもちろん、ファンからクリエイターに送られる依頼文のチェック、投稿されたイラスト・小説の内容のチェックも行っています。
リクエストの年齢制限設定(全年齢・R-18)に沿っていない依頼文があった場合、ファンの方に連絡をしたり、投稿された作品が利用規約やガイドラインに沿っていない場合は修正いただくよう、クリエイターの方と連絡を取っています。

また、エンジニアと連携したバグの修正や、決済チームと連携した支払いや返金に関する調査など、とにかくリクエストCMはたくさんの社員と関わっています。

最後に

ピクシブでは、こんなふうにプロジェクトの立ち上げに参加することがままあります。
経験をしたくてもそうタイミングよくプロジェクトが立ち上がることもないので、この経験はとても貴重ですし、機能やサービスを一からつくっていくことはとてもやりがいがあります。

現在は他部署の業務にあたっているため2020年12月末をもって私はリクエストチームを離れていますが、引き続き、頼もしい仲間たちが対応してくれています。
もっともっとリクエスト機能がユーザーさんに寄り添った良い機能になるよう、日々開発を続けています。
リクエスト機能を使っていてわからない点、疑問に思った点がありましたら、ぜひお問い合わせフォームからご連絡ください。チーム一同、全力で対応します。

本記事をご覧いただき興味をお持ちになった方は、以下応募サイトよりエントリーをお願いいたします。 https://recruit.jobcan.jp/pixiv/list?category_id=1845recruit.jobcan.jp

CIで Androidアプリのライブラリ更新を楽にする

こんにちは。ピクシブ株式会社でpixiv Androidアプリの開発を担当しているverno3632です。

今回は弊チームで導入しているライブラリ更新の仕組みについてお話します。

ライブラリ更新の流れ

まずpixiv Androidアプリの開発で利用しているツールを紹介します。基本的にGitHub上で開発を行い、CIにはBitrise、コミュニケーションツールにはSlackを利用しています。

GitHubへの更新をフックにしてBitriseでCIが走り、結果がSlackに投稿されるようになっています。

ライブラリ更新の流れはおおまかに以下のようになっています。

  • PR作成
  • 影響調査
  • マージ

PR作成

アプリ開発にはライブラリの更新作業がつきものですが、以前までは温もりのある手作業で運用されていました。

ライブラリの更新があるかの差分を出す Gradle Versions Plugin を週1回自動で動かし、その結果をSlackに投稿し、これを見てそれぞれのライブラリについて空いた時間に更新していく運用でした。

しかしpixiv Androidアプリの開発チームのプロジェクト運用では、更新タスクがどんどん溜まっていってしまいました。

Dependabot導入

そこでDependabotを導入しました。GitHubさえ使っていれば連携するだけで利用できます。   定期的にDependabotがライブラリの更新をチェックしてくれて、新しいバージョンがあるときにPRを自動で作ってくれます。   PRに対する様々な操作はコメントから行うことができ、"majorバージョンが上がるまでPRを作らない" といった設定が可能です。

例えば現在使っているバージョンと最新バージョンが大きく離れており、慎重に時間をかけて上げたいということがあります。このときpatchバージョンなど細かい更新は一旦無視したいため、設定すると便利です。

build.gradleとは別のファイルに定義されたライブラリの更新

Dependabotはgradleファイルに記述されたライブラリについて、バージョン番号を最新に変更したPRを出してくれますが、変数で定義してあっても更新してくれます。

pixiv Androidアプリではライブラリとバージョンの定義を1ファイルに定義し、それを build.gradle から利用する形にしていました。マルチモジュールを導入しており、異なるモジュールでも同じライブラリを用いる場合はバージョンを統一したいためです。

ここがハマリポイントで最初はDependabotがうまく動いてくれなかったのですが、以下の設定で解決しました。

影響調査

Dependabotが作ったPRはノーチェックでマージするわけにはいきません。

差分確認

ライブラリの更新にあたり、どの修正が行われたかを確認する必要があります。通常はライブラリのChangelog等を見る必要がありますが、Dependabotでは自動的に取得できるものについてPRに載せてくれます。

再帰的な依存ライブラリの差分確認

追加で確認しなければいけないのが、対象のライブラリが依存しているライブラリです。

ライブラリの更新内容にその依存ライブラリのバージョンアップが含まれることはよくあります。Changelog等から気づけると良いのですが、見落とすことも多く、その依存ライブラリの更に依存ライブラリまで考慮することは難しいです。また例えばbuild.gradleにライブラリをバージョン1.0で明示的に指定し、他のライブラリがそのライブラリのバージョン1.1に依存している場合、アプリが利用するのは明示的に記述していないバージョンである1.1になってしまうことがあります。

以前は更新前後に ./gradlew app:dependencies を行い、その差分を手動で確認していました。 ライブラリの依存関係は木構造なので更新があったライブラリはどこから依存されているか見たいのですが、diffコマンドを使うと更新があった行しか表示されません。

そこでライブラリの更新が依存元も合わせて表示される dependency-tree-diff を導入し、CIで自動で動作するようにしました。

以下はBitrise上でCIを走らせる際に用いるスクリプトです。

PRのマージ前後のコミットでの依存関係をファイルに書き出し、 dependency-tree-diff での差分を環境変数で次のステップに渡しています。次のステップではこの結果をPRへコメントしています。

./gradlew clean :app:dependencies --configuration productionReleaseRuntimeClasspath> $BITRISE_DEPLOY_DIR/new.txt
git fetch
git checkout origin/$BITRISEIO_GIT_BRANCH_DEST
./gradlew clean :app:dependencies --configuration productionReleaseRuntimeClasspath> $BITRISE_DEPLOY_DIR/old.txt

wget https://github.com/JakeWharton/dependency-tree-diff/releases/download/1.1.0/dependency-tree-diff.jar
chmod +x dependency-tree-diff.jar
./dependency-tree-diff.jar $BITRISE_DEPLOY_DIR/old.txt $BITRISE_DEPLOY_DIR/new.txt > diff_text
cat diff_text 

if [[ -s diff_text ]]; then
  echo '```diff' >> quoted
  cat diff_text >> quoted
  echo '```' >> quoted
  envman add --key COMMENT_BODY --valuefile quoted
  envman add --key EXIST_BODY --value "true"
fi

markdownのシンタックスハイライトに diff を指定することで、差分が色付きでよりわかりやすくなっています。

issueの確認

後から気づいたのですが、以前はライブラリ更新を反映するまでの時間が遅れたことで、ライブラリ側でバグの発見が行われ結果的にバグが発生しているバージョンを避けることができていました。

Dependabot導入でライブラリ更新を即時行えるになりましたが、バグのないアプリをリリースするためにも、PRが作られてもすぐにマージはしていません。2週間ほど様子を見た上で各ライブラリでissueが上がってないか確認しています。その後動作確認を行っています。

マージ

ここまで確認できて晴れてマージすることができます 🎉

例外的に自動マージを許容するパターン

実はDependabotには、あらかじめ指定していたライブラリをビルド成功後に自動でマージする機能があります。

本番に影響無いような、テストやデバッグでのみ使われるライブラリに限ってはマージもDependabotに任せています。特にテスト系のライブラリは頻繁に更新されるので、ここが自動マージできるだけでもライブラリ更新全体のコストはかなり下がります。

まとめ

pixiv Androidアプリでのライブラリ更新についてお話しました。ここではDependabotを用いることでライブラリの更新のコストを下げることができました。また運用方法を工夫したり依存関係の差分を自動で出力することで、より安全なバージョンアップを行っています。

今回は紹介しませんでしたが、他にもZapierのようなタスク自動化ツールやCodeClimateといった静的解析ツールを用いて効率を上げながら開発を行っています。

この記事でピクシブに興味をもった方がいれば是非、弊社で一緒に働きましょう!ピクシブ株式会社では、Androidアプリエンジニアを 新卒中途 ともに絶賛募集中です!

hrmos.co

hrmos.co