pixivリクエストで新しい創作の形を提供 リクエスト部の仕事とは

こんにちは。広報部のmikoです。
今回は、pixivリクエストの機能開発を継続的に進めているpixiv事業本部 ユーティリティユニットチームリクエスト部のメンバー3名に、仕事の内容やチームの雰囲気についてお話を伺いました。

自己紹介をお願いします。

wata:2018年に入社し、マネージャーを務めています。前職はスマートフォンゲーム開発会社でシステム開発に携わっていました。趣味はダーツとコーヒーです。

saito:2020年からアルバイトとしてピクシブで働きはじめ、2022年に新卒入社しました。2020年のpixivリクエストリリース直後からチームに参加し、様々な機能開発に関わってきました。最近は自炊にハマっています。

izmipo(いずみぽ):前職はIT系スタートアップ企業で、2021年にピクシブに入社しました。現在はpixivリクエストのPM(プロダクトマネージャー)として働いています。クラフトビールと読書が好きです。

リクエスト部について教えてください

wata:まず、pixivリクエストとは、ファンがクリエイターにつくって欲しい作品を有償で依頼できるpixivの機能です。クリエイターとファンの「好き」の気持ちをつなげ、創作活動を支える場のひとつとなっています。

リクエストサービスページ www.pixiv.net

リクエスト部は、pixivリクエストの開発や改善を行うチームで、ファンとクリエイター双方の視点に立ったプロダクト改善に日々取り組んでいます。

具体的な業務内容について教えてください

wata:マネージャーとしてチームの目標設定と進捗管理を行いながら、PMやエンジニア、デザイナーと連携しています。各メンバーの強みを活かしながら、プロジェクトの進行調整や、技術面やプロダクトの方向性についての意思決定に関わっています。

saito:pixivリクエストの機能開発や運用・保守に関わるエンジニアリング全般を担当しています。ユーザーからのお問い合わせに関する調査やデータ分析のサポートなども行うほか、pixivリクエストだけに留まらずpixiv全体に関わる取り組みを行うこともあります。

izmipo:プロダクトバックログの運用や、プロジェクトの起案と全体進行などに携わっています。リクエスト部では要件定義の段階からエンジニアと一緒に決めていくことが多く、PMとしてディスカッションの「場」を用意することも大きな役割です。

過去の印象的な業務はありますか?

wata:印象的なのは、pixivリクエストの立ち上げメンバーとして担当した決済周りの設計や実装です。決済基盤を扱うチームや経理チームと調整を重ねながら進行し、スムーズかつ安全な決済フローの構築を目指しました。現在も安定して稼働しており、ユーザーが安心して利用できるサービス基盤を支えている点に大きなやりがいを感じます。

saito:大規模なプロジェクトにリードエンジニアとして参加したことです。効率的に開発を進行できるように、事前に設計や技術選定のレビューをする仕組みを整えたり、開発経験が少ないメンバーでも自走できるようにドキュメントを整備しました。自分も他のメンバーも高い生産性を維持してプロジェクト終盤まで開発を進めることができたと思います。

izmipo:[クリエイターがリクエストの募集を再開したときに通知を受け取れる機能]の開発です。一見シンプルな機能ですが、開発の過程で多くの議論がありました。「さまざまな理由でリクエスト募集をお休みしているクリエイターの存在をどこまで可視化するか」「モチベーションに繋がるベストな通知頻度や内容は?」といった課題を、ターゲットの視点に立ちながら検討を重ねました。ありがたいことに、予想以上に多くのユーザーにご利用頂けています。

仕事のやりがいや楽しいところは?

saito:コミッションという事業ドメインはまだ新しく、今も成長し続けているため、pixivリクエストのシステムも常にアップデートしています。急速に変化する環境下で、ユーザーへ新たな価値を迅速かつ安定的に提供するためには、高度なエンジニアリングが不可欠です。エンジニアとして、そのような環境で腕を振るえることにやりがいを感じます。

izmipo:複数のステークホルダーの視点を踏まえて解決策に落とし込むのは、難しいですが取り組みがいがあります。「pixivリクエストのユーザー」と一言で言っても、クリエイター、ファン(リクエスト送信者)、相乗りファン(既にあるリクエストに同じ金額を上乗せして応援するファン)と様々な形が存在します。そのため一方のメリットを追求すると、他方の負担になることも発生し得ます。プロダクトとして一貫した方針はありますが、最終的な着地はステークホルダーを取り巻く状況を加味して決めることが多いです。

チームとしてはどのように連携していますか?

wata:リモートワークが基本で、必要に応じて出社しています。フレックス制度も活用して、各メンバーが生活スタイルに合わせた働き方をしていると思います。

saito:自分もほぼ毎日リモートワークです。リモートでも働きやすい仕組みや文化が醸成されており不便を感じることはほぼありませんが、コラボレーションが要求される業務では出社することもあります。

izmipo:同期・非同期でコミュニケーションを使い分けていますよね。リクエストチームは発足当初からNotionを使ったドキュメント文化が浸透しており、私がチームに参加したときも過去のログに助けられました。

wata:定期的なミーティングとNotionでの情報共有を通じて、非同期でもコミュニケーションを取りやすい環境が整っているのでありがたいです。

saito:Notionのプロジェクトテンプレートに加え、最近はエンジニア相談のSlackワークフローも追加するなど、仕組みをうまく活用しながらコミュニケーションを取れていると感じます。

チームの雰囲気について教えてください

wata:各メンバーが自走しながら、必要に応じて意見交換やサポートを行っています。互いに尊重し合う文化が根付いているため意見も出しやすく、また受け入れられる環境も整っています。幅広い視点をふまえてプロダクトをより良くし、その中でメンバー自身も成長できていると実感します。

saito:サービスリリース当初は、メンバーが各々の裁量を発揮して成果を上げるという良い意味で属人化したチームスタイルでした。最近はチームが成熟してきたこともあり、チーム全体で生産性を高めるスタイルに移行しつつあります。業務の仕組み化が進行しているため、オンボーディングが終了したばかりの新メンバーでも活躍しやすいと思います。

izmipo:課題に対し集合知で解決していく姿勢と、自律性の高さの2つを兼ね備えたチームだと感じます。また、過去にインターンやアルバイトを何名も受け入れた実績があり、新メンバーへのオンボーディングも常にアップデートされています。私がチームに参加した当初は自身のドメイン知識不足が気がかりでしたが、疑問の共有を歓迎する雰囲気がチームにあり、安心して一つずつ学ぶことができました。

チャレンジしていきたいことや目標など今後の展望を教えてください

wata:今後もチーム全体でスキルを高めつつ、ユーザーにとって価値あるプロダクトを提供していきたいです。特に、pixivリクエストのさらなる可能性の模索とサービス全体の安定性向上に注力したいと考えています。

saito:入社してすぐの頃はpixivリクエストのことで精一杯でしたが、ここ数年はpixivというプラットフォームの一部としてpixivリクエストを捉えられるようになってきました。今後は、pixivリクエストを足がかりとしてpixiv全体に寄与できるようなエンジニアリングに挑戦していきたいです。

izmipo:pixivリクエストは現在多くのユーザーに利用いただいていますが、まだまだ成長途中のプロダクトです。ピクシブの他のサービスとも連携して、コミッションに関する多様な楽しみ方を提供していきたいと思っています。

どんな人がこの仕事に向いていますか?

wata:チームでの連携を大切にし、ユーザー視点で考えられる人は向いていると思います。また、技術的なチャレンジを楽しみながらプロダクトの成長に貢献したい方も活躍できるのではないでしょうか。

saito:全体最適と個別最適のバランスを取ったエンジニアリングをできる人も向いていますね。pixivリクエストは、pixivやpixivFANBOXと同じコードベースでモノレポとして管理されています。そのため、pixivリクエストの機能を実装するときに他のプロダクトでも利用されているコードを参照することがよくあり、実装時にはバランス感覚が求められます。

izmipo:要件定義からディスカッションに加わることが多いので、ユーザーと開発者の目線を行き来できることも大事だと思います。コミッションに触れたことがあるかは重要ではなく、様々な立場にあるユーザーの存在を尊重できる人と一緒にプロダクトを良くしていきたいです。

ピクシブのここが好き!を教えてください

wata:お互いの趣味や好きなことを尊重し合う文化がとても良いなと感じます。趣味を楽しみながらプロフェッショナルとして成長できる環境も魅力です。

saito:社員みんなが、お互いの「好き」という気持ちを尊重しあっていますよね。"With Open Minds(多様な好きを受け容れよう。)" という企業バリューを体現していると思います。

izmipo:尊重しあう空気のおかげで、自分の趣味について臆せず話せる環境が嬉しいです。私はピクシブに入社してから小説を書くようになり、最近人生初のサークル参加を経験しました。身近な誰かが創作しているという状況に元気をもらいながら自身も創作を楽しんでいます!

ピクシブへの入社を検討している人にメッセージをお願いします

wata:チャレンジ精神を持って新しいことに取り組める環境が整っています。一緒にプロダクトを成長させ、ユーザーに喜んでもらえるサービスを作っていきましょう!

saito:創作を続けるためには、評価・数字・孤独・お金の不安といった様々な壁を超える必要があります。私達はコミッションという新しい創作の形を提供することでクリエイター支援を目指しています。エンジニアリングで一緒に創作文化を支えたいという人は、ぜひお待ちしています。

ユーティリティユニットチームリクエスト部の業務に興味をお持ちいただいた方は、下記エントリーフォームよりご応募をお待ちしています!

hrmos.co

今年もブックサンタやったよ〜

ほ〜、ほ〜、ほ〜。こんにちは、マーケティング部のhotepです。今年も「ブックサンタ」の季節がやってまいりました!

「ブックサンタ」とはNPO法人チャリティーサンタによる「厳しい環境にいる全国の子どもたちに本を届けること」を目的にしたチャリティーです。

2022年からpixivはNPO法人チャリティーサンタと協力し、「pixiv小説子どもチャリティー企画 ブックサンタ」という投稿企画や、RP数により寄付をするキャンペーンを行なっています。今年も絶賛開催中。

投稿企画はクリスマスをテーマにした小説やエッセイを執筆し、参加タグ「ブックサンタ2024」を付けてpixivに小説を投稿すれば、作品数×500円をピクシブ株式会社からNPO法人チャリティーサンタへ寄付します。

いままで累計1,417,250円の寄付を行っており、今年はさらに多くの子どもたちに本を届けたいと思っておりますので、ぜひご参加ください。

www.pixiv.net

また同時に、ピクシブの社員たちも個人的にこのブックサンタに参加しています。 ブックサンタは、提携書店で本を選び、購入時に「ブックサンタでお願いします」と伝えるだけで参加できます。 毎年、私は有志の社員たちと本屋に行きブックサンタで本を購入し、その後みんなで酒を飲むというのを恒例にしています。 今年もやってきました。 初めて話す社員もいましたが、小さい頃に読んだ本の話や、どんな本が喜ばれるかを話すことですっかり打ち解けられたと思います(だよね?) ブックサンタは参加する側も楽しいチャリティーだと思います。ぜひご興味ありましたら、公式HPを見てお友達や同僚と参加してみてください。

booksanta.charity-santa.com

最後に、今回はそんなズッ友な社員たちと寄付した本を紹介します。購入時の参考になれば幸いです。

星のカービィ あぶないグルメ屋敷!?の巻

チームメンバーから「小学生の姪っ子が読んでます」と教えてもらい、小学生のリアルバイってマストバイじゃんと選びました。それに、こういうキャラクターきっかけで活字を読みはじめることもあるんじゃないかなぁと。 というか、昭和生まれだしゲームをあまりやらない子どもだったのでカービィはファミコンでしか知らないのですが、カービィって人語を喋るんですね。(hotepstore.kadokawa.co.jp

これだけは知っておきたい 岩石・鉱物図鑑

図鑑はブックサンタでも人気らしいし、鉱物ってやっぱきれいだし、きれいなものの写真がたくさん載ってる本はプレゼントとしての特別感があるかなと選びました。「きれ〜」って理由でこの本を手にとって、本を読む楽しさに目覚めてくれたらうれしいですね。 それにもしこの本を読んだ子が将来オタクになり、小説でキャラの瞳を宝石に例えたい思ったとき、この本で得た知識はきっと役立ちます。(hoteppie.co.jp

メアリ・ポピンズ

シンプルに大好きな物語だから選びました。子どもの頃に読んで、人の想像力ってすごい!と改めてハッとした本です。空飛ぶ傘や天井でのお茶会や踊る牛や、え、何食ったらそんなお話考えられんの!?というお話がたくさん収録されています。 この本でクリエイティビティを高めて、将来pixivにマンガや小説を投稿してくれたらうれしいです。できれば私と同じジャンルだともっとうれしい。(hotepwww.asahipress.com

金曜日の砂糖ちゃん

可愛らしい少女と仄暗い夜の雰囲気が溶け合う酒井駒子先生の絵本は、子供の頃に書店で見かけて衝撃を受けました。この素敵な本がどうしても欲しい!と思ったのですが、当時のお小遣いではハードカバーの本を買うのは難しかったです。(uchienwww.kaiseisha.co.jp

たまごのはなし

友人のプレゼントで知った絵本です。精密な絵が魅力的です。主人公のたまごが途中で出会ったマシュマロに齧り付くのですが、その齧り跡がずっと取れないまま一緒に行動しているのが面白く、人と共有したくなりました。(uchienwww.bronze.co.jp

オーデュボンの祈り

小さな島で起こるカカシの殺人事件。読み進めやすいミステリーで、浮世離れした不思議な世界観を小冒険する気持ちにもなり、最後は満たされた気持ちになれる読後感が魅力的で、ぜひ贈り物にしたいと思う一冊です。(uchienwww.shinchosha.co.jp

飼育員がつくったサルの図鑑: かならず会いたくなっちゃう56のなかまたち

サルの種名、いくつ言えるでしょうか。ニホンザル、チンパンジー、ゴリラ、ヒト……。 この本には56種類載っています。マニアックで素晴らしいですね。「ブラッザグエノン」がどんな姿か想像できますか? 今回私が選んだ本はどれも同じものを持っているのですが、このブックサンタの選書のために書店を訪れて初めて、この本が児童書コーナーにも置かれていることを知りました。 「ブックサンタオンライン書店」にも掲載されていて、そこでの分類によると小学校中学年向けということらしいです。 こういう隙間の知識は何歳でも面白いものであるはずだし、次動物園へ行くときの楽しみが増えると良いな、と思って選びました。(roibanwww.kumonshuppan.com

石は元素の案内人 (たくさんのふしぎ傑作集)

福音館書店から毎月刊行されている「たくさんのふしぎ」という子供向けの絵本雑誌があり、その中の一号が単行本化されたものです。 岩塩や水晶といった様々な「石」を紹介しながら、それらが元素に関してどのような示唆を与えてくれるのか、やさしい語り口で教えてくれます。(roibanwww.fukuinkan.co.jp

世界ぐるぐる怪異紀行: どうして”わからないもの”はこわいの? (14歳の世渡り術)

ヒマラヤのイエティ、中国の鬼、ロシアの呪術信仰……。 世界各国の「怪異」とそれを取り巻く人々の暮らしを、9人の文化人類学者がそれぞれのフィールドワーク体験を軸に語る本です。 各話20ページ程度とさらっと読める長さでありつつ、記述に留まらず文化を深くまなざす学問の深みが感じられます。 「14歳の世渡り術」という中学生以上向けのシリーズから出ている一冊で、最近本屋で見つけて読み、面白かったので選びました。(roibanwww.kawade.co.jp

太陽系最後の日 (ザ・ベスト・オブ・アーサー・C・クラーク 1)

人類を救うべく、破滅が迫る地球へやってきたエイリアンたちの探検隊が見たものとは……。 『太陽系最後の日』はSF作家アーサー・C・クラークの短編集です。 私は今回がブックサンタ初参加で、「自分が読んで面白かった」以外に本を選ぶ基準がまだ分からず、つまりまあ趣味からこの本をここに含めました。 いわゆる児童文学という感じの作品とは違いますが、平易な言葉で科学をストーリーに落とし込むのが見事で、小学生くらいで出会っても十分価値がある気がしています。 (roibanwww.hayakawa-online.co.jp

めんどくさがりなきみのための文章教室

子どもの頃、小説って誰でも、自分でも書いていいんだ、と教えてくれたのがはやみねかおる先生の本でした。主人公の男の子とともに、文章の書き方を楽しく学んでくれたらいいなと思います。(ytowww.asukashinsha.co.jp

世界のかけら図鑑

旅行先の本屋で平積みされていたのが目に入って、自分用に1冊と、子どもが読んでも楽しいだろうなと思ってもう1冊をブックサンタにしました。イラストも文章も楽しい本なので、科学に興味を持ったり、より深く知りたいと思うきっかけになってくれたら嬉しいです。(ytowww.kadokawa.co.jp


ぼくらの七日間戦争

www.kadokawa.co.jp

ありふれた手法

www.shinchosha.co.jp

キノの旅 the Beautiful World

www.kadokawa.co.jp

二十億光年の孤独

www.shueisha.co.jp

深すぎてヤバい 宇宙の図鑑 宇宙のふしぎ、おもしろすぎて眠れない!

bookclub.kodansha.co.jp 私は昨年に続いて二回目の参加です!今回は小学生高学年くらいの方々を思い浮かながら、 「本の世界の入口」というテーマで上記5冊を選定しました。

「僕らの七日間戦争」は、ジュブナイル小説の 「ありふれた手法」はSFの 「キノの旅」はファンタジーの 「二十億光年の孤独」は自由詩の 「宇宙の図鑑」は、科学を志すものとしての それぞれの入口として。 本は広大で深淵なこの世界を知る窓であり、扉であり、乗り物だと思っています。 これらの本をきっかけにみなさんがワクワクする世界に没頭してもらえると嬉しいです。(ushio

pixivの全文検索基盤とElasticsearchによるリプレイス

まもなく17周年を迎えるpixivでは、長年にわたり作品などの全文検索基盤としてApache Solrを使用してきました。 しかし、サービスの規模が拡大する中で、従来の基盤に問題が生じていました。これを受けて、pixivでは全文検索基盤のリプレイスを実行しました。

今回のリプレイスにより、pixivでは検索結果の更新反映時間や検索APIのレイテンシが大幅に短縮されました。また、今後のスケールに対応可能になり、新機能開発においても全文検索が容易に利用できるようになりました。

本記事では、pixivの全文検索基盤の歴史や、今回オンプレミス環境でElasticsearchクラスタを構築し、リプレイスを完了するまでの取り組みについてご紹介します。


こんにちは。pixivのnamazuです。最近、私たちのチームで進めていたpixivの全文検索基盤のリプレイスが完了しました。この機会に、pixivの全文検索基盤の歴史などを交えながら、取り組みについてお伝えできればと思います。

なお始めに断りをいれさせてください。

  • 既存の仕組みがこれまでpixivを支えてくれたこと、そしてそれに関わったすべての方々に深い敬意を抱いています。
  • 今記事では、SolrとElasticsearchの優劣について触れる意図はありません。pixivがSolrの運用に課題を抱えていたのは、長期間にわたってSolrの更新やアーキテクチャの見直しを行わなかったことが主な原因です。

pixivの全文検索基盤の用途とその歴史

最初にpixivにおける全文検索基盤の用途とその歴史について説明します。

pixivでは、ユーザーが作品としてイラスト、マンガ、小説を投稿できます。これらの作品には、タイトルやキャプション、タグなど、さまざまな情報が付けられています。ユーザーは、これらの情報に基づいて検索ページで好みの内容を検索できます。また、AND、OR、NOTといった演算子を用いた複雑な条件設定や、特定の語句を除外するマイナス検索も可能です。

pixivで作品を検索する方法を知りたい – pixivヘルプセンター

このような検索ページでの絞り込みは、全文検索基盤が利用される典型例です。

さらに、全文検索基盤はpixivにおいて特定の条件に合致する作品を高速に抽出する能力を持っているため、単なる検索ページの機能を実現するだけでなく、さまざまな用途で広く活用されています。例えば、レコメンド機能の裏側を支えたり、そもそも全文検索基盤の絞り込み機能を前提として動作するpixiv関連サービスも存在します。

このように、pixivにおける全文検索基盤は随所で利用されており、不可欠な要素の一つです。参考までに、クエリ数はピーク時に約4k req/s程度に達します。

一般的に、全文検索基盤はサービスの初期から何らかの形で必要とされる事が多いと思います。pixivでも最初期から運用が行われてきました。 pixivの全文検索基盤は以下のような流れで変化しています。

  1. 2007年9月10日 pixivリリース (検索は必要に応じてMySQLのLIKEで実現 )
  2. 2007年12月頃 MySQL Tritton (senna)を利用した初代全文検索基盤の完成
  3. 2011年頃 スケールの問題によりApache Solrを用いてリプレイス
  4. (この間12年ほど) 検索の絞り込み条件を追加などのその都度の対応や保守
  5. 2024年4月~ Elasticsearchへの移行PJ本格稼働 ( 今回の主題 )
  6. 2024年8月末 Elasticsearch移行PJ完了、Solr運用終了

③で構築されたSolrの全文検索基盤が、pixivを長期間にわたって支えてくれたことが分かります。 本題であるElasticsearchへの移行について話す前に、まずはこのSolrの構成と、その際に直面した課題について説明します。

Apache Solrによる全文検索基盤のインフラ構成(2024年の最終構成)は、大まかに以下のようになります。

アプリケーションは、masterノードに対してインデックス処理をリクエストします。masterノードがインデックス処理を担当し、帯域の安定化のために通過させているrepeaterノードを介してデータをレプリケートします。一方、アプリケーションはdispatcherを介してオンライン検索を実行し、各replicaノードで分散して処理が行われます。

また、pixivのイラストなどのリソースは、以下のような形でシャーディングしてSolr上で取り扱っています。

Solrを構築した当時は、Solr Cloud(Solrの構成の一つ)が存在していなかったため、独自にシャーディングを行い、DistributedSearchを利用しました。

イラストなどのデータは、Solr上では主に「new」と呼ばれる1つのコア(インデックス)と、「00x」のように番号が振られた複数のコアで構成されています。newには直近の作品が入り、00xにはそれ以外の作品が格納されます。00xの各番号は、作品のIDをコアの数で割った際の余剰で決定されます。

newコアは高頻度のバッチ処理で更新され、一方の00xコアは1時間ごとのバッチ処理で更新されます。newコアに新規作品を追加し続けると、newが大きくなってしまうため、定期的にnew内の古い作品を00xコアに移す処理も行われます。

この仕組みにより、pixivに投稿された作品は比較的早く検索結果に反映され、またシャーディングによって、作品の増加に対応して検索処理をスケールさせることが可能となっています。

pixivの全文検索基盤の問題

さて、この全文検索基盤はサービス規模の拡大に伴い、以下のような問題に直面することになりました。

masterのインデックスが処理スケールしない問題 : masterノードがシングルノードでインデックス処理を行うため、スループットがmasterノード単一の性能に依存します。しかし、データの増加により、要求がスループットの上限を超えることが発生するようになりました。

replicaの検索負荷増加問題: 検索量が増加したため、より多くのreplicaで参照負荷を分散しないと処理が追いつかなくなりました。全てのreplicaが同一のデータを持ち、偏りがないため、全台で十分なメモリを搭載しないと安定しません。

all_update処理による障害の発生: 上記の2つの問題から、スキーマの変更などで行う「all_update」と社内で呼称するインデックスの再構築処理に非常に時間がかかるようになりました。さらに、実行中は随時行っている更新処理が遅延するなどの問題も発生します。これまでのインデックスから新しいインデックスに参照を切り替えるタイミングでは、キャッシュの関係などから検索リクエストを捌ききれず、一時的な障害が発生することもありました。その結果、all_updateは極力避けるべきオペレーションとなり、実施を回避する形で開発が行われるようになりました。

これらの問題に対処するため、サーバのLANを1Gbpsから10Gbpsに強化したり、メモリを増設したりすることで、何とか凌いできました。

その他、そこまで大きな問題ではないにせよ、解決したい点もいくつか出てきました。

SPOF(Single Point of Failure)の存在:構成上、masterノードを始めとするさまざまなSPOFが存在します。これらの箇所については基本的に自動復旧ができず(自働化のための開発がされていないため)、復旧にはインフラ部のオンコールによる即時対応が必要です。

シャーディングの複雑さ: 独自のシャーディングのため、アプリケーションでは各種バッチ処理を用意し、それを運用する必要があります。また、リシャーディングに関しては大変さの点から実現性に課題がありました。

バージョンの古さ:OSを含め、手が加えられておらず、かなりバージョンが古い状態になっていました。

問題の深刻化、OpenSearchの試みと撤退、そして次の準備

さて、Solrの全文検索基盤が単一のmasterノードでインデックス処理を行うために負荷が分散できないという問題は、2021年ごろに長文を取り扱う「小説本文のインデックス処理」で顕在化しました。

pixivの小説は長い本文を持っていますが、その内容に基づいて検索が可能です。しかし、小説の規模が増加するに伴い、処理しなければならない文章量が増加し、インデックス処理が限界に達することで、検索結果への反映が遅延する事態が発生しました。これは大きな問題です。この状況では、新しい検索フィールドやコアを追加することはさらに困難になります。 プロダクト開発では、随時機能を拡充しながら検索機能も改善したいところですが、この問題がそれを妨げてしまいました。

解決策を見つけるための検討が行われました。私は直接的には関わっていませんが、最初の候補として挙がったのは、AWSのOpenSearchへの移行でした。OpenSearchはSolrと同様にApache Luceneを基盤としているため、比較的容易に移行でき、差異も少ないと考えられます。また、クラウドリソースを活用し、ある程度の運用を委譲することで、物理サーバの調達や運用体制の確立にかかる時間を短縮できる点も魅力でした。

しかし、検証の結果、OpenSearchにpixivの全文検索基盤を載せ替えることは見送られることになりました。その理由は機能的な問題ではなく、費用面での問題です。 pixivはオンプレミス環境を保有しており、必要となるリソースが大きく、またそのリソースの需要に大きな変動がないため、オンプレミスで長期間にわたり安定運用することでコストを削減しています。 マネージドサービスの完成度の高さやオンプレミス運用に関わるチームのコストなど単なるサーバ費用以外にも考慮するべき点はありますが、pixivの検索全体という大規模なストレージや計算リソースをクラウドで確保するのと、オンプレミスで確保するのとでは、5年、10年という長期的な視点で見ると、サーバのランニングコストに大きな差が生じることが判明しました。 OpenSearchで全文検索基盤を実現する場合、当時算出された費用は、長期的な運用を考えると事業上難しいものでした。

この結果、全文検索基盤をオンプレミスでどうにかする方向に決定しました。これは2022年中頃から年末にかけての出来事です。

さて、オンプレミスでなんとかすると言っても、pixivの検索基盤を載せ替える規模のサーバがデータセンターに余っているわけではありません。pixivのSolrのreplicaは不安定であったために追加を続けた結果、1台あたりCPUが24コア48スレッド、RAMが192〜256GB程度のサーバが約14台で構成されています。仮に半分から始めるとしても、少なくとも7台は必要です。しかし、そのようなサーバは余っていなかったため、まずはサーバの調達から始めることになりました。

メモリの追加やLAN帯域の増強、アプリケーションの改善、ノード自体の追加によって、当時の問題はある程度抑えられていたため、急ぐ必要はなく、2023年はこのサーバの調達・キッティング・ラッキングを進めることになりました。

Elasticsearchへ

2024年4月、サーバも揃い、稼働できる状態になったので(その前はPHPとOSの更新を行っていました)、全文検索基盤の再構築が始まりました。

当時、サーバは既に用意されていましたが、具体的な設計はあまり決まっていませんでした。主な要求は以下の通りです。

  • ある程度のベアメタルサーバを用意したので、それを使って全文検索基盤を構築したい。
  • 次のピークであるお盆頃までには完了させたい。つまり4ヶ月で行いたい。
  • 少なくとも検索反映時間を短縮したい。また、プロダクトの要求に応じて、新しいフィールドを容易に追加したり、検索できる項目を増やせるようにしたい。
  • 可用性が高く、保守運用が楽なものにしたい。サーバ故障時にインフラ部の即時対応を必要としないようにしたい。

これらの要件を踏まえ、まずはミドルウェアとして何を使うかから検討が始まりました。

結論として、Elasticsearchで構築を進めることになりました。主にSolr(Solr Cloud構成)との比較が行われましたが、インフラ部では既にBOOTHやpixivコミックなどでオンプレミスのElasticsearchクラスタを運用している実績があったため、pixivもElasticsearchを採用することで、オンプレミス環境をElasticsearchに統一できることが期待されました。これにより、規模の経済を活かし、全体的な品質の向上と運用の容易化も同時に狙えると考えました。

Elasticsearchへの移行が難航する可能性もありましたが、ElasticsearchはSolrと同じくApache Luceneを基盤としており、過去にOpenSearchで行った検証の実績から、移行は現実的だろうと判断しました。トークナイザや各種前処理の違いによって、ElasticsearchとSolrでインデックス内容がずれ、体験差異が深刻化する懸念もありましたが、pixivでは主にn-gramが使用されているため、問題が表面化することは少ないだろうと考えました。最悪の場合でも、n-gramの差異に影響を与えるnormalizerなどを自分で実装することで回避できると判断しました。

また、Solrを採用した場合でも、現行のバージョン5.5から9への一気にバージョンアップが必要になるため、前後比較の工数は大して削減されないだろうという結論に至りました。

移行計画の作成

プロジェクトに際して、Elasticsearchへの移行計画を作成しました。問題が発覚した際に切り戻せないリスクを避けるため、また移行時の検索体験の差異が少ないことを立証できるよう、以下のステップを設定しました。

  1. Elasticsearchクラスタを構築する。
  2. ElasticsearchにSolrと同等のインデックスを作成し、Solrに加えてElasticsearchにも定期的な更新処理を適用する。 また、更新処理の負荷検証を行う。
  3. pixivでの検索時にSolrとElasticsearch両方にクエリを送る。 ユーザー向けには引き続きSolrの結果を使用するが、裏側でElasticsearchの結果件数と照合し、差異を確認する。差異が生じた場合は調査し、対処する。さらに、このリクエストを受けているElasticsearchクラスタに対して、ノードの隔離・投入、設定変更など日々発生する作業を試験的に実施し、挙動を確認する。Datadogのダッシュボードや監視用のMonitorといった運用に必要な環境を整備する。また、ハードウェア障害を模したシナリオをテストし、可用性を確認する。想定されるクエリがすべて流れている一定期間の状況を観察し、リクエストが捌ききれるか確認する。
  4. 移行の懸念点が払拭できたら、pixivからの検索時にユーザー向けにElasticsearchの結果を使用するよう、A/Bテストの仕組みを用いて段階的に切り替える。 問題が発生した場合は、Solrに切り戻して修正を行う。
  5. ②〜④の流れを、検索基盤で扱うすべてのインデックス(イラスト・小説・ユーザ・グループ・百科事典など)に対して行う。
  6. Elasticsearchへの移行が完全に完了した後、Solrへのデータ更新・検索クエリを停止する。
  7. Solrへのデータ更新処理やクエリ生成処理などをコードから削除し、Solrサーバを停止する。

この計画では、最終ステップである⑦番までSolrに対してデータの更新を続けるため、いつでもSolrへの切り戻しが可能です。また、③番の段階における検証作業によって、効果的に差異を検出することができます。 今回、pixivのSolr検索と同等のElasticsearchインデックスを作成し、クエリを完全にミスなく構築することは難しいと予想していました。そのため、何らかの漏れやミスが発生することを前提として計画を進め、最終的にはユーザーリクエストを通じて前後の振る舞いの差異を検出・修正することで、効果的に問題点を発見し、漏れがなくなっていることを立証できるようにしています。

実際、このアプローチにより、検証段階で多数のプログラムのバグや設定ミスを発見・修正することができ、効率的かつ効果的に移行作業を進めることができました。

インフラ構成

今回構築したElasticsearchの構成は、以下のような形としました。

全体として、Elasticsearchクラスタはmaster eligible nodeが3ノード、data nodeが11ノードで構成されています。

アプリケーションがElasticsearchのAPIにアクセスする際には、HAProxyをロードバランサーとして間に挟んでいます。 HAProxyはmasterノードに同居させており、3台のうち2台にKeepalivedで付与される仮想IPを介してアプリケーションから接続されています。HAProxyの負荷は高くないのですが、今回は構造上、1ノードへの集約を避けたかったため、2ノードに仮想IPを付与し、DNSラウンドロビンでアプリケーションからのリクエストが分散するように設定しています。ロードバランサを分散するメリットに対して、障害率の上昇や複雑性の増加がどの程度見合うかは微妙なところなので、将来的には集約させるかもしれません。

HAProxyはdataノードのElasticsearchに接続しています。アクティブヘルスチェックを行っており、ノードに障害が発生した際には自動で隔離されるようになっています。

なお、Elasticsearchに投入されるデータはすべてMySQLに元データが存在するため、現状ではバックアップは行っていません。万が一の破損時には、インデックスを再構築して復旧する予定です。

移行中に発覚した問題と対処

ここからは、移行中にpixivで発覚した問題とその対処について触れます。多くの問題は、ユーザリクエストを用いたSolrとElasticsearchの差分の動的検証の中で発覚し、対処することができました。そのため、ユーザへの影響を抑えた形で問題を把握し、解決することができました。

max_result_windowの調整

Elasticsearchのmax_result_windowの値に起因して、検索ページが特定ページ以上開けないという問題が発生しました。pixivでは、現在プレミアムアカウントを持つユーザーは、検索ページを最大5000ページまで辿ることができます。 max_result_windowの値を大きくすることで、クラスタが不安定になったり、クエリが終わらないのではないかという懸念がありましたが、設定を変更して様子を見たところ、pixivの環境では特段問題は発生しませんでした。そのため、検索ページで表示する件数に合わせてmax_result_windowの値を増加させました。

position_increment_gapの調整

position_increment_gapはインデックスにおいて、配列のように複数の要素を持つプロパティの各要素間の隙間を調整するパラメータですが、これが既存のSolrとの差異を生む原因となりました。

position_increment_gap | Elasticsearch Guide [8.15] | Elastic

pixivでは、次のようなケースでこの差異が発覚し、一部のフィールドにおいてこの値を0に明示的に設定する必要がありました。たとえば、ある作品に「A」と「B」という2つのタグが付いているとします。このとき、pixivのタグ部分一致検索で「AB」と検索すると、「A」と「B」のタグを持った作品が検索結果に表示されるという挙動がありました。 この挙動は、pixivで明示された検索仕様ではなく、タグの検索という文脈では一見不思議に思えるかもしれませんが、社内でヒアリングしたところ、この機能を意図して利用しているケースがあったため、今回はこの挙動を維持することを決めました。

SolrのqとElasticsearchのquery_stringの取り扱い

pixivでは、OR、AND、NOTや括弧を用いた検索クエリが利用できますが、これまではユーザーが入力したクエリをアプリケーション側で最低限のエスケープ処理を行ったうえで、Solrのqパラメータにそのまま入力していました。 今回、Elasticsearchに移行する際、既存コードからの移行のしやすさを考慮して、主にElasticsearchのquery_stringを使用しました。しかし、ElasticsearchとSolrの間で一部のクエリの解釈が異なることが判明しました。これに対処するため、Elasticsearchにクエリを渡す際には、ユーザー入力クエリを構文解析し、フレーズの取り扱いなどを適切に考慮したうえで、query_stringに渡す文字列を生成することになりました。

icu_normalizerの挙動

既存のSolrでも文字の正規化を行っていたため、Elasticsearchでもicu_normalizerを用いてnfkc_cf形式で正規化を行いました。移行当初から何らかの差が出るだろうと予測していましたが、実際にはいくつかのケースで致命的な影響が発生し、対処が必要となりました。 特にpixivで影響が大きかったのは、2点リーダーや3点リーダーの取り扱いです。これらは正規化されると複数のピリオドに変換されますが、pixivではピリオドがタグなどで特別な意味を持つことがあり、ピリオドを用いた検索時に混同を避ける必要がありました。

まず、icu_normalizerのunicode_set_filterオプションを用いて、特定の文字の正規化を回避することを検討しました。しかし、このオプションを適用したところ、絵文字のみで構成された長文の小説に対するインデックス処理が終了しない問題が発覚し、この方法は断念しました。

短時間で原因を特定するのが難しかったため、最終的には前段でchar_filterのmappingを使用し、3点リーダーなど正規化を避けたい文字をUnicodeの私用領域のバイト列に置き換えることで、一時的に対処しました。

移行してみて

さて、様々な課題がありましたが、この移行プロジェクトは8月23日にSolrサーバを全台シャットダウンした事を以て完了しました。ここでは、全文検索基盤のリプレイスによる結果について触れます。

検索結果反映速度が向上

検索結果への反映速度が大幅に向上しました。これまでは、直近に投稿された作品については比較的早く数分以内に検索に反映されていましたが、すべての作品を対象とすると、タグを付けてから検索結果に表示されるまでに1時間程度かかることがありました。 今回の再構築に合わせてインデックス処理を見直したことで、イラストや小説がタグ付けなどの変更を行ってから、検索結果に反映されるまでの時間を30秒以内に短縮することができました。

これにより、作品を編集後、検索結果に素早く反映されるようになり、ユーザーがより早く好みの作品に出会いやすくなったと考えています。また、「検索結果への反映が遅い」というご意見も頂いていたため、これらに応えることができました。

各要素の検索結果への反映時間は、定期的にバッチで測定し、Datadogにカスタムメトリクスとして送信して監視しています。

検索APIを始めとするSolrに依存していたエンドポイントでレイテンシが改善

元々の検索基盤が不安定だったため、pixivの検索APIのレイテンシが不安定でした。今回の移行によって、このレイテンシを安定させることができ、特に複雑な検索におけるレイテンシを大幅に削減することができました。

グラフは、モバイル版pixivが利用するイラスト検索APIのレイテンシを示したものです(上位から99.9%、99%、95%、90%、75%、50%タイル)。特に重い検索に該当する99%タイルなどにおいて、レイテンシが顕著に改善したことがわかります。このAPIは、秒間100リクエストを超える頻度で利用されているため、恒常的に発生していた重い検索のレイテンシを大幅に改善できたと言えます。

また、これまで検索APIはSolrが不安定な際にエラーを返すことがありましたが、移行後はエラー数も減少しています。

重い検索リクエストの多くは、ORやマイナス検索を多数組み合わせたユーザーの好みに応じたものが多いため、これによりユーザーが自分の好きな作品をより快適に探すことができるようになったと言えるでしょう。

保守運用が容易に

今回の構成では、ノードが2台故障してもクラスタの可用性を維持できるようになったため、システムの可用性が向上しましたが、インフラ部の保守運用面でも大きな改善ができました。

これまでは、深夜や休日を問わず発生するディスク故障などによるノード故障に対して、自動化が十分ではなかったため、オンコール体制に基づく即時対応が必要でした。しかし、今回の構成変更により、これらの対応を翌営業日まで延期することが可能になりました。

これにより、オンコールによる深夜・休日対応を減らすという点で、一歩前進できたと言えます。

新検索基盤のこれから

今回構築した構成はまだ運用を開始して間もないため、さまざまな点で未熟な部分があります。こうした点については、運用を続けながら進化させていきたいと考えています。

例えば、オンプレミスのベアメタル環境では、使用するパーツの性能に大きく依存する部分があります。今回の場合ディスク周りについては、改善の余地があると考えています。現状は安価なSATA SSDを使用していますが、データセンター向けのDWPD3程度あるNVMe SSDなどに切り替えることで、より安定し、リクエスト処理能力の高いクラスタにできると見込んでいます。こうした点については、今後数年の運用状況を見ながら調整していきたいと思います。

また、Elasticsearch自体の課題ではありませんが、検索インデックスの差分更新については、現状MySQLのトリガーを用いてデータ変更を検出しています。現行のpixivのMySQL構成では、ステートメントベースのバイナリログを使用する必要があり、廃止が予告されているため対応が必要です。今後は、アプリケーション側で検索インデックスの差分更新に関するリアーキテクチャに取り組む必要があります。

課題は多く残っていますが、今回の変更により、プロダクトとして全文検索をより柔軟に活用できるようになったため、今後のpixivのさまざまな施策にこの改善を活かしていきたいと考えています。

おわりに

長い記事となりましたが、ご一読いただきありがとうございました。

また、本件に関して、pixivのアプリケーションプログラムのコーディングでも挑戦的なリアーキテクチャに取り組んでおり、私に加えて本件に取り組んでいたpicopicoより、別途Inside記事にて詳しく触れさせていただく予定です。そちらも楽しみにしていただければ幸いです。

パスキーにデフォルトの名前をつける方法について 〜AAGUIDの活用とフォールバックの工夫〜

こんにちは、プラットフォーム開発部で認証認可基盤の開発を担当しているabcangです。

先日pixivでパスキーが利用できるようになりました。パスキーはパスワードに代わる認証方法で、顔認証・指紋認証・PINなどのデバイスの認証機能を使ってpixivや関連サービスにログインすることができます。

パスキーはデバイスやクラウドサービス(iCloudやGoogleなど)ごとに作成する必要があるため、1アカウントで複数のパスキーを作成することは一般的です。そのため、パスキーを削除するときなどに判別しやすくするには、パスキーに適切な名前をつけることが重要になります。しかし、ユーザーに適切な名前をつけてもらうのは大変なので、pixivでパスキーを作成した場合はそれらしい名前をデフォルトで設定しています。今回はそれらしい名前をどうやって設定しているかご紹介します。

AAGUIDを使用する

AAGUID(Authenticator Attestation Globally Unique Identifier)は認証器のモデルの種類を示す一意なIDで、パスキーを作成する際に取得できます。そして、passkeydeveloper/passkey-authenticator-aaguidsというコミュニティ主導のリポジトリがあり、これを使うことでAAGUIDからパスキーのプロバイダーの名前を取得することができます。

これについてはweb.devで紹介されています。 web.dev

AAGUIDが取得でき、それに対応する名前がpasskey-authenticator-aaguidsに登録されている場合、その名前をデフォルトの名前として採用しています。しかし、認証器によってはAAGUIDを取得できない場合があります。例えば、iCloud KeychaninのAAGUIDは2023年の終盤までは取得できませんでした。また、Windows HelloのAAGUIDは PublicKeyCredentialCreationOptions.attestationdirect の場合は取得できますが、 none の場合は取得できません。

attestation のオプションはAttestation(使用した認証器や生成したパスキーのデータの正当性を証明するために使われるデータ)の伝達方法を決めるもので、 Attestationをそのまま受け取りたい場合は direct を指定し、Attestationが不要な場合は none を指定します。

Attestationは主に利用可能な認証器を制限する目的で使われます。しかし、pixivでは認証器の制限の必要性を感じておらず、むしろ認証器を制限するとパスキーを使える機会が減ってしまうため、Attestationの要求自体が必要ないと考えていました。「Attestationを受け取りながらも認証器の制限を行わない」という選択肢もありますが、パスキーの仕様に従うためには受け取ったAttestationをきちんと検証する必要があるため、実装や保守の手間が増えてしまいます。そのため、AAGUIDを取得するためだけに direct を指定することに抵抗がありました。

また、プラットフォーム認証器(デバイスに内蔵されている認証器)ではattestationnone の場合にもAAGUIDを取得できるように仕様を改善する動きがあり、今後はAAGUIDを取得できないケースは減っていく予感がしました。 github.com github.com

そのため、attestation のオプションは none にしつつAAGUIDを取得できない場合はUserAgentを使ってフォールバックすることに決めました。

UserAgentを使う

UserAgentを使うと言っても、単純にUserAgentに含まれるOSやブラウザの名前をつけるのが適切とは限りません。例えば、Windows HelloのAAGUIDを取得できなかった場合にUserAgentをもとに「Windows (Edge)」のような名前をつけると、WindowsのChromeでも使えることに違和感が出てきてしまいます。他にも、iCloud KeychainはiOS、iPadOS、macOSで使えるため、パスキーを作成したときのOSの名前を採用することも必ずしも適切とは限りません。

そこで、OSに備わっているプラットフォーム認証器はある程度決まっているため、AAGUIDが取得できなかった場合はUserAgentのOSの情報からプラットフォーム認証器の名前を推測することにしました。先程挙げた例の場合、Windowsでパスキーを作成した場合は「Windows Hello」をデフォルトの名前にして、iOS、iPadOS、macOSでパスキーを作成した場合は「iCloud Keychain」をデフォルトの名前にします。

ただし、セキュリティキーなどのローミング認証器(デバイスに取り付けて使う認証器)やHybrid transport(プラットフォーム認証器がある別端末をローミング認証器として使う機能)を使う場合はUserAgentから正しく推測することができないため、プラットフォーム認証器で作成したパスキーでしかこの推測方法は使えません。プラットフォーム認証器で作成したかどうかは RegistrationResponseJSONauthenticatorAttachment フィールドが platform になっているかどうかで確認できます。また、そもそもパスキーを作成する際の authenticatorSelection.authenticatorAttachment オプションを platform にすることでプラットフォーム認証器でしかパスキーを作成できないようにするやり方もあります。

他にも注意点があり、OSに備わっているプラットフォーム認証器以外のブラウザ独自の認証器やサードパーティ製の認証器を使える場合は正しく推測できない場合があります。例えば、macOSのChromeではiCloud Keychain以外にChrome独自の認証器が使用できて、AAGUIDも取得できます。macOSのChrome独自の認証器のようにAAGUIDを取得できる場合は判別ができますが、AAGUIDが取得できない場合は判別できないため正しく推測できません。このようなケースは稀だとは思いますが、この場合はAAGUIDとそれに対応する名前が取得できるようになるまでの間はユーザー自身で適切な名前に変えてもらうしかなさそうです。

また、Linuxのようなプラットフォーム認証器がないOSの場合もブラウザ独自の認証器やサードパーティ製の認証器を使うことでパスキーを作成できますが、この場合はプラットフォーム認証器の名前を推測しようがないためやむを得ずUserAgentから抽出したOSやブラウザの名前を使用しています。この場合もAAGUIDとそれに対応する名前の取得ができるようになるまでの間はユーザー自身で適切な名前に変えてもらうしかなさそうです。

将来的な話

現在、認証器の表示名を取得するための拡張機能が提案されています。 github.com

WebAuthn Level 3の拡張機能の1つである credProps の仕様に追加されるようで、Editor’s Draft, 31 July 2024に記載がありました。この時点の仕様だと、credProps オプションを true にしてパスキーを作成すると、レスポンスの credProps.authenticatorDisplayName フィールドに認証器の表示名がセットされるようです。

今後この仕様が正式なものになって多くの認証器・ブラウザで利用可能になれば、パスキーのデフォルトの名前をつけるのがより簡単・正確になります。この仕様に限らず、今後パスキーがさらに便利になっていくのが楽しみですね。

ピクシブでは認証認可に興味のあるエンジニアを募集しています。 hrmos.co

pixiv1億アカウント突破企画にかけた想い!担当者インタビュー

こんにちは、広報部のmikoです。
pixivは2023年に総登録アカウント数が1億を突破し、それを記念した企画が今年1月から展開されました。企画テーマは「My pixiv Memories」。大きな節目となるこの企画に携わったメイン担当者2名に、企画の背景やこだわりについて話を伺いました。

自己紹介をお願いします

matcha:マーケティング部でマーケティングディレクターをしています。各プロダクトのマーケティング戦略やプロモーション設計を担当しています。

amanatsu:コミュニティユニットビジネス部でプランナーをしてます。pixivの投稿企画やコンテストの運営、外部イベントへの出展などを主に担当しています。

企画の背景や「My pixiv Memories」というテーマに決まった経緯を教えてください

amanatsu:pixivアカウント数の1億突破が見えてきた中で、2023年8月頃にプロジェクトチームを立ち上げました。pixivでは普段から多くの投稿企画が開催されており、今回も投稿企画を軸にしよう、という方針は当初から決めていました。

matcha:まず、この大きな節目でユーザーの皆さんと一緒に盛り上がりたいという想いがありました。「1億アカウント突破」というのがユーザー視点で見た時にどういう意味を持つのか考えていく中で、「それはつまりpixiv登録のきっかけとなった思い出が1億人分あるということではないか?」という答えに行き着きました。

amanatsu:各ユーザーの思い出をきっかけに大きく盛りあがることができるのでは、とmatchaさんから提案してもらったコンセプトはとてもしっくりきました。そこからプロジェクトメンバーで更に話し合い、最終的に「My pixiv Memories」というキーワードにたどり着きました。

実際にどんな企画を実施しましたか?

amanatsu:投稿企画として、「思い出の○○」を振り返る作品を募集しました。好きな作品やキャラクターとの出会い、pixiv登録のきっかけ、創作活動や仲間とのエピソードなど、pixivでの思い出にまつわる作品であれば何でもOK、という企画です。 さらに「思い出の○○」に関する作品を1時間で作って投稿してもらうワンドロ・ワンライ企画を開催しました。

matcha:ユーザーが思い出を振り返るきっかけになるよう、pixivを利用しているクリエイターを起用し、思い出をマンガにして投稿して頂きました。マンガを読んだユーザーが共感し、自身の投稿にも繋がるようなインフルエンサー施策になったと思います。

<投稿作品> www.pixivision.net www.pixivision.net

amanatsu:投稿企画以外に、新たにpixivのことを知ってもらうための施策として池袋駅でピールオフ広告も実施しました。ポスターにオリジナルスケッチブックを貼り付けて、通行者が自由に剥がして持って帰ることのできるプロモーション型の広告です。

matcha:楽しさやpixivらしさを広告でどう表現するか考えた結果、何か持って帰ってもらえる施策としてピールオフ広告にたどり着きました。配るものは創作にまつわるグッズにしたいと考えてスケッチブックに決めました。スケッチブックには、真っ白なページの他に創作のお題やテンプレートを盛り込んだページも用意しました。pixivらしくイラスト、マンガ、小説それぞれのお題を用意したのもポイントです。

amanatsu:スケッチブックを手にした人が、お題を使ってワンドロ&ワンライ企画に参加する流れになれば良いなという気持ちもありました。一度、ピールオフ広告をやってみたいと思っていたので実現できて嬉しかったです。

企画をつくる上で意識した点や苦労した点を教えてください

matcha:プロモーション全体を通して、ユーザーがこの企画に触れた時にどのような反応をしてくれるかを常に意識していました。「どうしたら投稿するモチベーションになってくれるか」「ピールオフ広告では何をもらったら嬉しいか」と、できる限りユーザーの視点で考えるよう心がけました。

amanatsu:イラスト、マンガ、小説横断しての投稿企画はこれまであまりない構成でした。通常の企画は、イラスト、マンガ、小説それぞれの部門で分かれ、さらにプロ志向の方や創作を楽しむ方など、細かくターゲットを設定しています。 今回はジャンルやターゲットを分けず、全ユーザーに向けた記念企画としたため、幅広いターゲット層にどうアプローチしていくかが難しかったです。

matcha:これまで自分は版権作品をベースにした広告を手がけたことはあったのですが「pixiv」単体で広告を打つことは初めてで、宣伝方法にとても悩みました。「創作活動を、もっと楽しくする。」という企業ミッションに立ち返り、ユーザーにそのメッセージがきちんと届くものにしようということを意識して進めました。

amanatsu:pixivはプラットフォームなので、ユーザーにとっては無色透明に近い存在で、こちらから強く主張するものではないんじゃないかな、と思っています。その上でこちらのメッセージをいかに伝えていくかを、投稿企画のお題を考える中で気をつけていました。

matcha:主張しすぎずユーザーに想いを伝えるというのは難しいバランスでしたよね。 版権作品を広告ベースとしないため、キービジュアルづくりも課題の一つでした。デザイン担当者には「広告を見ただけで終わらせず、既存ユーザーの方も新規の方もpixivに訪れたくなるようなクリエイティブにしたい」と相談しました。 例えばピールオフ広告はノベルティを剥がす体験が大きな魅力ですが、スケッチブック内にお題ページを設けることで、剥がした後にも創作を楽しむ体験が続いて欲しいという想いを込めています。

ユーザーの反応はいかがでしたか?

matcha:ピールオフ広告のスケッチブックは、2日間で合計400部が各日1時間でなくなるほど好評でした。とくに既存ユーザーの反応がとても良く、pixivが広告を出していることやアカウント数1億突破について驚きの声を頂きました。スケッチブック自体も好評でしたし、剥がした後に表れるお題もちゃんと見てもらえていたので安心しました。 実施後にpixiv内でアンケートも取ったのですが、利用駅で広告を見つけて嬉しかったという声や、写真を撮ってシェアしてくださっていたことが伺えて、良いプロモーションができたと改めて実感しました。

amanatsu:投稿企画も、本当に素敵な作品ばかりでした。pixivのコンテストで受賞しステップアップした方や、pixivを活用して作品を見てもらえる体験をした方など、感謝の声や嬉しい声をたくさんいただき、私自身も読んでいてぐっときました。

ピクシブ社員からも「良い作品がたくさんあった」「読んで泣きそうになった」という声を多数もらい、ユーザーの声が会社全体のモチベーションアップに繋がったと感じています。

最後に

amanatsu:以前、小説でワンライ企画に携わったのですが、今回イラストやマンガでもワンドロ&ワンライ企画を実施し、多くの方に参加いただけてとても嬉しかったです。難しいことも多くあった企画でしたが、たくさんの新しいことに挑戦できました。今回の企画が、ジャンル横断の投稿企画実績として今後の社内モデルにもなれたら嬉しいです。

matcha:今回の企画を通し、pixivの可能性はまだまだあると感じました。ピールオフ広告を見ながらpixivの読み方を知らないという会話をしている人がいたのですが、その人のカバンにはキャラクターグッズがついていたので、おそらくpixivと親和性が高い層ではあるのだろうと思います。そういった方にも、今回のような節目の企画でどんどんアピールしてpixivを知ってもらいたいと感じました。

たくさんのこだわりを語ってくれた担当のお二人、ありがとうございました! 投稿企画は2月で終了しましたが、応募作品は以下からご覧いただけます。 たくさんの「思い出の○○」をぜひチェックしてみてください!

イラスト・マンガ
www.pixiv.net

小説
www.pixiv.net

夏インターンでPHPStanのバグを直してコントリビュートした話

こんにちは。先日PIXIV SUMMER BOOT CAMP 2023にpixivウェブエンジニアリングコースで参加した、zer0-starです。

インターン期間中に、メンターのtadsanによりPHPStanのバグが発見され、僕がそれを直しました。

せっかくなので、直したバグについて話していこうと思います。

なお、今回出したPRは、以下になります。

https://github.com/phpstan/phpstan-src/pull/2591

バグの概要

僕が直したバグの概要ですが、端的に言うと「array_sum()に渡される配列の要素の型が定数型を含んでいたときなどに、推論される返り値がありえない型になる」というものです。

array_sum()とは、その名のとおり、引数として渡された配列の和を返す関数です。

www.php.net

定数型とは、1 2.3 'foo' のように、定数を表す型のことです。TypeScriptでお馴染みかもしれませんね。

例えば、"整数の配列"を表すarray<int>という型の値がarray_sum()に渡されたときは、返り値がintと推論されていました。これは正しい結果です。

しかし、例えば"要素が全て1であるような配列"を表すarray<1>という型の値が渡されたときには、期待される返り値の型は"0以上の整数"を表すint<0, max>か、少なくともそのスーパータイプであって欲しいでしょう。にもかかわらず、実際に推論された型は1となっていました。この結果は明らかに正しくありません。

他にも、例えば[1, 2, 3] が渡されたときには返り値が6と推論されて欲しいですが、実際には1 または2 または3 を表わす 1 | 2 | 3 と推論されてしまっていました。困ります!

では、なぜこのようなバグが発生してしまったのでしょうか。それは、array_sum() の型を推論するためのPHPStan拡張の実装に問題があったからです。

元の実装では、要素の型をほとんどそのまま返すようになっていました。これは、定数型が渡されなければ、問題なく動作する実装でした。つまり array_sum() の入力が array<int> なら intarray<float> なら floatarray<int|float> ならば int|float を返すということです。

これは一見よさそうですが、array<1|2>array_sum() に入力した結果は 1|2 ではありません。要素が1と2しかなかったとしても、空のリストならば結果は 0 、長さが2以上ならば結果はそれに応じて増加していきます。

この拡張の実装時には int[]non-empty-array<float> のような型のテストしかなく [1, 2, 3] のような定数を直接渡していなかったため、この問題はこれまで見過ごされていたようです。

PHPStan拡張とは

ここで登場したPHPStan拡張というものについて説明します。(ここでは、型推論に絞って話します)

実は、PHPStanは素の状態では型の表現力が(TypeScript等と比べて)あまり高くありません。

しかし、それを補う仕組みとして、ユーザーやライブラリの作成者が、PHPStanの静的解析をアドホックに拡張する拡張機能を作成することができます。拡張機能では、例えば関数やメソッド単位で型推論をオーバーライドして、通常では不可能な精密な型付けをすることができます。

型の表現力を強くする代わりに拡張機能という形式をとっていることのメリットとして、型付けのルールをPHPのコードとして書くことができるので、複雑な型付けも読み書きしやすく、型を操作するための専用の構文などに習熟する必要がないというものが挙げられます。

array_sum() の返り値の型推論では、PHPStan本体に同梱されているarray_sum() 専用の拡張機能が使用されています。先程のバグは、この拡張機能が原因で発生していたのでした。

PHPStan拡張ってどうやって実装するの

関数の型推論をする拡張機能は、具体的には関数の定義、関数適用の構文木、そして型環境をとって型を返すようなメソッドとして実装できます。例えば、「ある関数について返り値の型を引数全ての型のunionに推論する」ような拡張機能を作りたいなら、次のようなメソッドを書くことになるでしょう。

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
{
    $resultTypes = [];

    foreach ($functionCall->getArgs() as $arg) {
        $resultTypes[] = $scope->getType($arg->value);
    }

    return TypeCombinator::union(...$resultTypes);
}

コードの詳細については説明しません。型をデータとして操作していることが分かればOKです。

Scope::getType() を使うことで、自分の関心がない箇所についてはPHPStanの側に型推論を任せ、拡張側では型を組み合わせて最終的な型を作ることに専念できます。

実際にバグを直してみる

ここまで分かれば、あとは拡張を正しい動作をするように書き換えるだけです。

APIリファレンスとにらめっこして……よし、書けた!大量に追加したテストケースも通ったし、PRを出すぞ!

そうしてできたのが、こちらのコードです。

https://github.com/phpstan/phpstan-src/pull/2591/commits/2bef891930282fa1bb347590f9ab2a0404fb1cd6

配列の形やunionを全て場合分けで対応しているので、複雑なコードになってしまいました……泣

無限の場合分けと無限のネストを兼ね備えたコード

これに対してPHPStanの作者のOndřej Mirtes氏からのフィードバックは、以下の通りでした。

Hi, I don't really love that this PR is working with specific scalar values and calls array_sum internally. It misses out on various properties of the type system, like working with IntegerRangeType.

I'd much rather be if for ConstantArrayType objects, like [$one, $two, $three], it tried to do $scope->getType(new Plus($one, new Plus($two, $three))). Because Plus expr handling in MutatingScope already knows how to work with al possible Types, we can just call it here like that.

Because ConstantArrayType contains Type and not Expr, you can take advantage of TypeExpr virtual node to build an Expr out of Type instances.

つまり、要素の型がunionか定数かintかfloatかといった情報をいちいち手動で場合分けするのではなく、PHPStanの型推論に任せようということです。

そのために使える機能として、TypeExprというクラスを使うといいというアドバイスをもらいました。

ReturnType拡張を作成する際に頻出するPHPStan\Analyser\Scope::getType()というメソッドは、ソースコードをパースしたPHP Parserの構文木のノードを受け取って型を解決できます。1 + 1と書かれた式のノードを渡すと、定数型のint<2>rand() + rand() なら int が得られるということです。ここまでは普通の使い方なのですが、PHPStanはVirtualNodeというPHP Parserを拡張した独自のノードを提供しています。TypeExprはVirtualNodeのひとつで、PHPStanのTypeオブジェクトをPHP Parserのノードとして扱えるようにするためのアダプターです。

つまり、array_sum([1, 2]) というコードの結果は $scope->getType(new Add(new LNumber(1), new LNumber(2))) のようなノードを組み立てることで得られますが、new LNumber(1)のような部分を別の部分から $scope->getType() で得られたTypeオブジェクトをTypeExprでラップしたものに置き換えても型を解決できるということです。これを活用することで、要素が変数か定数かといった細かな場合分けを完全に廃することができました。このTypeExpr については、PHPStanを"完全に理解した"というメンターのtadsanも初めて聞いた機能だったとのことです。

その後も修正とフィードバックを繰り返し、コードの質だけでなく型推論の性能も高くすることができました。

そして、インターン最終日の最終課題発表をしている最中に、PRがマージされました!ありがとう……

おわりに

PHPStanでは容易に拡張機能を書いて静的解析の効果を上げることができます。みんなも、お気に入りの拡張を育てて自分だけのPHPStanを作りあげよう!

今年もやってるぞブックサンタ

今年もブックサンタの季節がやってまいりました!

ブックサンタとは、NPO法人チャリティーサンタが主催し、「厳しい環境にいる全国の子どもたちに本を届けること」を目的に活動している「ブックサンタ2023〜書店で誰でもサンタクロースに〜 | NPO法人チャリティーサンタ」のことです。

昨年から、pixivはブックサンタと協力して「pixiv小説子どもチャリティー企画 ブックサンタ」というコンテストを開催しています。今年も絶賛開催中。 オリジナル、ファンフィクション問わず、クリスマスをテーマにした小説やエッセイに「ブックサンタ2023」のタグをつけて投稿すれば、ピクシブ株式会社がNPO法人チャリティーサンタへ1作品ごとに500円寄付します。ちなみに、昨年は総額581,000円の寄付が集まりました。 さらに今年はRPキャンペーンも実施中。1RPごとに5円寄付しますので、小説を書くのは難しいかも…という人は、ぜひRPをお願いいたします。

さて、今回はこの「ブックサンタ」の取り組みに賛同した社員たちが、寄付した本の一部をご紹介していきます。

昨年同様、一部社員と一緒に本屋に行って選びました。お互い小さい頃どんな本を読んでいたのかなど話しながら本を選ぶのはとても楽しかったです。

寄付は12月24日まで受けつけているそうなので、興味がわいたらブックサンタの公式ページを見てみてくださいね。

コンテストの詳細を見る

『小学館の図鑑NEO[新版]岩石・鉱物・化石』萩谷 宏(監修)、門馬綱一(監修)、大路樹生(監修)/小学館

図鑑は人気の本だと聞きましたし、石は身近なものなので楽しく読めるかなと選びました。私も小さい頃はきれいな色や形の石を集めていました。私はただきれいだなーと思っていただけですが、この図鑑を読んだ子はその石がどこでできたか、名前はなんというのか知ることができます。そういう知るよろこびから、色々なことに興味持ってくれたらうれしいです。(hotep) www.shogakukan.co.jp

『かいけつゾロリのチョコレートじょう』原 ゆたか/ポプラ社

読んだのが30年以上前なのでストーリーについては曖昧ですが、出てくるチョコレートがすごくおいしそうだったことだけは覚えています。確か、ミルク、ビター、ホワイトチョコの三層になったチョコレート。私も食べてみたいぜ…チョコレートじょうに行きたいぜ…とワクワクしました。私みたいな食いしん坊の子と、このワクワクを共有できたらとってもハッピー!(hotep) www.poplar.co.jp

『うろんな客』エドワード・ゴーリー (著)、柴田 元幸 (訳)/河出書房新社

カギ鼻頭のヘンな生き物がやってきたのはヴィクトリア朝の館。とある一家の生活の中に突然入り込んできてそして、それから…。大人のためと言われている絵本ですが、私は幼い頃にこの作品を夢中で読んだ記憶があります。可愛くも不思議な生物に惹かれて手を取り「うろん」ってどういう意味? 「この漢字はどう読むの?」と母を質問攻めにして、内容を完全に理解できては居なかったけれど言葉のリズムの面白さに何度もページを捲りました。イラスト、文章、装丁、何か一つでも惹かれるものがあれば、本を超えて世界が広がる一冊です。(risari) www.kawade.co.jp

『空想街雑貨店の旅する塗り絵』西村典子、西村祐紀/河出書房新社

周りの友達ほど上手く絵が描けないなと思って落ち込んでいたころ、気分転換に遊んでいたのが塗り絵だったように思います。自分だけの街、自分だけの雑貨店、自分だけの旅。白と黒の線だけのイラストから空気や音が伝わってくるこの塗り絵は、開くだけで無限の可能性が広がります。創作が好きだったり、興味がある子どもが手に取って、その世界に夢中になってくれたら嬉しいです。(risari) www.kawade.co.jp

『カラフル』森 絵都/文藝春秋

ブックサンタ初参加です。寄付される本の中でも小学生向けの本が不足する傾向にあるらしく、運営が小学生向けの推薦図書リストを公開していたので、そちらを参考にしました。最終的にリストの中から森絵都さんの小説「カラフル」を選びました。自分も過去に読んで心に残っている作品です。有名な作品だとは思いますが、このプログラムを通して、今何かに思い悩んでいる子にこの本が届くといいなと思ってます。(jovz) bookclub.kodansha.co.jp

『新版 ファーブルこんちゅう記 (1) タマコロガシものがたり』小林清之介 (文)、横内 襄 (絵)/小峰書店

小さい頃に読んでた本のタイトルってあまり覚えてないんですけど、ファーブル昆虫記を読んだことは覚えてました。今は全く虫に興味のない私も、子供の頃は本や図鑑で虫の名前を色々調べていた気がします。タマコロガシのような普段接点のないものを知るきっかけになって欲しいです(ところで今ってタマコロガシって呼ぶんですね。昔と呼び方が変わってて驚きました)(rokuroku) www.komineshoten.co.jp

『ポケットモンスター テラパゴスのさがしもの』喜瀬 りっか/小学館

たまたま発売直後だったみたいで、書店で目につくところに置かれていました。見かけた瞬間のニャオハ・絵が良い・装丁が綺麗という第一印象で選びました。とても綺麗な表紙なのでそういうところからでも本を好きになるきっかけになれば良いなと思います。(rokuroku) www.shogakukan.co.jp

『死んだかいぞく』下田 昌克/ポプラ社

クリスマスにもらう本は、心になんとなく残るものがいいなと考え、引きと癖のつよい作品を選びました。この作品は海賊が海の底に徐々に沈んでいく様子を描写しているのですが青が濃くなる背景がとても綺麗で印象的な作品です。音読にも適した内容なので、声にだして読んでほしいと思っています。(ushio) www.poplar.co.jp

『小学館の図鑑NEO まどあけずかん きょうりゅう』小林快次(監修)、 田中康平(監修)、 オガワユミエ(絵)、高橋 進(絵)/小学館

みんな大好き恐竜図鑑!さらに各ページ窓開けの仕掛けがあるので一つ一つ楽しみながら開けまくってほしいと思いプレゼントに選びました。園や学校に持っていってお友達と楽しんでほしいと思います!(ushio) www.shogakukan.co.jp