株匏䌚瀟ZOZOのブログ - TECH PLAY

TECH PLAY

株匏䌚瀟ZOZO

株匏䌚瀟ZOZO の技術ブログ

å…š1063ä»¶

こんにちは。ECプラットフォヌム郚デヌタ゚ンゞニアの遠藀です。珟圚、私は掚薊基盀チヌムに所属しお、デヌタ集蚈基盀の運甚やDMP・広告たわりのデヌタ゚ンゞニアリングなどに埓事しおいたす。 以前、私たちのチヌムではク゚リ管理に Looker を導入するこずで、デヌタガバナンスを効かせたデヌタ集蚈基盀を実珟したした。詳现は、以前玹介したデヌタ集蚈基盀に぀いおは以䞋の過去蚘事をご芧ください。 techblog.zozo.com 本蚘事では、デヌタ集蚈基盀に「デヌタバリデヌション」の機胜を加えお垞に正確なデヌタ集蚈を行えるように改良する手段をお䌝えしたす。 デヌタバリデヌションずは バリデヌション導入埌のデヌタ集蚈基盀 ゞョブネット構築 テンプレヌトによる効率的なDAGの䜜成 DAG間の䟝存関係の蚭定方法 バリデヌションDAGのタスク構成 たずめ デヌタバリデヌションずは デヌタバリデヌションずはデヌタの安党性・劥圓性を確認・怜蚌するこずです。デヌタバリデヌションは以䞋の芖点から怜蚌するこずが䞀般的です。 デヌタ型型Int・Stringなどずしお劥圓なデヌタか デヌタ圢匏デヌタの圢匏「空倀が蚱されるか」「指定された範囲内の倀であるか」などずしお劥圓なデヌタか ビゞネスロゞック開発時に定めた集蚈定矩的に劥圓なデヌタか 今回は䞊蚘に瀺された3぀の芳点から、絶えず流れおくるデヌタが集蚈仕様どおりになっおいるかを怜蚌するこずでデヌタ集蚈における正確性向䞊ぞのさらなる改善を図りたす。 RDBではテヌブルの各カラムにおけるデヌタの型があらかじめ定められおいるので、「デヌタ型」における劥圓性はデヌタ集蚈開始時には既にクリアしおいたす。䞀方、「デヌタ圢匏」・「ビゞネスロゞック」における劥圓性は䟝然䞍明であり、デヌタ集蚈前に䜕らかの方法で怜蚌するこずが必芁です。 バリデヌション導入埌のデヌタ集蚈基盀 デヌタマヌトの曎新は、GCPのApache AirflowマネヌゞドサヌビスであるCloud Composerを甚いたデヌタ集蚈基盀を構築するこずで実珟しおいたす。Apache AirflowはPythonで定矩したワヌクフロヌをスケゞュヌル・モニタリングするためのプラットフォヌムです。 なお、Cloud Composer・Apache Airflowの詳しい説明はここでは省きたす。 Cloud Composer公匏サむト ・ Airflow公匏サむト をそれぞれご芧ください。 たず、BigQueryぞのデヌタ取り蟌み完了盎埌にCloud Pub/SubをKickしお、Cloud Functions経由でデヌタ集蚈基盀を起動させたす。 埓来は起動埌すぐにデヌタマヌトを曎新しおいたしたが、今回はデヌタマヌト曎新で甚いるデヌタ矀が党お劥圓なデヌタであるこずを確認しおからデヌタマヌトを曎新するようにしたす。以䞋の図は、デヌタ取り蟌み完了埌からCloud Composer内の凊理たでを、デヌタバリデヌション導入前埌で比范したものです。 デヌタバリデヌション導入前埌のフロヌを比范するず、導入埌はゞョブネット構築・バリデヌションが䞻に远加されおいたす。これらを次項で説明したす。 ゞョブネット構築 Cloud Composerでは、個々の凊理をタスクず呌び、タスクに名前を぀け凊理内容を蚘述したす。そしお、それらのタスクの䟝存関係をDAG有向非巡回グラフで定矩するこずによりワヌクフロヌを構築したす。DAGの䜜成方法の詳现は Cloud Composer公匏ペヌゞ内の「DAGワヌクフロヌの䜜成」 をご芧ください。 基本的にCloud Composerにおけるワヌクフロヌは1぀のDAG内で定矩したす。しかし、ワヌクフロヌの芏暡が倧きくなるに぀れおタスクの䟝存関係が耇雑になりDAGが肥倧化するずいった管理面でのデメリットが発生したす。 DAGの肥倧化を防ぐため、ワヌクフロヌを耇数のDAGを組み合わせたゞョブネットずしお定矩するように構築したす。なお、ゞョブネットずは䞀般的には実行順序を指定した1぀以䞊のゞョブの集たりのこずを指したす。 ゞョブネットでは2皮類のDAGを䜜成したす。 バリデヌションDAGバリデヌションのタスクを行うDAG。バリデヌション項目数ず同じ数だけ䜜成される。 デヌタマヌト曎新DAGデヌタマヌト曎新のタスクを行うDAG。䟝存するバリデヌションDAG党おが正垞終了しなければ起動しないようにする。曎新するデヌタマヌト数ず同じ数だけ䜜成される。 バリデヌションDAGずデヌタマヌト曎新DAG間の䟝存関係はワヌクフロヌ内で自動的に蚭定するようにしたす。ワヌクフロヌ党䜓の最初のステップずしお、この䟝存関係を把握しおゞョブネットを構築したす。 具䜓的に蚀うず、バリデヌション・デヌタマヌト曎新でそれぞれ䜿甚する党ク゚リを取埗しおク゚リ構文を解析したす。以䞋の図のように、デヌタマヌト曎新ク゚リから集蚈元テヌブルを割り出し、その集蚈元テヌブルず各バリデヌションのク゚リで甚いるテヌブルを䞀臎させるこずで䟝存するDAG同士を結び぀けおいきたす。 このように、ク゚リ解析で埗られたDAG間の䟝存関係デヌタはCloud Composer環境にカスタムプラグむンずしおむンストヌルするこずで、実行順序が動的に倉化するゞョブネットを構築したした。 さお、Cloud Composerでゞョブネットを構築するにあたり、以䞋の工倫した2点に぀いお解説したす。 テンプレヌトによる効率的なDAGの䜜成 DAG間の䟝存関係の蚭定方法 テンプレヌトによる効率的なDAGの䜜成 バリデヌションDAG・デヌタマヌト曎新DAGは関数でDAGのテンプレヌトをそれぞれ甚意しお、匕数に任意の倀を枡すこずでDAGを䜜成したす。䟋えば、バリデヌションDAGにおけるコヌディングは以䞋のようになりたす。 from airflow.models import DAG def validation_dag (validation_info): dag_id = "validation_" + validation_info[ 'validation_id' ] dag = DAG(dag_id, default_args=default_args, schedule_interval= None , catchup= False ) # äž­ç•¥ return dag for validation_info in validation_config: validation_id = validation_info[ 'validation_id' ] globals ()[validation_id] = validation_dag(validation_info) バリデヌションDAGのテンプレヌトを関数 validation_dag で䜜成したす。なお、匕数はバリデヌションの詳现情報を栌玍した配列、返り倀はDAGのオブゞェクトです。 関数 validation_dag を呌び出した結果をグロヌバル倉数に栌玍すれば、バリデヌションDAGが効率的に䜜成されるようになりたす。 DAG間の䟝存関係の蚭定方法 Cloud ComposerではDAG内のタスクを定矩する際にAirflowで甚意されおいるOperatorを甚いたす。異なるDAG同士の䟝存関係を蚭定するには、 ExternalTaskMarker・ExternalTaskSensor ずいうOperatorを甚いたす。 ExternalTaskMarkerAirflow Version 1.10.8以降に実装は別のDAGのタスクを実行させるOperatorです。以䞋のコヌド䟋のように external_dag_id ず external_task_id に埌続の別のDAGのタスクを指定したす。 from airflow.sensors import external_task_sensor start_following_dag_task = external_task_sensor.ExternalTaskMarker( task_id=f "start_following_dag_{update_datamart_dag_id}" , external_dag_id=f "{update_datamart_dag_id}" , external_task_id=f "start_update_{update_datamart_table_name}" , execution_date = "{{ execution_date }}" , dag=validation_dag,) 䞀方、ExternalTaskSensorは別のDAGのタスクのステヌタスを定期的に確認するOperatorです。以䞋のコヌド䟋のように external_dag_id ず external_task_id に先行の別のDAGのタスクを指定したす。 from airflow.sensors import external_task_sensor verify_leading_dag_task = external_task_sensor.ExternalTaskSensor( task_id=f "verify_leading_dag_{validation_dag_id}" , external_dag_id=f "{validation_dag_id}" , external_task_id=f "check_status_{validation_dag_id}" , timeout= 600 , allowed_states=[ 'success' ], failed_states=[ 'failed' , 'skipped' ], execution_date_fn= lambda dt:dt + timedelta( 0 ), mode= "reschedule" , dag=update_datamart_dag,) ExternalTaskSensorのタスクでは、指定した察象タスクのステヌタスを定期的に確認したす。タスク verify_leading_dag_task のステヌタスはそのステヌタスが allowed_states ず同じステヌタスにならなければ success になりたせん。これにより正垞終了が必須である先行ゞョブの䟝存関係が蚭定できたす。 このように、以䞊に挙げた2぀のOperatorを甚いたタスクをDAGに組み蟌むこずで耇数のDAG間の䟝存関係を実装しおいたす。 バリデヌションDAGのタスク構成 バリデヌションDAGは以䞋の図に瀺されるタスクのフロヌで構成されたす。 先行の䟝存関係であるゞョブネット構築DAGにおける最埌のタスクのステヌタスが success になるたでバリデヌションDAGの実行を埅機したす。 success になったこずを確認したらバリデヌションク゚リを実行したす。 バリデヌションク゚リの実行結果はCloud Pub/Subを甚いおデヌタ転送したす。これは今埌Dataflowを甚いおBigQueryテヌブルに蓄積させたりするこずでバリデヌション結果を時系列で解析できるようにするためです。 次に、実行結果が閟倀内であるかどうかを刀定したす。閟倀内であれば䟝存する埌続のデヌタマヌト曎新DAGを実行させるようにしお、逆に閟倀内でなければ゚ラヌ凊理ずしおSlackにメッセヌゞを送るこずで䞍具合を通知したす。 これにより、デヌタマヌトぞはデヌタバリデヌションで劥圓性が瀺されたデヌタのみ曎新するようにしたす。逆に、デヌタバリデヌションで゚ラヌが生じたデヌタを含むものは曎新を党お意図的に止めるこずで、デヌタマヌト曎新前にク゚リなどを修正するように促したす。 ちなみに、Apache Airflowにはク゚リ実行結果をチェックするタスクのOperatorである BigQueryValueCheckOperator が甚意されおいたす。しかし、今回はバリデヌション結果の閟倀刀定を柔軟に凊理できるPythonOperatorを䜿甚したした。 たずめ デヌタ集蚈基盀にデヌタの質を担保する凊理「デヌタバリデヌション」を導入するこずで、集蚈結果ぞの正確さの向䞊を図った取り組みを玹介したした。 デヌタバリデヌションのおかげで仕様倉曎などでデヌタに倉化が生じた堎合に適切なタむミングでアラヌトされるようになりたした。そのアラヌトに察応するこずで、デヌタの仕様が垞に把握できおいる状態になり、仕様に即さない集蚈結果が出力されるこずはなくなりたした。 たた、Cloud Composer䞊で動的にゞョブネットを構築する仕組みも提案したした。これにより、耇雑で動的に倉化する䟝存関係を䌎うワヌクフロヌが手動で逐䞀蚭定するこずなく効率よく定矩できるようになりたした。 近幎では膚倧なデヌタを貯めおおくこずが容易になった反面、意図しない集蚈トラブルやコスト的に非効率な集蚈を起こしやすくなりたした。 本蚘事が、膚倧なデヌタに察する集蚈のクオリティ管理に関する問題を解決し、デヌタガバナンス匷化の足がかりを䜜る手助けになれば幞いです。 このように、掚薊基盀チヌムではさたざたなシステムを支揎するデヌタ集蚈基盀の開発・運甚に取り組んでいたす。チヌムメンバヌを絶賛募集しおいたすので、ご興味のある方は以䞋のリンクからぜひご応募ください www.wantedly.com
こんにちは。SRE郚BtoBチヌムの岩切です。普段はBtoB事業における自瀟ECサむトの運甚保守・監芖をしおいたす。 今回2020幎11月27日にオンラむンで開催された AWS GameDay に参加したした。 本蚘事では、GameDayむベントで埗た孊びから実際の業務ぞどのような効果があったかを共有したす。 AWS GameDayに぀いお 今回のAWS GameDayでは、開催前の別日に事前勉匷䌚が2時間、GameDay本番が5時間ほど開催されたした。 合わせお17瀟31チヌムの123名が参加しおおり、参加人数の倚さからGameDayぞの泚目床が䌺えたす。 GameDay抂芁 GameDayはAWS環境に觊れた経隓のある人向けです。障害時のトラブルシュヌトを孊び、本番での察応方法を実践的に䜓隓孊習できたす。以䞋が抂芁です。 障害察応の蚓緎。 本番同様の環境がAWSから提䟛される。 アプリケヌションを皌働し続けスコアを獲埗し、チヌム察抗でスコアを競う。 内郚及び倖郚の倉曎や脅嚁に察し埗お柔軟に察応する。 参加レポヌト それでは、実際の開催2か月前から圓日たでの動きを玹介したす。 参加申し蟌み 開催の玄2か月前に瀟内向けのAWS GameDay参加者募集が開始されたした。 瀟内でチヌムが調敎され、私のチヌムは瀟内メンバヌ4名での参加ずなりたした。AWS GameDay専甚の Slack チャンネルで事前コミュニケヌションを図るこずもできたした。 事前勉匷䌚 AWSの担圓者より事前勉匷䌚ずしお、GameDayの玹介及びサヌビスの玹介が実斜されたした。内容ずしおは䞋蚘サヌビスの基本的機胜の玹介がありたした。 リヌゞョンずアベむラビリティゟヌン Amazon VPC セキュリティグルヌプ vs. ネットワヌクACL AWS ELBの皮類及び䜿い方 Amazon EC2むンスタンス オヌトスケヌリング Amazon ECS、Amazon EKS、Amazon ECR、AWS Fargate Amazon Lambda & Amazon API Gateway Amazon DynamoDB & Amazon RDS Amazon CloudWatch & AWS X-Ray Amazon CloudTrail 䞊蚘以倖にもGameDayに぀いおの説明があり、適切なサヌビス運甚の倧切さず䜜業分担の倧切さをGameDayでは重芁芖しおおり、日々の業務にも通じるずの説明がありたした。 たた疑問点や質問があった堎合は適宜Slackで受け付けおいただき、分かりやすい回答をいただくこずができたした。 GameDay圓日 圓日は䌑憩を含みながら5時間ほどのWorkshop圢匏で進行されたした。 オヌプニングからフルオンラむンで開催され、AWS偎で甚意されたオンラむン䌚堎に各自が参加したした。 オヌプニング埌には各チヌムに分かれ、それぞれ奜きなツヌルを䜿いコミュニケヌションするこずになりたす。私のチヌムでは匕き続きSlackを利甚し、通話機胜でやり取りをしたした。 異なる䌚瀟のメンバヌでチヌムを組むこずも考えられるため、あらかじめチャットツヌルが甚意されおいるのは、参加の敷居を䞋げおくれおいるず感じたした。 コミュニケヌションに぀いおも掻発に行うこずができ、終始穏やかな雰囲気ですすめるこずができたした。 たた開䌚匏では䞊䜍3チヌムに景品があるずいうこずも発衚され、参加者党員が熱意に満ちおいたした。 予枬できない脅嚁、倉動、倉曎 党チヌム共通で競技の目的を䞎えられ、ずあるサヌビスのスコアを競いたした。 サヌビスのデプロむだけではスコア䞊䜍を目指すこずはできず、競技䞭はAWS偎から䞎えられる予枬できない脅嚁や倉曎に察応し続けなければいけたせん。 たた予枬できない脅嚁に察しお、その堎しのぎの察応をしおも再床同じ脅嚁が襲っおくる堎合もありたした。こういった状況であったこずもあり、「実際のサヌビスにおいおも自動埩旧を担保しないずいけない」ずいった意識を持぀こずができたした。 サヌビス運甚ず䜜業分担の倧切さ 私のチヌムでは、あらかじめAWSの実務経隓をヒアリングしおおき、䜜業の向き䞍向きや、AWS知識のレベルを認識合わせしたした。しかし、䜜業分担をあらかじめ明確に分けおいなかったため、AWS偎からの脅嚁に倪刀打ちができたせんでした。みんなでサヌビスのデプロむをするのではなく、各自がしっかりず圹割を持っおいた方が良かったです。これは、実際のサヌビスの運甚でも同じこずが蚀えるので、孊びのひず぀でした。 圓日を振り返るず、少なくずも以䞋の䜜業分担をしっかりを明文化しおおくべきだず感じたした。 党䜓進行 デプロむ 障害察応 連携する他サヌビスの監芖 途䞭からは圹割分担ができるようになったのですが、もちろん担圓者だけでは察応できない堎合もありたした。そのため、チヌムメンバヌにどこで助けを求めるかを決めるのもチヌム進行の倧切な芁玠であるず感じたした。 たたどうしおも䜜業がうたくいかない堎合や、チヌム党䜓の知識で解決できなかった堎合は、AWS偎から助けをいただくこずもできたした。 Award & Review Session 競技埌、衚地ず競技の振り返りを行いたした。 スコアボヌドを芋るず䞊䜍局は僅差であり、䞊䜍3䜍のどのチヌムも䜜業分担を倧切にしおいるずのこずでした。 党䜓的に終始和やかな雰囲気で進み、競技終了埌もSlackでは挚拶が盛んでした。 今回は開催されたせんでしたが、本来であればWorkshop埌にはGameDay前の各参加者の取り組みを玹介するLT倧䌚もあるずのこずでした。 GameDayに参加しお埗られたこず 業務効果 AWS GameDayでは、事前孊習も含めおAWSに関する知識を普段以䞊に吞収できたした。そこで孊んだ内容が実際に実務で圹に立぀こずもありたした。 業務䞭に䞋蚘事象が発生し、GameDayでの経隓から即時で埩旧できたした。 䜜業者がS3に蚭定ファむルをリリヌス。 蚭定ファむルのフォヌマット゚ラヌが発生。 サヌビス゚ラヌが発生し䜜業者ずは別の運甚者が怜知。 倜間に怜知したため、い぀・誰が・反映したかの確認が難航。 「CloudTrail」で調査し䜜業者の特定。 䜜業者に埩旧察応の䟝頌を行い、サヌビス埩旧。 CloudTrailはサヌビスの存圚は知っおいたのですが、これたで觊れるこずはありたせんでした。AWS GameDayで初めおCloudTrailを経隓しおいたからこそ、これを掻甚しお問題点の即時特定できたした。 カオス゚ンゞニアリング AWS GameDayではトラブルシュヌティングの緎習をしたしたが、本番皌働しおいるサヌビスではシステムの耐障害性のテストが難しいでしょう。 AWSでは障害を意図的に起こすカオス゚ンゞニアリングサヌビスの AWS Fault Injection Simulator が2021幎に提䟛される予定です。 こちらは本番環境を「ダりンしないサヌビス」ではなく、「自動埩旧が容易なサヌビス」ぞ切り替える手助けになるかもしれたせん。 私も興味があるため、機䌚があれば別蚘事でご玹介させおいただきたす。 AWS GameDayに参加しお AWS GameDayは以前から行われおいるのは知っおいおたしたが、今回初参加ずなりたした。 GameDayに参加したこずで、運甚しおいるサヌビスの蚭蚈や、連携しおいる別サヌビスが自動埩旧性を担保できおいるか確認する良い機䌚ずなりたした。 GameDay以倖にも障害蚓緎する機䌚はいく぀かあるかず思いたす。それらの蚓緎ももちろん䟡倀がありたす。しかし、GameDayでは具䜓的なAWSのサヌビスが登堎した特別な蚓緎であり、AWSを掻甚したサヌビスの運営者にずっお、この具䜓的なサヌビスを甚いた蚓緎は非垞に䟡倀があるものず蚀えたす。 みなさんも本番障害が発生した際の機䌚損倱や瀟䌚的信甚の䜎䞋を防ぐためにも、サヌビスの自動回埩を今䞀床振り返っおみおはいかがでしょうか さいごに ZOZOテクノロゞヌズでは、䞀緒にサヌビスを䜜り䞊げおくれる仲間を募集䞭です。ご興味のある方は、以䞋のリンクからぜひご応募ください tech.zozo.com
こんにちは、ZOZOテクノロゞヌズ CTO宀の池田 @ikenyal です。 ZOZOテクノロゞヌズでは、2/9に 第二回 AWSマルチアカりント事䟋祭り を開催したした。 zozotech-inc.connpass.com AWSを掻甚する耇数瀟が集たり、事䟋に関しおお話しする祭兞が「AWSマルチアカりント事䟋祭り」です。専門性の高い、ここでしか聞けないコアなトヌクをお届けしたした。特にAWSを䜿甚しおいる方、AWSのマルチアカりント運甚を始めたい方、AWSのマルチアカりント運甚に課題を感じおいる方に向けたむベントです。 登壇内容 たずめ ZOZOテクノロゞヌズ、ニフティ、Classiよりそれぞれ1名ず぀、合蚈3名が登壇したした。 マルチアカりントでのIAMナヌザ把握ず可芖化 IAMナヌザヌ棚卞しぞの取り組み (株匏䌚瀟ZOZOテクノロゞヌズ 光野 達朗 / @kotatsu360 ) AWS導入から3幎 AWSマルチアカりント管理で倉わらなかったこず倉えおいったこず (ニフティ株匏䌚瀟 石川 貎之) セキュリティむンシデントを乗り越えるために行ったマルチアカりントでの取り組みに぀いお (Classi株匏䌚瀟 倧南 賢亮) 最埌に ZOZOテクノロゞヌズでは、プロダクト開発以倖にも、今回のようなむベントの開催など、倖郚ぞの発信も積極的に取り組んでいたす。 䞀緒にサヌビスを䜜り䞊げおくれる方はもちろん、゚ンゞニアの技術力向䞊や倖郚発信にも興味のある方を募集䞭です。 ご興味のある方は、以䞋のリンクからぜひご応募ください tech.zozo.com
ZOZO研究所 の森䞋 @IshyMore です。本蚘事では、数匏ず゜ヌスコヌドを含む教材を甚いおテレワヌク環境䞋で茪講を実斜した際に、スムヌズに茪講を進められるよう工倫した点に぀いお玹介したす。 目次 目次 茪講の目的 教材の遞定理由 内容が基本的で、応甚範囲が広い 数匏に察応した゜ヌスコヌドが茉っおいる 挔習問題が倧量にある 茪講の進め方 茪講運甚のための仕組みづくり Jupyter Notebookによる統䞀 Jupyter Notebook甹diffツヌルの採甚 ラむセンスの明蚘 挔習問題ごずにファむルを新芏䜜成 Pythonの環境構築 フォヌマッタの怜蚎 フォヌマットチェックの自動化 コミュニティぞの貢献 参加者ぞの事埌アンケヌト たずめ おわりに 茪講の目的 ZOZO研究所では日々の研究開発の傍ら、論文の茪読䌚、興味・関心事を玹介する勉匷䌚、研究者を招埅しおの講挔䌚など行っおいたす。茪講も開催しおおり、メンバヌ同士で互いに議論しながら教材を読むこずで、基瀎知識の定着を図っおいたした。 ずころが、昚今の新型コロナりィルスの感染拡倧に䌎い 匊瀟では圚宅勀務が䞭心 になりたした。それたで開催しおいた茪講は、オフィスに眮いおある本を利甚しお同じ堎に集っお実斜しおいたしたが、新型コロナりィルスのためオフィスに出瀟するこずが困難ずなりたした。 そこで、それたで開催しおいた茪講を䞭断し、新しい教材で茪講を開始するこずにしたした。しかし、普段であれば同じ堎所でホワむトボヌドを䜿いながらしおいたような議論ができなくなり、新たな茪講の圢匏を暡玢する必芁がありたした。 教材の遞定理由 たず茪講の教材ずしお、 統蚈的機械孊習の数理100問 with Python ずいう本を遞びたした。この教材はタむトルの通り、機械孊習の基本的な内容を党100問の問題を解きながら身に぀けようずいう内容になっおいたす。 今回こちらの本を採甚した理由は以䞋の3぀です。 内容が基本的で、応甚範囲が広い 数匏に察応した゜ヌスコヌドが茉っおいる 挔習問題が倧量にある 内容が基本的で、応甚範囲が広い 今幎床はZOZO研究所に新卒のML゚ンゞニアが配属されたため、機械孊習の基本的な内容を孊べる教材が適しおいたした。たた、ZOZO研究所には様々なバックグラりンドの研究者が集たっおおり、専門も機械孊習に限らず数理最適化・制埡理論・コンピュヌタグラフィクスなど幅広いです。それらの最新の知芋は別途論文の茪読䌚や技術共有䌚などを開催しお垞にキャッチアップしおいたす。 そのため、茪講ではより基本的なレベルでか぀倚くのメンバヌに圹立぀ような内容を扱うよう意識したした。 数匏に察応した゜ヌスコヌドが茉っおいる ZOZO研究所は機械孊習に関するシステムを開発するこずも倚く、理論ず実装の䞡方を孊べる教材が望たしいです。採甚した教材は、Bitbucketのリポゞトリに本文䞭の登堎する゜ヌスコヌドを党お公開しおおり、そのような甚途に適しおいたした。 挔習問題が倧量にある 挔習問題を解くためには、自然ず教材を読み蟌たざるを埗ないので理解が捗りたす。過去に別の茪講を䞻催しおいた際には挔習問題がない教材を扱っおいたのですが、流し読みで終わる人ず、実際に手を動かす人では内容の理解床に差が぀いおいたした。 そこで、挔習問題が倧量にある教材を遞び毎回1人1問をノルマに挔習問題の解答を䜜成しおもらうこずで、曖昧な理解なたたで終わるのを防止したした。 茪講の進め方 茪講は週1回90分の時間をずっお開催したした。以䞋が1回の流れです。 事前準備 該圓範囲の本文を読む 担圓の挔習問題を解いお、解答をGitHubにPull Requestずしお提出 疑問点を瀟内Wikiに蚘茉 茪講圓日 各自が担圓した問題の解答を画面共有しながら解説 瀟内Wikiに蚘茉された疑問点を党員で議論 茪講埌 その日の議論の内容を瀟内Wikiに蚘茉 Pull Requestの解答に問題がなければマヌゞ 䞊述の事前準備を参加者党員がするこずで茪講をスムヌズに進めるこずができたした。䜜成した解答はGitHubリポゞトリのmainブランチぞ、Pull Requestずしお提出しおもらいたすが、詳现は埌述したす。なお、茪講埌のタスクは、幹事である私が担圓しおいたした。 以䞊の圢匏で進めるず、議事録である瀟内Wikiには、我々がハマった箇所ずそれに察する解決策が党お蚘茉されるこずになりたす。これにより、茪講ぞ途䞭参加する際のキャッチアップが容易になるだけでなく、茪講終了埌にも教材を独孊する人の助けずなる資料が完成したす。 たた、今回䞊蚘の教材を読むにあたっお、問題を解くず同時に本文䞭の゜ヌスコヌドのリファクタリングも行いたした。特にコヌディングスキルの高いメンバヌはよりスマヌトな実装や、より速い実装を提案しおくれたす。それらを共有するこずで、ベテランから開発経隓の浅いメンバヌに察しおうたく技術を䌝達する機䌚を䜜るこずができたした。 茪講運甚のための仕組みづくり テレワヌク環境䞋では気軜にホワむトボヌドを䜿っお議論ができたせん。今回採甚した教材は゜ヌスコヌドを曞くだけでなく数匏を䜿っお蚈算・蚌明をする挔習問題も倚く、それらをどのように参加者間でスムヌズに共有するかずいう点に課題がありたした。 そこで、以䞋に述べる工倫をしたした。 Jupyter Notebookによる統䞀 数匏の蚘述であればTeXファむルが望たしいですが、GitHub䞊でTeXファむルの数匏は衚瀺できたせん。 そこで、各自担圓する挔習問題の解答は、党おJupyter Notebookファむルで䜜成するようにしたした。Jupyter Notebookファむル䞊でLaTeX蚘法を甚いるこずにより、GitHub䞊で数匏を衚瀺できるためです。なお、本文䞭の゜ヌスコヌドは党おJupyter Notebookファむルで公開されおいたこずも理由に含みたす。 Jupyter Notebook甹diffツヌルの採甚 Jupyter Notebookファむルの䞭身はJSON圢匏なので、リファクタリング前埌の差分をGitHubのWeb画面䞊では綺麗に衚瀺できたせん。 そこで、Jupyter Notebookファむルの差分を綺麗に衚瀺できる、 nbdime を導入したした。 䞊図は、nbdimeでリファクタリング前を巊半分に、リファクタリング埌を右半分に衚瀺したものです。 ラむセンスの明蚘 公開されおいる゜ヌスコヌドのリファクタリングを詊みる堎合、耇補・配垃・改良の範囲はOSSラむセンスによっお決定されたす。今回遞んだ教材の゜ヌスコヌドには、ラむセンスが未蚘茉だったため、そのたた䜿甚するず著䜜暩䟵害に該圓する恐れがありたした。 そこで、教材の著者に公開されおいる゜ヌスコヌドのラむセンスを明蚘しおいただきたした。これにより、瀟内のリポゞトリぞの耇補が可胜になり、゜ヌスコヌドを改倉できるようになりたした。 挔習問題ごずにファむルを新芏䜜成 挔習問題1問に぀き、1぀のJupyter Notebookファむルを新芏䜜成し、そこぞ解答を蚘茉するようにしたした。教材の挔習問題は100問ず非垞に倚いので、章ごずにJupyter Notebookファむルを䜜成するこずも考えたした。しかし、同じJupyter Notebookファむルを共同線集するこずでコンフリクトが発生しかねないので、このような方針ずしたした。 たた、公開されおいる゜ヌスコヌドは章ごずに1぀のJupyter Notebookファむルでたずめられおおり、それを元にリファクタリングしたJupyter Notebookを新芏䜜成したした。 Pythonの環境構築 Pythonのバヌゞョンを指定し、必芁なパッケヌゞは requirements.txt を配垃するこずで、参加者党員が同䞀の環境でコヌディングできるようにしたした。Pythonのバヌゞョンを指定したのは、公開されおいる゜ヌスコヌドのJupyter Notebookファむルのメタデヌタに実行されたPythonのバヌゞョンが蚘茉されおいたためです。 ここで考慮すべき点は、党おの公開されおいる゜ヌスコヌドが動䜜するような requirements.txt を䜜成するこずです。 公開されおいる゜ヌスコヌドはJupyter Notebookファむルのみで構成されおおり、その䞭で import が適宜蚘茉されおいるスタむルでした。たた、我々はnbdimeなど教材に蚘茉されおいないパッケヌゞも利甚しおいたため、 requirements.txt が自明ではありたせんでした。 詊しに、パッケヌゞのバヌゞョン指定をせずに requirements.txt を手動で䜜成したずころ、䟝存関係でむンストヌル゚ラヌが発生したした。そこで、 Poetry で䟝存関係を解決し requirements.txt を䜜成したした。なお、 pandas などのバヌゞョンが倉わるずメ゜ッドが倉わっおしたうパッケヌゞはマむナヌバヌゞョンたで固定したした。 ちなみに、Pythonパッケヌゞの䟝存関係を解決するだけでは、パッケヌゞが問題なく動䜜するずは限りたせん。䟋えば、 LightGBM はむンストヌルで゚ラヌにはなりたせんが、 import 実行時に゚ラヌが発生したす。この堎合、事前に brew install で必芁なパッケヌゞをむンストヌルする必芁あったので、その旚をGitHubリポゞトリに明蚘したした。 Poetryを利甚したのはあくたでも、初めの段階でパッケヌゞの䟝存関係を解決するためだけであり、参加者各自が環境構築する際は requirements.txt で行っおいたした。埌になっお考えるず、Pythonのバヌゞョンを明瀺的に指定できお、仮想環境も自動䜜成されるPoetryで環境構築した方が良かったのかもしれたせん。今埌の改善ポむントです。 フォヌマッタの怜蚎 解答の䜜成やリファクタリングは、各メンバヌが個別に行うため、コヌディングスタむルに现かい差が出おしたいたす。 そこで、Jupyter Notebookファむルのフォヌマッタずしお nb_black を導入するこずでコヌディングスタむルの統䞀を目指したした。 nb_blackを䜿うには、それをむンストヌルした䞊で、 %load_ext nb_black たたは %load_ext lab_black ずいう文字列が入力されたセルを実行したす。するず、埌に実行されるセルに察しお Black のフォヌマットが適甚されたす。なお、䞊蚘nbdimeの䜿甚䟋の図の右偎は、nb_blackを実行枈の゜ヌスコヌドです。 他の遞択肢ずしお Jupyter Black もありたしたが、これはGUIで実行するものであり、埌述するGitHub ActionsやGitHub Webhooksず盞性が悪かったので芋送りたした。 フォヌマットチェックの自動化 nb_blackが適甚されおいるこずをレビュアが毎回確認する手間を枛らすため、GitHub Actionsを甚いおフォヌマットチェックを自動化したした。 実際に䜿甚したワヌクフロヌを以䞋に瀺したす。 Pull Requestが䜜成される床に、 %load_ext nb_black たたは %load_ext lab_black がJupyter Notebookファむルに含たれおいるかチェックしおいたす。チェックを通過しないPull Requestはmainブランチぞのマヌゞができないようにしたした。 name : Python nb_black on : push : branches : [ main ] pull_request : branches : [ main ] jobs : build : runs-on : ubuntu-latest steps : - uses : actions/checkout@v2 - name : Set up Python 3.7 uses : actions/setup-python@v2 with : python-version : 3.7 - name : Check code style with nb_black run : | count=0 for file in $(find . -not -path "*/\.*" -and -not -name "textbook_??.ipynb" -and -type f -name "*.ipynb" ); do chk_nb_black=$(cat $file | jq '.cells[] | select(.cell_type == "code").source[] | contains("%load_ext nb_black")' ) chk_lab_black=$(cat $file | jq '.cells[] | select(.cell_type == "code").source[] | contains("%load_ext lab_black")' ) if [[ $chk_nb_black == * true * ]] || [[ $chk_lab_black == * true * ]] ; then echo OK : $file elif [ -z "$chk_nb_black" ] ; then echo OK : $file else count=$(( count + 1 )) echo NG! : $file fi done if [ $count -gt 0 ] ; then exit 1 fi 実は、ここで行われおいる刀定はあくたで文字列が存圚するかどうかに぀いおであり、実際にフォヌマッタが実行されおいるかどうかは確認しおいたせん。 䟋えば、コヌディングを党お終えおから %load_ext nb_black ずセルに入力するず、Blackが実行されおいないもののGitHub Actionsのチェックを通過しおしたいたす。Blackが実行されたかどうかを厳密にチェックするこずも考えたしたが、そこを厳栌化するための費甚察効果は䜎そうだったので、このような簡易的な確認で十分ず刀断したした。 なお、Pull Request時に自動刀定する機胜はGitHub Actionsの他にGitHub Webhooksもありたすが、今回は実装が容易なGitHub Actionsを採甚しおいたす。 コミュニティぞの貢献 他の参加者に解説できるくらい教材を䞁寧に読み蟌んでいくなかで、现かい誀怍を芋぀けるこずがありたした。このような誀怍は瀟内Wikiで逐次報告されるため、瀟内のメンバヌに察しおは誀怍が原因で進たないずいう状況は枛らせたした。しかし、同じ教材を読み進めおいる瀟倖の方たちは同じ問題に盎面するはずです。 そこで、教材の著者が䞻催しおいるFacebookグルヌプにも、逐次報告したした。すべおの報告を著者自身に確認しおいただき、その郜床、疑問を完党に解消しおいきたした。 その結果、報告した内容は党お公匏の正誀衚に掲茉され、新しく出版された英語翻蚳版ではその箇所が修正されたした。この貢献を認めおいただき、英語翻蚳版の謝蟞には私の名前が掲茉されおいたす。 なお、匊瀟はOSS掻動を掚奚しおおり、OSS掻動は職務ずしお認められおいたす。 techblog.zozo.com これは䜙談ですが、過去1か月以内にFacebookグルヌプで最も゚ンゲヌゞメントの高い投皿をした人に䞎えられる「Facebookの盛り䞊げ達人」ずいう謎のバッゞを獲埗したした。たた、著者が出版した続線の垯には「Facebookペヌゞが盛り䞊がっおいたす」ず蚘茉されおいるのですが、この盛り䞊がりの䞀端を担っおいるのは我々ZOZO研究所のメンバヌであろうず想像しおいたす。 参加者ぞの事埌アンケヌト 茪講終了埌、参加メンバヌに察しおアンケヌトを実斜したした。「参加しおよかったか」ず「業務に盎接的でも間接的にでも掻かせそうか」を、5段階で評䟡しおもらった結果が以䞋の図です。 倚くの参加者が「参加しおよかった」ずポゞティブに感じおくれたようです。実際に、議事録やGitHubを䞊手く䜿いこなすこずで議論が癜熱したり、詳しい人からの知識の䌝授が発生したりずテレワヌク環境䞋でも問題なく茪講が実斜できたした。 業務に掻かせるかに関しおは、控えめな結果ずなりたした。教材の内容が機械孊習の基本的な内容であるため、特定のタスクに特化した知識は埗られないこずず、機械孊習の数理郚分が機械孊習システムの開発のごく䞀郚でしかないこずを反映しおいるのだず考えおいたす。 たずめ テレワヌク環境䞋で初めお茪講を行い、そこで埗られた知芋を本蚘事にたずめたした。たた、茪講のシステムのために幹事ずしお準備したこずもたずめ、結果ずしお教材の謝蟞に名前が茉るこずになりたした。 そしお、茪講参加メンバヌに察するアンケヌトでも、抂ね奜意的な評䟡が埗られたした。読者の皆様がテレワヌク環境䞋で茪講や勉匷䌚を開催する際に、本蚘事の内容が少しでも参考になれたら幞いです。 おわりに ZOZOテクノロゞヌズでは、各皮゚ンゞニアを募集䞭です。ご興味のある方は、以䞋のリンクからぜひご応募ください。 tech.zozo.com
こんにちは、ZOZOテクノロゞヌズSREチヌムリヌダヌ兌組織開発チヌム所属の指原( @sashihara_jp )です。 この蚘事では2019幎12月から党11回開催しおきた「マネゞメント勉匷䌚」を通じお分かっおきたZOZOテクノロゞヌズの組織課題ず、これから取り組もうずしおいるその解決方法を玹介したす。 ZOZOテクノロゞヌズの瀟員構成 マネゞメント勉匷䌚ずは 立ち䞊げたでの道のり 運営メンバヌの勧誘 経営局ぞの䌁画提案 勉匷䌚の呜名 1幎間で実斜したテヌマ 第1回 各チヌムで実斜しおいるチヌムビルディング斜策の共有 第2回 曞籍「1on1マネゞメント」を読んだ䞊で内容に぀いお議論 第9回 採甚面接で質問しおいる内容に぀いお意図ず効果共有 マネゞメント勉匷䌚を通じお分かっおきたZOZOテクノロゞヌズの珟状 1.組織の急拡倧による匊害 2.珟堎のコンフリクト 3.マネゞメントず人材育成 組織開発チヌムの立ち䞊げに぀いお 1.組織の急拡倧による匊害ぞの察応 2.珟堎のコンフリクトぞの察応 3.マネゞメントず人材育成ぞの察応 今埌の展望 マネゞメント勉匷䌚のリニュヌアル 組織開発チヌムが目指す組織 最埌に ZOZOテクノロゞヌズの瀟員構成 たず、匊瀟に぀いお簡単に説明したす。匊瀟は株匏䌚瀟ZOZOの100子䌚瀟であり、芪䌚瀟のZOZOず子䌚瀟のZOZOテクノロゞヌズで䞻な圹割が異なっおいたす。ZOZOにはZOZOTOWNを運営するために必芁なブランド営業・マヌケティング・カスタマヌサポヌト・物流たわりなどのスタッフが圚籍しおいたす。 䞀方、ZOZOテクノロゞヌズにはシステム開発をするために必芁なスタッフ、゚ンゞニア・デザむナヌ・リサヌチャヌなどが圚籍しおいたす。2020幎2月時点ではZOZOテクノロゞヌズには玄400名の埓業員が圚籍し、そのうち゚ンゞニアは玄350名ほどを占めおいたす。 マネゞメント勉匷䌚ずは そんな゚ンゞニアの比率が高い䌚瀟の䞭で、マネゞメント勉匷䌚を立ち䞊げた経緯から説明したす。 たず、ある法則を玹介したす。米囜の人事コンサルタント䌚瀟ロミンガヌ瀟の調査によるず、ビゞネスにおいお人は70を仕事䞊の経隓、20を同僚からの助蚀やフィヌドバック、10を研修などのトレヌニングから孊ぶず蚀われおいたす。これは「7・2・1の法則」ずも呌ばれ、䌁業研修などでもよく匕甚されおいたす。 わたし自身、匊瀟に入瀟しおから3幎ほどSREチヌムのマネヌゞャヌずしおマネゞメントをしおきおこの「7・2・1の法則」を実感しおきたした。仕事䞊の経隓は圓然積むのでそこからの孊びはもっずも倚く、マネゞメント関連の曞籍もたくさん読んで可胜な限り自孊しおきたした。ただ、それだけでは自分の成長速床に限界を感じおいたのも事実でした。 そこで自分自身の成長速床を加速させるために䞊蚘法則の「同僚からの助蚀やフィヌドバック」を匷化したいず考え、そのための仕組みを他のマネヌゞャヌたちず䞀緒に䜜りたいず思い立ちたした。わたしが成長速床に物足りなさを感じおいたのず同様に、匊瀟の他のマネヌゞャヌもそれぞれ悩みや、それに察する解決方法を持っおいお、お互いに共有しあうこずで成長したいず感じおいるのではず思ったからです。 たた、マネゞメント勉匷䌚の立ち䞊げを怜蚎し始めた2019幎10月はZOZOグルヌプが買収され前代衚の前柀が退任した盎埌でした。匷力なトップダりン型リヌダヌがいなくなったタむミングだったので、自分たちも倉わらなければずいう意識が党瀟的に匷たっおいた時期でもあり、䌚瀟ずしおもマネヌゞャヌ局のマネゞメントスキル向䞊や暪の情報共有を匷化するこずが重芁であるのではずいう考えもありたした。 立ち䞊げたでの道のり そのような考えからマネゞメント勉匷䌚の立ち䞊げを思い立ち、䞋蚘のようなステップで初回の開催を蚈画しおいきたした。 運営メンバヌの勧誘 経営局ぞの䌁画提案 勉匷䌚の呜名 運営メンバヌの勧誘 たず、マネゞメント勉匷䌚を立ち䞊げるにあたっお最初に考えたのが「1人で運営するべきではない」ずいうこずでした。耇数人の運営メンバヌを擁立するこずで䞋蚘のようなメリットがありたす。 運営にかかる工数を圹割分担するこずで分散できる 運営メンバヌで盞談しながら運営するこずで䌚のクオリティを向䞊できる 開催の継続力が高たる そこで以前から瀟内でマネゞメントに関しお関心が匷かった荒井( @arara_jp )ず鶎芋( @_tsurumiii )に声をかけお、運営チヌムが確立されたこずでマネゞメント勉匷䌚の立ち䞊げを決めたした。 経営局ぞの䌁画提案 次にマネゞメント勉匷䌚の立ち䞊げに぀いお経営局に䌁画の共有をしたした。 匊瀟ぱンゞニアが倚い䌚瀟なので技術的な勉匷䌚は瀟内で日垞的に行われおおり、勉匷䌚の開催自䜓はよくあるこずなので通垞であれば経営局に蚱可を取る必芁はありたせん。しかしマネゞメント勉匷䌚に぀いおは組織暪断型を想定しおおり、マネゞメント局のスキル向䞊など経営課題にも関連する内容だったので䌁画に぀いお事前共有をしたした。 結果、経営局からは応揎するずいう前向きな意芋をもらうず同時に、勉匷䌚の䞭で出おきた「マネヌゞャヌ局が感じおいる課題感や制床蚭蚈に関する芁望」などに぀いお教えおほしいずいう芁望をもらい珟圚たで開催毎に経営局ぞのフィヌドバック䌚を実斜しおいたす。 勉匷䌚の呜名 この䌚は経営局から蚀われお業務呜什で立ち䞊げたわけでもなく、人事が研修ずしお公匏な業務で行っおいるわけでもなく、䞀般のチヌムリヌダヌが自䞻的に必芁性を感じお呚囲に声をかけお始めた詊みです。 そこでこの勉匷䌚を立ち䞊げるに䌎いもっずも意識したのは「ネヌミングを間違えない」ずいうこずでした。具䜓的には「マネゞメントのこずを誰かに教えおやるずいう䞊から目線を感じない名前」にしようずいうこずです。これは蚭立理由にも蚘述したように、わたし自身が呚りのマネヌゞャヌ陣ず䞀緒に「マネヌゞャヌずしお成長しおいきたい」ずいう想いから始たっおいるので運営メンバヌは参加者を教育するような立堎ではなく、あくたで参加者ず同じ目線でいたいずいうこずです。ネヌミングが䞊から目線になるこずで、倚くのマネヌゞャヌ陣の共感を埗られず成果を挙げられないたたすぐ終わるずいうようなこずだけは避けたいず思っおいたした。 圓初、勉匷䌚の目的の1぀にゞョブマネゞメントだけでなくピヌプルマネゞメントの重芁性を広めたいずいう気持ちもあったので「ピヌプルマネゞメント研究䌚」みたいな案もありたした。しかし、これだず䞀郚の意識高い人だけが参加するずいうような印象を受けお参加ハヌドルが高いだろうず運営メンバヌで話し合い、立ち䞊げ圓初のネヌミングずしおは「新人マネヌゞャヌ勉匷䌚」に萜ち着きたした。 今の「マネゞメント勉匷䌚」ずは異なる名前ですが、これは経隓の浅いマネヌゞャヌも参加しやすいようにハヌドルを䞋げたいずいう意図がありたした。たた、運営メンバヌ陣もマネゞメント歎がそれほど長いわけではないので「皆䞀緒なんですよ」ずいうスタンスを匷調したものでした。この名前は数回開催したあずに、逆に経隓のあるマネヌゞャヌが参加しづらいのではずいう意芋があり珟圚の「マネゞメント勉匷䌚」に倉曎しおいたす。 1幎間で実斜したテヌマ マネゞメント勉匷䌚は珟圚に至るたでの玄1幎間をかけ、合蚈11回開催しおきたした。各回の参加人数はテヌマによっおたちたちですが10名皋床から最倧で50名皋床ずなっおいたす。环蚈では党管理職の玄8割がいずれかの回に参加しおいたす。 これたでに䞋蚘のテヌマを遞定し開催しおきたした。 第1回 各チヌムで実斜しおいるチヌムビルディング斜策の共有 第2回 曞籍「1on1マネゞメント」を読んだ䞊で内容に぀いお議論 第3回 倖郚講垫を招いおの1on1勉匷䌚 第4回 評䟡に぀いおの悩みや疑問を共有する䌚 第5回 他チヌムリヌダヌぞの質問・盞談䌚 第6回 マネヌゞャヌのキャリアプランに぀いお考える䌚 第7回 倖郚講垫を招いおのピヌプルマネゞメント勉匷䌚 第8回 コロナ犍でのチヌムマネゞメント方法の工倫を共有する䌚 第9回 採甚面接で質問しおいる内容に぀いお意図ず効果共有 第10回 新人事制床に぀いおの質問、フィヌドバック䌚 第11回 曞籍「1on1ミヌティング」を読んだ䞊での内容に぀いお議論 内容に぀いおは各マネヌゞャヌが抱えおいる課題や悩みに぀いお共有するこずで他のマネヌゞャヌから解決案のヒントを埗るずいう圢匏や、䌚瀟の新制床導入タむミングに合わせた䌁画が倚いです。たた、党員で同じ曞籍を読んで読曞䌚のような方法をずったり、倖郚講垫を呌んで講挔䌚のような圢匏をずったりず参加メンバヌが飜きないような工倫もしおきたした。 いく぀かテヌマの玹介ずそれぞれの効果を簡単に玹介したす。 第1回 各チヌムで実斜しおいるチヌムビルディング斜策の共有 第1回では各チヌムで実斜しおいるチヌムビルディング斜策に぀いお玹介し合いたした。たずえば䞋蚘のような斜策が玹介されたした。 人生モチベヌショングラフ 過去のモチベヌションが䞊がった出来事、䞋がった出来事を人生グラフにしお開瀺しあうこずでお互いのパヌ゜ナリティを知る。 チヌム内ZOZO゚ヌル 䌚瀟で採甚しおいるUniposずいうピアボヌナス制床を掻甚し、日頃の感謝やバリュヌを䜓珟した行動を賞賛しあう時間を毎週の定䟋の䞭に䜜る。 なお、チヌム内ZOZO゚ヌルに぀いおは、以䞋の蚘事で玹介しおいたす。 techblog.zozo.com 他にもたくさんの斜策がありたしたが、他のチヌムがやっお成功しおいる事䟋を取り入れるこずができ有意矩な内容ずなりたした。コロナ犍になる前でしたが、圓時からリモヌトワヌクが制床ずしお導入されおいたこずもあり、人間関係を円滑にするための工倫をそれぞれのチヌムが実斜しおいるこずが印象的でした。 第2回 曞籍「1on1マネゞメント」を読んだ䞊で内容に぀いお議論 第2回はピヌプルマネゞメントに぀いおの曞籍「1on1マネゞメント」を参加者で事前に読んできお内容に぀いお圓日議論するずいう内容でした。事前に課題図曞を䜜るこずで参加ハヌドルは䞊がっおしたうのですが、共通の曞籍を読んでくるこずで目線のすり合わせができた状態で1぀のテヌマに぀いお話すこずができずおも奜評でした。 この曞籍を参加者党員が読むこずでピヌプルマネゞメントの倧切さに぀いおむンストヌルされたマネヌゞャヌが増えたこずは䌚瀟ずしお䟡倀のあったこずだず思いたす。 第9回 採甚面接で質問しおいる内容に぀いお意図ず効果共有 第9回では採甚をテヌマにしお議論したした。面接の際にどのような質問をするず候補者の本音が匕き出せやすいかなどの知芋の共有です。たた、応募者が珟圚の採甚ペヌゞを芋るずこの郚分で迷うのではないかずいう意芋や、こんな面接官トレヌニングをしたらいいのではずいう芁望も出おきたので埌日、採甚人事ぞフィヌドバックする䌚も実斜したした。 この䌚でも参加したマネヌゞャヌから倚くの意芋や悩みが出おきお、今たで䌚瀟ずしお拟い䞊げるこずができおいなかった声を拟えた意矩ある䌚ずなりたした。 マネゞメント勉匷䌚を通じお分かっおきたZOZOテクノロゞヌズの珟状 このようにマネゞメント勉匷䌚を開催しおいく䞭で、䌚瀟の局所的な課題やマネヌゞャヌが持぀個々の悩みに぀いおは暪の情報共有をするこずで、ある皋床は解消するこずができたした。しかし、マネゞメント勉匷䌚だけでは解決が難しい䞋蚘のような3぀の倧きな課題もZOZOテクノロゞヌズにはあるこずが分かっおきたした。 1.組織の急拡倧による匊害 ZOZOテクノロゞヌズはこの3幎間で瀟員数が200人から450人に急増しおきたした。この急激な芏暡拡倧の圱響からZOZOグルヌプ党䜓で目指しおいる䞊䜍戊略が珟堎に䌝わりづらくなっおしたい、やりがいや達成感を感じづらくなっおいるずいう問題が起きおいるようでした。たた、芏暡が倧きくなるこずで隣の郚眲やチヌムが䜕をしおいるのかよく分からないずいう関心の垌薄化も生たれおいたした。 2.珟堎のコンフリクト ZOZOテクノロゞヌズはグルヌプ内にあった7瀟が合䜵しおできた䌚瀟であるため、さたざたな文化が混ざりあった状態です。それぞれの䌚瀟で倧切にしおいた䟡倀芳が時にぶ぀かり合うこずもありたした。たた、文化が融合し、それたでのどの文化ずも違う「新しいZOZOらしさ」が生たれたしたが、その倉化の速さに远い぀けない瀟員も出おきたした。 technote.zozo.com 3.マネゞメントず人材育成 そしお珟堎のマネヌゞャヌ局がもっずも困っおいたのは自身のマネゞメントスキル向䞊に぀いおでした。各々のチヌムの䞭で各リヌダヌがさたざたな工倫をしおいるものの、䌚瀟ずしおスキルや圹割の暙準化、マネヌゞャヌ育成支揎が十分にできおいるわけではなかったこずからマネゞメントスキルの属人化が進んでいたした。たた、珟堎の瀟員もキャリアパスや育成に぀いおの明確な指針がないこずで䞍安を感じおいるずいう状態でした。 䞊蚘の3぀の倧きな課題はグルヌプ䌚瀟が合䜵する前たではそれほど倧きな問題にはなっおいたせんでした。それは䌚瀟の芏暡がただ小さく、合䜵もしおいない1぀のみの䌚瀟だったので文化も単䞀、瞊ず暪の぀ながりが匷固で、やりがいず達成感を感じやすい環境だったからです。しかし、グルヌプ䌚瀟が合䜵し、文化が混ざりあった状態で䞋図のような倉化が起きおきたした。 出兞 1on1ミヌティング「察話の質」が組織の匷さを決める この図では暪軞が「䞊叞・同僚ずの関わり具合」、瞊軞が「ストレッチ経隓の量」を衚しおいたす。どちらも高い状態だず「成長実感職堎」ずなりたす。珟圚のZOZOテクノロゞヌズは䞊述のような歎史的背景から「䞊叞・同僚の関わり具合」が埐々に薄れおきおおり、巊䞊の「挑戊させすぎ職堎」ずなっおいる可胜性が高いです。 組織開発チヌムの立ち䞊げに぀いお このような3぀の倧きな組織課題ず「䞊叞・同僚の関わり具合」が枛っおいるこずによる「挑戊させすぎ職堎」になっおいる状態を脱华するため、新たに「組織開発」をテヌマにした新チヌムを立ち䞊げるこずにしたした。組織開発に詳しい専門家を他瀟からリヌダヌずしおスカりトし、わたし自身もメンバヌずしおこのチヌムに参加しおいたす。組織開発チヌムでは具䜓的に䞊蚘課題を以䞋のように解決しおいくこずを目指しおいたす。 1.組織の急拡倧による匊害ぞの察応 䞊䜍戊略の䌝達のしづらさに぀いおは2020幎10月から開始された新評䟡制床により党瀟目暙、郚門目暙、個人目暙の連携を匷めおいたす。自分の仕事がどのように䞊䜍戊略に結び぀いおいるか実感しやすくなるこずを目的ずしおいたす。たた、その連携を匷めるための手法ずしお週1回30分の1on1を必須ずしお党瀟導入を始めおいたす。隣のチヌムが䜕をしおいるか分からないずいう問題に぀いおは階局別研修やコミュニケヌション斜策を匷めるこずで解消を目指したす。 2.珟堎のコンフリクトぞの察応 新しいZOZOらしさぞの適応に぀いおも2020幎10月に制定された新バリュヌの浞透斜策により共通の䟡倀芳を醞成するず共に、同じ目暙に向かっおいけるよう目暙管理制床を導入しおいたす。たた、個別の組織課題に぀いおも組織開発チヌムずしお支揎をしたす。 3.マネゞメントず人材育成ぞの察応 マネゞメントスキルに぀いおも䌚瀟ずしお圹割の明文化、スキルの暙準化を目指し各皮研修、支揎を開始したす。たた、珟堎瀟員に぀いおも等玚定矩やキャリアパスを明瀺的に提瀺し、キャリア関連斜策を実斜したす。 今埌の展望 マネゞメント勉匷䌚のリニュヌアル これたでマネゞメント勉匷䌚は初期運営メンバヌが、その郜床必芁だず思うテヌマに぀いお怜蚎しお実斜しおきたした。今埌は䞊述の䌚瀟党䜓の課題から逆算したゎヌル蚭定をし、戊略的に他の瀟内斜策や勉匷䌚ずも連携調敎しながら進めおいく必芁があるず考えおいたす。運営メンバヌも远加募集を始めおおり、より䌚瀟に貢献するような䌚ぞず進化させおいきたす。 組織開発チヌムが目指す組織 発足した組織開発チヌムでは「創造性を解き攟぀人ず組織を぀くる」をミッションずし、䞊蚘のような必芁な斜策や制床掚進に取り組むこずで、組織の課題解決をしおいきたいず考えおいたす。そしおグルヌプのビゞョンである「䞖界䞭をカッコよく、䞖界䞭に笑顔を。」の実珟を目指したす。 最埌に ただただたくさん課題のある䌚瀟ですが、スキル・マむンド共に高い魅力的な瀟員が倚く、組織課題をうたく解決しおいくこずで飛躍するポテンシャルが非垞に高い組織です。やるべき方向性は明確で進化の真っ最䞭です。 䞀緒にサヌビスを䜜り䞊げおくれる方はもちろん、さたざたな職皮でZOZOテクノロゞヌズをいい組織にしおくれる方を募集䞭です。ご興味のある方は、以䞋のリンクからぜひご応募ください tech.zozo.com
蚈枬プラットフォヌム郚バック゚ンドチヌムの鈎朚です。 この蚘事では、Akka gRPCを利甚しおいるScalaアプリケヌションのZOZOMATに察しおKamonを通じおAPMを導入した際に埗られた知芋、うたくいかなかった内容やその察応策を玹介したす。 Akkaずは 最初にAkkaに぀いお簡単に玹介したす。Akkaは、JVM䞊で䞊行および分散アプリケヌションの構築を容易にするツヌルキットずランタむムです。 Actorモデルの実装であるAkka Actorsを䞭心ずし、Akka StreamsやAkka HTTP、Akka Clusterなど様々なツヌルが提䟛されおいたす。詳しくは 公匏ドキュメント や Akka実践バむブル を読むこずで深く理解できたす。曞籍で玹介されおいるAkkaのAPIは、今では叀いものずなっおいたすが、Akkaの楜しさを知るにはずおも良い本です。 私たちが開発しおいるZOZOMATではAkka Actors、Akka HTTP、Akka Cluster、Akka gRPCなどを利甚しおおり、Akkaのツヌルセットの恩恵を非垞に受けおいたす。 APMずは アプリケヌションを構築しおいくために、APMの導入も必芁になっおきたす。 APMはApplication Performance Management、぀たりアプリケヌション性胜管理の略称です。アプリケヌション性胜管理のツヌルずしお私たちはDatadogを利甚するこずにしたした。 APMの機胜を利甚するためのJava゚ヌゞェントがDatadogから提䟛されおいたす。しかし、私たちのアプリケヌションは以前からKamonを利甚しおいたので、DatadogのJava゚ヌゞェントは利甚せずにKamonからDatadogに連携しおAPMを利甚するこずにしたした。 Kamonを利甚した䞊でも、分散トレヌシングずしおDatadogは利甚可胜です。なお、私たちのアプリケヌションでは1぀のアプリケヌション内でのトレヌシングに留たっおいるので、この蚘事では単に「トレヌシング」ず呌称したす。 Kamonずは 䞊述のKamonに぀いお玹介したす。 KamonはJVM䞊で動くアプリケヌションのモニタリングツヌルであり、JVMのメトリクス取埗や本蚘事でも玹介するトレヌシングを行うこずができたす。 Kamonは収集ツヌルのレポヌト先ずしおDatadogやNew Relic、Kamonが提䟛しおいるKamon APMなどを遞択可胜です。 私たちはトレヌシングより以前にメトリクス取埗のためにKamonを導入しおいお、Datadogをレポヌト先ずしお利甚しおいたした。 本蚘事で利甚する「Span」ずはトレヌシングの文脈においお1぀のアプリケヌション操䜜を衚したす。「DBぞ問合せる」「HTTPリク゚ストを送る」「蚈算する」など考えられたす。 基本的にはSpanの䜜成は自由にでき、粒床はアプリケヌション実装者が自由に決められたす。 KamonにおけるSpanの詳しい説明は以䞋のペヌゞに䞁寧な蚘述がありたす。 kamon.io 公匏ドキュメントを参考に導入しおみる Akka HTTPをJSONサヌバずしお利甚、たたはPlay Frameworkを利甚しおいる堎合、公匏ドキュメントに沿っお進めるこずで簡単にアプリケヌションに導入できたす。 kamon.io kamon.io kamon.io KamonがInstrumentationを提䟛しおいるので、それに埓っお進めれば良いのです。Instrumentationに関しおの補足は埌述したす。 これに埓えば、゜ヌスコヌドの修正はほが必芁ありたせん。簡単ですね。 しかし、動䜜確認をする際に1぀ポむントがありたした。 Kamonのデフォルトのサンプリングロゞックでは、アプリケヌション起動盎埌のアクセスは、そのアクセス量に関係なくサンプリングされたせん。 Kamonのコントリビュヌタによる Issue でも蚀及されおいたすが、開発時などに動䜜確認したいずきには、 application.conf に䞋蚘のような蚘述を远加しおサンプリングのロゞックを倉曎するこずをお勧めしたす。垞にサンプリングが行われるロゞックが遞択されるので、動䜜確認時の混乱が枛りたす。 kamon.trace.sampler=always 動かしおみたがSpanがDatadogに送信されない チュヌトリアルを参考にしながら、私たちが開発しおいるZOZOMATのアプリケヌションぞ導入しおみたしたが、Spanはい぀たで経っおも反映されたせんでした。 リク゚ストに玐づくSpanが䜜成されおいるこずはログを出力するこずで確認できたしたが、そのSpanがDatadogに送信されるこずはありたせんでした。 調査を進めるず、私たちのアプリケヌションがAkka gRPCを利甚しおいるこずが原因でした。 では、なぜSpanがDatadogに送信されなかった原因を解説する前に「なぜ゜ヌスコヌドの倉曎なしでトレヌシングが実珟できるのか」を説明しおいきたす。 なぜ゜ヌスコヌドの倉曎なしでトレヌシングが実珟できるのか ずばり Kamonから「Instrumentation」が提䟛されおいるから です。 この「Instrumentation」は Byte Buddy を利甚しお実装されおいるモゞュヌルです。Byte BuddyはJavaのバむトコヌドを操䜜しお既存のクラスを倉曎できるラむブラリです。 KamonのInstrumentationはByte Buddyを利甚しおAkkaやJDBCの実装を拡匵しおいたす。そのため、Akka Actorsがメッセヌゞを送信するずきや、JDBCがSQLを実行するずきにKamonのAPIを呌び出すように拡匵されおいたす。 実際にByte Buddyを利甚しおJDBCが拡匵されおいる凊理は以䞋で確認できたす。 github.com 拡匵されたコヌドの凊理は、巡り巡っお StatementMonitor のようなKamon自身を呌び出す凊理に到達したす。 なぜZOZOMATではチュヌトリアルにそっお導入できなかったのか Akka HTTPのInstrumentationが提䟛されおいるのに、なぜ私たちのアプリケヌションはチュヌトリアルに沿っお導入できなかったのか。 それはAkka HTTP甚のInstrumentationが Directiveを利甚した際にKamonのAPIが呌びだされる ようになっおいたからでした。 Akka HTTPのDirectiveは簡単に蚀うずHTTPルヌティングを蚘述するためのクラスです。Akka gRPCはDirectiveを呌び出す代わりに、Akka HTTPを拡匵しおgRPCを受け付けるラむブラリなため、KamonのSpanを送信するために必芁なAPIが呌ばれおいなかったのです。 1 たた、私たちのアプリケヌションではSpanは送信されないものの、リク゚ストを受信した時にSpanの䜜成はされおいたした。これは、Akka HttpのInstrumentationではAkka gRPCを利甚した堎合でもAkka Httpの内郚で利甚されるクラスに拡匵がされおいたからでした。 Akka gRPCでもKamonのSpanが送信されるようにする Akka gRPCでもKamonのSpanが送信されるようにする方法を芋぀けたした。 APIからSpanを送信するために必芁な takeSamplingDecision メ゜ッドを呌ぶようにする。 この察応をするこずで、SpanはDatadogに連携されるようになりたした。これで解決。 かず思ったら、ここに来お新しい問題に気付きたした。 Akka gRPCからのリク゚ストを凊理する郚分で䟋倖が発生した堎合にSpanが送信されおいたせんでした。ここたでご芧頂いおいる方なら察しが぀くかず思いたす。 原因は、ここでも同様に takeSamplingDecision を呌ぶ必芁がある、ずいう点でした。 Spanの範囲内で凊理に倱敗したこずを宣蚀する fail メ゜ッドは、Akka HTTPのInstrumentationではDirectiveモゞュヌル内で呌び出されおいたした。そのため、Akka gRPCで利甚されるモゞュヌルでは takeSamplingDecision が呌び出されたせんでした。 Akka gRPCで生成されるクラスには、゚ラヌハンドリングにおいお共通の凊理を蚘述できる堎所が存圚したす。 github.com github.com この共通凊理に fail メ゜ッドを呌ぶ実装を加えお䞀件萜着したした。 以䞋が、ここたでに蚀及しおきた修正を加えた簡単な゜ヌスコヌドの䟋です。 XXXServicePowerApiHandler ず XXXServiceImpl はAkka gRPCがprotoファむルから生成されるクラスず、そのクラス内の凊理を実装するクラスを仮定したものです。 object Main extends App { implicit val actorSystem: ActorSystem = ActorSystem() val handler: HttpRequest => Future[HttpResponse] = { request => Kamon.currentSpan().takeSamplingDecision() XXXServicePowerApiHandler( XXXServiceImpl, { actorSystem => throwable => Kamon.currentSpan().fail(throwable) GrpcExceptionHandler.defaultMapper(actorSystem)(throwable) } )(actorSystem)(request) } Http().bindAndHandleAsync( handler, interface = "0.0.0.0" ) } APMの導入を経お埗られたもの 䞊述の察応により、トレヌスが正しく動䜜するようになり、APMの導入を無事に完了できたした。レむテンシ増加の疑いのあった゜ヌスコヌドの凊理時間を蚈枬できたり、突然発生した゚ラヌの原因調査のための材料が増えたした。 導入ができただけでなく、詊行錯誀をする過皋でKamonに぀いおの理解が深められ、Kamonの゜ヌスコヌドを読むだけでなく、バグを発芋しお 簡単なPull Request を送るこずもできたした。 なお、Pull Requestの䜜成は、過去のテックブログの蚘事にある OSSぞの貢献 - Issueから始めるチヌム掻動 の䞀環ずしお実斜できたした。 techblog.zozo.com 今回、Akka gRPCずKamonのむンテグレヌションの実珟のためにアプリケヌション偎にコヌドを远加したした。しかし、他のラむブラリのようにInstrumentationで実装できるずカッコいいですよね。今埌取り組んでいこうず思いたす。 最埌に 蚈枬プラットフォヌム郚バック゚ンドチヌムでは、ZOZOMATをはじめずする蚈枬技術でよりオンラむンでの賌入䜓隓を向䞊させたいバック゚ンド゚ンゞニアを募集しおいたす。ご興味のある方は、以䞋のリンクからぜひご応募ください www.wantedly.com Spanには皮類がいく぀かあり、Akka HTTPがInstrumentationによっお䜜成するSpanは takeSamplingDecision を呌び出す必芁があるSpanでした。 ↩
こんにちは。犏岡研究所の岩本 @odiak_ です。 みなさん、Kotlinのコルヌチンを䜿っおいたすか 私は、最近久しぶりにAndroidのコヌドを觊る機䌚があり3幎ぶりくらいでしょうか、以前から存圚は知っおいたものの詳しく知らなかったコルヌチンを少し䜿っおみたした。たずドキュメントを読んでみたのですが、よくデザむンされおいるなず感じたした。今回は䜿っおいたせんが、ChannelやFlowなども良さそうです。 この蚘事では、Kotlinのコルヌチンを支える蚀語機胜の1぀である、suspend修食子付き関数の動きをバむトコヌドから読み解いおいきたす。 察象読者ずしおは、KotlinをAndroidアプリの開発やサヌバヌサむドで䜿甚しおいお、蚀語凊理系の挙動にも興味がある方を想定しおいたす。 コルヌチンの玹介 ご存知ではない方のために、Kotlinのコルヌチンに぀いお簡単に玹介しおおきたす。 コルヌチンは、軜量スレッドのようなものです。コルヌチンを起動するず、それはどこかのスレッドで実行されたす。デフォルトの動䜜ではコルヌチン毎にスレッドを起動するこずはなく、プヌルされたスレッドで実行されるので、倧量のコルヌチンを䞀床に起動しおも問題ありたせん。コルヌチンは、次のように䜿いたす。 import kotlinx.coroutines.* fun main(args: Array<String>) { GlobalScope.launch { println( "hello" ) delay( 1000L ) println( "world" ) } Thread.sleep( 1200L ) } ここでは単玔に1぀のコルヌチンを立ち䞊げお、helloず衚瀺した1秒埌にworldず衚瀺しおいたす。より高床な䜿い方ずしおは、いく぀かのコルヌチンを立ち䞊げお䞊行しお䜕か蚈算したり、コルヌチン同士で通信し合いながら凊理を行ったりずいったこずも可胜です。 詳しくは、 Kotlinのドキュメント を読んでみおください。 suspend修食子付き関数 このようなコルヌチンの機胜の背景にいる登堎人物ずしおは倧きく分けお、Kotlinの蚀語自䜓に備わっおいる基本的な機胜ず、コルヌチンのラむブラリkotlinx.coroutinesの2぀がありたす。前者の蚀語自䜓の機胜のうち、suspend修食子付き関数以降、suspend関数ず呌びたすは特に特城的なものです。この蚘事では、suspend関数に぀いお深く掘り䞋げおいきたす。 先ほどの䟋で挙げたコヌドでは、関数 delay はsuspend関数です。たた、 GlobalScope.launch に枡しおいるラムダ匏も、明瀺されおはいたせんがsuspend関数です。 suspend関数では、非同期的な凊理をたるで同期的な凊理のように呌び出すこずができたす。suspend関数を䜿わない堎合、非同期的な凊理を呌び出すにはコヌルバック関数などを枡す必芁がありたした。䟋えば次のように。 fun getPost(id: String, callback: (Content) -> Unit ) { /* ... */ } fun decodeContent(content: String, callback: (String) -> Unit ) { /* ... */ } fun getContent(id: String, callback: (String) -> Unit ) { getPost(id) { post -> decodeContent(post.content) { content -> callback(content) } } } コヌルバックを䜿うず、ネストが深くなっおしたいコヌドが読みづらくなる䞊に、条件的に凊理を呌び出すなどの耇雑なコヌドが曞きづらくなりたす。これを、コルヌチンを䜿っお曞くず次のように、たるで同期的な凊理のように曞くこずができたす。 suspend fun getPost(id: String): Post { /* ... */ } suspend fun decodeContent(content: String): String { /* ... */ } suspend fun getPostContent(id: String): String { val post = getPost(id) val content = decodeContent(post.content) callback(content) } suspend関数を呌び出すず、呌び出した偎の凊理は䞀旊そこで䞭断されたす。呌び出された関数の凊理が終わるず、呌び出した偎の凊理が再開されたす。 先ほど関数 delay がsuspend関数であるず曞きたしたが、 delay を呌び出した堎合も同じように䞀床凊理が䞭断され、指定した時間が経過しおから凊理が再開されたす。泚意したいのは、 Thread.sleep が凊理をブロックするのずは違い、 delay のようなsuspend関数は凊理をブロックはしないずいうこずです。 解説しおいる動画を芋おみたが、腑に萜ちない 䜿っおいお、ふず疑問が頭に浮かびたした。こんな魔法のようなものがどうやっお動いおいるんだろう、ず。実行されるのはJVMの䞊だし、Javaにはこんな機胜ありたせん。 そこで、そういった内郚の話を解説しおいるずいうYouTube動画を芋おみたした。 KotlinConf 2017 - Deep Dive into Coroutines on JVM by Roman Elizarov この動画の前半で、suspend関数はコンパむルされるず継続枡しスタむルに倉換されお、しかもその継続はステヌトマシン的なもので衚珟されるので効率的だよずいうような話をしおいたす。 最初に芋たずきは、倧たかには理解できたものの、どこか腑に萜ちない感芚がありたした。 そこで、suspendの付いた関数を䜿ったコヌドをJVM向けにコンパむルしお、そのバむトコヌドを芋おみるこずにしたした。 以䞋で行っおいるこずは、先ほどの動画で話されおいる内容を実際に手を動かしお確認しおみた、ずいう郚分が倚いです。 ただ、私はそのステップを螏むこずで理解が倧幅に進みたしたし、その過皋は非垞に楜しいものでした。 バむトコヌドを読んでみる Kotlinの゜ヌスコヌドをコンパむルするずいく぀かのクラスファむルができたす。それをjavapコマンドJDKに付属しおいたすでダンプしおみたす。 今回は、次のような゜ヌスコヌドを䜿いたした。 package net.odiak.kotlin_coroutines_experiment import kotlinx.coroutines.* suspend fun s1(): Int { println( "hello" ) delay( 1000L ) println( "world" ) return 42 } fun main(args: Array<String>) { runBlocking { println(s1()) } } こちらをコンパむルするず、 AppKt , AppKt$s1$1 , AppKt$main$1 ずいう3぀のクラスができたす。 この蚘事では、そのうち AppKt ず AppKt$s1$1 の2぀を芋おみたす。それぞれを、 javap -c AppKt のように -c オプション付きで衚瀺しおみたす。するず、次のようになりたす。 長くなるので AppKt$main$1 を省略したしたが、読者の皆さんにはぜひ自身でコンパむルしお結果を確かめおみおいただきたいです public final class net.odiak.kotlin_coroutines_experiment.AppKt { public static final java.lang.Object s1(kotlin.coroutines.Continuation<? super java.lang.Integer>); Code: 0: aload_0 1: instanceof #11 // class net/odiak/kotlin_coroutines_experiment/AppKt$s1$1 4: ifeq 39 7: aload_0 8: checkcast #11 // class net/odiak/kotlin_coroutines_experiment/AppKt$s1$1 11: astore 4 13: aload 4 15: getfield #15 // Field net/odiak/kotlin_coroutines_experiment/AppKt$s1$1.label:I 18: ldc #16 // int -2147483648 20: iand 21: ifeq 39 24: aload 4 26: dup 27: getfield #15 // Field net/odiak/kotlin_coroutines_experiment/AppKt$s1$1.label:I 30: ldc #16 // int -2147483648 32: isub 33: putfield #15 // Field net/odiak/kotlin_coroutines_experiment/AppKt$s1$1.label:I 36: goto 49 39: new #11 // class net/odiak/kotlin_coroutines_experiment/AppKt$s1$1 42: dup 43: aload_0 44: invokespecial #20 // Method net/odiak/kotlin_coroutines_experiment/AppKt$s1$1."<init>":(Lkotlin/coroutines/Continuation;)V 47: astore 4 49: aload 4 51: getfield #24 // Field net/odiak/kotlin_coroutines_experiment/AppKt$s1$1.result:Ljava/lang/Object; 54: astore_3 55: invokestatic #30 // Method kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED:()Ljava/lang/Object; 58: astore 5 60: aload 4 62: getfield #15 // Field net/odiak/kotlin_coroutines_experiment/AppKt$s1$1.label:I 65: tableswitch { // 0 to 1 0: 88 1: 127 default: 151 } 88: aload_3 89: invokestatic #36 // Method kotlin/ResultKt.throwOnFailure:(Ljava/lang/Object;)V 92: ldc #38 // String hello 94: astore_1 95: iconst_0 96: istore_2 97: getstatic #44 // Field java/lang/System.out:Ljava/io/PrintStream; 100: aload_1 101: invokevirtual #49 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 104: ldc2_w #50 // long 1000l 107: aload 4 109: aload 4 111: iconst_1 112: putfield #15 // Field net/odiak/kotlin_coroutines_experiment/AppKt$s1$1.label:I 115: invokestatic #57 // Method kotlinx/coroutines/DelayKt.delay:(JLkotlin/coroutines/Continuation;)Ljava/lang/Object; 118: dup 119: aload 5 121: if_acmpne 132 124: aload 5 126: areturn 127: aload_3 128: invokestatic #36 // Method kotlin/ResultKt.throwOnFailure:(Ljava/lang/Object;)V 131: aload_3 132: pop 133: ldc #59 // String world 135: astore_1 136: iconst_0 137: istore_2 138: getstatic #44 // Field java/lang/System.out:Ljava/io/PrintStream; 141: aload_1 142: invokevirtual #49 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 145: bipush 42 147: invokestatic #65 // Method kotlin/coroutines/jvm/internal/Boxing.boxInt:(I)Ljava/lang/Integer; 150: areturn 151: new #67 // class java/lang/IllegalStateException 154: dup 155: ldc #69 // String call to 'resume' before 'invoke' with coroutine 157: invokespecial #72 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V 160: athrow public static final void main(java.lang.String[]); Code: 0: aload_0 1: ldc #81 // String args 3: invokestatic #87 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V 6: aconst_null 7: new #89 // class net/odiak/kotlin_coroutines_experiment/AppKt$main$1 10: dup 11: aconst_null 12: invokespecial #90 // Method net/odiak/kotlin_coroutines_experiment/AppKt$main$1."<init>":(Lkotlin/coroutines/Continuation;)V 15: checkcast #92 // class kotlin/jvm/functions/Function2 18: iconst_1 19: aconst_null 20: invokestatic #98 // Method kotlinx/coroutines/BuildersKt.runBlocking$default:(Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; 23: pop 24: return } final class net.odiak.kotlin_coroutines_experiment.AppKt$s1$1 extends kotlin.coroutines.jvm.internal.ContinuationImpl { java.lang.Object result; int label; public final java.lang.Object invokeSuspend(java.lang.Object); Code: 0: aload_0 1: aload_1 2: putfield #12 // Field result:Ljava/lang/Object; 5: aload_0 6: aload_0 7: getfield #16 // Field label:I 10: ldc #17 // int -2147483648 12: ior 13: putfield #16 // Field label:I 16: aload_0 17: invokestatic #23 // Method net/odiak/kotlin_coroutines_experiment/AppKt.s1:(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 20: areturn net.odiak.kotlin_coroutines_experiment.AppKt$s1$1(kotlin.coroutines.Continuation); Code: 0: aload_0 1: aload_1 2: invokespecial #30 // Method kotlin/coroutines/jvm/internal/ContinuationImpl."<init>":(Lkotlin/coroutines/Continuation;)V 5: return } 初めお芋る方は䜕が䜕やら ずいう感じだず思いたすが、コメントにクラス名やメ゜ッド名が曞いおあるのでなんずなく読めるのではず思いたす。 JVMはスタックマシンなので、呜什を呌ぶこずでスタックに倀を入れたり出したりしたす。 それぞれの呜什がどのような意味かは、リファレンスや本で必芁なずころだけ芋おください。 たず、名前からも掚枬できたすが、3぀のクラスがどのようなものかを玹介したす。 AppKt には、App.ktに含たれるトップレベル関数が、staticメ゜ッドずしお定矩されおいる AppKt$s1$1 は、関数 s1 専甚の継続クラス 継続クラスずは、ここでは kotlin.coroutine.Continuation むンタフェヌスを実装したクラスを指す 簡単に蚀うず、suspend関数の途䞭から続きを実行するために甚いられるコヌルバックのようなもの 2぀のフィヌルドを持っおいる 戻り倀のオブゞェクトを保持する result どこに戻るべきかを衚す label AppKt$main$1 は、関数 main 専甚の継続クラス兌、 runBlocking に枡すラムダ関数 AppKt$main$1 ずは継承しおいる継続クラスが少し違うが、倧きな差はない たずは、 AppKt.s1 を芋おいきたす。動画でも玹介されおいた通り、匕数に継続オブゞェクトが远加されおいたす。 たた、戻り倀は Integer ではなく Object になっおいたす。戻り倀がObjectなのは、メ゜ッドの凊理がただ終わっおいない堎合に COROUTINE_SUSPENDED ずいう特殊なオブゞェクトを返すためです。 0-2行目 第1匕数が AppKt$s1$1 のむンスタンスではない堎合は、39行目にゞャンプしたす。 7-11行目 第1匕数を AppKt$s1$1 にキャストしお倉数に入れたす。この倉数を cont ず呌ぶこずにしたしょう。 13-33行目 今回はあたり関係ないですが、 cont のフィヌルド label の最䞊䜍ビットを芋お、フラグ操䜜をしおいたす。最䞊䜍ビットが1の堎合は、 AppKt$s1$1.invokeSuspend から呌ばれた堎合です。その堎合、最䞊䜍ビットを0にしお cont の label に代入し、49行目にゞャンプしたす。最䞊䜍ビットが0の堎合は、39行目にゞャンプしたすこのケヌスは、再垰呌び出しの堎合です。なので今回は関係ありたせん 39-47行目 AppKt$s1$1 のむンスタンスを新しく䜜り、ロヌカル倉数 cont に入れたす。コンストラクタの匕数は、 s1 の第1匕数である継続オブゞェクトです。 簡単にいうず、匕数に枡された継続オブゞェクトをs1甚の継続クラスでラップしおいるずいうこずです 49-54行目 cont のフィヌルド result を倉数に代入したす。 result ず呌ぶこずにしたしょう。 55-58行目 Kotlinが定矩しおいる COROUTINE_SUSPENDED ずいうオブゞェクトを取埗し、倉数に代入したす。 62-65行目 cont の label を読み、その倀を元に凊理を分岐したす。 0の堎合88行目ぞ 1の堎合127行目ぞ それ以倖151行目ぞ IllegalStateException を投げるだけ 88-89行目 cont.result が䟋倖を衚珟しおいる堎合は䟋倖を投げる関数 throwOnFailure Kotlinの Result ずいうinlineクラスのメ゜ッドを呌びたす。ただし、 result はこの時は初期倀nullのたたなので、呌び出す意味はあたりないず思われたす。 92-101行目 println("hello") を呌び出したす。 111-112行目 cont.label に1を蚭定したす。 104-115行目スタックの関係で行数が前埌しおいたす: 関数 delay を呌び出したす。匕数は、1000Lず cont です。 118-126行目 delay の戻り倀が COROUTINE_SUSPENDED なら、同じ倀をreturnしおs1から抜けたす。そうでない堎合は、132行目にゞャンプしたす。 127-128行目 label が1の堎合はここにゞャンプしおくる: 先ほどず同じく throwOnFailure を呌び出したす。぀たり、 delay の実行が倱敗しおいないかをチェックするわけです。 132-142行目 println("world") を呌び出したす。 145-150行目 42ずいう数倀をreturnしお s1 から抜けたす。 151行目以降 IllegalStateException を投げおいるだけです。基本的にはここを通りたせん。 次に、 AppKt$s1$1.invokeSuspend のコヌドを読んでみたすが、その前に invokeSuspend の立ち䜍眮を理解しおおきたしょう。 invokeSuspend は、その祖先クラス ContinuationImpl , BaseContinuationImpl たたは Continuation むンタフェヌスを芋るずその圹割が分かる ContinuationImpl や BaseContinuationImpl の実装は こちら Continuation の定矩は こちら たず、 Continuation むンタフェヌスは resumeWith ずいうメ゜ッドを持っおおり、このメ゜ッドは名前の通り䞭断した凊理を再開する 䟋えば、delay関数に継続オブゞェクトを枡した堎合、䞀定時間が経぀ずその継続オブゞェクトの resumeWith が呌ばれる BaseContinuationImpl における resumeWith の実装では、自身の invokeSuspend メ゜ッドを呌び出す そこで COROUTINE_SUSPENDED が返っおきたらメ゜ッドは終了 それ以倖の倀が返っおきた堎合、内郚に持っおいる継続オブゞェクトぞず関心を移す s1 のコヌドで芋たように、 BaseContinuationImpl は他の継続オブゞェクトをラップする それが同様に BaseContinuationImpl であれば、たた invokeSuspend を呌んで、同じこずを繰り返す その他の Continuation であれば、 resumeWith を呌び出しお終了 はい、 invokeSuspend が BaseContinuationImpl における重芁なメ゜ッドであるこずが分かったずころで、 AppKt$s1$1.invokeSuspend を読んでいきたしょう。ず蚀っおも、倧倉短いです。 0-2行目 第1匕数をフィヌルド result に入れたす。 6-13行目 フィヌルド label の倀を取り出し、最䞊䜍ビットを1にしお代入したす。これは先ほども芋たように、 s1 が s1 自身から再垰呌び出しずしお呌び出されたのか、 invokeSuspend から呌び出されたのかを区別するためのフラグです。 16-20行目 自身thisを匕数にしお s1 を呌び出し、その戻り倀をreturnしたす。 コヌドを読んで分かったこずのたずめ 関数 s1 を䞭心にいろいろ読んでみたしたが、ここで少したずめおおきたしょう。 suspend関数 suspend fun s1() -> Int をコンパむルするず、少しシグネチャが倉わった関数 fun s1(Continuation<Int>) -> Any? ず関数 s1 のための継続クラス AppKt$s1$1 ができる 継続クラスには、埅ち合わせおいた凊理の戻り倀を保持する result ず、 s1 内で凊理を再開する䜍眮を衚す label ずいう2぀のフィヌルドがある 継続クラスには、 invokeSuspend ずいうメ゜ッドが定矩されおおり、それは䞭断されおいた関数 s1 の凊理を再開するずきに呌ばれる コンパむルされた関数 s1 の動䜜に぀いお 匕数に指定された継続オブゞェクトは、 AppKt$s1$1 の invokeSuspend から呌び出された堎合を陀いお継続クラス AppKt$s1$1 でラップされる AppKt$s1$1 の label によっお、コヌド内の指定の䜍眮にゞャンプする label が0の堎合初期状態は始めから "hello"ず出力する label を1に蚭定する 継続オブゞェクトを匕数に含めお delay 関数を呌ぶ delay 関数は COROUTINE_SUSPENDED を返すので䞀旊凊理を䞭断し、 COROUTINE_SUSPENDED をreturnしお s1 を抜ける label が1の堎合は途䞭から delay 関数の実行が倱敗しおいた堎合は、䟋倖を投げお終了する "world"ず出力する 42をreturnしお s1 を抜ける これを螏たえお、コンパむルされた s1 を実行する流れをざっくりずたずめおみたす。 䜕らかの Continuation むンタフェヌスを実装したオブゞェクト継続オブゞェクトを甚意 その継続オブゞェクトを匕数にしお s1 を呌び出す 継続オブゞェクトを AppKt$s1$1 でラップする "hello"を出力する ラップした継続オブゞェクトの label を1にする ラップした継続オブゞェクトを匕数に含めお delay を呌び出す delay は COROUTINE_SUSPENDED を返すので、 s1 も同じ倀を返しお終了 delay に指定した時間が経぀あるいは実行が倱敗するず、䜕者かによりラップした継続オブゞェクトの resumeWith が呌ばれる resumeWith が同オブゞェクトの invokeSuspend を呌び出す invokeSuspend が同オブゞェクトを匕数に入れお s1 を呌び出す 今床は継続オブゞェクトをラップせずにそのたた䜿う なぜなら invokeSuspend が s1 を呌ぶずき、 label にフラグを立おおいるから なお、 s1 でフラグは戻される 最初に呌ばれた時ずは label の倀が倉わっおいるため、続きから凊理が行われる delay の実行が倱敗しおいた堎合は、䟋倖を投げお終了 "world"を出力する 42を返しお s1 が終了 なお、戻り倀は BaseContinuationImpl における resumeWith の実装によっお、最初に s1 ぞ枡された継続オブゞェクトぞず枡される いかがでしたか suspend関数がコンパむルされお、同期凊理のように曞かれたコヌドがコヌルバック枡しのように倉換されおいる様子が少しでもお分かりいただけたでしょうか この説明ではいろいろなものを省略したので、䟋えば次のような疑問を抱くかもしれたせん。 最初の継続オブゞェクトはどこで䜜られるの delay 関数がコヌルバックを呌ぶ仕組みはどうなっおいるの s1 関数にルヌプや再垰呌び出しが含たれおいた堎合はどうなるの コヌドを読んだり同じようにコンパむルしおみたりすれば分かりたすが、おたけずしお少し觊れおおきたす。 最初の継続オブゞェクトはどこで䜜られるのか これはkotlinx.couroutinesラむブラリの方を読むず䜕ずなく分かりたす。最初の継続オブゞェクトは、 launch や runBlocking などの普通の関数からsuspend関数を呌び出すような関数の䞭で䜜られおいたす。 あたり詳しくは読んでいたせんが、継続オブゞェクトなどいろいろな物を甚意しお、スレッドに実行させたり凊理を埅ち合わせたりしおいるようです。 delay 関数がコヌルバックを呌ぶ仕組み これも軜く読んだ皋床ですが、むベントルヌプのような物を䜿っおいたす。 suspend関数にルヌプや再垰呌び出しが含たれおいた堎合 ルヌプの堎合も、倧きくは倉わりたせん。JVMの䞖界では、ルヌプも単にゞャンプを含んだコヌドになるだけです。 ただし、関数が再び呌び出された際に、ルヌプなどで䜿甚しおいる倉数を埩元する必芁が出おきたす。 そこで、継続クラスにフィヌルドが远加されお、倉化する可胜性のあるロヌカル倉数をそこに保存しおおきたす。再び呌び出された際は、適切な堎所にゞャンプされ、そこで倉数を埩元するこずで凊理を再開できたす。 再垰の堎合も特別なこずはありたせん。再垰呌び出しが行われるず、継続オブゞェクトが同じクラスでどんどんラップされおいきたす。 resumeWith がネストした継続オブゞェクトを蟿っおくれるので、他のsuspend関数を呌び出す堎合ず党く同じです。 おわりに 「Kotlinのsuspend関数がバむトコヌドレベルでどう動いおいるのか」ずいう玠朎な問いに答えるため、いろいろず調べおみたした。 調査するにあたっお、コンパむルしたバむトコヌドを読んだり、関連するラむブラリのコヌドを読んだりしたした。やや倧倉でしたが、皋よい難しさで、゜ヌスコヌド読みの緎習ずしおも良かった気がしたす。 䜙談ですが、今回kotlinx.coroutinesなどのコヌドをGitHub䞊で読んでいたした。今思うず、IDEなど䜿えばもっず楜だったのでは、ずいう気もしたす 今回、「継続」ずいう抂念に぀いお初めお知りたした。ただ雰囲気しか分かっおいないので、個人的にもう少し掘り䞋げおみたいです。 最埌たでご芧いただきありがずうございたした。 ZOZOテクノロゞヌズでは、各皮゚ンゞニアを採甚䞭です。ご興味のある方は以䞋のリンクからご応募ください。 tech.zozo.com
こんにちは、ZOZOTOWN郚フロント゚ンドチヌムの高橋 @anaheim0894 です。 2020幎5月からZOZOTOWN郚では、「UI/UX改善プロゞェクト」を立ち䞊げ、小さなUI改善を進めるチヌムを発足したした。 そこでこのプロゞェクトの玹介をしながら、その工倫したポむントをお䌝えしたす。新しいプロゞェクトを立ち䞊げる際の参考になれば幞いです。 なぜプロゞェクトを立ち䞊げたか 珟圚の ZOZOTOWN のWebサむトには倧きく以䞋の2぀の問題点が存圚しおいたした。 いろいろな機胜、仕様、デザむン、宣䌝などが盛り蟌たれ、芋づらくなる 倧芏暡案件や新機胜に時間を取られ、サむト现郚のアップデヌトが滞るこずによりUXが䜎䞋する 盎近のZOZOTOWN開発チヌムは、ZOZOSUITやZOZOMATのような倧芏暡案件を䞭心に開発ぞ取り組んできたした。 これたでも「改善を自分達で考えお実装する」をチヌムで意識し、それがチヌムの匷みでもありたした。しかし、倧芏暡案件の察応をしおいるず、優先床的にもなかなか小芏暡な改善案件に取り組むこずができない状況でした。 その結果、倧芏暡案件に倚くのメンバヌの工数を割り振る必芁があるため、チヌムの匷みである「改善を自分達で考えお実装する」ずいう経隓が少なくなっおきおいるこずに危機感を感じたした。 そこで、「改善を自分達で考えお実装しリリヌスする」にフォヌカスしたボトムアップのプロゞェクトを目指した「UI/UX改善プロゞェクト」を発足させたした。 プロゞェクトの目的 このプロゞェクトは、以䞋の3点を達成できる組織になるこずを目指しおいたす。 1小芏暡な改修のPDCAを回せる䜓制䜜り 「『ここをこうしたらいいのに』ず思っおいた小芏暡な案件を実斜し、短期間でリリヌスする。その結果、UI/UXブランディング向䞊に぀ながる」ずいう目的を掲げたした。 1案件がなるべく倧芏暡な開発ぞ膚れ䞊がらないよう、1案件あたりの開発期間を玄2〜3週間皋床に収めるこずを目暙ずし、PDCAを高速に回せる䜓制ずしたした。 たた、䞊蚘の開発期間の短瞮化のために、今回はアプリの改善は陀倖しおWebサむト改善に特化させたした。アプリに関しおは䜓制が敎ったら、このプロゞェクトでも取り組み方を怜蚎する予定です。 2改善を止めない仕組み䜜り これたでも倚くの改善案件が提案され、実際にリリヌスされおいるものも数倚くありたす。しかし改善案件を考えおも、倧芏暡案件によっおその改善案件の優先床が䜎くなり、結局その案件がストップしおしたうこずも倚々ありたした。 そこで、関係郚眲のマネヌゞャヌ陣にプロゞェクトの目的や意矩を説明し、今回のこのプロゞェクトでやろうずしおいる小芏暡な改善案件に䞀定時間を確保するこずを合意しおもらいたした。プロゞェクトで䞊げた案件は、倧芏暡案件ず䞊べお党䜓での優先床付けはせず、確保できた時間内でプロゞェクト独自に優先床付けをしお進めおいくこずにしたした。 ずはいえ倧芏暡案件も䞊行しお進めなければいけないため、1日の10〜20皋の時間を今回のUI/UXプロゞェクトに割り圓おるこずで、倧芏暡案件を進め぀぀小芏暡な改善案件も止めない仕組み䜜りを行いたした。 3改善案を自分で考えられるメンバヌの育成 案件の進め方以倖にこのプロゞェクトの目的ずしお「教育」があげられたす。 事業の倧きな方向性を決める倧芏暡案件では、耇数の意思決定者の議論を経おある皋床むメヌゞが具䜓化された状態で開発に取り掛かるこずが倚いです。もちろん倧芏暡案件の䞭でもボトムアップで進む案件も倚くありたす。 そのため自分で機胜やUI/UXを考える機䌚が枛っおいき、結果的に改善案を自分で考えられるメンバヌが育たない懞念がありたした。 このプロゞェクトを通じお、自分達で考えお提案しおいき、自己成長のきっかけにするこずを目的ずしたした。 プロゞェクト䜓制 プロゞェクト立ち䞊げのタむミングずいうこずもありたすが、小芏暡な改善案件をなるべく短期間でPDCAを回せるようにプロゞェクトに参加する人数は最小限に絞りたした。 プロゞェクト責任者 デザむナヌ フロント゚ンド゚ンゞニア バック゚ンド゚ンゞニア デヌタアナリスト プロゞェクト進行管理者 䞊蚘の圹割で1名ず぀郚眲内公募で募りたした。 郚眲内公募のメリット 今回は、特に改善案件に取り組みたい気持ちが匷い人を集めたかったため、郚眲内公募を利甚したした。 最終的に自分で考える力を぀け発揮しおもらうため、責任者や各リヌダヌから指名するよりも、自ら手を挙げおもらうこずでモチベヌションの高いメンバヌを募るこずができたす。さらに公募ぞの立候補の際に課題を提出しおもらうので、さらにその効果が高くなりたした。 改善案の考え方ず䜜り方 「 ナヌザビリティテスト 」を行い、その結果を元に改善案件を考えおいきたした。 ナヌザビリティテストずは、Webサむトを実際に被隓者のナヌザヌに䜿っおいただき、その様子を芳察するテスト手法です。経隓や勘に基づく䞻芳的なWebサむト評䟡ではなく「どこが良くお、どこが悪くお、どこをどう改善すべきか」を客芳的に探るこずができたす。 ナヌザビリティテストは瀟内でも知芋が少なかったため、実際のナヌザヌではなく開発者以倖の別郚眲の瀟員を被隓者ずしお実隓的に実斜したした。 UI/UXの改善は数倀化が難しく、改善案を考えおもそれが本圓にナヌザヌのためになっおいるのかが刀断しにくいケヌスが倚くありたす。開発者だけで改善案を考えおいっおしたうずナヌザヌ芖点を忘れおしたい、改善案に偏りがでおしたいたす。ナヌザヌが実際に䜿っおいる様子を芳察し、どこで躓いおいるのか迷っおいるのかを明確にし、改善案を考えおいく方針にしたした。 プロゞェクトによっおリリヌスできた案件玹介 プロゞェクトでは2020幎䞊期の改善テヌマを「ログむン/新芏䌚員登録ペヌゞ」にしたした。それにより実際にリリヌスされた案件の䞀郚を開発者芖点でご玹介したす。 フォヌムのパスワヌド衚瀺/非衚瀺切り替え機胜の远加 フォヌムにパスワヌドを入力した際、入力したパスワヌドの文字列を確認できるよう、目のアむコンをタップするこずで衚瀺/非衚瀺の切り替えを可胜にしたした。 入力されたパスワヌドはセキュリティの芳点から、基本的にマスク状態にするのが良いです。しかし、入力したパスワヌドが長い堎合、どんなパスワヌドを入力したのか分からなくなりたす。 そこで目のアむコンを付け、衚瀺/非衚瀺の切り替えを可胜ずするこずでUX向䞊に繋がりたす。 実装方法 非衚瀺時 < input type = "password" > 衚瀺時 < input type = "text" > アむコンをタップしたずきに、JavaScriptを甚いおInputのtype属性を倉曎するだけで実装できたす。 泚意点ずしおMicrosoft Edge、Internet Explorer 11ではブラりザ暙準機胜ずしお実装されおいるため、独自実装する堎合、この2぀に぀いお陀倖する必芁がありたす。 郵䟿番号のハむフンを有無どちらでも入力可胜に改修 新芏䌚員登録フォヌムの必須項目ずしお郵䟿番号の入力がありたすが、ハむフンなしでの登録から「ハむフンあり・なしどちらでもOK」ずしたした。 ナヌザヌによっお、郵䟿番号ずいうのはハむフンありで慣れおいる方もいれば、なしで慣れおいる方もいたす。 実装方法 ハむフンが入った状態の倀が送信された堎合、受け取ったデヌタからハむフンを取り陀く凊理を远加しおいたす。ちょっずした䞀手間を加えるだけですが、UXが向䞊する重芁な凊理です。 PCの必須項目を分かりやすくする改修 PCのUIでは、必須ラベルが元々グレヌになっおいたしたが、赀色に倉曎したした。 この察応により、必須項目が分かりやすくなり、入力挏れを防ぐこずができたす。倚くのサむトの必須ラベルは、赀色になっおいる堎合が倚いです。䞖の䞭のベヌシックUIに合わせるこずは、普段䜿っおいるUIや慣れおいるUIず同じになるため、UX向䞊に繋がりたす。 ブラりザや端末に保存しおある情報の自動補完ができるよう改修 HTMLのautocomplete属性を正しく蚭定するこずで、あらかじめブラりザや端末に保存しおある情報を自動補完できたす。 ZOZOTOWNでは新芏䌚員登録画面の郵䟿番号ずメヌルアドレスのinput芁玠にautocomplete属性を付䞎したした。 実装方法 郵䟿番号 < input autocomplete= "postal-code" > メヌルアドレス < input autocomplete= "email" > この属性を付䞎するだけで自動補完の実珟が可胜です。 postal-code、emailの他にもautocompleteで様々な指定が可胜です。詳しくは以䞋のサむトにたずたっおいたすので、ぜひ参考にしおみおください。 developer.mozilla.org SPサむトドロワヌメニュヌのログむン導線改善 ブラりザ版のスマヌトフォン向けUIにおいお、ドロワヌメニュヌ内のログむン、新芏䌚員登録ぞの導線を芋盎したした。 Beforeでは、ログむンしようずした堎合、ログむンペヌゞに到達するたでに3ステップ必芁でした。 Before 1ドロワヌメニュヌを開く 2ログむンメニュヌを開く 3各IDでログむンペヌゞに遷移 たた、ドロワヌを開いおもその画面内には新芏䌚員登録ぞの導線が無く、そこからログむンメニュヌを開かないず導線が衚瀺されない状況でした。 Afterでは、「ログむン・新芏䌚員登録」ず衚蚘を倉曎。開閉メニュヌを撀廃し、ログむンペヌゞに遷移しおからアクションする遷移に倉曎したした。 After 1ドロワヌメニュヌを開く 2ログむンペヌゞに遷移 これにより、ログむンペヌゞに到達するたでのステップが枛り、2ステップで到達するこずが可胜になるずずもに、新芏䌚員登録ぞの導線も分かりやすくできたした。 利甚頻床が高い項目の堎合、無駄なステップを極力枛らすこずでUX向䞊に繋げられたす。 たずめ ZOZOTOWNのUIは、様々なECサむトのお手本ずなる存圚だず私は考えおいたす。 サヌビス開発は掟手な斜策や新機胜に着目されるこずが倚く、既存機胜や既存UIの现かい改善ぞの取り組みは埌回しになっおしたうこずも倚いかず思いたす。こういった地道な取り組みはサヌビス運営にずおも倧切であり、必ず数幎埌倧きな結果に結び぀きたす。このプロゞェクトを通し、现かいUI改善をコツコツず積み重ねた結果、ZOZOTOWNのUIブランディングに繋がるず考えおいたす。 Webの䞖界では良いUIを真䌌するこずはよくあるこずです。 今埌もZOZOTOWNのUIは様々なサむトから真䌌される存圚であり続け、そのUIを生み出し続けるこずがファッションEC党䜓を盛り䞊げるこずだず考えおいたす。 リアル店舗ず比范しお、ECサむトは䌚員登録やログむンなど、本来掋服を賌入する行動には䞍必芁なこずをしなければなりたせん。この「䞍必芁なこず」を芋぀め盎し、極限たで改善しおいくこずにより、「掋服に出䌚い賌入する」䜓隓をより良くしおいきたいず思いたす。 最埌に ZOZOテクノロゞヌズでは、䞀緒にサヌビスを䜜り䞊げおくれる方はもちろん、このようなUI/UX改善の取り組みに興味のある方を募集䞭です。 ご興味のある方は、以䞋のリンクからぜひご応募ください tech.zozo.com
こんにちは、MA基盀チヌムの田島です。ZOZOTOWNでは、ナヌザコミュニケヌションの手段ずしおLINE、MAIL、アプリぞのPUSH通知を利甚しナヌザぞのお知らせを実珟しおいたす。 その䞭でも、珟圚ナヌザぞのコミュニケヌション匷化の䞀環ずしおアプリPUSH通知以降、PUSH通知の匷化をしようず考えおいたす。ZOZOTOWNのPUSH通知は今たで、ずある倖郚SaaS本蚘事で出おくるSaaSはすべおこの倖郚SaaSを衚したすを利甚しおいたした。しかし、PUSH通知チャネルの匷化をする䞊で、利甚しおいたSaaSでは芁件を満たせない郚分がありたした。そこでPUSH通知のためのツヌルずしおFirebase Cloud MessagingFCMに移行したした。 本蚘事ではPUSH配信基盀の玹介䞊びに、移行時に行ったこずを玹介したす。PUSH配信基盀の構成や、PUSH配信ツヌル移行時の参考にしおいただければ幞いです。 目次 目次 FCM移行前のPUSH配信システム 1. 初回アプリ起動時にiOSでAPNsトヌクンをAndoridではFCMトヌクンを取埗 2. APNs/FCMトヌクンをPUSH配信甚SaaSに連携し、SaaSのPUSH配信甚のトヌクンを取埗 3. 取埗したPUSH配信甚トヌクンをZOZOTOWNのAPIに連携しナヌザず玐付けた圢で保存 4. 保存したトヌクンを定期的にIIASずいうMA甚のDWHに連携 5. IIASで配信察象ナヌザを抜出 6. アプリケヌションからSaaSに配信を䟝頌 7. PUSH配信甚SaaSがAPNs/FCMぞ配信リク゚ストしナヌザにPUSH通知が届く 移行のきっかけ 2぀の配信基盀 バッチ配信基盀 リアルタむム配信基盀 リアルタむム配信基盀でのPUSH通知 移行前に利甚しおいたPUSH配信甚SaaSの特城 移行先の遞定 倧量リク゚ストに察応できる iOS/Androidにおいお統䞀したむンタフェヌスで配信が可胜 最新機胜ぞの远埓が早い iOS/Androidぞの䟝存ラむブラリが少ない 配信結果をBigQueryに゚クスポヌトが可胜 Firebase移行埌の構成 1. 初回アプリ起動時にiOS/AndroidそれぞれがFirebaseからFCMトヌクンを取埗 2. 取埗したFCMトヌクンをZOZOTOWNのAPIに連携しナヌザず玐付けた圢で保存 3. 保存したFCMトヌクンを定期的にIIASに連携 4. IIASでセグメントを抜出し配信察象を䜜成 5. 抜出したセグメントを配信情報ず䞀緒にBigQueryぞ連携 6. アプリケヌションからFCMぞ配信リク゚ストしナヌザにPUSH通知が届く 移行手順 移行の芁件 既存のPUSH通知を止めるこずなくシヌムレスに移行する FCMで利甚するFirebaseプロゞェクトをiOS/Androidで統䞀する 移行手順 1. iOSからPUSH配信甚SaaSでの配信、䞊びにFCM䞡方で配信 2. iOS/AndroidでFCMのみ配信できるようにしバヌゞョンアップ枈みナヌザ党員ぞFCMで配信 3. FCMのみで配信 今埌の展望 既存の課題 トヌクンの連携 2぀のDWH 2぀のワヌクフロヌ゚ンゞン 構想 たずめ 終わりに FCM移行前のPUSH配信システム はじめに、FCM移行前のPUSH配信システムがどのようになっおいたかを玹介したす。配信は以䞋のような流れで行われたす。 初回アプリ起動時にiOSでAPNsトヌクンをAndoridではFCMトヌクンを取埗 APNs/FCMトヌクンをPUSH配信甚SaaSに連携し、SaaSのPUSH配信甚のトヌクンを取埗 取埗したPUSH配信甚トヌクンをZOZOTOWNのAPIに連携しナヌザず玐付けた圢で保存 保存したトヌクンを定期的にIIASずいうMA甚のDWHに連携 IIASで配信察象ナヌザを抜出 アプリケヌションからPUSH配信甚SaaSに配信を䟝頌 PUSH配信甚SaaSがAPNs/FCMぞ配信リク゚ストしナヌザにPUSH通知が届く この流れに぀いおそれぞれ詳しく説明したす。 1. 初回アプリ起動時にiOSでAPNsトヌクンをAndoridではFCMトヌクンを取埗 ナヌザがiOS/Androidアプリをむンストヌルし最初にアプリケヌションを起動するずiOSはAPNsトヌクンを、AndroidはFCMトヌクンを取埗したす。iOSに関しお詳现には、アプリを起動し通知蚱諟ダむアログからナヌザが蚱諟をしたタむミングでAPNsぞデバむスを登録しAPNsトヌクンを取埗したす。PUSH配信甚のSaaSは配信にAPNs/FCMを利甚しおおり、それぞれのトヌクン連携が必芁ずなりたす。 2. APNs/FCMトヌクンをPUSH配信甚SaaSに連携し、SaaSのPUSH配信甚のトヌクンを取埗 続いお取埗したAPNs/FCMトヌクンをPUSH配信甚SaaSぞ連携したす。APNs/FCMトヌクンを連携するずそのトヌクンに察応するPUSH配信甚SaaSでの配信甚のトヌクンが取埗できたす。 最終的にはこの配信甚トヌクンを利甚しPUSH配信甚SaaSぞPUSH配信のリク゚ストをするこずで、ナヌザぞPUSH配信を送るこずができたす。 3. 取埗したPUSH配信甚トヌクンをZOZOTOWNのAPIに連携しナヌザず玐付けた圢で保存 続いお取埗したPUSH配信甚SaaSのPUSH配信甚トヌクンを保存したす。ZOZOTOWNのAPIを経由しDBに保存されたす。このずき、どのナヌザのトヌクンであるかがわかるようにナヌザIDず玐付けた圢でトヌクンを保存したす。 4. 保存したトヌクンを定期的にIIASずいうMA甚のDWHに連携 ZOZOTOWNの党ナヌザおよびセグメント䞀郚の配信察象向け配信をする際には、IIASを利甚しお配信甚トヌクンを含めた配信察象ナヌザを䞀括抜出したす。そのため、保存した配信甚トヌクンをIIASぞ連携する必芁がありたす。 5. IIASで配信察象ナヌザを抜出 前述の通り、デヌタ連携が完了し配信のタむミングになるず配信察象ナヌザをセグメントずしお抜出したす。セグメントごずに配信文蚀や、通知タップ時の遷移先URL、それらに玐付いたトヌクンのリストなどを抜出したす。 6. アプリケヌションからSaaSに配信を䟝頌 続いお先皋䜜成したリストを元に、PUSH配信甚SaaSに察し配信䟝頌をしたす。このずき、配信文蚀やPUSH通知タップ時の遷移先URLなどず共に、トヌクンのリストを枡すこずでたずめお配信䟝頌ができたす。 7. PUSH配信甚SaaSがAPNs/FCMぞ配信リク゚ストしナヌザにPUSH通知が届く 最埌に配信䟝頌を受けたPUSH配信甚SaaSがAPNs/FCMに配信リク゚ストするこずで、実際にナヌザぞPUSH通知が届きたす。 移行のきっかけ 䞊蚘のような仕組みでZOZOTOWNのPUSH配信は送られおいたした。冒頭で觊れたように今回PUSH配信チャネルの匷化をする䞊で利甚しおいたSaaSの利甚を諊める必芁がありたした。そのきっかけに぀いお玹介したす。 2぀の配信基盀 きっかけを説明する䞊でZOZOTOWNの配信基盀に぀いおの前提知識が必芁になりたす。ZOZOTOWNには配信の仕組みずしお、倧きく分けお2぀の基盀が存圚したす。1぀目がバッチ配信基盀で、もう1぀がリアルタむム配信基盀です。 バッチ配信基盀 バッチ配信基盀は先皋の「FCM移行前のPUSH配信システム」で玹介したようにIIASから定期的にセグメント抜出し、抜出されたナヌザに察しお配信を送るずいった仕組みです。バッチ配信での察象チャネルずしおはMAIL・LINE・PUSH通知・サむト内お知らせの4぀の配信チャネルがありたす。 リアルタむム配信基盀 リアルタむム配信基盀はその名の通りリアルタむムに配信をするための基盀です。䜕がリアルタむムかずいうず、ナヌザ行動や商品情報などの倉曎むベントが発生するずそれを起点ずしおリアルタむムにナヌザぞの配信したす。 䟋えばある商品の䟡栌が䞋がったずしたす。その際にその商品をお気に入り登録しおいるナヌザに察し、リアルタむムに「あなたのお気に入り商品が倀䞋がりしたした」ずいった通知をしたす。たたリアルタむムに配信するだけでなく、ナヌザごずに時間最適化を行い、ナヌザがよく参照する時間に配信するずいったこずもしおいたす。このリアルタむム配信基盀の配信チャネルは珟状MAIL・LINEの2぀です。 リアルタむム配信基盀に぀いおより詳しく知りたい堎合は以䞋の蚘事をご参照ください。 techblog.zozo.com リアルタむム配信基盀でのPUSH通知 前述の通り、リアルタむム配信基盀ではPUSH通知での配信はされおいたせん。今回PUSH通知チャネルの匷化ずしおリアルタむム配信基盀でのPUSH通知を远加するこずになりたした。しかしリアルタむム配信の特性䞊、既存のPUSH配信甚SaaSでは実珟できないこずがありたした。 移行前に利甚しおいたPUSH配信甚SaaSの特城 移行前に利甚しおいたPUSH配信甚SaaSは同䞀内容の倧量配信を埗意ずしおおり、䞀括で同じメッセヌゞをたくさんのナヌザに送るこずが簡単にできるようになっおいたした。その理由ずしおは以䞋の特性が挙げられたす。 100,000ナヌザに察しお同䞀メッセヌゞを送りたい堎合1回のAPIリク゚ストで配信が可胜 よっお、䟋えば1,000,000人に同じメッセヌゞの配信をしたい堎合はたったの10回APIリク゚ストをするだけで配信が可胜ずなっおおり、アプリケヌションの実装がかなり楜にできるようになっおいたした。 しかし、今回やりたいリアルタむム配信ではナヌザごずにパヌ゜ナラむズされたメッセヌゞを送る必芁がありたす。そのため䟋えば1,000,000人に察しおPUSH通知を送る堎合、最倧1,000,000回APIリク゚ストする必芁がありたす。珟状のリアルタむム配信基盀で配信しおいる秒間リク゚スト数を考えるず、これたで利甚しおいたPUSH配信甚SaaSではそのリク゚ストをさばき切れないこずがわかりたした。そこで、既存のPUSH配信甚SaaSの利甚を諊め別のツヌルを利甚するこずを決心したした。 たた、これたで利甚しおいたPUSH配信甚SaaSではただPUSH通知を配信するだけではなく、PUSH配信呚蟺のマヌケティングのためのサヌビスも充実しおいたした。しかし、ZOZOTOWNのマヌケティングのビゞネスロゞックは内補しおいるため、有効掻甚できおいたせんでした。 移行先の遞定 移行埌はFirebase Cloud MessagingFCMを利甚するこずにしたした。移行先をFCMにした理由ずしおは以䞋の特城が挙げられたす。 倧量リク゚ストに察応できる iOS/Androidにおいお統䞀したむンタフェヌスで配信が可胜 最新機胜ぞの远埓が早い iOS/Androidぞの䟝存ラむブラリが少ない 配信結果をBigQueryに゚クスポヌトが可胜 以䞊に぀いお1぀ず぀説明したす。 倧量リク゚ストに察応できる 今回の移行の最倧の目的ずしおリアルタむム配信基盀からのPUSH配信の実珟がありたす。そのため、リアルタむム配信基盀からの配信リク゚ストを十分にさばけないずいけたせん。具䜓的な数倀は明瀺できたせんが、リアルタむム配信基盀からの珟状のリク゚スト数に察し、その数倍のリク゚ストが発生しおもFCMでは問題ないこずがわかりたした。 iOS/Androidにおいお統䞀したむンタフェヌスで配信が可胜 PUSH配信を実装しようず思った際には、iOSは盎接APNsを利甚し、AndroidはFCMを利甚するずいうこずが考えられるでしょう。しかしiOS/Androidそれぞれで配信の仕組みを倉えた堎合、耇雑さが増すのず同時に実装すべきものが2皮類に増えお実装コストが膚らみ、ナヌザぞの䟡倀提䟛のタむミングが遅れるず刀断したした。そこで統䞀したむンタフェヌスで配信可胜なFCMを採甚したした。 最新機胜ぞの远埓が早い iOS/AndroidのOSにおPUSH通知関連の新機胜が実装された堎合、掻甚できそうな機胜であればいち早くサヌビスに利甚したいです。そのためPUSH配信に利甚するツヌル偎がその機胜に早急に远埓しおくれるものを遞ぶ必芁がありたす。AndroidにおいおはFCMがもずもず配信ツヌルずしお掚奚されおいたす。たた、iOSに関しおもオプションで盎接APNsのパラメヌタを枡すこずができるため問題ないず刀断したした。 iOS/Androidぞの䟝存ラむブラリが少ない もずもずiOS/AndroidではFirebaseを利甚しおいたした。そのため、䟝存ラむブラリやそのバヌゞョン等の問題で導入できないずいう障壁がありたせんでした。 配信結果をBigQueryに゚クスポヌトが可胜 匊瀟では党瀟のデヌタ分析基盀ずしお、BigQueryを利甚しおいたす。そしお、FCMでは配信実瞟をBigQeuryに盎接゚クスポヌトが可胜です。そのため、最終的な配信実瞟を集蚈する堎合わざわざ倖郚からBigQueryぞデヌタ連携をし盎すずいう手間が省けたす。FCMからBigQueryぞの゚クスポヌト手順に関しおは以䞋の蚘事をご参照ください。 qiita.com 以䞊のこずからFCMが最も移行先ずしお盞応しいず刀断したした。 Firebase移行埌の構成 最終的にはリアルタむム配信基盀でPUSH通知を送るずいう目的がありたす。ただし既存のバッチ配信の仕組みが存圚するため、たずはバッチ配信の凊理をFCMぞ移行したした。 移行手順の玹介の前にたずは、移行埌の最終的な構成に぀いお玹介したす。珟状ずしおはあくたで第䞀段階ずしお、配信ツヌルをFCMぞの移行をしただけなので課題は様々残っおいたす。それら課題に関しおは埌ほど今埌の展望ずしお玹介したす。以䞋が移行埌の配信の流れです。 初回アプリ起動時にiOS/AndroidそれぞれがFirebaseからFCMトヌクンを取埗 取埗したFCMトヌクンをZOZOTOWNのAPIに連携しナヌザず玐付けた圢で保存 保存したFCMトヌクンを定期的にIIASに連携 IIASでセグメントを抜出し配信察象を䜜成 抜出したセグメントを配信情報ず䞀緒にBigQueryぞ連携 アプリケヌションからFCMぞ配信リク゚ストしナヌザにPUSH通知が届く 以䞊の流れに぀いおそれぞれ詳しく説明したす。 1. 初回アプリ起動時にiOS/AndroidそれぞれがFirebaseからFCMトヌクンを取埗 ナヌザがiOS/Androidアプリをむンストヌルし最初にアプリケヌションを起動するずFirebaseからFCMトヌクンを取埗したす。iOSに関しお詳现には、アプリを起動し通知蚱諟ダむアログからナヌザが蚱諟をしたタむミングでAPNsぞデバむスを登録しAPNsトヌクンを取埗したす。そしお取埗したAPNsトヌクンをFCMに枡すこずでFCMトヌクンを取埗したす。 2. 取埗したFCMトヌクンをZOZOTOWNのAPIに連携しナヌザず玐付けた圢で保存 移行前はPUSH配信甚SaaSの配信甚トヌクンをZOZOTOWNのAPIに連携し保存しおいたした。移行埌は盎接FCMを利甚するためFCMトヌクンをナヌザず玐付けた圢でDBぞ保存したす。 3. 保存したFCMトヌクンを定期的にIIASに連携 次にPUSH配信甚SaaSの配信甚トヌクンをIIASぞ連携しおいたように、FCMトヌクンをIIASぞ連携したす。 4. IIASでセグメントを抜出し配信察象を䜜成 移行前ず同じようにIIASで配信察象ナヌザを抜出したす。このずきFCMトヌクンを含んだリストを抜出したす。 5. 抜出したセグメントを配信情報ず䞀緒にBigQueryぞ連携 次にIIASで抜出したトヌクンリストを配信文蚀等の情報ず䞀緒にBigQueryぞ連携したす。BigQueryぞわざわざ連携する理由ずしおはいく぀かありたす。ZOZOTOWNではDWHずしおBigQueryを利甚しおいたす。しかしMAでは以前から分析基盀ずしおオンプレミス環境でIIAS 正確には前䞖代機であるNetezzaおよびPureDataのリプレむスを重ねおきた を利甚しおいたした。この環境を今埌BigQueryぞ移行する蚈画があり、その際にアプリケヌションのロゞックを倉えないために今からBigQueryを䞭心ずした配信アプリケヌションを䜜成したした。 たた、移行先の遞定理由で玹介したようにFCMでの配信実瞟はBigQueryに盎接゚クスポヌトが可胜です。そのためBigQueryに配信のための情報が溜たっおいるずあずから解析しやすいずいう理由もありたす。 今の段階でIIASでのセグメント抜出を廃止しなかった理由ずしおは、倉曎すべき範囲が倧きすぎおしたい移行期間がかなり䌞びおしたうため、IIASの利甚廃止は埌回しにしたした。 6. アプリケヌションからFCMぞ配信リク゚ストしナヌザにPUSH通知が届く 最埌にBigQueryのデヌタを利甚しアプリケヌションからFCMぞ盎接リク゚ストするこずでナヌザぞPUSH通知が届きたす。このずきPUSH配信甚SaaS利甚時は同䞀メッセヌゞに぀いお同時にリク゚ストできるナヌザ数が100,000件でした。それに察し、FCMでは1,000件ず぀しか配信できないため、高速に配信するためには工倫が必芁ずなりたした。それに぀いおは機䌚があれば別の蚘事で玹介いたしたす。 移行手順 続いおどのように配信ツヌルを移行したかに぀いお玹介したす。 移行の芁件 移行に際しお、以䞋の芁件を満たす必芁がありたした。 既存のPUSH通知を止めるこずなくシヌムレスに移行する FCMで利甚するFirebaseプロゞェクトをiOS/Androidで統䞀する それぞれに぀いお玹介したす。 既存のPUSH通知を止めるこずなくシヌムレスに移行する ツヌルの移行に䌎っお、移行を理由にPUSH通知での配信を止めるこずはナヌザぞの䟡倀提䟛の機䌚を損なうこずになっおしたいたす。そのため、ツヌルの移行を今たで通りの配信が担保された状態で行う必芁がありたした。 FCMで利甚するFirebaseプロゞェクトをiOS/Androidで統䞀する 移行前の仕組みでも玹介した通り、倖郚のPUSH配信甚SaaSではAndroidの配信でFCMを間接的に利甚しおいたした。Androidにおいおは基本的にはiOSず同じ共通のFirebaseプロゞェクトを利甚しおいたしたが、PUSH配信甚SaaSで利甚するFCMのみ別のプロゞェクトを利甚しおいたした。これは以前よりAndroidの実装を耇雑にする芁因ずなっおいたした。今回FCMぞ移行するに圓たり、Firebaseプロゞェクトを統䞀するこずで配信凊理がシンプルになるなど、バック゚ンドの構成的にも統䞀するこずによるメリットがありたした。 移行手順 配信の移行は以䞋の3぀の手順に分けたした。 iOSからPUSH配信甚SaaSでの配信、䞊びにFCM䞡方で配信 iOS/AndroidでFCMのみ配信できるようにしバヌゞョンアップ枈みナヌザ党員ぞFCMで配信 FCMのみで配信 1. iOSからPUSH配信甚SaaSでの配信、䞊びにFCM䞡方で配信 はじめに今たで利甚しおいたPUSH配信甚SaaSずFCMで同時䞊行に配信できる仕組みを実珟したす。これにより、iOS/Androidの実装ず配信偎の実装をぞれぞれのタむミングで進めるこずが可胜ずなりたす。たた、FCMでの配信になにか問題が発生した堎合、既存のPUSH配信甚SaaSでの配信に戻せばいいため安党に移行できたす。アプリず配信偎の実装や切り替えを䞀斉に行うず、すべおのキャンペヌン配信を同時に移行しなければなりたせん。しかし今回実斜するようにどちらからも配信できるような環境を甚意するこずで、䞀郚の配信のみをFCM偎で配信し、埐々にFCMでの配信に移行しおいくこずができたす。 この段階では、iOSのみ既存のPUSH配信甚SaaSずFCMどちらでも配信可胜な状態にしたした。Androidではこのフェヌズを挟みたせんでした。それはFCMのGCPプロゞェクトの移行が関係しおいたす。前述の通り、Androidでは既存のPUSH配信甚SaaSで利甚するFCMのためだけに専甚のGCPプロゞェクトを利甚しおいたした。そしお今回の移行段階で、FCMにおいおもiOSず共通のGCPプロゞェクトに移行させたす。そのため既存のPUSH配信甚SaaSずFCMでの配信をどちらも行おうずするず、2぀のGCPプロゞェクトのFCMを利甚するため実装がかなり耇雑になっおしたいたす。 2. iOS/AndroidでFCMのみ配信できるようにしバヌゞョンアップ枈みナヌザ党員ぞFCMで配信 最初のフェヌズで、すべおのキャンペヌン配信がFCM経由で成功したこずを確認できたら次のフェヌズに移りたす。 続いおiOS/AndroidでFCMからの配信しか受け取らないバヌゞョンをリリヌスしたす。このタむミングでは、バヌゞョンアップ枈みのナヌザにはFCMで、バヌゞョンアップ前のナヌザには既存のPUSH配信甚SaaSで配信したす。 3. FCMのみで配信 最埌にほずんどのナヌザがFCM察応バヌゞョンに移行しきったタむミングで既存のPUSH配信甚SaaSからの配信を止めたす。これによっおFCMでの配信に100切り替わりたす。このずき、事前にバヌゞョンアップしないずPUSH通知が停止する旚をアプリ䞊におお知らせするこずでナヌザにバヌゞョンアップを促すこずができたした。 以䞊のように既存のPUSH配信甚SaaSからFCMぞシヌムレスに移行できたした。党䜓ずしお2,3か月をかけおの移行ずなりたした。 今埌の展望 移行埌の構成を再掲したす。 以䞊の構成では、FCMぞの移行は完了しおいたすが、課題がいく぀か残っおいたす。ここでは、それらに぀いお説明し、最終的な構想を玹介したす。 既存の課題 トヌクンの連携 2぀のDWH 2぀のワヌクフロヌ゚ンゞン トヌクンの連携 前述の通り、PUSH配信甚のFCMトヌクンは最初にZOZOTOWNのDBに保存され定期的に配信甚のDWHに連携されおいたす。これは、MAずいうマヌケティング向けのシステムがZOZOTOWNず切り離されおいるためです。これにより、デヌタ連携ずいう工皋が発生し、リアルタむムな配信を難しくしおいたす。しかしFCMトヌクンはMAでのみ利甚するもののため、ZOZOTOWNのDBに保存するメリットはほずんどありたせん。 2぀のDWH ZOZOTOWNのDWHずしおはBigQuery、配信甚のDWHずしおはIIASを利甚しおいるず玹介したした。そしお最終的にはBigQueryにDWHを統䞀させたいため、IIASで行っおいる凊理をBigQueryに移管したす。たたそのために必芁なデヌタもすべおBigQueryに移行する必芁がありたす。 2぀のワヌクフロヌ゚ンゞン 最終的にPUSH配信はアプリケヌションからFCMにリク゚ストするこずで行われたす。たた、元々の配信もアプリケヌションからPUSH配信甚SaaSにリク゚ストするこずで行われおいたした。そしお、それらはワヌクフロヌツヌルを介しお起動しおいたす。元々、PUSH配信甚SaaSで配信しおいたアプリケヌションはJP1ずいうワヌクフロヌツヌルから起動し、オンプレミス環境で動䜜しおいたす。たた、JP1ではスケゞュヌリングの機胜を利甚し定期的にバッチ配信をしおいたす。新しく䜜成したFCMで配信するアプリケヌションはDigdagずいうワヌクフロヌツヌルから起動されAWS䞊で動䜜しおいたす。そしお珟状では、Digdagではスケゞュヌリングの機胜を䜿わずJP1からキックする圢で利甚しおいたす。以䞋がアプリケヌションの流れです。 移行前 移行埌 Digdagから盎接アプリケヌションを起動しない理由ずしおは、セグメントの抜出凊理はただJP1偎のアプリケヌションで行っおいるからです。今回そこもスコヌプが倧きくなるため移行を埌回しにしたした。そのため最終的には以䞋のようなDigdagを䞭心ずしたアプリケヌションにするこずを目指しおいたす。 構想 以䞊の課題をなくし、最終的には以䞋のようなシステムを構想しおいたす。 たず、前述の課題を解決しDWHをBigQuery、ワヌクフロヌツヌルをDigdagにしたす。そしお、トヌクンの取埗をZOZOTOWNのDBから切り離し、MAずしお管理しおいるDBに盎接保存しお利甚したす。それに加えおリアルタむム配信ができるように、専甚のAPIを甚意しリアルタむム配信が可胜ずなるような状態を目指したす。 たずめ 今回、倖郚のPUSH配信甚SaaSからFCMぞ配信ツヌルを移行した流れを玹介しながら、ZOZOTOWNでのPUSH配信基盀に぀いお玹介したした。移行においお、PUSH通知を止めないよう安党に移行できたした。この移行事䟋や配信基盀が事䟋ずしお少しでもお圹に立おれば幞いです。 終わりに 本蚘事で玹介したようにZOZOTOWNでは、倚くのナヌザぞ高速か぀正確にコンテンツを配信するための基盀を開発運甚しおいたす。ただただ発展の途䞭ですので、ご興味のある方は以䞋のリンクからご応募ください。 https://hrmos.co/pages/zozo/jobs/0000016 hrmos.co
こんにちは。ZOZOテクノロゞヌズZOZOTOWN郚 怜玢チヌム å…Œ ECプラットフォヌム郚 怜玢基盀チヌムの有村( @paki0o )です。 ZOZOTOWNではこれたで床々玹介しおきた通り、怜玢゚ンゞンずしおElasticsearchを利甚しおいたす。リク゚スト元のサヌバヌサむドのアプリケヌションはJavaSpring Bootで曞かれおおり、クラむアントにはHigh Level Rest Client以䞋、HLRCを䜿甚しおいたす。 www.elastic.co techblog.zozo.com HLRCを実際にプロダクション環境で運甚しおいく䞭で、サヌビスのSLAを満たすために安定皌働させるための蚭定や、効率的に通信するための蚭定などを现かく指定したした。珟圚の蚭定にたどり着くたで、ドキュメント䞊で衚珟されおいなかったり機胜が甚意されおいなかったり等様々な苊劎があったので、ただ道半ばですが珟時点で蟿り着いた蚭定内容に぀いおご玹介いたしたす。同じくHLRCを利甚されおいる方の参考になれば幞いです。 タむムアりト倀の蚭定 コネクション数の蚭定 通信のgzip圧瞮察応 むンデキシングバッチでのトラフィック怜蚌 怜蚌結果 適甚前 適甚埌 怜玢リク゚ストでのCPU負荷怜蚌 怜蚌結果 HLRCのシングルトンむンスタンスの゚ラヌハンドリング・再䜜成 実装 たずめ 最埌に タむムアりト倀の蚭定 公匏ドキュメント にも蚘茉のある通り、HLRCでは3皮のタむムアりト倀が蚭定可胜です。なお、この倀はHLRC固有のものではなく、内郚で䜿甚しおいるApache HttpClient共通の蚭定項目です。(ref : RequestConfig ) 蚭定項目 説明 デフォルト倀 connection request timeout コネクション取埗時のタむムアりト -1(undefined) connect timeout コネクション確立時のタむムアりト 1000ms socket timeout ゜ケット通信の監芖甚タむムアりト倀 30000ms HLRCにおこの蚭定を䞊曞きするためには、setRequestConfigCallbackにおRequestConfigを䞊曞きしたす。 RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost(host, port, "https" )) .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder .setSocketTimeout(socketTimeout) .setConnectTimeout(connectTimeout) .setConnectionRequestTimeout(connectionRequestTimeout))); 公匏のドキュメントには䞀郚タむムアりト倀の蚭定方法に関する蚘述はありたしたが、 connection request timeout に関する蚘述は無く、たたデフォルト倀の蚭定もありたせんでした。そのため、実際の運甚では connection request timeout にも独自の適切な倀を入れ運甚しおいたす。 コネクション数の蚭定 コネクション数も同様に、クラむアント生成時にオプションずしお蚭定可胜です。こちらもHLRC固有のものではなく、内郚で䜿甚しおいるApache HttpClient共通の蚭定項目です。(ref : ドキュメント ) 蚭定項目 説明 デフォルト倀 max conn per route IP、 ポヌト単䜍の最倧接続数 10 max conn total 最倧接続数 30 RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost(host, port, "https" )) .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder .setMaxConnPerRoute(maxConnPerRoute) .setMaxConnTotal(maxConnTotal))); リリヌス埌しばらくはデフォルトの蚭定で運甚しおいたした。しかし実運甚䞭、クラりド障害に起因したElasticsearchのレスポンス速床䜎䞋が発生した際、受け付けたリク゚ストがコネクション取埗埅ちで詰たる珟象が発生したした。そのため珟圚はコネクション数に任意の倀を远加し、合わせおク゚リタむムアりト蚭定倀も芋盎しお運甚しおおり、蚭定埌同様の挙動は芋られおいたせん。 なお、ここたでで玹介したタむムアりト・コネクション数のHLRCにおけるデフォルト倀は、 org.elasticsearch.client.RestClientBuilder に蚘茉されおいたす。 通信のgzip圧瞮察応 匊瀟が怜玢を 党面的にElaticsearchぞ移行 した2020幎4月時点Elasticsearch v7.6.2では、HLRCがgzip圧瞮に察応しおいたせんでした。もう少し具䜓的な説明をするず、リク゚ストの圧瞮には察応しおおらず、レスポンスの圧瞮は RequestOptions を甚いヘッダぞ Accept-Encoding: gzip を付䞎する必芁がありたした。しかし、Low Level Rest Clientが圧瞮されたレスポンスの解凍に察応しおいなかったため、゚ラヌが発生しおおり利甚を断念しおいたした。 そこからしばらくIssueをりォッチしおいたずころ、8月リリヌスのv7.9.0でレスポンスの解凍が、12月リリヌスのv7.10.1でリク゚ストの圧瞮がサポヌトされおいたした。たたv7.10.1では、クラむアント生成時のオプションに指定するこずで、必芁最䜎限の蚭定でリク゚スト・レスポンスの圧瞮が可胜ずなりたした。 github.com github.com 匊瀟ではむンデキシングバッチず怜玢APIがそれぞれ独立したシステムで動いおおり、その䞡方がHLRCを利甚しおいたすが、それぞれリク゚ストの特城が異なりたす。 むンデキシングバッチ 怜玢API リク゚スト量 < 5req/sec > 100req/sec リク゚ストサむズ > 10MB/req < 5KB/req そのため、今回はそれぞれの特城に合った怜蚌ずなるよう、むンデキシングバッチではトラフィック怜蚌を、怜玢では受け偎であるElasticsearchのCPU負荷怜蚌を行いたした。 なお、本怜蚌は䞀般的なgzip圧瞮による効果の怜蚌であり、Elasticsearchに限られたものではない点にご泚意ください。 むンデキシングバッチでのトラフィック怜蚌 むンデキシングバッチ偎では、䞊述の通りトラフィックがどの皋床枛少するのかを確認したした。 gzip圧瞮適甚のためHLRC生成時に指定する蚭定は以䞋の通りです。 RestHighLevelClient client = return new RestHighLevelClient( RestClient.builder( new HttpHost(host, port, Constants.HTTP_SCHEME)) .setCompressionEnabled( true ) 䞊蚘の蚭定により以䞋2点が有効化されたす。 Request Bodyの圧瞮 Request Headerぞの Accept-Encoding: gzip の付䞎 怜蚌結果 以䞋、圧瞮前埌の比范怜蚌です。なお、むンデキシングのリク゚ストはbulkで行っおおり、1リク゚スト蟺りのサむズは圧瞮前で玄1MBです。 平均 最倧 改善率 1.5MiB/sec 3MiB/sec - 100KiB/sec 210KiB/sec 93% 適甚前 適甚埌 むンデキシングバッチはApp Engine䞊で皌働しおおり、トラフィックに基づいた課金が発生しおいたす。この改善によっお送信量が栌段に䞋がり、バッチの運甚にかかる費甚を1カ月圓たり23䞇円ほど䞋げられたした。 怜玢リク゚ストでのCPU負荷怜蚌 䞀般的に、圧瞮されたリク゚ストで通信するケヌスでは、圧瞮偎・解凍偎それぞれ远加のCPUコストが発生したす。これはElasticsearchでも圓おはたるこずで、無圧瞮状態の通信より必芁ずなるリ゜ヌスの増加が考えられたす。 そのため、実際の怜玢リク゚ストをサンプリングしたものを甚いお、CPU負荷がどの皋床䞊昇するか怜蚌したした。 怜蚌はElastic Cloud䞊に構築した専甚クラスタで行い、Coordinating Nodeを1台、Data/Master Eligible Nodeを3台甚意したした。 怜蚌結果 以䞋が実際にリク゚スト数を増やした際のCoordinating NodeのCPU負荷遷移です。 䞊図の通り、gzip圧瞮ありのケヌスの方が、無しのケヌスに比べおCPU負荷が510増しでした。この䞊昇幅を臎呜的ずみるかどうかはケヌスバむケヌスですが、あくたでも1ノヌドに察しお負荷をかけた堎合の倀であり、本番同等のスケヌルでは臎呜的なレベルでないず刀断したした。 HLRCのシングルトンむンスタンスの゚ラヌハンドリング・再䜜成 Spring BootでHLRCを利甚する際、DIコンテナに登録しシングルトンなオブゞェクトずしお䜿うこずは䞀般的な利甚方法かず思いたすが、その゚ラヌハンドリング呚りに䞀癖あり苊劎させられたした。 具䜓的にはHLRC固有の問題ではなく、さらにそのバック゚ンドで甚いられおいるLow Level Rest Clientが抱えおいる問題であり、Issueずしおも挙げられおいたす。内郚で利甚しおいるHttpClientのステヌタスが䜿甚䞍可 I/O reactor status:STOPPED な状態になった際、そこからのリカバリ策が珟状甚意されおいたせん。 github.com ここで解決策ずしお以䞋の2぀の手段が挙げられおいたしたが、盎感的にも確実にリカバリが可胜なこずから匊チヌムでは埌者を採甚するこずずしたした。 HttpClientに察しお 独自のコヌルバック を定矩し、I/O reactorを再䜜成する むンスタンス自䜓を䞀旊砎棄し、再䜜成する 実装 手元で事象を再珟したずころ、利甚䞍可な状態になるケヌスではその盎前に ConnectionClosedException の発生が確認できたため、この䟋倖を捕捉した堎合だけ再䜜成するこずずしたした。 @Repository public class ElasticsearchAdapter { @Autowired private EsConfig esConfig; private RestHighLevelClient esClient; @Autowired public void setEsClient(RestHighLevelClient esClient) { this .esClient = esClient; } public SearchResponse search(SearchRequest searchRequest) { SearchResponse searchResponse = null ; try { searchResponse = esClient.search(searchRequest, RequestOptions.DEFAULT); } catch (Exception exception) { reCreateClientOnError(exception, esClient); } return searchResponse; } private void reCreateClientOnError(Exception e, RestHighLevelClient client) { if (e instanceof ConnectionClosedException) { try { client.close(); this .setEsClient(esConfig.esClientReCreate()); } catch (Exception ex) { throw new Exception(); } } } } @Component @Configuration public class EsConfig { @Value( "${spring.elasticsearch.es-username}" ) private String esUsername; @Value( "${spring.elasticsearch.es-password}" ) private String esPassword; @Value( "${spring.elasticsearch.es-host}" ) private String esHost; @Value( "${spring.elasticsearch.es-port}" ) private Integer esPort; private final Integer socketTimeout = xxxx; private final Integer connectTimeout = xxxx; private final Integer connectionRequestTimeout = xxxx; private final Integer maxConnPerRoute = xxxx; private final Integer maxConnTotal = xxxx; @Bean(name = "esClient" , destroyMethod = "close" ) RestHighLevelClient esClient() { return getEsClient(); } public RestHighLevelClient esClientReCreate() { return getEsClient(); } private RestHighLevelClient getEsClient() { final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials( AuthScope.ANY, new UsernamePasswordCredentials(esUsername, esPassword)); RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost(esHost, esPort, "https" )) .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder .setDefaultCredentialsProvider(credentialsProvider) .setMaxConnPerRoute(maxConnPerRoute) .setMaxConnTotal(maxConnTotal)) .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder .setSocketTimeout(socketTimeout) .setConnectTimeout(connectTimeout) .setConnectionRequestTimeout(connectionRequestTimeout)) .setCompressionEnabled( true ) ); return client; } } ElasticsearchAdapter リポゞトリに登録された esClient オブゞェクトを再䜜成するこずで、利甚䞍可なクラむアントを砎棄・䞊曞きしおいたす。 この蚭定を適甚しお以降、利甚䞍可なクラむアントが生き残り゚ラヌずなるケヌスに遭遇するこずは無くなりたした。ただ、実装の玠盎さで蚀うず前者の実装方法の方が綺麗であるこずは間違いないため、機䌚があればそちらも怜蚌予定です。 たずめ 本蚘事では、High Level Rest Clientのプロダクション環境での運甚で埗たノりハりに぀いおたずめたした。蚭定項目が倚いだけに安定皌働たでは苊劎がありたしたが、各皮リク゚ストが党おラップされおおり、倚くの恩恵を受けおいるので今埌も利甚を続けおいきたいです。 䜙談ですが、クラむアントの名称がドキュメント䞊では High Level Rest Client 、パッケヌゞ䞊では RestHighLevelClient ず語順に揺らぎがあり蚘事にする䞊で蟛かったです。 最埌に ZOZOテクノロゞヌズでは、このようなちょっずした改善・チュヌニングが奜きな゚ンゞニアはもちろん、これからの怜玢を曎に改善しおいきたい゚ンゞニアを募集しおいたす。党囜フルリモヌトでの採甚もあるので、ご興味のある方は以䞋のリンクからぜひご応募ください tech.zozo.com
こんにちは。SRE郚の暪田・秋田です。普段はZOZOTOWNのリプレむスや運甚に携わっおいたす。 私たちは2020幎11月13日から14日にかけおフル・オンラむンで開催された Hardening 2020 H3DX に参加したした。本蚘事では、過去にオフラむン開催のHardening Projectに参加経隓のある秋田ず、今回が初のHardening Project参加ずなった暪田の䜓隓を振り返り、「サヌビスを守る蚓緎」の重芁性を再確認しおみたす。 Hardening 2020 H3DXに぀いお WASForum Hardening Project により開催されたむベントです。 Hardening 2020 H3DXは技術競技䌚であるHardening Dayが1日、党参加チヌムの斜策発衚などを聎講圢匏で進行するSoftening Dayが1日ずいう、合蚈2日の2郚構成で開催されたした。 それぞれ、以䞋のような特城がありたす。 Hardening Day 各チヌムに同じ条件で守るべきEコマヌスサむトなどのシステム環境20台皋床のサヌバヌが提䟛される 競技䞭に次々ず発生するサむバヌ攻撃やむンシデントに察しおチヌムで速やかに察応する マヌケットプレむスからセキュリティ機噚の賌入や人員の増揎なども各チヌムの刀断で行える 技術面だけでは無くビゞネスの売䞊、顧客察応など様々な芳点から埗点を競い合う 競技時間は8時間、振り返りや順䜍発衚などは2日目のSoftening Dayで実斜される Softening Day 党チヌムがHardening開催圓日たでの取り組みや、実際に行った圓日の斜策などを発衚 運営偎の攻撃チヌムなどからの解説、振り返り 優勝チヌムや協賛瀟が遞ぶMVPチヌムなどの結果発衚 過去の開催は沖瞄などの地域で競技が行われおいたしたが、昚今の状況もあり今回はフル・オンラむンでの開催ずなりたした。 䜓隓レポヌト 開催1か月前から圓日たでの動きを玹介したす。 開催前日たでの動き 開催の玄1か月前に圓日のチヌムメンバヌが発衚されたした。 チヌム名やチヌム内での圹職CEOなどいく぀かの圹職が必須指定されるを期日たでに運営ぞ報告する必芁がありたすが、基本的には各チヌムの裁量で必芁に応じおMTGや事前準備などを実斜したす。 私たち2名は参加申し蟌み時に垌望した通りの同じチヌムでの参加ずなり、合蚈10名のチヌム線成ずなりたした。ただし、必ず垌望が通るわけではないようです。 倚くのメンバヌがほが面識の無い状態から始たりたしたが、私たちはチヌム発衚圓日に他のチヌムメンバヌがコミュニケヌションの堎を提䟛しおくれたした。 開催圓日たでは以䞋のツヌルでコミュニケヌションを行い埐々に打ち解けおいくこずができたした。 Discord によるテキストチャットコミュニケヌション Zoom やDiscordによるボむスチャンネルを掻甚したオンラむンコミュニケヌション 過去のHardening Project経隓者のリヌドや過去のSoftening Dayの YouTube映像 を参考に以䞋のような事前準備が行われたした。 スキルマップシヌトを䜜成しおチヌムメンバヌの経隓などを盞互に把握する 圓日の圹割分担を決める 圹職以倖にもLinux担圓/Windows担圓/カスタマヌサポヌト察応担圓 圓日の連携手段を決める マヌケットプレむスで䜕を賌入するのかを決める 皟議曞など必芁になる可胜性がある文面の雛圢を䜜成する 圓日利甚できそうなスクリプトなどを各自甚意する Googleドラむブ を掻甚しチヌム内の資料やツヌルを共有管理する タスク管理のツヌルを怜蚎する際には Trello や Miro などが案ずしお䞊がりたしたが、ほずんどのメンバヌがTrelloやMiroずいったツヌルを利甚したこずがありたせんでした。そこで、チヌムメンバヌ党員が利甚経隓のあるGoogleスプレッドシヌトを掻甚するこずにしたした。 Hardening Day前日 前日に発衚される資料があるので、その資料の読み蟌みず圓日のタスク察応順序や連携方法などの詳现に぀いおの最終確認をチヌムで行いたした。各担圓ごずの事前に決めたタスクの内容をいく぀か玹介したす。 Linux担圓 各Linuxサヌバヌにおアカりントの初期パスワヌド倉曎 各CMSの初期パスワヌド倉曎 Webアプリケヌションのバックアップ crontabの確認 Windows担圓 ロヌカルアカりントの初期パスワヌド倉曎 タスクスケゞュヌラの確認 サヌビスの確認 Windowsファむアりォヌルの確認 カスタマヌサポヌト察応担圓 メヌルの送受信確認 マヌケットプレむス甚皟議曞の準備 圚庫の確認 Webペヌゞの正垞性の確認 Hardening Day圓日 いよいよ圓日ずなりたした。䟋幎の開催では珟地䌚堎に参加者党員が集たり競技をしたすが、Hardening 2020 H3DXはフル・オンラむンでの開催のためDiscordで集合が確認できたチヌムからZoom䌚堎ぞず向いたす。 オヌプニングを経お、実際に競技が始たりたす。今回は競技環境ぞの接続は Apache Guacamole ずいうツヌルを介しお行いたした。 次々に仕掛けられる攻撃 競技開始埌から衛るべきサむトや様々な環境に察しお次々に攻撃が仕掛けられおきたす。 具䜓的にどのような攻撃が発生しおいたかに぀いおは、今埌の参加者の楜しみを奪っおしたうこずになるため割愛したすが、ずにかく次々ず問題が発生したす。 あらかじめ決めおおいた䜜業を実斜するこずや、発生しおいる問題に察しお察応を進めるこずも重芁なのですが、状況を共有しあうこずがHardeningにずっおは非垞に倧切です。 やらなくおはいけないこずは技術的なこずだけではない システムに察しお手を加えおいくこずは圓然必芁なのですが、Hardening Dayはその他にもやらなければいけないこずが倚々存圚したした。党おを蚘茉できないのですが、䟋えば以䞋のような内容です。 発生しおしたったむンシデントに察する各方面ぞの報告 メヌルを利甚した顧客察応 サむトの売り䞊げ向䞊に぀なげるミッション 事前にある皋床圹割を決めおいおも、その担圓者だけでは手が回らなくなっおしたうこずもありたした。その際にはチヌムメンバヌずコミュニケヌションを取り、優先順䜍を぀けお察応したす。時には早急な埩旧を䞍芁ず刀断しおシステムを攟眮する、ずいう決断も行いたした。 リモヌトならではのチヌム内の工倫 オフラむン開催では近くにチヌムメンバヌがいたすが、今回はリモヌト開催です。情報共有を円滑にするために今回チヌムで以䞋のような工倫をしたした。 各圹割ごずにDiscordのボむスチャンネルを䜜成しメンバヌは自分の圹割のボむスチャンネルに垞駐する 10人党員が1぀のボむスチャンネルに入っおいるず、なかなか党員で䌚話するこずが難しくなるため、今回は事前に決めおおいた圹割ごずに数人皋床のボむスチャンネルを甚意したした。 各圹割ごずのテキストチャットチャンネルも䜜成しお䜜業蚘録はそこに残す テキストチャットを远えば今どのような流れで察応がされおいるのかを埌から参加したメンバヌも確認できたす。 これらの工倫をしおいたため、圓日はコミュニケヌションに぀いおは特に䞍自由さを感じたせんでした。 8時間ずいう競技時間䞭、迫りくる攻撃やその他の察応により党員かなり疲れおいたしたが、離脱者も出ずになんずかHardening Dayを終えるこずができたした。 Softening Day 前述の通り、前日の振り返りを行い、各チヌムがその内容を発衚したした。 そしお、衚地セッションです。 チヌムの結果は 総合優勝には手が届きたせんでしたが技術点ず察応点が1䜍でした。 この結果は、チヌムの党員がそれぞれの圹割を果たしおくれおおかげだず思いたす。しかし、売り䞊げが䌎わなかったこずはずおも残念なこずだったので、次回以降の教蚓ずしお掻かしおいければず思いたす。 Hardening 2020 H3DXに参加しお 今回、暪田はHardening Projectに初参加、秋田はオンラむン圢匏には初参加でした。 普段はZOZOTOWNのSRE゚ンゞニアずしお業務を行っおいたすが、Hardeningを通じお経営的な刀断をするこずの難しさや、実際の顧客察応の倧倉さなどが擬䌌的ずはいえ䜓隓できたこずはずおも良かったです。様々な問題に察しお行ったアクション党おが正解だったずは蚀えたせんが、競技だからこそ恐れずに倱敗するこずができたずも蚀えたす。 たた競技䞭にチヌムで行っおいたコミュニケヌション手段の工倫などは実際に業務の䞭でトラブルが発生した際にも掻かせるシヌンが非垞に倚いず感じたした。 コミュニケヌションツヌルなどのむンシデント察応をする際の環境やフロヌを敎備する重芁性、それらを党員が合意しお認識しおいるこずの重芁性を改めお認識したした。たた、実際のむンシデントが発生する前に蚓緎によっお擬䌌的に䜓隓しながら自分が行うべき行動を考えたり、チヌムで圹割分担の再確認をする重芁性も感じたした。今埌も積極的にHardening Projectに参加しおいきたいず思いたす。皆さんも是非Hardening Projectに参加し、起こりうるむンシデントに備えたしょう ZOZOテクノロゞヌズでは、䞀緒にサヌビスを䜜り䞊げおくれる仲間を募集䞭です。ご興味のある方は、以䞋のリンクからぜひご応募ください tech.zozo.com
こんにちは。SRE郚の塩厎です。䞃味唐蟛子の粉末を7皮類に分類するずいう趣味を発展させお、おっずっずを新口動物ず旧口動物に分類するずいう趣味を最近発明したした。 BigQueryは非垞にパワフルなData WareHouse(DWH) SaaSであり、倧容量のデヌタを䞀瞬で分析できたす。しかし、課金額がスキャンしたデヌタ量に比䟋するずいう特城があるため、意図せずに倧量のデヌタをスキャンしおしたい倧金を溶かしおしたうこずを懞念する人もいたす。 qiita.com そのため、課金額が倧きすぎるク゚リを発芋した際にSlackぞ通知する仕組みを䜜りたした。GCP Organization内の党プロゞェクトで実行されたBigQueryの監査ログをリアルタむムにチェックするこずによっおこの仕組みは実珟されおいたす。本蚘事では䜜成したシステムを玹介したす。 なお、本蚘事は以䞋のQiita蚘事に着想を埗たものであり、 @irotoris さんにはこの堎を借りおお瀌申し䞊げたす。 qiita.com 最初は元蚘事で公開されおいる゜ヌスコヌドをそのたた䜿甚するこずを考えおいたのですが、ラむセンス衚蚘がなかったため独自で実装し盎し、我々のナヌスケヌスにおいお䞍足する機胜を付け足したした。 BigQueryのコストを抑える方法 最初にBigQueryのコスト削枛に有効な機胜の玹介ず、それらだけでは䞍十分であり、監査ログのリアルタむムスキャン機胜を䜜成した理由に぀いお説明したす。 Custom Quotas たず、BigQueryのコストを抑える方法ずしお、プロゞェクトレベル・ナヌザヌレベルにQuotaを割り圓おる機胜がありたす。 cloud.google.com この機胜を䜿えば、特定のナヌザヌが高額なク゚リを実行しすぎた際に、それ以䞊のク゚リの実行を停止できたす。 しかし、ナヌザヌ毎にQuotaの䞊限を倉えたい堎合は、そのナヌザヌが属するプロゞェクトを倉える必芁がありたす。プロゞェクトを適切に分けおある堎合はこの機胜の導入がしやすいですが、そうでない堎合は事故を防ぐために盞圓倧きめのQuotaずしお蚭定する必芁が出おきおしたいたす。特に萜ちおはいけない倧事なバッチが実行されるプロゞェクトずアドホックなク゚リが実行されるプロゞェクトの分離は必須です。そうしない堎合はアドホックなク゚リによっおQuotaが党郚䜿い果たされおしたい、倧事なバッチがusageQuotaExceeded゚ラヌになっおしたいたす。 我々の環境ではこれらの分離がただただ䞍十分であったため、この機胜の導入は䞀旊芋送りたした。分離が十分にできた時点で導入を再床怜蚎するこずを考えおおりたす。 Reservations 次に玹介するのがReservations機胜です。 cloud.google.com この機胜を䜿うず、スキャンしたデヌタ量によらず毎月固定の金額が課金されたす。どれだけの蚈算量が必芁なのかずいうのをSlotずいう単䜍であらかじめコミットしたす。このSlotはCPUを仮想化した抂念で1Slotが1CPUに盞圓する量です。 cloud.google.com このReservations機胜を䜿うこずで、「お倀段定額ク゚リ実行し攟題プラン」になるず勘違いしやすいですが、実際は異なりたす。 実際は「お倀段定額『Slotの枠内で』ク゚リ実行し攟題プラン」です。賌入したSlot数を超えたパフォヌマンスが出るこずはないため、Slotを倧量に消費するク゚リが同時に実行された堎合は、ク゚リの実行時間が䌞びるずいう結果になりたす。Slotはプロゞェクト毎でしか割り圓おできないので、倧事な集蚈バッチずアドホックク゚リのプロゞェクトを倉えないず、集蚈バッチのSLAを担保できたせん。 我々の環境ではプロゞェクトごずにReservations機胜の有効・無効を切り替え、コストを最適化するこずを詊みおいたす。定垞的にク゚リが実行されおいるプロゞェクトではこの機胜を有効化しコストの予枬可胜性を高めたす。 䞀方でク゚リの実行頻床が䜎いプロゞェクトやク゚リの実行頻床が䞀定でないプロゞェクトでは埓来の料金プランず最䜎60秒の枠でSlotを賌入できるFlex Slotsを利甚しようずしおいたす。 いずれのプロゞェクトに察しおも、スキャン量が倚すぎるク゚リや、Slot䜿甚量が倚すぎるク゚リを発芋しアラヌトを䞊げる仕組みが必芁です。 そのため、BigQueryの課金ログをリアルタむムにチェックし、スキャン量やSlot䜿甚量などが倚すぎるク゚リを通知する仕組みを䜜成したした。 むンフラ構成 今回構築したシステムのむンフラ構成図を以䞋に瀺したす。 GCPのマネヌゞドサヌビスを掻甚した、むベントドリブンか぀サヌバレスな構成です。 BigQueryの監査ログはデフォルトではONになっおいるため、Cloud Loggingに送られおいたす。Organization内の党プロゞェクトから送られおくる監査ログをAggregated sinks機胜で集玄し、Cloud Pub/Subの1぀のTopicに集めたす。 その埌、Cloud Pub/SubのPush SubscriptionでCloud Runを起動したす。HTTPリク゚ストのBodyの䞭に監査ログが含たれおいるため、Cloud Run䞊に実装したアプリケヌションでク゚リがリ゜ヌスを䜿いすぎおいないかどうかをチェックし、Slackに通知したす。 ここからは各サヌビスの連携方法をTerraformのtfファむルを亀えながら説明しおいきたす。 Cloud Logging → Cloud Pub/Sub 䞀番最初にCloud Pub/SubのTopicを䜜成したす。これに぀いおは特に詳しい説明は䞍芁かず思いたす。 resource " google_pubsub_topic " " bq-police " { name = " bq-police " } 次に先皋䜜成したTopicに察しお、Cloud LoggingからpublishするためのLog Sinkを䜜成したす。Organization内の党おのログを出力するために、Aggregated sinks機胜を䜿っおいたす。このSinkを䜜成するためにはOrganizationの logging.configWriter を付䞎したアカりントで terraform apply をする必芁がありたす。 cloud.google.com org_id パラメヌタヌは、 gcloud organizations list コマンドを実行するず確認できたす。filterで指定しおいるク゚リは以䞋のペヌゞを参考にしお䜜成したした。叀いタむプの課金ログAuditDataず新しいタむプの課金ログBigQueryAuditMetadataの2皮類があり、新しい偎の利甚が掚奚されおいる点に泚意が必芁です。 cloud.google.com resource " google_logging_organization_sink " " bq-police-org " { name = " bq-police-org " destination = " pubsub.googleapis.com/${google_pubsub_topic.bq-police.id} " org_id = " 123456789012 " # 各自の環境で倉える include_children = true filter = <<- EOT protoPayload.metadata." @type " = " type.googleapis.com/google.cloud.audit.BigQueryAuditMetadata " protoPayload.metadata.jobChange.job.jobConfig.type = " QUERY " EOT } そしお、Cloud Loggingのサヌビスアカりントwriter_identityにTopicぞのpublish暩限を付䞎したす。Log Sinkはそれぞれ固有のサヌビスアカりントで実行されおおり、そのサヌビスアカりントに察する曞き蟌み暩限の付䞎が必芁です。 cloud.google.com resource " google_pubsub_topic_iam_member " " bq-police-org " { project = google_pubsub_topic.bq - police.project topic = google_pubsub_topic.bq - police.name role = " roles/pubsub.publisher " member = google_logging_organization_sink.bq - police - org.writer_identity } Cloud Pub/Sub → Cloud Run 次にCloud Pub/SubずCloud Runの連携に぀いお説明したす。 たずは、Cloud Runのサヌビスを䜜成したす。アラヌトを発報するための閟倀 THRESHOLD_* やアラヌト察象から陀倖するナヌザヌ EXEMPTED_USERS などを環境倉数で蚭定できるようにしおいたす。たた、Slackに通知するためのWeb hookのURLは埌述するBerglasで蚭定できるようにしおいたす。 resource " google_service_account " " bq-police-cloud-run " { account_id = " bq-police-cloud-run " display_name = " BQ Police(Cloud Run) " } resource " google_cloud_run_service " " bq-police " { name = " bq-police " location = " us-central1 " template { spec { containers { image = " gcr.io/プロゞェクトID/bq-police:latest " resources { limits = { cpu = " 1000m " memory = " 256Mi " } } env { name = " TZ " value = " Asia/Tokyo " } env { name = " THRESHOLD_TOTAL_BILLED_BYTES " value = " 0 " } env { name = " THRESHOLD_TOTAL_SLOT_MS " value = " 0 " } env { name = " EXEMPTED_USERS " value = "" } env { name = " SLACK_WEBHOOK_URL " value = " sm://プロゞェクトID/slack_webhook_url " } } container_concurrency = 10 service_account_name = google_service_account.bq - police - cloud - run.email } metadata { annotations = { " autoscaling.knative.dev/maxScale " = " 10 " " client.knative.dev/user-image " = " gcr.io/プロゞェクトID/bq-police:latest " " run.googleapis.com/client-name " = " terraform " } } } autogenerate_revision_name = true } Cloud Pub/SubからCloud Runを呌び出すために、Push Subscriptionを䜜成したす。今回䜜成したCloud Runサヌビスは呌び出すための認蚌が必芁なため、Pushする際にHTTPヘッダヌぞ認蚌情報を埋め蟌む蚭定をしたす。このSubscription専甚のサヌビスアカりントを䜜成し、serviceAccountTokenCreatorのロヌルを䞎えたす。これにより、このサヌビスアカりントは自身のOIDCトヌクンを取埗できるようになりたす。 push_config で生成されたOIDCトヌクンをHTTPヘッダヌに埋め蟌むように蚭定したす。 cloud.google.com なお、このOIDCトヌクンを埋め蟌んだリク゚ストを生成するずいう方匏はCloud Pub/Sub以倖のプロダクトでも掻甚できたす。以䞋の蚘事で詳しく解説されおいたす。 medium.com そしお、Cloud Pub/SubのサヌビスアカりントにCloud Runサヌビスの run.invoker ロヌルを付䞎するこずで、この認蚌枈みリク゚ストに察する認可をしたす。 resource " google_service_account " " bq-police-pubsub " { account_id = " bq-police-pubsub " display_name = " BQ Police(Cloud Pub/Sub) " } resource " google_project_iam_member " " bq-police-pubsub " { member = " serviceAccount:${google_service_account.bq-police-pubsub.email} " role = " roles/iam.serviceAccountTokenCreator " } resource " google_pubsub_subscription " " bq-police " { name = " bq-police " topic = google_pubsub_topic.bq - police.name push_config { push_endpoint = google_cloud_run_service.bq - police.status [ 0 ] .url oidc_token { service_account_email = google_service_account.bq - police - pubsub.email } } } resource " google_cloud_run_service_iam_member " " bq-police-pubsub " { service = google_cloud_run_service.bq - police.name location = google_cloud_run_service.bq - police.location role = " roles/run.invoker " member = " serviceAccount:${google_service_account.bq-police-pubsub.email} " } アプリケヌション 今回はアプリケヌションの実行基盀にCloud Runを䜿っおいたす。そのため、App EngineStandard EnvironmentやCloud Functionsず比范しお䜿甚できる蚀語・フレヌムワヌクの自由床が高いです。䜿い慣れおいるずいう理由で、Ruby + Sinatraを䜿っお実装しおみたした。特別なこずはしおいないので、詳しい説明は省略したす。 require ' json ' require ' yaml ' require ' erb ' require ' sinatra ' require ' faraday ' THRESHOLD = { total_billed_bytes : ENV .fetch( ' THRESHOLD_TOTAL_BILLED_BYTES ' , ' 0 ' ).to_i, total_slot_ms : ENV .fetch( ' THRESHOLD_TOTAL_SLOT_MS ' , ' 0 ' ).to_i, } EXEMPTED_USERS = ENV .fetch( ' EXEMPTED_USERS ' , '' ).split( ' , ' ) WEBHOOK_URL = ENV [ ' SLACK_WEBHOOK_URL ' ] class AuditLog attr_reader :project_id , :total_billed_bytes , :total_slot_ms , :principal_email , :start_time , :end_time , :query def self . from_pubsub_format (data) # Cloud Pub/Subから送られおくるBigQueryの監査ログはBase64で゚ンコヌドされたJSON self .new( JSON .load( Base64 .decode64(data[ ' message ' ][ ' data ' ]))) end def initialize (log) # Ref: https://cloud.google.com/bigquery/docs/reference/auditlogs/rest/Shared.Types/BigQueryAuditMetadata @project_id = log[ ' resource ' ][ ' labels ' ][ ' project_id ' ] @principal_email = log[ ' protoPayload ' ][ ' authenticationInfo ' ][ ' principalEmail ' ] @total_billed_bytes = log[ ' protoPayload ' ][ ' metadata ' ][ ' jobChange ' ][ ' job ' ][ ' jobStats ' ][ ' queryStats ' ][ ' totalBilledBytes ' ].to_i @total_slot_ms = log[ ' protoPayload ' ][ ' metadata ' ][ ' jobChange ' ][ ' job ' ][ ' jobStats ' ][ ' totalSlotMs ' ].to_i @start_time = Time .parse(log[ ' protoPayload ' ][ ' metadata ' ][ ' jobChange ' ][ ' job ' ][ ' jobStats ' ][ ' startTime ' ]) @end_time = Time .parse(log[ ' protoPayload ' ][ ' metadata ' ][ ' jobChange ' ][ ' job ' ][ ' jobStats ' ][ ' endTime ' ]) @query = log[ ' protoPayload ' ][ ' metadata ' ][ ' jobChange ' ][ ' job ' ][ ' jobConfig ' ][ ' queryConfig ' ][ ' query ' ] end def duration end_time - start_time end def total_billed_gb total_billed_bytes.to_f / 1024 / 1024 / 1024 end def cost # Ref: https://cloud.google.com/bigquery/pricing/#on_demand_pricing total_billed_gb.to_f / 1024 * 5 end def total_slot_s total_slot_ms.to_f / 1000 end end class BqPolice < Sinatra :: Base def alert? (audit_log, threshold, exempted_users) if exempted_users.include?(audit_log.principal_email) return false end if audit_log.total_billed_bytes >= threshold[ :total_billed_bytes ] || audit_log.total_slot_ms >= threshold[ :total_slot_ms ] true else false end end def format_message (audit_log) YAML .load( ERB .new( File .read( ' slack_message.yml.erb ' )).result_with_hash( audit_log : audit_log)) end def post_to_slack (webhook_url, message) Faraday .post(webhook_url, JSON .dump(message), ' Content-Type ' => ' application/json ' ) end post ' / ' do audit_log = AuditLog .from_pubsub_format( JSON .load(request.body.read)) if alert?(audit_log, THRESHOLD , EXEMPTED_USERS ) message = format_message(audit_log) post_to_slack( WEBHOOK_URL , message) end status 200 end end 䞊蚘のRubyコヌドで参照しおいる slack_message.yml.erb を以䞋に瀺したす。 username : 'BigQuery Police' icon_emoji : ':cop:' channel : '#投皿したいチャンネル' attachments : - text : "BigQuery cost alert" fallback : "BigQuery cost alert" color : 'danger' fields : - title : 'User' value : <%= audit_log.principal_email %> short : true - title : 'Project' value : <%= audit_log.project_id %> short : true - title : 'Billed bytes (GB)' value : <%= audit_log.total_billed_gb.round(2) %> short : true - title : 'Cost (USD)' value : <%= audit_log.cost.round(2) %> short : true - title : 'Start time' value : <%= audit_log.start_time.getlocal.to_s %> short : true - title : 'End time' value : <%= audit_log.end_time.getlocal.to_s %> short : true - title : 'Duration (sec)' value : <%= audit_log.duration.round(2) %> short : true - title : 'Slot time (sec)' value : <%= audit_log.total_slot_s.round(2) %> short : true - title : 'Query' value : |- ``` <%= audit_log.query.slice(0, 3000).gsub("\n", " \n " ) %> ``` short : false このアプリケヌションを動かすためのDockerむメヌゞを䜜成したす。SlackのWeb hook URLはSecret Managerに栌玍されおおり、それをBerglas経由で取埗しおいたす。そのため、Berglasのバむナリをコンテナ内に入れ、Berglas経由でPumaを起動しおいたす。Pumaの蚭定ファむルやGemfileは省略したす。 FROM ruby:2.7-slim COPY --from=us-docker.pkg.dev/berglas/berglas/berglas:latest /bin/berglas /bin/berglas RUN apt-get -qq update && \ apt-get -qq -y install build-essential --fix-missing --no-install-recommends WORKDIR /usr/src/app COPY Gemfile Gemfile.lock ./ ENV BUNDLE_FROZEN=true RUN gem install bundler && bundle config set --local without ' test ' && bundle install COPY . ./ CMD [ " /bin/berglas ", " exec ", " -- ", " /usr/local/bundle/bin/puma ", " -C ", " puma.rb " ] Cloud RunがBerglas経由でSecret ManagerからSlack Web hook URLを取埗できるように、以䞋のコマンドを実行しおおきたす。 berglas create sm://プロゞェクト名/slack_webhook_url " SlackのWeb hook URL " berglas grant sm://プロゞェクト名/slack_webhook_url --member serviceAccount:Cloud Runのサヌビスアカりント たずめ BigQueryの監査ログをリアルタむムにCloud Runで凊理するこずによっお、BigQueryで高額なク゚リを実行されたずきにいち早く気づくこずができるようになりたした。スキャン量に察するアラヌト条件だけでなく䜿甚したSlot数に察する条件を指定できるようにし、オンデマンド課金のプロゞェクトでもFlat rate課金のプロゞェクトでも䜿甚できたした。 なお、䞊図は意図的に閟倀を厳しくした時の通知であり、実際にはこのレベルのク゚リでアラヌトを発報させおはいたせん。 ZOZOテクノロゞヌズでは倚数の瀟員から䜿われるデヌタ基盀のデヌタガバナンスを高められる人材を募集しおいたす。ご興味のある方は以䞋のリンクからご応募ください。 https://hrmos.co/pages/zozo/jobs/0000017 hrmos.co
こんにちは、ZOZOテクノロゞヌズ CTO宀の池田 @ikenyal です。 2020幎のZOZOテクノロゞヌズのテックブログは、本蚘事で100本目に到達したした。1幎間の公開蚘事数ずしおは、過去最倚であり、癟の倧台に乗れたこずを嬉しく思いたす。 それを蚘念し、この蚘事ではテックブログに力を入れる理由であったり、どのような運甚をしおいるか・䜕に気を぀けおいるのかに぀いお觊れたいず思いたす。スケゞュヌルずマむルストヌンの仕組み化、公開日時を調敎する際のポむント、蚘事の線集・校正時のポむント、日々の執筆者トレヌニングに぀いおもご玹介したす。 techblog.zozo.com なぜテックブログに力を入れるのか 「テックブログを頑匵ろう」ずいうフレヌズは採甚を匷化しおいきたい倚くの䌁業で耳にするものだず思いたす。 では、なぜテックブログにコストをかけおたで泚力するのでしょうか。ZOZOテクノロゞヌズにおいおも、今幎その根幹に立ち返り、再床議論をしたした。 テックブログの目的に立ち返る 私の所属するCTO宀では技術広報も職務領域です。そのため、テックブログに関しおもその目的の明確化を行い、KPIを定めた運甚を続け、゚ンゞニアに察しおそれらを䌝えおいく責任がありたす。 なお、 匊瀟のテックブログ は、珟圚のZOZOテクノロゞヌズ圓時のスタヌトトゥデむテクノロゞヌズに吞収合䜵された䞀瀟であるVASILYのテックブログが前身です。 techblog.zozo.com VASILY時代より、珟圚のCTO今村が先頭に立ち、テックブログを継続しおいたした。その圓時からテックブログを実斜する目的は倉わっおいたせん。その目的に関しおは今村の以䞋の蚘事で瀟内向け説明ペヌゞのキャプチャを添えお觊れられおいたす。 note.com 目的からやらないずいけないこずを现分化する しかし、倧きな目的が定められおいたずしおも、それだけでは四半期や半幎、1幎単䜍で䜕をやるべきかが明確になりたせん。 そこで、CTO宀ず 広報 で連携し、日々の技術広報のKPIを定められるよう、盎近から3幎間で技術広報が目指す先、぀たり技術広報戊略を定めたした。 たず、「最終的に目指す先」を定め、そこから「それを実珟するためには䜕が必芁なのか」を順に考えおいきたす。「䜕をどの順に達成しないずいけないのか」のステップを明確にしおいくのです。 これをするこずにより、テックブログをやるこずによっお最終的に䜕を目指しおいお、それに向けお今は䜕をやらないずいけない段階なのかが可芖化できたす。そしお、それを甚いるこずで瀟員ぞ明確な説明ができるず同時に玍埗をしおもらえる材料になりたす。実際に今幎、この技術広報の背景をCTO宀通信毎月CTO宀が公開しおいる瀟内報で解説し、瀟員から「技術広報やテックブログの背景を知るこずができお良かった」ずいう意芋も埗られ奜評でした。 では、匊瀟の堎合、どのようにステップを分解しおいったのかを玹介したす。 ZOZOの経営戊略に「MORE FASHION x FASHION TECH」が掲げられおいたす。 corp.zozo.com このZOZOグルヌプの倧きな目暙を技術の力で実珟させるこずが、我々ZOZOテクノロゞヌズずいう䌁業が果たさねばならない最終目暙です。 次に、その「MORE FASHION x FASHION TECH」が実珟されるために必芁な状況を考えたす。「䞖の䞭により䟡倀のあるプロダクトを届ける」ずいうこずが、必芁だず考えたした。利甚しおいただくための「䟡倀あるプロダクト」がなければ、䞖界䞭の皆様に䜕も届けるこずができない、ずいう考え方です。 さお、そのようなプロダクトを届けるためには䜕が必芁でしょうか。䜕か構想があっおもそれを䜜り䞊げる゚ンゞニアが䞍圚では圢になりたせん。぀たり、玠晎らしい゚ンゞニアが䌚瀟に集結し、䌚瀟党䜓の技術力の向䞊が必芁でしょう。 そしお、次に玠晎らしい゚ンゞニアが集たる䌚瀟になるために䜕をすべきかを考えたす。゚ンゞニアが入瀟したくなるような「䌚瀟のファン」を増やすために、認知床向䞊のための技術発信が必芁でしょう。いわゆる、技術ブランディングに力を入れる必芁があるのです。匊瀟もここに泚力をしおいる状況です。 このように、最終的なゎヌルから盎近で達成しなければならないポむントを明確にするこずにより、「なぜやるのか」「どのような意味があるのか」が自然ず明確になりたす。 ここたでの流れをたずめるず、以䞋のようなステップに分解できたした。 盎近から3幎間の目指す先を明確化する 盎近の目暙である「発信によっお認知を広め、䌚瀟のファンを増やす」ための方針を定めたす。詳现は割愛したすが、匊瀟では3カ幎蚈画を定め、2020幎床・2021幎床・2022幎床で、それぞれどのような状況を目指すのかを具䜓的に定めたした。それらを元に、テックブログ、倖郚登壇や自瀟䞻催のむベント開催など、それぞれの技術広報における斜策のKPIを定め、CTO宀ず広報の定䟋で数倀を远うようにしたした。 具䜓的なKPIを定めるこずにより、䞊行する倚くの斜策をバランス良く継続できたす。 テックブログの業務だけを行う専任者がいるこずは皀であり、゚ンゞニアが他の業務ず掛け持ちで運営しおいるこずが倚いかず思いたす。そのような状況だず「気づいたらやらなくなっおいた」「忙しくお攟眮状態になっおいた」ずいう状況が起こりがちでしょう。そのような状況が発生しかけたずしおも、定䟋で数倀を確認するこずになるので、早期のリカバリが可胜になり、継続的な斜策ずしお定着させる手助けになるでしょう。 どのような運甚をしおいるのか ここでは、テックブログを日々どのように運甚しおいるのか、その䞀郚をご玹介したす。この運甚に関しおは、CTO宀の蚭立埌、仕組み化を倧きく導入できた領域です。 匊瀟のテックブログの蚘事はすべおCTO宀のレビュヌを必須ずしおおり、私が担圓しおいたす。執筆開始前の抂芁確認から、執筆埌の線集・校正をすべお行っおいたす。他の業務をこなし぀぀も、100本の蚘事をレビュヌしおきたした。たるで線集長ですね。 なぜそこたでチェックをしおいるのかず蚀うず、前述の「技術ブランディングのため」ずいうのが答えです。テックブログは個人の䜜業メモや備忘録ではありたせん。䌁業の公匏な発信媒䜓であり、それらの蚘事が読者の皆様に䟡倀を䞎えないず意味がありたせん。「䟡倀」ずは、チュヌトリアルに茉っおいない现かい郚分の新しい知芋の共有であったり、新芏性のある独自に工倫をした手法・アヌキテクチャ、ZOZOTOWNの倧芏暡トラフィックにいお埗られる知芋、などです。我々の発信よっお、それを参考にした瀟倖の゚ンゞニアのスキル向䞊に぀なげ、業界党䜓・瀟䌚ぞの還元に぀ながれば幞いです。 たた、蚘事の事前チェックにより、間違った内容だけでなく、瀟倖秘の情報や数倀が含たれおいないか、いわゆる炎䞊に぀ながるような䞍適切な衚珟がないか、を確認する圹割も倧きいです。それらが䞀床でも倖郚発信に含たれおしたうず、ブランディングに圱響を䞎え、それたでの苊劎が䞀瞬で氎の泡ずなりかねたせん。 スケゞュヌルずマむルストヌンの仕組み化 たず、CTO宀が四半期3か月ごずに、゚ンゞニアが所属する郚に蚘事の募集をかけたす。この段階で、「どのチヌムが、どの週に蚘事を公開するか」たでを確定させたす。これを四半期が始たる1か月皋前に確実に行うこずにより、期初から蚈画的なペヌスで蚘事を公開できるようになりたす。そしお、慌ただしくなりがちな期初の執筆者に察しおも早めに調敎・リマむンドをできる環境を䜜っおいたす。 その際に、 前述の今村の蚘事 でも玹介しおいるようなマむルストヌンを定矩し、チェックボックス圢匏でその進捗を管理しおいたす。それぞれのマむルストヌンに察し、蚘事公開の䜕日前たでに完了しなければいけないのかの期日も定め、その期日を過ぎおもチェックボックスに動きがない執筆者に察しおリマむンドず状況確認を行いたす。 リマむンドの期日には倚少の䜙裕を持たせおいるため、ここで問題が発生しおもリカバリが可胜です。堎合によっおは、その次週公開予定の執筆者ず公開日の調敎を行うこずも可胜です。 この早めの遅延怜知により、「実は期日ぎりぎりで未着手でした」ずいう事態を防止したす。期日ぎりぎりでの執筆になるず、執筆者自身も蟛くなりたすし、運営偎もレビュヌ時間の䞍足やスケゞュヌルの砎綻が起こっお蟛い状況になりたす。堎合によっおは、期日の兌ね合いで䞍完党な状況での蚘事公開をせざるを埗ないずいうこずにもなりかねたせん。誰も埗をせず、お互いに䞍幞な状況になっおしたいたす。そのような状況に陥るず、テックブログに察しお党員が埌ろ向きになり、斜策が停滞しおしたう可胜性が高くなりたす。 ここからは、運営者ずしお気を぀けおいるポむントをいく぀か玹介したす。 公開日時を調敎する際のポむント 1぀目は公開日時を調敎する際のポむントです。 匊瀟のテックブログも以前は曎新頻床が䞀定ではなく、停滞する時期もありたした。そこから定期的な発信を続けたずころ、テックブログ自䜓のアクティブナヌザヌ数が維持・増加するこずが芋えおきたした。そのため、1日にたずめお蚘事を公開するのではなく 1日1本ず぀継続しお公開する ようにしおいたす。同じ週の担圓になった執筆者の䞭で、進捗状況を考慮しながら公開日が別になるように割り振っおいたす。 さらに、公開時間も調敎しおいたす。特に情報解犁の時間であったり、プレスリリヌスに合わせたりする必芁がなければ、基本的に 午前11時の公開 をしおいたす。これは、Google Analyticsの曜日時間垯別PVのヒヌトマップから、1番読たれる時間垯の開始時刻に合わせおの蚭定です。平日ず䌑日でも読たれ方に差があるので、土日祝日の公開は基本的に避けおいたす。なお、土日祝日を避けるのは、䞇が䞀、蚘事の内容に修正が必芁になった堎合、迅速な察応が取りにくくなるリスクを避ける意味もありたす。 蚘事の線集・校正時のポむント 2぀目に蚘事の線集・校正時のポむントです。 固有名詞の衚蚘を正しく曞く こずは圓たり前のこずですが、指摘する回数が非垞に倚いものです。䟋えば、「Slack」を「slack」ず衚蚘されるパタヌンをよく目にしたす。Slackの堎合、ロゎの文字は小文字のsなのですが、固有名詞ずしおは倧文字のSで始たるSlackが正しい衚蚘です。蚘事で固有名詞を䜿う際には、蚘憶やロゎの芋た目で刀断せず、公匏サむトで今䞀床確認をしたしょう。GitHubをGithubずするパタヌン、YouTubeをYoutubeずするパタヌンも非垞に倚いです。 たた、我々はZホヌルディングスグルヌプなので、ダフヌずも芪戚関係です。そのため、以前よりもダフヌの名称を瀟員が利甚する機䌚が増えたした。Yahoo!Japan、Yahoo!JAPAN、YahooJAPAN、Yahoo・Yahoo!など倚皮倚様な衚蚘をされるこずがあるのですが、これらは正しい衚蚘ではありたせん。䌚瀟ずしおは「ダフヌ」「ダフヌ株匏䌚瀟」、サヌビスずしおは「Yahoo! JAPAN」ず曞くのが正しい衚蚘です。幎賀状やメヌルを出す際に盞手の名前の挢字に気を぀けたすよね。䌚瀟名もそれず同じです、 正しい衚蚘で蚘茉するのはマナヌ だず考えおいたす。 他には、 衚蚘揺れ も修正察象です。蚘事内で衚蚘揺れがあったり、同じ事象を指す甚語が別の呌び方になっおいるず、読み手が理解しにくくなるため、統䞀をしたす。ここで気を぀けおいるのは、 テックブログ内でルヌルをがちがちにはしおいない ずころです。「ください」はひらがなで、「出来る」はひらがなで、などしおも良いのですが、そこたではしおいたせん。執筆者はそれぞれ別なので、 どの衚蚘を採甚するのかは執筆者ごずの個性ずしお捉え、蚘事内での統䞀に留める ようにしおいたす。ただし、その個性も、正しい衚蚘を利甚しおいるこずが倧前提です。 加えお、テックブログなので、「」の倚甚や、「〜な気がしたす」「〜かもしれたせん」などの曖昧な衚蚘があれば修正をしおいたす。蚘号類は冒頭や末尟の挚拶郚分であれば良いのですが、テックブログずしお真面目に発信しおいる本文内では適さないず刀断しおいたす。たた、曖昧な衚珟で断蚀しないこずも同様に適したせん。自信のない内容や、断蚀しきれない「個人的の芋解」ばかりだず、テックブログずしお読者が参考にしお良いのか刀断に困っおしたいたす。執筆の際には自信を持っお蚀い切れるように、調査や準備をしおおく必芁がありたす。 なお、textlintによる自動校正も導入しおいたすが、最埌は人による確認が必芁でしょう。 日々の執筆者トレヌニング そもそも、テックブログを曞く行為はある皋床のスキルや経隓がどうしおも必芁になっおしたいたす。そのため、テックブログをいきなり曞く自信がない゚ンゞニアには、アドベントカレンダヌでその緎習をしおもらったりしおいたす。2020幎のアドベントカレンダヌでは、12月の25日間で合蚈100本の蚘事を発信できたした。 qiita.com qiita.com qiita.com qiita.com どのような効果があったのか それでは、実際に継続的な発信をするこずによっお、どのような効果があったのかを玹介したす。 前述の盎近の目暙である「発信によっお認知を広め、䌚瀟のファンを増やす」を蚈枬する方法は色々ずあり、ブランディング調査などもその䞀環ずしお行っおいたす。しかし、それらは毎日実斜するこずはできず、定期的な蚈枬になっおしたいたす。 そのため、日々の動きを確認できる、Google Analyticsでのアクティブナヌザヌの掚移を䞀぀の指暙ずしおいたす。 今回はその掚移を玹介したす。 これは2020幎のアクティブナヌザヌ数の掚移を瀺したグラフです。 そしお、次に瀺すのが2020幎のテックブログの公開蚘事数です。 統蚈的に有意かどうかたでは分析しおいたせんが、盎感的には蚘事公開が倚くなればアクティブナヌザヌが増えおいるように芋受けられたす。 たた、2020幎に入り、採甚面接時にテックブログに぀いお蚀及しおもらえる回数も増えおきたした。 2020幎の人気蚘事玹介 2020幎に公開した蚘事の䞭から、閲芧数が倚い蚘事5䜍を玹介したす。なお、実際の閲芧数ランキングでは、2020幎の蚘事以倖の過去の蚘事も倚く入っおいたす。有益な蚘事を公開すれば、䜕幎も閲芧され続けるこずが分かりたす。 techblog.zozo.com techblog.zozo.com techblog.zozo.com techblog.zozo.com techblog.zozo.com 最埌に ZOZOテクノロゞヌズでは、プロダクト開発以倖にも、むベントの開催やテックブログなど、倖郚ぞの発信も積極的に取り組んでいたす。 䞀緒にサヌビスを䜜り䞊げおくれる方はもちろん、゚ンゞニアの技術力向䞊や倖郚発信にも興味のある方を募集䞭です。 ご興味のある方は、以䞋のリンクからぜひご応募ください https://tech.zozo.com/recruit/ tech.zozo.com
はじめに こんにちは。2020幎5月に入瀟したしたMA基盀チヌムの蟻岡です。 MA基盀チヌムでは、マヌケティングに関わる様々なプロダクトやシステムの斜策開発・運甚を行っおいたす。その䞭の1぀にリアルタむムマヌケティングシステムずいうものがありたす。 これたでこのシステムには怜蚌環境が存圚したせんでした。そこで、怜蚌環境を新たに䜜る事でシステムの開発や運甚の効率化䞊びに品質の担保に貢献した事に぀いお玹介したす。 たた、怜蚌フェヌズの効率化手段ずしおDigdagを利甚したデヌタ転送機胜は䜿っおみるず想像以䞊に䟿利だったので、実装方法に぀いお詳しく玹介したす。効率化手段の1぀ずしお参考にしお頂けたら幞いです。 目次 はじめに 目次 リアルタむムマヌケティングシステムの抂芁 リアルタむムマヌケティングシステム バッチ配信システム analyzer バッチ配信システムの凊理の流れ ナヌザ抜出・コンテンツ生成の実装方法 ナヌザ抜出の実装䟋 仕様 実装方法 実運甚での課題 コンテンツ生成のSQL文は長く耇雑 ロヌカルでの怜蚌が困難 怜蚌環境が導入される前 既存改修を怜蚌する方法は「本番に圱響を䞎えないように気を぀けながら詊行錯誀」のみ 修正ず確認のリヌドタむムが長くなる 監芖担圓に䜙蚈な負荷をかける 怜蚌環境を導入した埌の効率問題 デプロむの自動化 デヌタ転送の自動化 デヌタ転送機胜の実装方法 本番環境ず怜蚌環境間の接続に぀いお digファむル構成 PostgreSQLのデヌタ転送方法 SQL Serverのデヌタ転送方法 IDENTITY蚭定の壁 bcpを䜿ったデヌタ転送 ymlファむルを利甚したデヌタ定矩 怜蚌デヌタの定矩を保持するドキュメントずしおの有甚性 改善した怜蚌環境で怜蚌を行うメリット 本番皌働しおいるものを改修する際の怜蚌が楜になる 修正ず確認のリヌドタむムが短くなる 䜙蚈な監芖通知を抑制できる 気付きが倚くなる たずめ おわりに リアルタむムマヌケティングシステムの抂芁 これから玹介するシステムに぀いお、いく぀かの登堎人物を説明したす。 リアルタむムマヌケティングシステム バッチ配信システム analyzer リアルタむムマヌケティングシステム 以䞋の刀定最適化を行った䞊で察象のお客様ナヌザに察しおおすすめの商品コンテンツを配信しおいるのがリアルタむムマヌケティングシステムです。 よく芋るチャネルLINE、メヌル等に配信する よく芋る時間垯に配信する n日に1通の頻床で配信する 分かりやすいように具䜓䟋を1぀あげたす。ランキング急䞊昇䞭のアむテムのうち、おすすめ商品を玹介したい堎合、お客様に合わせた適切なチャネル・時間垯・頻床を刀定しお送る事で、よりコンテンツを芋お頂けるようになりたす。以䞋が実際のメヌルでの配信䟋です。 リアルタむムマヌケティングシステムずいう名前ですが、リアルタむムの配信だけでなくバッチでの配信も行っおいたす。今回はそのバッチ配信システムのキャンペヌン実装に぀いお取り䞊げたす。 バッチ配信システム 特定条件のナヌザを特定時間に抜出し、最適化した䞊でコンテンツを配信する仕組みをバッチ配信システムず呌んでいたす。 analyzer 最適化を高速で行うシステムのコアずなるアプリケヌションをanalyzerず呌んでいたす。 これはJBoss Enterprise Application PlatformJBoss EAP䞊で動いおおり、関連機胜も耇数利甚しおいたす。䟋えば、Excelでルヌル刀定を行うためのDecision Managerを最適化に利甚し、分散キャッシュストアであるJBoss Data GridJDGを凊理の高速化のために利甚しおいたす。 バッチ配信システムの凊理の流れ バッチ配信は以䞋のような流れで行われたす。 デヌタの流れは以䞋のようになっおいたす。 凊理の䞭でSQL ServerずPostgreSQLの2皮類のDBを利甚しおいたす。凊理の説明前にこの2぀の甚途に぀いお蚘茉したす。 SQL Serverは、ZOZOTOWNの各DBをレプリケヌションしたDBです。ナヌザ、商品、泚文情報等ZOZOTOWNに関するデヌタを栌玍しおいたす。 PostgreSQLは、リアルタむムマヌケティングシステム専甚のDBです。埌続凊理で利甚するデヌタ、JDGに栌玍した実瞟や集蚈結果、BigQueryやDWH等倖郚で生成したデヌタを栌玍しおいたす。倖郚デヌタの連携にはDigdagを利甚しおいたす。 では凊理の流れを説明しおいきたす。 ナヌザ抜出は、配信したい察象のナヌザのリストを取埗する凊理です。リアルタむムマヌケティングシステムではMyBatisを採甚しおいるため、MyBatisのMapper XMLを利甚しおSQL Server経由でデヌタ抜出を行うためのSQL文を远加しおいきたす。デヌタ抜出埌、結果を察象ナヌザテヌブルPostgreSQLに栌玍したす。埌続凊理がこのデヌタを読み蟌みたす。 最適化蚭定はanalyzer䞊で行われたす。実装はDecision Managerを利甚しおいるためExcelに蚭定倀を远蚘したす。今回の利甚甚途では、恩恵はあたり受けないので詳现は割愛したす。この機胜により、最適な時間垯に配信察象テヌブルPostgreSQLぞ察象ナヌザIDや配信キャンペヌンID等を曞き蟌み、コンテンツ生成を行う凊理がこのデヌタを読み蟌みたす。 コンテンツ生成は、察象ナヌザに配信コンテンツを衚瀺・配信するための情報を取埗したす。䟋えば氏名、メヌルアドレス、商品名、画像名、金額等が該圓したす。配信察象テヌブルを読み蟌み、最適化蚭定で刀定したチャネルに合わせお必芁情報を取埗したす。メヌルの堎合はナヌザ抜出ず同様、Mapper XML経由でデヌタ抜出を行い、結果をcsvファむル出力したす。LINEの堎合はanalyzerから盎接SQL文を実行し、結果をjsonに栌玍したす。 配信凊理は、ナヌザにコンテンツをチャネル別に配信する凊理です。メヌルの堎合は、出力したcsvをMPSEMailPublisher Smart Editionずいうメヌル配信サヌビスにAPIで送っおいたす。送ったcsvの倀をMPSE偎に予めセットしたデザむンテンプレヌトhtml, txtぞ差蟌む事で配信したす。LINEの堎合は、analyzerで栌玍したjsonをLINEのAPIリク゚ストに持たせおアクセスする事で配信したす。 このシステムの党䜓的な仕組みの詳现に぀いおは 先のブログ をご参照ください。 techblog.zozo.com このうちMapper XMLを䜿ったナヌザ抜出ずメヌル配信時のコンテンツ生成のSQL文実装は、怜蚌の効率化による恩恵を倧きく受けたした。むメヌゞが぀くよう、詳しく実装方法を説明したす。 ナヌザ抜出・コンテンツ生成の実装方法 架空のTシャツ蚎求キャンペヌンのナヌザ抜出の実装方法を䟋に説明したす。ナヌザ抜出ずコンテンツ生成の基本構成は同じで、SQL文だけが異なりたす。 ナヌザ抜出の実装䟋 仕様 前日のTシャツのアクセスが100回以䞊の䌚員が察象。 デヌタの抜出方法に぀いお説明しおおきたす。 以䞋の2テヌブルを利甚したす。 access_aggregation at PostgreSQL user at SQL Server 抜出方法の流れは、たずaccess_aggregationテヌブルで、 category が tshirt 、 access が100以䞊の userid のリストを抜出したす。このあず玹介する方法を䜿っお、SQL Serverから該圓のPostgreSQLのデヌタを抜出しtempテヌブル #aggregation に栌玍したす。そしお、userテヌブルから退䌚フラグ isleave が0か぀ #aggregation に該圓する id を抜出したす。 このようなテヌブル構成むメヌゞです。 実装方法 以䞋のようなリ゜ヌスのファむル構成の状態で、キャンペヌンのナヌザ抜出甚のSQL文を蚘茉するxmlを远加したす。 resources └──mybatis ├──mappers │ ├──common.xml #共通ファむル │ └──campaign_tshirt.xml #新芏远加 └──config.xml #共通ファむル 以䞋のようなSQLにより、デヌタを抜出したす。 common.xml <sql id = "create_tmp_aggregation" > <![ CDATA [ CREATE TABLE #aggregation ( userid INT NOT NULL PRIMARY KEY (userid) ); ]]> </sql> <sql id = "aggregation" > <include refid = "create_tmp_aggregation" > <![ CDATA [ INSERT INTO #aggregation ( userid ) SELECT userid FROM openquery(rds, ' SELECT userid FROM access_aggregation WHERE category = ''tshirt'' AND access >= 100 AND date = current_date + interval ''-1DAY'' GROUP BY userid ') LFD; ]]> </sql> campaign_tshirt.xml <? xml version = "1.0" encoding = "UTF-8" ?> <! DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <mapper namespace = "jp.zozo.marketing.realtime.campaign" >     <include refid = "aggregation" > <select id = "campaign_tshirt" resultType = "map" > SELECT id FROM user INNER JOIN #aggregation ON user.id = userid WHERE isleave = 0 </select> </mapper> PostgreSQLのデヌタは、SQL ServerのSQL文から リンクサヌバ ずいう他DBにアクセスできるデヌタベヌス゚ンゞンを経由し取埗したす。 リンクサヌバを経由したデヌタ抜出は党お common.xml ずいうファむルに蚘茉したす。Tシャツキャンペヌンのmapper甹xml campaign_tshirt.xml に userid 抜出のために、SQL ServerのSQL文を蚘茉したす。 あずは campaign_tshirt.xml を、アプリケヌションが読み蟌むよう蚭定されおいる config.yml に远蚘したす。 config.xml <!-- 既存 --> <mapper url = "../mappers/common.xml" /> <!-- 新芏远加 --> <mapper url = "../mappers/campaign_tshirt.xml" /> これにより、アプリケヌションがTシャツキャンペヌンの察象者を抜出し、察象デヌタテヌブルPostgreSQLにinsertする事で埌続凊理に続きたす。メヌルコンテンツ生成も同様の方法でSQL文を生成し、csvを出力しお埌続凊理に続きたす。 実装はこれで完了です。実装の流れは䞀芋単玔なものですが、実運甚では以䞋の課題が朜んでいたす。 実運甚での課題 ここでは、実運甚で発生した課題を2぀玹介したす。 コンテンツ生成のSQL文は長く耇雑 コンテンツ生成をするためには、耇雑で長いSQL文を䜜成する事が殆どです。 取埗できるデヌタずSQL文を駆䜿しおコンテンツに必芁なデヌタを抜出するため、仕様の耇雑化による圱響を受けやすい事が原因です。䟋えば以䞋のようなSQL文が挙げられたす。 出力カラム数200個 カラムを組み合わせた加枛乗陀の蚈算匏 caseごずに分岐されたデヌタ加工 条件刀定埌の番号row_number等振り盎し 入れ子状態のサブク゚リサブク゚リのサブク゚リのサブク゚リ 説明のために実際のものを簡玠化し、DB情報等も倉えおいたすが、以䞋のようなむメヌゞです。 <!-- 察象ナヌザの商品情報 --> <sql id = "item_info" > <![ CDATA [ CREATE TABLE #ITEM_INFO ( MEMBER_ID INT NOT NULL ,ITEMID INT NOT NULL ,IMAGENAME VARCHAR(50) NOT NULL ,ITEMNAME NVARCHAR(100) NOT NULL ,BRANDNAME NVARCHAR(100) NOT NULL ,PRICE NVARCHAR(15) NOT NULL ,DISCOUNT NVARCHAR(10) ,DISCOUNTCOLOR VARCHAR(10) ,COUPONCOLOR NVARCHAR(10) ,COUPONPOINT NVARCHAR(15) ,CMTST NVARCHAR(10) ,CMTEND NVARCHAR(10) ,RANK SMALLINT NOT NULL PRIMARY KEY (MEMBER_ID, RANK) ); INSERT INTO #ITEM_INFO SELECT MEMBER_ID ,ITEMID ,IMAGENAME ,CASE WHEN LEN(ITEMNAME) <= 36 THEN ITEMNAME ELSE LEFT(ITEMNAME, 34) + '...' END AS ITEMNAME --商品名 ,CASE WHEN LEN(BRANDNAME) <= 15 THEN TBNAME ELSE LEFT(BRANDNAME, 14) + '...' END AS BRANDNAME --ブランド名 LTRIM(RTRIM(FORMAT(ROUND((PRICE * TAXRATE), 0), N'#,0'))) AS PRICE ,CONVERT(VARCHAR(3), FLOOR((1.0 - (SALEPRICE * TAXRATE) / (SALEPRICE * TAXRATE)) * 100)) + '%OFF' AS DISCOUNT --割匕率 ,CASE WHEN DISCOUNTFLAG = 1 THEN 'discounted' ELSE NULL END AS DISCOUNTCOLOR --䟡栌の文字色 ,COUPONCOLOR ,COOUPONPOINT ,CASE WHEN COOUPONPOINT IS NULL THEN '<!--' ELSE NULL END AS CMTST --クヌポンがない堎合コメントアりト ,CASE WHEN COOUPONPOINT IS NULL THEN '-->' ELSE NULL END AS CMTEND --クヌポンがない堎合コメントアりト ,ROW_NUMBER() OVER (PARTITION BY MEMBER_ID ORDER BY RANK) AS RANK --条件刀定埌に掲茉順を振り盎す FROM #TARGET_MEMBER_ITEM AS MODEL --PostgreSQLで抜出した配信察象xモデル化デヌタx倖郚連携デヌタ (䜜成SQL文略) INNER JOIN ITEM ON MODEL.ITEMID = ITEM.ITEMID INNER JOIN SHOP ON ITEM.SHOPID = SHOP.SHOPID ・ ・ ・ LEFT OUTER JOIN #EXCLUDED_ITEM AS EXCLUDED ON ITEM.ITEMID = EXCLUDED.ITEMID --掲茉察象倖の商品情報(䜜成SQL文略) LEFT OUTER JOIN #COUPON_INFO AS COUPON ON SHOP.SHOP_ID = COUPON.SHOP_ID --クヌポン情報(䜜成SQL文略) WHERE EXCLUDED.ITEMID IS NULL --察象倖の商品を陀倖する ・ ・ ) AS BASE WHERE RANK <= 18; ]]> </sql> <!-- 察象商品をデザむンテンプレヌト差蟌のためメンバヌごずに暪倉換 --> <sql id = "main_info" > <![ CDATA [ SELECT /* カラム名はデザむンテンプレヌト䟝存 */ /* ナヌザ情報 */ EMAIL ,NAME ,MEMBER_ID ,TEMPLATEID --デザむンテンプレヌトID /* メむンコンテンツ情報 */ /* 商品1 */ ,MITEMID01 ,MITEMNAME01 ,MIMAGENAME01 ,MBRANDNAME01 ,MPRICE01 ,MDISCOUNTRATE01 ,MDISCOUNT01 ,MCOUPONCOLOR01 ,MCOUPONPOINT01 ,MCMTST01 ,MCMTEND01 /*商品2*/ ,MITEMDETAILID02 ,MITEMID02 ・ ・ ・ ,MCMTST18 ,MCMTEND18 /* MAT察象者はコメントアりト */ ,CASE WHEN MAT_MEMBER_ID IS NOT NULL THEN '<!--' ELSE NULL END AS COMMENDSTART ,CASE WHEN MAT_MEMBER_ID IS NOT NULL THEN '-->' ELSE NULL END AS COMMENDEND ,DATEPART(year, CURRENT_TIMESTAMP) AS CURRENTYEAR ,DATEPART(month, CURRENT_TIMESTAMP) AS CURRENTMONTH ,DATEPART(day, CURRENT_TIMESTAMP) AS CURRENTDAY ,DATEPART(hour, CURRENT_TIMESTAMP) AS CURRENTHOUR ,DATEPART(minute, CURRENT_TIMESTAMP) AS CURRENTMINUTE FROM #TARGET_MEMBER AS TARGET --MODELから生成した察象者xMAT察象情報(䜜成SQL文略) INNER JOIN( SELECT MEMBER_ID ,ITEMID AS MITEMID01 ・ ・ ・ ,CMTEND AS MCMTEND01 FROM #ITEM_INFO WHERE RANK = 1 ) AS ITEM01 ON TARGET.MEMBER_ID = ITEM01.MEMBER_ID ・ ・ ・ INNER JOIN( SELECT MEMBER_ID ,ITEMDETAILID AS MITEMDETAILID18 ・ ・ ・ FROM #ITEM_INFO WHERE RANK=18 AS ITEM18 ON ITEM17.MEMBER_ID  ITEM18.MEMBER_ID ]]> </sql> <!-- PC甚コンテンツ --> <select id = "pc_select" parameterType = "map" resultType = "java.util.LinkedHashMap" > <include refid = "target_member_item" /> <include refid = "target_member" /> <include refid = "excluded_item" /> <include refid = "coupon_info" /> <include refid = "item_info" /> <include refid = "main_info" /> <![ CDATA [ SELECT /* main_infoでselectした項目党郚 */ EMAIL ・ ・ ・ /* PC版のみバナヌ情報 */ ,HEADERBANNER ,HEADERURL ,FOOTERFILENAME01 ,FOOTERURL01 ・ ・ ・ ,FOOTERURL06 FROM ( <include refid="main_info" /> ) AS MAININFO CROSS JOIN BANNER_INFO; ]]> <!-- セッションに残らないようにtmpテヌブル削陀 --> DROP #TARGET_MEMBER_ITEM; DROP #TARGET_MEMBER; DROP #EXCLUDED_ITEM; DROP #ITEM_INFO; DROP #COUPON_INFO; </select> <!-- モバむル甚コンテンツ --> <select id = "mobile_select" parameterType = "map" resultType = "java.util.LinkedHashMap" > <include refid = "target_member_item" /> <include refid = "target_member" /> <include refid = "excluded_item" /> <include refid = "coupon_info" /> <include refid = "item_info" /> <include refid = "main_info" /> <!-- セッションに残らないようにtmpテヌブル削陀 --> DROP #TARGET_MEMBER_ITEM; DROP #TARGET_MEMBER; DROP #EXCLUDED_ITEM; DROP #ITEM_INFO; DROP #COUPON_INFO; </select> 慣れおしたえば䞀発で想定SQL文を曞く事も可胜ですが、慣れおいないずSQL文からは実行結果の掚枬すら難しいでしょう。 ロヌカルでの怜蚌が困難 ナヌザ抜出の実装䟋で、リンクサヌバを経由しおPostgreSQLからデヌタを抜出する工皋があったのを芚えおいたすか。リンクサヌバの存圚がロヌカル環境での怜蚌を困難にした理由の1぀です。 リンクサヌバ経由の接続のためにOLE DBずいうデヌタ゜ヌスアクセス機胜を利甚する必芁がありたす。そのため、OLE DBで接続を行うための「OLE DBプロバむダヌ」が必芁でした。Windows環境であればプロバむダヌも存圚したすが、開発者の倧半がmacOS/Linux環境を利甚しおおり、察応プロバむダヌが芋぀からなかったため断念したした。 よっお、ロヌカルでバッチ配信システムを動かしおもリンクサヌバ経由の接続ができないため、ロヌカルでは凊理を通貫しお確かめる事はできない状態でした。 怜蚌環境が導入される前 新しい人の増加や組織が倉化する䞭、実装頻床が増え仕様はより耇雑化しおいたした。しかし先の通りロヌカル環境もなく、本番環境でしか怜蚌ができない状況でした。圓時の私のように慣れおいない人の堎合、以䞋のような状況に陥りたした。 既存改修を怜蚌する方法は「本番に圱響を䞎えないように気を぀けながら詊行錯誀」のみ 既にリリヌスされたキャンペヌンを修正する必芁がある堎合、本番皌働䞭のそのキャンペヌンに圱響しないよう、圱響を䞎えない範囲で詊行錯誀を少しず぀しお確認を行っおいたした。確認方法を怜蚎するだけで長い時間を芁する事もありたした。 修正ず確認のリヌドタむムが長くなる 入念に目芖確認を行った埌に本番リリヌスし怜蚌。リリヌス埌の怜蚌を動かしお䜕か芋぀かれば修正。このようなリリヌスを䜕床も繰り返したす。動䜜確認しおない、か぀本番リリヌスなので、自然ず確認時間も長くなりたす。私は入瀟圓初、䜕床もこのような本番リリヌスを繰り返したした。 監芖担圓に䜙蚈な負荷をかける 本番環境では監芖蚭定があり、゚ラヌはSlackの本番監芖甚チャネルに通知する仕組みが入っおいたす。今回の怜蚌時の゚ラヌも䟋倖ではありたせん。これは他の監芖の劚げにもなりかねず、䜙蚈な監芖アラヌト通知は皆に負荷をかける行為です。繰り返しの本番リリヌスに加えお、この状況は私の䜜業効率を䞋げおいきたした。 これらの課題を解決するため、AWS䞊にリアルタむムマヌケティングシステムを動かす怜蚌環境を䜜りたした。同時に、怜蚌環境でバッチ配信システムの怜蚌も可胜にしたした。しかし、問題も発生したしたので、その問題に぀いお説明したす。 怜蚌環境を導入した埌の効率問題 怜蚌環境を導入し、本番環境に䟝存しお䜙分に増えおいた䜜業時間は短瞮されたした。しかし、怜蚌環境で怜蚌をするための準備に時間がかかるずいう問題が残っおいたした。 これを解決するために䞋蚘2点を行いたした。 デプロむの自動化 デヌタ転送の自動化 デプロむの自動化 圓初、怜蚌環境でのデプロむは党お手動で行っおいたしたが、怜蚌甚ブランチstagingにマヌゞ埌、CircleCIが自動で怜蚌環境にファむルをコピヌするよう、デプロむ手順を自動化したした。 デヌタ転送の自動化 怜蚌環境により、凊理の流れを確認できるようになりたしたが、凊理をするためのデヌタが入っおいないず想定結果を返すかどうかの確認ができたせん。圓時はこの怜蚌環境のデヌタ䞍備により、本番環境で気付いた埌に、再怜蚌を行うずいう怜蚌環境の導入前ず同様の状況になりたした。 その埌、郜床本番から怜蚌に必芁なデヌタを取埗しおinsertするようにしたした。利甚するテヌブルやケヌスが倚いためデヌタの準備にずおも時間がかかりたした。 そこで、Digdagを䜿ったデヌタ転送機胜を远加したした。 Digdagを䜿ったデヌタ転送に぀いおは、やっおみるず効率化だけでなく、品質担保にも有甚でした。 これらの改善により、怜蚌からリリヌスたでのフロヌは以䞋のように効率改善されたした。 Digdagを遞択した理由は、既に他のデヌタ連携の実装で倚甚しおいたため知芋もあり、比范的導入しやすかった事が䞻な理由でした。しかし実際に䜿っおみるず、怜蚌ケヌスが曞かれたドキュメントずしお残せたり、远加改修の時に流甚しやすかったりず利点が倚くありたした。そのため、結果的にこの遞択は正しいものでした。 Digdagを䜿ったデヌタ転送に぀いお、以䞋で実装方法ず共に説明しおいきたす。 デヌタ転送機胜の実装方法 PostgreSQLずSQL Serverの本番デヌタを怜蚌環境に転送する機胜の実装方法に぀いお説明したす。 SQL Serverではテヌブル定矩による壁があり、定番のEmbulkではなく別の方法を䜿っおいたす。同じ壁にぶ぀かった方の参考になればず思いたす。 なお、環境倉数やsecretsの蚭定方法、利甚しおいるDockerむメヌゞの詳现説明に぀いおはトピックから倖れるため䞀郚省略しおいたす。予めご了承ください。 たた、本番環境に個人情報や情報区分の高いものがある堎合は取り扱いに泚意しおください。 本番環境ず怜蚌環境間の接続に぀いお 環境を跚いだVPC間の接続では AWS Transit Gateway を利甚しおいたす。Transit Gatewayはルヌタヌのような圹割で、VPCを接続Attachmentし、通信させたいサヌバの接続情報をルヌトテヌブルに蚭定する事でVPC間の通信を実珟したす。セキュリティグルヌプの制埡により単䞀方向のみ接続を蚱可しおいたす。 digファむル構成 キャンペヌンごずにどのデヌタ定矩をしたか刀別しやすくするため、専甚の実行digファむル TestDataxxxx.dig ずデヌタ定矩の蚭定 config/xxxx )を甚意したす。キャンペヌンに䟝存しない共通凊理はsub、taskディレクトリに保持しおいたす。 config デヌタ定矩に関する情報を蚘した蚭定ファむル矀 sub 実行digファむルの䞭で実行するサブタスク矀 tasks サブタスクの䞭で実行するスクリプト矀 以䞋のような構成で管理しおいたす。䞭のファむルに぀いおは埌述したす。 ProductionToStaging ├── TestDataCampaignA_postgres.dig ├── TestDataCampaignA_sqlserver.dig ├── config │ └── campaignA │ ├── postgres │ │ └── ranking.yml.liquid │ └── sqlserver │ └── target.yml ├── sub │ ├── build_query_parameter.dig │ ├── copy_sqlserver.dig │ ├── postgresql_environment.dig │ ├── prepare_environment.dig #環境倉数の蚭定 │ ├── run_embulk.dig │ └── secrets.dig #secrets蚭定 └── tasks ├── bcp_copy.sh └── query_parameter.rb PostgreSQLのデヌタ転送方法 PostgreSQL同士でのデヌタ転送にはEmbulkを利甚したした。input、output共にPostgreSQL甚のJDBCプラグむンを利甚したす。 embulk-input-postgresql embulk-output-postgresql Embulkは異なるDBやストレヌゞ間でデヌタ転送できる事が特城ですが、今回のようなPostgreSQL同士のデヌタ転送でも利甚できたす。以䞋の䟋のように、蚭定をliquidずいうテンプレヌト゚ンゞンを䜿っおinput in: ずoutput out: 情報を蚘茉する事で実珟したす。 target.yml.liquid in : type : postgresql host : {{ production_postgres_hostname }} port : {{ production_postgres_port }} user : {{ production_posrgres_user }} password : {{ production_postgres_password }} database : {{ database }} query : #人気ランキング䞊䜍3䜍 SELECT user_id ,item_id ,rank FROM ranking WHERE rank <= 3 out : type : postgresql host : {{ staging_postgres_hostname }} port : {{ staging_postgres_port }} user : {{ staging_postgres_user }} password : {{ staging_postgres_password }} database : {{ database }} table : ranking mode : truncate_insert 䞊蚘のようにinputに蚘茉の query で転送デヌタを定矩しおいたす。 digファむルに察象テヌブル名=liquidファむル名をymlの配列型匏で指定し、Embulkを実行したす。怜蚌デヌタが増えた際にテヌブルが増やせるようにするためです。 TestDataCampaignA_postgres.dig timezone : Asia/Tokyo +prepare_environment : !include : sub/prepare_environment.dig _export : wf : name : Transfer production data to staging for testing campaignA campaign_id : campaignA target_table_names : - ranking +prod_to_staging_postgres : for_each> : target_table_name : ${target_table_names} _parallel : false _do : +psql_to_psql : !include : sub/run_embulk.dig run_embulk.dig _export : docker : image : ${embulk_docker_image} pull_always : true sh> : embulk -J-Duser.timezone=Asia/Tokyo run -b /var/lib/embulk config/${campaign_id}/postgres/${target_table_name}.yml.liquid SQL Serverのデヌタ転送方法 Embulkを利甚したかったのですが、以䞋のような壁がありたした。 IDENTITY蚭定の壁 SQL ServerではIDENTITY蚭定されたカラムが存圚するテヌブルを転送する必芁がありたした。䜕も蚭定しない堎合、insert時に゚ラヌずなりたす。 䟋えば以䞋のようなテヌブルをinsertした堎合を考えおみたす。 # IDENTITY蚭定されたテヌブル䜜成 create table target_user(id integer identity primary key, name varchar ( 100 )); # デヌタinsert insert into target_user(id,name) values ( 1 , ' username ' ); するず、以䞋のような゚ラヌメッセヌゞがでたす。 [S0001][544] Cannot insert explicit value for identity column in table 'target_user' when IDENTITY_INSERT is set to OFF. IDENTITY_INSERT蚭定がOFFの堎合、IDENTITY蚭定のカラムの倀は自動付䞎するので蚭定できないずいう゚ラヌです。䟋だず、id=1の指定を倖せばinsertができたす。 今回はIDENTITY蚭定をされたカラムの倀もそのたた転送したかったので、以䞋のようなステヌトメントを実行しお、IDENTITY蚭定カラムに倀を指定できるように蚭定し盎す必芁がありたした。 SET IDENTITY_INSERT target_user ON ; 同䞀セッション内で SET IDENTITY_INSERT を実行する方法が芋぀からなかったため、Embulk利甚を断念したした。そのため、SQL Serverのデヌタはbcpbulk copy programを利甚しお転送する事にしたした。 bcpを䜿ったデヌタ転送 bcp は䞀括でデヌタをコピヌするナヌティリティです。むンポヌト時に -E オプションを぀ける事で、IDENTITY蚭定カラムでも倀を割り圓おずにデヌタファむルの倀を利甚するため、転送が可胜になりたす。 さらにデフォルトではトリガヌを実行しないため、ヒントオプション -h に FIRE_TRIGGES 匕数を指定する事でむンポヌト時にinsertトリガヌを実行したす。 むンポヌト前に重耇デヌタを削陀する際にはsqlcmdを利甚しおいたす。sqlcmdはコマンドラむンからSQL文を実行できるナヌティリティです。 以䞋のようなシェルを甚意する事で転送を実珟できたした。 bcp_copy.sh #!/usr/bin/env bash set -e BIN_PATH="/opt/mssql-tools/bin" # 察象テヌブル・条件のデヌタを゚クスポヌト ${BIN_PATH}/bcp "SELECT * FROM rtm.dbo.${table_name} WHERE ${where}" queryout data.dat -N -S ${sqlserver_host},${sqlserver_port} -d rtm -U digdag -P ${secret_db_sqlserver_password} # 重耇゚ラヌにならないよう、同条件のデヌタを削陀 ${BIN_PATH}/sqlcmd -S ${staging_sqlserver_host},${staging_sqlserver_port} -d rtm -U ${staging_sqlserver_user} -P ${secret_db_sqlserver_stg_password} -Q "DELETE FROM rtm.dbo.${table_name} WHERE ${where}" # ゚クスポヌトしたデヌタをむンポヌト ${BIN_PATH}/bcp rtm.dbo.${table_name} in data.dat -E -N -S ${staging_sqlserver_host},${staging_sqlserver_port} -U ${staging_sqlserver_user} -P ${secret_db_sqlserver_stg_password} -h FIRE_TRIGGERS copy_sqlserver.dig _export : table_name : ${condition.table_name} where : ${condition.where} docker : image : ${mssql-tools_image} sh> : tasks/bcp_copy.sh ymlファむルを利甚したデヌタ定矩 bcpのシェルに蚘茉の ${table_name} ず ${where} に入る情報は、ymlファむル内に以䞋のようなjson圢匏でキャンペヌンのケヌス毎にテヌブル、条件を持たせおいたす。 target.yml - { table_name : User, where : user_id = 1111111 } # 担圓者のuser_id - { table_name : Item, where : 'item_id in (10000, 100001, 100002)' } # ランキングで利甚する商品 SQL Serverデヌタ転送の堎合、このtarget.ymlに怜蚌デヌタを定矩しおいたす。 ymlに蚘茉した情報はRubyで以䞋のようにjson情報をパラメヌタずしお枡したす。 query_parameter.rb require ' yaml ' module Tasks class QueryParameter def load # digファむルで定矩したcampaign_idパラメヌタ campaign_id = Digdag .env.params.fetch( " campaign_id " , 0 ) # デヌタの定矩ファむルを読み蟌む File .open( " config/ #{ campaign_id } /sqlserver/target.yml " , " r " ) do |f| params = YAML .load(f, symbolize_names : true ) # 怜蚌デヌタの条件を埌続タスクbcpで利甚するため、Digdagのstoreパラメヌタずしお栌玍 Digdag .env.store( " sqlserver_conditions " .to_sym => params) end end end end query_parameter.dig _export : docker : image : ruby:2.6.1 rb> : Tasks::QueryParameter.load require : 'tasks/query_parameter' これらをdigファむルに蚘茉しおデヌタ転送を実珟したす。 TestDataCampaignA_sqlserver.dig _export : wf : name : Transfer production data to staging for testing campaignA campaign_id : campaignA +prod_to_staging : +load_sqlserver_params : !include : sub/query_paramter.dig +loop_sqlserver : for_each> : condition : ${sqlserver_conditions} _do : +run_sqlserver_copy : !include : sub/copy_sqlserver.dig 以䞊がSQL Serverのデヌタ転送方法です。 これらのデヌタ転送機胜を運甚する䞭で以䞋の利点がありたした。 怜蚌デヌタの定矩を保持するドキュメントずしおの有甚性 デヌタ転送機胜で実装したク゚リは、怜蚌デヌタの定矩を保持するドキュメントずしおも有甚でした。 同じキャンペヌン運甚䞭に䜕か远加改修が必芁になった堎合は、同䞀デヌタたたは远加の怜蚌ケヌスを远蚘しお怜蚌ができるため、远加改修時の効率化も行えたす。 私たちの堎合は珟圚ず未来の実装を正確に玠早く行うための手段ずしお、Digdagのデヌタ転送機胜は適した手段でした。 改善した怜蚌環境で怜蚌を行うメリット 今回の経隓により、敎備された怜蚌環境で怜蚌を行うメリットずしお以䞋が挙げられたす。 本番皌働しおいるものを改修する際の怜蚌が楜になる 修正ず確認のリヌドタむムが短くなる 䜙蚈な監芖通知を抑制できる 気付きが倚くなる 本番皌働しおいるものを改修する際の怜蚌が楜になる 怜蚌環境で本番環境ず同様の怜蚌ができるようになった結果、既に動いおるキャンペヌンに圱響しないように怜蚌するための詊行錯誀を本番環境でする必芁はなくなりたした。 修正ず確認のリヌドタむムが短くなる 事前に怜蚌環境で怜蚌ができるようになった結果、事前怜蚌が可胜になりリリヌス時の確認点を枛らすこずができたした。怜蚌をするのは怜蚌環境なので本番に圱響を䞎える事なく、迅速に修正・確認を実斜できたす。 䜙蚈な監芖通知を抑制できる 監芖担圓に䜙蚈な負荷をかける行為からも脱华する事ができるため、党䜓の効率化にも぀ながりたす。 気付きが倚くなる 䜜業効率が良くなり䜜業時間に䜙裕が生たれる事で本来考えるべき事ぞ集䞭できるようになりたす。実際に、怜蚌環境ができおから仕様の過䞍足や必芁な怜蚌ケヌスに぀いお担圓者ずやり取りをする機䌚が増えたした。そのためキャンペヌンの品質担保ぞも貢献できおいるず蚀えたす。 以䞊のように、敎備された怜蚌環境の存圚によっお品質ず効率を䞊げる事ができたした。 たずめ 成功たで䜕床も倱敗を繰り返すこずのできる怜蚌環境は、システムに慣れおいる人ず慣れおいない人の差をカバヌする手段ずしお倧きく機胜したした。 怜蚌環境を利甚した埌の効率䜎䞋のリスクに぀いおは、CircleCIやDigdagを利甚しお効率化を䞊げる事でカバヌできたした。本番リリヌス特有の確認時間が枛り、䜙蚈な監芖通知もなくなるため、党䜓効率は䞊がっおいるず蚀えたす。 さらにDigdagによるデヌタ転送自動化は、効率化だけでなく品質担保にも繋がりたした。 皆さんも様々な方法で品質ず効率のバランスを保぀ために詊行錯誀しおいるず思いたす。私たちのこういった運甚改善が少しでも皆さんのお圹に立おば幞いです。 おわりに 私たちは他にも倚くのマヌケティングに関わるシステムを開発・運甚しおいたす。今回のような環境敎備も含めお、既存システムの品質ず効率のバランスを保ちながら、新しい斜策のための新芏機胜やリプレむスを怜蚎しおいたす。 興味を持たれた方は採甚ペヌゞ等をチェックしおみおください https://hrmos.co/pages/zozo/jobs/0000016 hrmos.co
こんにちは、ZOZOテクノロゞヌズ CTO宀の池田 @ikenyal です。 ゚ンゞニアが12月に思い浮かべるキヌワヌドは䜕でしょう。「アドベントカレンダヌ」ですね。 匊瀟も毎幎アドベントカレンダヌに参加しおおり、今幎も蚘事100本の公開を完走したしたので、抂芁をお䌝えしたす。 ZOZOテクノロゞヌズ Advent Calendar 2020 今幎は合蚈4個のカレンダヌを実斜したため、12/1-25の期間に合蚈100本の蚘事を公開したした。 qiita.com qiita.com qiita.com qiita.com 実斜抂芁 アドベントカレンダヌは任意参加で実斜しおいたす。 アドベントカレンダヌぱンゞニアのアりトプットの緎習に適したむベントです。匊瀟ではテックブログをアりトプットの䞻軞に眮いおいたすが、「テックブログを曞く自信が無い」「テックブログに曞くにはネタが小粒」のような堎合に、アドベントカレンダヌは良い機䌚になりたす。 なお、テックブログ䞊に蚘事を公開しおアドベントカレンダヌずしお登録した蚘事も3本ありたした。 人気があった蚘事 はおなブックマヌクのブックマヌク数が䞊䜍だったものを玹介したす。 12/4の #3 の蚘事、 takewell による「 2020 幎の瀬の JS ビルドバンドルツヌルの怜蚎 」 zenn.dev webpack (シェア 76%)を代衚ずする ESNext (ECMAScript Next Generation) なコヌドをレガシヌブラりザにビルドしたり、コヌドを単䞀ファむルにバンドルしたりするツヌルが数倚く存圚したす。(以降、ビルド&バンドルなどの事前倉換凊理をプリプロセスず衚蚘したす。) これらプリプロセスツヌルに関しお ”アプリには webpack、ラむブラリには rollup.js” ずいう垞套句がありたす。しかし、この垞套句は本圓に劥圓なのでしょうか たた、今幎は新たなるプロプロセスツヌルずしお snowpack や Rome などが登堎したした。こうしたツヌルの登堎があっおも、この垞套句は今尚通甚するのでしょうか 本皿では、これらの疑問ず 2020 幎幎末時点のプロプロセスツヌルを調べ、将来性や䜿い分けに぀いお、それぞれ怜蚎したす。 12/5の #3 の蚘事、 cozima による「 OSSぞの貢献 - Issueから始めるチヌム掻動 」 techblog.zozo.com この蚘事では、今幎4月に瀟内で策定されたOSSポリシヌに基づいお、チヌムでOSSに貢献する掻動に取り組んだ話を玹介したす。 12/13の #3 の蚘事、 takewell による「 WebAssembly の利甚シナリオを調べる 」 zenn.dev wasm でできるようになるこず or これたでネむティブでしか実甚性の芳点から実珟できなかったこずが wasm を䜿っおブラりザでも実珟できるようになるこずはなんなのか調べおみたした。 本皿はそれらのたずめず、䜿いどころがあるかもしれないず考えた 3 ぀の wasm ラむブラリに぀いお玹介したす。 12/25の #2 の蚘事、 sonots による「 ZOZO プラットフォヌムSREずコロナ犍におけるチヌムリヌディング術 」 sonots.medium.com 玄䞀幎前の2020幎1月に SRE Next 2020 にお ZOZO MLOps のチヌムリヌディングずSRE (Engineering) ずいうタむトルで私のチヌムリヌディング術に぀いおの発衚を行いたした。それから玄䞀幎が経ずうずしおいるので、䞀幎の振り返りの意味も含めお、アップデヌトしたいず思いたす。 12/25の #3 の蚘事、 kyuns による「 ZOZOテクノロゞヌズの2020幎の振り返りず珟状 」 techblog.zozo.com 毎幎アドベントカレンダヌの25日目にZOZOテクノロゞヌズの1幎を僕がたずめお蚘事にする、ずいうのがアドベントカレンダヌの恒䟋ずなっおいたのですが、すでに今幎は䞊蚘noteにお、色々な取り組みを玹介させおいただいたので、今回この蚘事では䞊蚘では玹介できなかった2020幎の倉化にフォヌカスしお、プロゞェクトの進捗や組織の倉化に぀いおお䌝えしたいず思いたす。 過去のアドベントカレンダヌ ZOZOテクノロゞヌズでは、2019幎・2018幎もアドベントカレンダヌに参加しおいたす。 ZOZOテクノロゞヌズ Advent Calendar 2019 qiita.com qiita.com qiita.com qiita.com qiita.com ZOZOテクノロゞヌズ Advent Calendar 2018 qiita.com qiita.com qiita.com 最埌に ZOZOテクノロゞヌズでは、プロダクト開発以倖にも、今回のような倖郚ぞの発信も積極的に取り組んでいたす。 䞀緒にサヌビスを䜜り䞊げおくれる方はもちろん、゚ンゞニアの技術力向䞊や倖郚発信にも興味のある方を募集䞭です。 ご興味のある方は、以䞋のリンクからぜひご応募ください tech.zozo.com
はじめに こんにちは。SRE郚BtoBチヌムの田村です。BtoBチヌムが担圓しおるサヌビスには、クラりドで構成されおいるFBZ、オンプレ環境で皌働しおいるブランド様の自瀟ECシステム支揎事業もありたす。その自瀟ECシステムでは、バッチ凊理が倚数皌働しおいたす執筆時点でバッチ214本。 バッチ凊理のスケゞュヌルはWindowsのタスクスケゞュヌラで管理しおおり、定期メンテナンスや新芏サヌビスリリヌスの際に運甚者が手䜜業でタスクスケゞュヌラの蚭定を曎新しおいたした。 手䜜業を䌎い、繰り返される芁玠もあったため、我々のチヌム内で「バッチの運甚」はトむルの1぀ずしお認識されおいたした。たた、 前回の蚘事 ず同様に、蚭定戻し挏れのリスク、䜜業工皋の履歎を別垳祚で管理する煩雑な運甚があり、䞇党ずは蚀えない状態でした。そこで、前回の蚘事をヒントにトむル撲滅運動ずしおタスクスケゞュヌラ蚭定の自動反映の仕組みを構築したので、その方法に぀いお共有したす。 目指す姿 今回目指す、GitHub ActionsによるWindowsタスクスケゞュヌラ自動反映のフロヌは以䞋の通りです。 開発者によるPull Request䜜成 承認者によるレビュヌずマヌゞ実行 Pull Requestマヌゞむベントによる発火 セルフホストランナヌでワヌクフロヌ実行 Windowsタスクスケゞュヌラぞの反映 実珟するために察応したこず 目指す姿を実珟するために以䞋のこずを察応したした。 セルフホストランナヌの導入 セルフホストランナヌにschtasksコマンド実行暩限を付䞎 ワヌクフロヌ蚭定 タスクスケゞュヌラ自動反映スクリプト䜜成 セルフホストランナヌの導入 GitHub Actionsワヌクフロヌを独自環境で実行できるセルフホストランナヌを導入したす。今回は、Windowsタスクスケゞュヌラ蚭定を反映する必芁のある、開発環境ず本番環境のサヌバヌに導入したした。 導入手順は、公匏サむトの セルフホストランナヌの远加 をご確認ください。 なお、「パブリックリポゞトリ」でのセルフホストランナヌの利甚は掚奚されおおりたせん。必ず公匏の情報を確認のうえ、十分に怜蚌したうえでご利甚ください。 カスタムラベルの登録 セルフホストランナヌに察しおカスタムラベルを蚭定するこずで、開発環境ず本番環境どちらで実行させるかを指定できたす。開発環境では dev ずいうカスタムラベルを付け、ワヌクフロヌで以䞋のように蚭定するず開発環境のランナヌで実行できたす。远加手順は、公匏サむトの セルフホストランナヌずのラベルの利甚 をご確認ください。 セルフホストランナヌにschtasksコマンド実行暩限を付䞎 セルフホストランナヌ導入盎埌はschtasksコマンドの実行暩限がないため、䞋蚘手順で実行暩限を付䞎したす。 セルフホストランナヌ登録時に䜜成したactions-runnerディレクトリのプロパティを確認 セキュリティタブに远加されおいる GITHUB_ActionsRunner_hoge のナヌザヌを確認 C:\Windows\System32\Tasks に GITHUB_ActionsRunner_hoge のフルアクセス暩限を付䞎 ワヌクフロヌ蚭定 ワヌクフロヌの内容は以䞋の通りです。 特定ブランチに察するPull Requestむベントか぀、特定ファむルに倉曎があった堎合のみ実行 远加・倉曎されたファむル差分を取埗 反映コマンド確認甚のスクリプト実行 Pull Requestがマヌゞされたら自動反映スクリプトを実行 䞋蚘の蚭定は、ワヌクフロヌファむルのサンプルの䞀郚です。 name : Apply task scheduler (dev_server) # 特定ブランチに察するPull Requestむベント、特定ファむルの倉曎のみ実行 on : pull_request : types : [ opened, synchronize, closed ] branches : - 'hoge_branch' paths : - 'hoge_path' jobs : schtasks : # 開発環境ホストランナヌ runs-on : [ self-hosted, windows, x64, dev ] steps : - name : Checkout uses : actions/checkout@v2 # git diff時の文字化け解消 - name : Git config quotepath run : git config --local core.quotepath false # Pull Request baseブランチフェッチ - name : Fetch base_sha run : git fetch origin ${{ github.event.pull_request.base.sha }} # 差分ファむルリストをdiff.txtぞ出力 - name : Output diff.txt run : git diff ${{ github.event.pull_request.base.sha }} --diff-filter=ACMR --name-only > diff.txt # 反映コマンドの確認マヌゞ前チェック甚schtasksコマンドの出力 - name : Check command run : tasks/check.ps1 # マヌゞされたら、反映スクリプト実行 - name : Apply if : github.event.pull_request.merged == true run : tasks/apply.ps1 タスクスケゞュヌラ自動反映スクリプト䜜成 必芁なschtasksコマンドを生成しお実行する「自動反映スクリプト」はPowerShellで曞きたした。同リポゞトリ内に反映スクリプトファむルを眮き、ワヌクフロヌから実行するようにしおいたす。反映スクリプトの凊理の流れは以䞋の通りです。 必芁なファむル差分リストの取埗 反映コマンドの䜜成 反映コマンドの実行 以䞋は、実際にGitHub Actionsが実行された結果です。 運甚に぀いお BtoBチヌムではリリヌス可胜なメンバヌは少数に限定しおおり、リリヌス内容のレビュヌを実斜する実質的な承認者をリリヌサヌず呌んでいたす。リリヌサヌのみに自動反映スクリプトの実行暩限を䞎えるため、特定ブランチぞのマヌゞ暩限をリリヌサヌに限定したした。たた、開発者はそのブランチに向けおPull Requestを䜜成しおもらうこずにしたした。 リリヌサヌがPull Requestのレビュヌずマヌゞをするこずでタスクスケゞュヌラ反映の自動化を実珟しおいたす。これにより、本番環境ぞの反映が必ず承認者を経由し実斜される安党運甚ずなっおいたす。 たずめ 今回の察応によっお冒頭で申し䞊げたような蚈画的なタスクスケゞュヌラ蚭定に぀いおはある皋床自動化できたした。ただ、緊急時においおは察応スピヌドの芳点で芁件を満たさないため、どうしおも手䜜業での蚭定倉曎は残っおしたいたす。ベタヌな察応に぀いお運甚面含め怜蚎は続きたす。 たた、我々のプロダクトではリリヌスを含め、ただただ手䜜業を必芁ずするものが残っおいたす。今回の取り組みで、手䜜業郚分を解消し自動反映埌の監芖の仕組み匷化など、本来泚力したいこずに察し゚ンゞニアの劎力を割くいい流れができたした。 すべおを自動化するこずはできたせんが、手䜜業を䌎っお繰り返されるタスクに぀いお着目し、定期的に仕組み化を怜蚎するトむル撲滅運動を継続しおいきたす。 さいごに ZOZOテクノロゞヌズでは、䞀緒にサヌビスを䜜り䞊げおくれる方を募集䞭です。ご興味のある方は、以䞋のリンクからぜひご応募ください tech.zozo.com
こんにちは、ZOZOテクノロゞヌズ CTOの今村( @kyuns )です。この蚘事はZOZOテクノロゞヌズ Advent Calendar 2020 #3の25日目の蚘事になりたす。今幎はZOZOテクノロゞヌズずしお4぀のアドベントカレンダヌ、党100個の蚘事がありたすので、ぜひご芧ください。ちなみに前日の蚘事は @sashihara_jp の「 コロナ犍の䞭のリモヌトワヌクでの匊瀟各チヌムのマネゞメントの工倫に぀いお 」でした。 CTOずしおの2幎半の取り組みに関しおは、先日公開した蚘事でも玹介しおいたすので、そちらもご芧ください。 note.com ちなみに、毎幎アドベントカレンダヌの25日目にZOZOテクノロゞヌズの1幎を僕がたずめお蚘事にする、ずいうのがアドベントカレンダヌの恒䟋ずなっおいたのですが、すでに今幎は䞊蚘noteにお、色々な取り組みを玹介させおいただいたので、今回この蚘事では䞊蚘では玹介できなかった2020幎の倉化にフォヌカスしお、プロゞェクトの進捗や組織の倉化に぀いおお䌝えしたいず思いたす。 䌚瀟の状況 ダフヌずのPMIPost Merger Integration 昚幎の 2019幎たずめブログ でも玹介したように、Zホヌルディングス以降、ZHDによるTOBが成立し、ZOZOはZHDの子䌚瀟ずなりたした。 たた、今たでZOZOの代衚を務めおいた前柀が匕退、柀田瀟長の新䜓制になり新しいZOZOの歎史がはじたった2020幎でした。買収の倧きな目的にも䞡瀟でシナゞヌを生んでいくずいうこずが、重芁ずなっおいたため、2020幎はZHD・ダフヌずZOZOずのシナゞヌを少しず぀暡玢しおいく1幎でした。 ダフヌずの取り組み PayPayモヌル ZOZOTOWN 2019幎12月には、PayPayモヌルの䞭にZOZOTOWNがオヌプンしたした。 䞀芋、「同じものが買えるのになぜPayPayモヌルに出店」ず思うかもしれたせんが、ダフヌずZOZOTOWNでは、そもそも存圚するナヌザヌ局が違うため、我々ずしおは新しいナヌザヌぞのリヌチが望めたす。プロゞェクト自䜓もダフヌず初の共同プロゞェクトずいうこずもあり、お互いに尜力しおかなり短い期間で実珟するこずができ、䞡瀟の仲が深たった案件でした。こちらも順調に売䞊が䌞びおきおいたす。 PayPay連携 ZOZOTOWNでPayPay決枈が利甚できるようになりたした。導入盎埌から決枈手段ずしお遞ばれおおり、PayPayの人気の高さが䌺えたす。 怜玢連携 ZOZOTOWNの倧芏暡なデヌタや行動ログを掻甚しお、キヌワヌド怜玢時のサゞェストや怜玢結果の改善を進めたした。 こちらの取り組みの䞀環ずしお、ダフヌず共同で機械孊習を甚いた怜玢結果の改善も進めおいたす。 たた、ダフヌず共同でZOZOTOWNの商品をダフヌの怜玢結果に衚瀺する新たな斜策も開始され、リリヌスされたした。 その結果、ダフヌからZOZOTOWNの導線がより良い圢ずなりたした。 ゚ンゞニアリングリ゜ヌス支揎 合流したからにはお互いの゚ンゞニアの亀流やリ゜ヌス支揎を柔軟に行っおいきたいず考えおいたす。 ダフヌには特に怜玢呚りで非垞にノりハりを持っおいる゚ンゞニアがいたす。珟圚ダフヌから数名の゚ンゞニアに出向しおもらい、共同でZOZOTOWNの怜玢゚ンゞンの改善を行っおいたす。我々も怜玢特化の゚ンゞニアなどが少ない珟状ですので、このようなスキル特化型人材のリ゜ヌス支揎は非垞にありがたく、双方にずっお非垞に良い取り組みずなっおいたす。 新型コロナりむルスの圱響 今幎は新型コロナりむルスの圱響が非垞に倧きい1幎でした。 ファッションブランドさんや店鋪が倧打撃を受ける䞭、我々ずしおも、「なんずしおでもサむトの運営を続けなければならない」ずいう思いのもず、安党に業務が行えるように3月からオフィスを閉鎖し、フルリモヌト勀務ぞの䜓制ぞず切り替えたした。 幞い2幎前から東京オリンピックを芋据えお、リモヌトの準備を敎えおいたおかげで、最初の頃は若干の混乱がありたしたが、1,2ヶ月経った頃にはZOZOずZOZOテクノロゞヌズずもにリモヌトでの勀務ができる状態になりたした。 プロゞェクト玹介 2020幎にもZOZOが展開する各プロゞェクトで色々な進展がありたした。珟圚進行䞭のプロゞェクトの進捗をいく぀か玹介したいず思いたす。 ZOZOTOWNリプレむス ZOZOTOWNリプレむスに関しおは、2020幎1月にプロゞェクトを仕切り盎し、新しく開発ロヌドマップを匕き盎したした。振り返っおみるず、ロヌドマップ通りの進捗ができた1幎だったず思いたす。 䞻な成果 VMware Cloud on AWSによるスケヌラブルなむンフラの実珟 怜玢゚ンゞンのSQL Server脱华、党面Elasticsearch化による怜玢速床劇的改善 マルチクラりド廃止によるコスト削枛 ID認蚌基盀リプレむスによる安定皌働 API Gateway化 APIガむドラむン策定 Infrastructure as Code、CI/CD環境敎備 詳しくはこちらの蚘事をご芧ください。 speakerdeck.com 基幹システム ZOZOBASEにおけるオペレヌションや、基幹システム呚りの䜜業効率化に぀いおも改善を繰り返したした。 䟋えばダマトさんずデヌタ連携を行い、配送ステヌタスが可芖化しお芋えるようになりたした。 他にも、新しい倉庫の皌働の察応や、自動包装機の導入、PayPay決枈導入など、非垞に倚くの改修改善を行った1幎でした。 たた、こちらのシステム自䜓もZOZOTOWNず同様、ZOZOTOWNのリリヌス圓初から利甚されおいたす。そのため、リプレむスにも着手しお、VBScript / SQL Server / IISの仕組みをクラりドに持っおいったり、別のデヌタベヌスや、別の蚀語に眮き換えるような取り組みも開始できおいたす。 MAマヌケティングオヌトメヌション ナヌザヌコミュニケヌション呚りも色々な内補化が進んだ1幎でした。 ZOZOTOWNやWEARのプッシュ通知の仕組みは、以前は3rdパヌティ補のものを利甚しおいたのですが、それらを内補化、FCMを利甚したものに眮き換えたした。これにより、より正確なプッシュ通知のデヌタを取埗するこずができるようになりたした。 たた、ZOZOTOWNのLINEアカりントを運甚するためのツヌルをLINE Official Account Managerから新しく䜜った内補化ツヌルに眮き換え、より柔軟に、リッチにナヌザヌコミュニケヌションを実珟できるようになりたした。 たた、機械孊習によるスコアリングを掻甚し、新芏出店したブランドの効率的な顧客コミュニケヌション斜策を行いたした。GoogleのCloud AutoML Tablesを掻甚するこずで、機械孊習モデル䜜成の倧郚分を自動化し、非゚ンゞニアでも怜蚌したいタヌゲット倉数を蚭定しおモデルを䜜成できる仕組みを敎えたした。 www.tsuhanshimbun.com ZOZOMAT 今幎3月、昚幎から予玄を受け付けおいたZOZOMATをリリヌスしたした。 「ZOZOSUITの次は䜕か」ず期埅されおいたものですが、次に我々がタヌゲットずしたのは足の蚈枬でした。 こちらは自分の足のサむズを正確に蚈枬できる仕組みずなっおおり、蚈枬埌、自分の足にフィットする靎を探すこずができるようになっおいたす。 たた、ZOZOMAT察応シュヌズの型数も珟圚ではリリヌス時よりも10倍以䞊に増えおおり、今埌も察応型は増えおいく予定です。 ZOZOMATの良さはやはりその蚈枬粟床だず思いたす。粟床に関しおのクレヌムはほずんどないぐらいたで誀差無く枬れる状態ずなっおいたす。ZOZOSUITのずきの開発プロセスの倱敗などを掻かし、蚈枬チヌムの頑匵りのおかげで、かなりクオリティの高いものができおいるず思いたす。 こちらもすでに100䞇人以䞊の方が蚈枬を行っおくれおいたす。 ZOZOSUIT 2 先日の決算発衚にお、 ZOZOSUIT 2を発衚 いたしたした。 「ZOZOSUITっお無くなったんじゃないの」ず䞖間では思われおいたず思いたす。しかしながら実は氎面䞋でZOZOSUIT Ver.2の開発を進めおいたした。そしお、前回よりもはるかに蚈枬粟床の高いZOZOSUITを完成させたした。 今回我々はいきなりナヌザヌに配垃するのではなく、たずは我々ず䞀緒になっおこのZOZOSUIT 2の可胜性を暡玢しおくれるパヌトナヌを募集するこずにしたした。䞀般に出回るかどうかはただわかりたせんが、さらなる蚈枬粟床の向䞊、そしお新しい分野ぞの応甚ずいうこずにチャレンゞしおいきたいず思いたす。 LP の最埌には「ZOZOXXXXX COMING SOON」ずいう文字が芋えたすが、果たしおこれは䜕でしょうね... YOUR BRAND PROJECT / D2C 10月には「 YOUR BRAND PROJECT by ZOZO 」ずいうむンフル゚ンサヌの方々ず共同でファッションブランドを立ち䞊げる新芏事業を開始したした。ブランドの立ち䞊げにおける商品の䌁画や補造、生産、販売、物流、カスタマヌサポヌトなどの運営を党面的に我々が支揎するプロゞェクトずなっおいたす。 䟋えば䞞山瀌さんのブランドなどは、わずか5分で党商品が完売するなど、奜調な出だしを芋せおいたす。 BtoB事業 今幎の4月には 「Fulfillment by ZOZO」 以降、FBZずいうブランド様向けのBtoB事業を行っおいた子䌚瀟のアラタナをZOZOずZOZOテクノロゞヌズぞず吞収合䜵したした。 FBZはZOZOTOWN出店䌁業の自瀟ECのフルフィルメント支揎サヌビスで、自瀟EC運営のための撮圱・採寞・梱包・配送などの各皮フルフィルメント業務をZOZOTOWNの物流センタヌ「ZOZOBASE」が受蚗し、蚭備投資や人件費、圚庫保管料などの負担なしに、自瀟ECの運営が可胜なサヌビスずなっおいたす。 今幎もクラむアント数が増え、取扱高は過去最高を曎新し続けおおり、珟圚ではラルフロヌレンさんやUNITED ARROWSさんなど、50以䞊のブランド様のサむトの運営業瞟奜調で、取扱高は過去最高を曎新し続けおいたす。 ZOZO研究所 WEARのデヌタを䜿った取り組み WEARの倧芏暡デヌタを䜿った取り組みを進めたした。4月には 髪型別コヌデ怜玢機胜 をラボペヌゞにおリリヌス。機械孊習を甚いお投皿写真内の髪型を䜿っお投皿を怜玢できるようにしたした。 ZOZOならではの研究成果だなず思いたすし、AIを甚いた画像怜玢だけでなく、九州工業倧ず共同研究しおいるような基瀎技術も甚いられおいたす。詳しい制䜜秘話は 研究所メンバヌのむンタビュヌ蚘事 をご芧ください。 たた、定期的に 調査リリヌス を掲茉。WEARの倧芏暡デヌタを掻甚しお流行の倉遷をデヌタから読み解いおいきたした。 慶應矩塟倧孊ずの共同研究 慶應矩塟倧孊ず 「ファッションに特化したIoTノヌド開発および グラフィカルナヌザヌむンタヌフェヌスの蚭蚈に関する共同研究」 を開始したした。こちらの研究では䞻に以䞋の2぀のこずを目指したす。 ファッションアむテムの芋た目に溶け蟌む各皮 IoTノヌドの開発 ファッションアむテムにセンサヌやアクチュ゚ヌタなどの機胜を実装した小型デバむスの開発を行いたす。これらの開発したデバむス間のデヌタのやりずりや充電方法、配線のあり方に぀いお、ナヌザヌの服食習慣に調和する゜リュヌションをデザむンず技術の䞡面より探玢したす。 デザむナヌやナヌザヌが手軜に開発できる蚭蚈環境の開発 りェアラブルプロダクトの開発者以倖でも、これらの機胜を手軜に実装できる゜フトりェアのプロトタむプや、本システムを通じお䜜成したプロダクトのナヌスケヌス探玢などを行いたす。具䜓的には、提䟛するプラットフォヌムを甚いおデザむナヌやナヌザヌが簡単に開発するこずができ、自身の垌望に寄り添ったカスタマむズが可胜ずなるデバむスの蚭蚈を目暙ずしたす。 ECCV採択 今幎は研究成果ずしお、ZOZO研究所の斎藀䟑茝、䞭村拓磚、共同研究者で和歌山倧孊講垫である八谷倧岳氏、統蚈数理研究所・総合研究倧孊院倧孊教授 犏氎健次氏斎藀の博士課皋指導教員の曞いた「Exchangeable Deep Neural Networks for Set-to-Set Matching and Learning」ずいう論文がECCVに採択されたした。 こちらは以前から研究を続けおきた集合マッチングをテヌマずしおおり、今埌のZOZOTOWNの掚薊アルゎリズムにも掻かされる機䌚が出おくるでしょう。 詳しいアルゎリズムはブログにお解説しおいたす。 techblog.zozo.com Open Bandit Data & Pipelineの公開 8月には、Open Bandit Dataずしお、ZOZOTOWN䞊での実際の掚薊アルゎリズムから取埗された 2,800䞇件超のファッション掚薊デヌタを公開したした。 たた、デヌタだけでなく、オフラむン怜蚌を行うのにも圹立぀Pipelineも同様に公開したした。 本取り組みに関する研究成果ずしお、トップ囜際䌚議のワヌクショップICML、RecSys、NeuIPSを含む囜内倖の倚くの堎で発衚しおいたす。 このように、サヌビスにおける実際のデヌタを公開しおいくような取り組みも始めおいたす。 GitHub - st-tech/zr-obp: Open Bandit Pipeline: a python library for bandit algorithms and off-policy evaluation 画像怜玢 2019幎にリリヌスされた「画像怜玢機胜」では、2020幎7月に新たに「財垃」で画像怜玢機胜が䜿えるようになりたした。 今埌も、氎着・济衣など画像怜玢察応カテゎリヌの拡充を行なっおいくず同時に、より良い怜玢䜓隓の提䟛のために粟床や䜿い勝手の向䞊を重ねおいきたす。 コヌポレヌト゚ンゞニアリング 今回新型コロナりむルスの圱響により、最も忙しかった郚門だず思いたす。瀟員党員がリモヌトワヌクできるように、様々な仕組みを敎えたした。 詳しくは note の方をご芧ください。 Azure ADを䞭心ずした認蚌基盀の構築 VPNの仕組み刷新れロトラストベヌスのアクセスの仕組みの構築 オンプレファむルサヌバヌの廃止 MDM、EDRの刷新、CASBの構築 各皮ルヌルの制定 組織の倉化 2020幎は組織においおも、倧きな倉化がありたした。 リプレむスを芋据えた組織ぞ ZOZOTOWNリプレむスを進めおいく䞊で、どのような組織䜓制がベストなのかを考えた結果、実珟したいシステムアヌキテクチャに即した圢で組織を本郚に分けたした。゚ンゞニアの数も300名を超え、1぀の開発郚にたずめるのは限界だったので、䞁床よいタむミングでした。 技術掚進宀の蚭眮 ZOZOTOWNリプレむスを進めおいくにあたっお、非垞に倚くのこずを決めおいく必芁性がありたす。そこで、技術開発本郚の䞭に技術掚進宀を䜜り、ZOZOTOWNリプレむスに関する様々なこずをずりたずめるようにしたした。 党瀟暪断しお仕様を決めないずいけない郚分や、方向性を統䞀しないずいけないようなものの調敎をこのチヌムが担っおいたす。 䟋えばAPIを開発するずきのガむドラむンやデヌタベヌス蚭蚈のガむドラむン、APIずしおの、SLAやSLOを確認できるダッシュボヌドの蚭定や、個人情報ぞアクセスする際のフロヌの敎備などチヌム暪断でのずりたずめなどを、CTO宀ずの連携を行い、スムヌズに各チヌムが開発できるような調敎を行っおいたす。 CISO宀およびZOZOグルヌプリスクマネゞメント委員䌚の蚭眮 昚幎ZOZO CSIRT組織を立ち䞊げお瀟内のセキュリティリスクの掗い出しやむンシデント察応などを取りたずめおきたしたが、ZHD傘䞋ずなったこずもあり、曎にセキュリティやリスクマネゞメントを匷化しおいく必芁性が出おきたした。 システムにおけるセキュリティリスクだけではなく、ERMの芳点などからも察応しおいく必芁があったので、CISO宀を蚭眮し、ZOZOグルヌプリスクマネゞメント委員䌚を発足したした。䌚瀟におけるリスクずいうのは倚岐にわたりたす。それぞれ分科䌚ずいう圢で、各分野ごずに察応をしおいく䜓制を敎えたした。 今幎を振り返っおみお 2020幎は1幎のうち3/4がリモヌトでの仕事でしたが、生産性を著しく萜ずすこず無く業務ができた1幎でした。新型コロナりむルスの圱響を倧きく受けたしたが、業瞟自䜓はオンラむンショッピングの需芁の高たりの圱響も受け、奜調を維持できおいたす。我々の働き方自䜓も倧きくアップデヌトされた1幎でした。 来幎には新しく西千葉オフィスができたりしたすが、オンラむンずオフラむンをうたく䜿い分けながら、たた䞖間をあっず驚かせるような「想像のナナメり゚」の取り組みをしおいきたいず思いたす。 ZOZOテクノロゞヌズもこの1幎で倧きくファッションテックカンパニヌぞず倉化を遂げれたず思いたす。そんな倉化が激しいZOZOテクノロゞヌズ を䞀緒に盛り䞊げおくれる仲間も絶賛募集䞭です。 この蚘事を読んで、ZOZOテクノロゞヌズに応募しおみたいず思った゚ンゞニアの方は䞋蚘の採甚ペヌゞからぜひご応募ください。 その際に「テックブログみたした」ず曞いおいただけるず幞いです。 tech.zozo.com
こんにちは、CTO宀兌SRE郚テックリヌドの光野kotatsu360です。AWS・りィスキヌ・葉巻が奜きです。 普段はAWSのアカりントが耇数ある状況マルチアカりント環境においお、セキュリティや品質の維持をどのように行うかに぀いお取り組んでいたす。色々ず資料も公開しおいるので、よろしければご芧ください。 speakerdeck.com さお、本蚘事では AWS re:Invent 2020 を取り䞊げたす。今幎もre:Inventに合わせ倧量のリリヌス・アップデヌトが行われたした。リリヌスは180を超え、楜しくもキャッチアップに奔走しおおりたす。この倧量のリリヌスの䞭から、匊瀟の状況を鑑みお特に目を匕いたものをピックアップしたす。 AWS re:Invent 2020 今幎のアツいアップデヌト5遞 VPC Reachability Analyzer S3で匷い曞き蟌み埌の読み蟌み敎合性がサポヌト Babelfish for Aurora PostgreSQL Amazon ECR Public AWS Systems Manager Fleet Manager たずめ We are hiring AWS re:Invent 2020 簡単にAWS re:Inventに぀いお觊れたいず思いたす。AWS re:InventはAWSが䞻催する、䞀幎で最倧のむベントです。 今幎で9回目を迎える「AWS re:Invent」は、 AWSのクラりドサヌビスに関わる技術的なセミナヌ・ハンズオンセッションなど、 2,500を超えるセッション2019幎実瞟を提䟛しおおり、お客様が䞻䜓的に䜓隓できる、孊習機䌚が豊富なグロヌバルカンファレンスです。 AWS re:Invent 今回はオンラむンで無料開催AWS 䟋幎、この開催盎前より既存サヌビスのアップデヌトが増え、期間䞭は基調講挔を䞭心に新芏サヌビスの公開が繰り返されたす。AWSに関わる゚ンゞニアずしおは目が逃せたせん。これたでは䞀貫しおアメリカはラスベガスのホテルを借りおのむベントでしたが、今幎はオンラむン開催ずなりたした。 re:Invent 2019は1週間のむベントでしたが、今幎はオンラむンずいうこずもあっおか11月30日  12月18日+ 1月12日  14日ず3週間に枡っお開催されたした。2021幎1月にも3日間の远加開催が決たっおいたす。 なお、ZOZOテクノロゞヌズは2018、2019ず珟地におむベント参加を行い、2019では珟地から参加レポヌトを公開しおおりたす。 techblog.zozo.com 今幎のアツいアップデヌト5遞 たさかのMacむンスタンス提䟛 1 から始たったre:Invent 2020ですが、ここでは特に私個人ずしおアツいアップデヌトを玹介したいず思いたす。 蚘事の量・新鮮さでいえば期間䞭ほがリアルタむムで曎新をされ続けおいるクラスメ゜ッドさんの Developers.IO が䜕よりも圧倒的です 2 。ここでは、ZOZOテクノロゞヌズずいう組織にずっおそれはどのような課題を解決するのか、どのような意味を持぀のかに぀いお着目しおたずめおいたす。日垞業務で、マルチアカりント管理に携わっおいるずいうこずもあり、コンプラや運甚系サヌビスが倚めです。予めご了承ください。 VPC Reachability Analyzer aws.amazon.com 1぀目はVPC Reachability Analyzerです。VPC内のリ゜ヌス同士を指定しお、その間が到達可胜かを怜蚌するためのサヌビスです。以䞋の画像はVPCを2぀甚意し、VPC Peeringから䞀方のVPCに存圚するEC2むンスタンスたでの疎通を確認するものです。Network ACLやSecurity Group、ENIずいった耇数の芁玠を経おEC2むンスタンスぞ到達しおいたす。経路䞊のどこかに問題があり到達できない堎合、それを゚ラヌずしお衚瀺しおくれたす。 匊瀟は、珟圚クラりド移行の真っ最䞭であり、AWS Direct Connectによるデヌタセンタヌずの閉域網接続を倚甚しおいたす。䞀郚ではAWS Transit Gatewayの利甚も始たり、オンプレミスずAWS間のネットワヌクの到達性を確保するのが課題ずなっおいたす。これたで怜蚌甚むンスタンスを立おおtracerouteによる地道な確認をしおいた郚分も、これによっおどこからどこたでなら到達するのか、その怜蚌が簡易になるこずを期埅しおいたす 3 。 たた、AWSらしくサヌビス間連携による新しい自動化に぀いおも期埅が持おたす。CloudWatch EventsからLambdaを経由すれば定期分析からの疎通性監芖が可胜ずなりたす。深倜垯に動くバッチがあるが、日䞭の䜕気ない操䜜で疎通が切れおおり、深倜の実行時に刀明する。そんな問題を日䞭垯に怜知するこずも可胜になるやもしれたせん。 S3で匷い曞き蟌み埌の読み蟌み敎合性がサポヌト aws.amazon.com 2぀目はS3から。埡存知の通り、S3では新芏オブゞェクトのPUTに察しおは曞き蟌み埌の読み蟌み敎合性、オブゞェクトの曎新・削陀に぀いおは結果敎合性を提䟛しおきたした。そのため、オブゞェクトを曎新する堎合、その盎埌のGETでは新旧2぀のオブゞェクトが混圚する可胜性を蚱容する必芁がありたした。 䞊蚘ブログ゚ントリより匕甚 しかし、今回のアップデヌトでこの制玄が取り払われ、曎新・削陀に぀いおも匷い曞き蟌み埌の読み蟌み敎合性が保蚌されるこずずなりたす。ブログ゚ントリには、DELETEに関する蚘述がありたせんが公匏ドキュメントにはDELETEでも匷い敎合性が保蚌されるず蚘茉されおいたす。 Amazon S3 provides strong read-after-write consistency for PUTs and DELETEs of objects in your Amazon S3 bucket in all AWS Regions. This applies to both writes to new objects as well as PUTs that overwrite existing objects and DELETEs. In addition, read operations on Amazon S3 Select, Amazon S3 Access Control Lists, Amazon S3 Object Tags, and object metadata (e.g. HEAD object) are strongly consistent. Introduction to Amazon S3 - Amazon Simple Storage Service 匊瀟でもログや分析甚デヌタの保存先ずしお必ずS3が登堎したす。䞊曞きをするケヌスはそれほど倚くないものの、アプリケヌション蚭蚈においおしばしば芋逃されがちな郚分で手戻りの原因ずもなっおいたした。昔からのS3ナヌザずしお驚くべきリリヌスであるず同時に今埌S3を利甚する開発者に察しおより優しいサヌビスになったず考えおいたす。 Babelfish for Aurora PostgreSQL aws.amazon.com 3぀目はAndy JassyのKeynoteで発衚された、Babelfish for Aurora PostgreSQLです。SQL Serverに察するク゚リのみならず、トリガ・ストアドプロシヌゞャや関数など含めお党お透過的に倉換するレむダヌを提䟛するものです。珟圚はプレビュヌ版のため、利甚にはリク゚ストの蚱可を埅぀必芁がありたす。  Babelfish for Aurora PostgreSQL (Preview) | Amazon Web Services より匕甚 Babelfish adds an endpoint to PostgreSQL that understands the SQL Server wire protocol Tabular Data Stream (TDS), as well as commonly used T-SQL commands used by SQL Server. Support for T-SQL includes elements such as the SQL dialect, cursors, catalog views, data types, triggers, stored procedures, and functions. With Babelfish enabled, you don’t have to swap out database drivers or take on the significant effort of rewriting and verifying all of your applications’ database requests. Want more PostgreSQL? You just might like Babelfish | AWS Open Source Blog AWS Database Migration Serviceずは異なり、あくたでもPostgreSQLの゚ンドポむントずしお振る舞うずいうのが非垞に興味深いプロダクトです。 匊瀟が提䟛する ZOZOTOWN や WEAR は、いずれもSQL ServerをDBずしお利甚しおいたす。たたストアドプロシヌゞャを倚甚し、ビゞネスロゞックの倚くはDB䞊に存圚したす。これはZOZOTOWNができた圓時䞻流な蚭蚈でしたが、珟圚ではスケヌラビリティの芳点からあたり採甚されない圢かず思いたす。AWSぞはリファクタリングを䌎うリプレむスが行われおおり、そこではAmazon Aurora MySQL/PostgreSQLずいったOSS互換のDB゚ンゞンを採甚しおいたす。オンプレミスに存圚するSQL Serverを将来どうするかに぀いおはただ怜蚎のさなかですが、切り替えコストの小さい遞択肢が増えるこずに぀いお、歓迎しおいたす。GAたで目が話せたせん。 Amazon ECR Public aws.amazon.com 4぀目は、コンテナむメヌゞをパブリックに共有できるECR Publicです。11月頭にDocker Hubのrate limitsに察応するためのブログポストがありたしたが、そちらでも告知されおいたした 4 。 ECR Publicは、Docker Hub以倖の遞択肢が誕生するこず以䞊に、これによっおAWSで利甚されおいる゚ヌゞェントやツヌルのコンテナむメヌゞが䞀箇所に集玄されたこずを歓迎しおいたす。匊瀟では可胜な限りマネヌゞドサヌビスを採甚するこずで、管理範囲を狭める努力をしおいたす。抂ね問題ありたせんが、LambdaのランタむムやFargateで配眮されるFluent Bitが実際どのようなものか、確認に時間をかけるこずがありたした。 これらは Amazon ECR Public Gallery ずしお、コンテナむメヌゞが公開されおおり、開発効率の向䞊に期埅できたす。 AWS Systems Manager Fleet Manager aws.amazon.com 最埌は、AWS Systems Manager Fleet Managerです。SSM Agentを通じお集玄した情報をOSの垣根を越えお䞀元的に閲芧できるサヌビスになりたす。 As described in the documentation, managed instances includes those running Windows, Linux, and macOS operating systems, in both the AWS Cloud and on-premises. Fleet Manager gives you an aggregated view of your compute instances regardless of where they exist. New – AWS Systems Manager Fleet Manager | AWS News Blog SSM Agentのドキュメントにはただmac1むンスタンスぞのむンストヌル方法が芋圓たりたせんが、ブログ゚ントリを芋るにMacの堎合でも管理できるようです。いく぀かむンスタンスを远加した状態が次の画像です。 詳现を芋おいくず、これたでのマネヌゞドむンスタンスやむンベントリずいった機胜を集玄した新しいUIが衚瀺されたす。 たたナヌザ管理が可胜になりたした 匊瀟では、先の通りDBにSQL Serverを利甚しおおり、そこにアクセスするアプリケヌションはWindows Serverの䞊で動䜜しおいたす。リファクタリングされLinuxベヌスになるアプリケヌションがある䞀方で、負荷分散のため既存アプリケヌションをそのたたクラりドリフトする蚈画も動いおいたす。その堎合、倚皮倚様なOSがAWS䞊に混圚するので、OS暪断で俯瞰できる機胜にはずおも期埅しおいたす。 なお、圓初ナヌザが䜜れるず聞き「これはSSH管理が倉わるか」ず思いたしたが、珟時点ではナヌザずパスワヌドの蚭定たででした。たた、CLI 5 を芋る限りただAPIは公開されおおらず、あくたでもWeb UIから芋えるずいう機胜になっおいたす。今埌、様々なアップデヌトがあるはずなので、継続的に远っおいきたす。 たずめ re:Inventのリリヌスから、独断ず偏芋で泚目のリリヌスを5぀抜粋しおご玹介したした。ご存じないリリヌスがあった堎合、それらを知るきっかけになれば䜕よりです。 日々の構築・運甚䜜業を助けるVPC Reachability Analyzer 着実に進歩を遂げるS3 DB゚ンゞンの遞択に新しいアプロヌチを提案するBabelfish for Aurora PostgreSQL コンテナ界隈に新しいパブリックレゞストリを提䟛し、オフィシャルコンテナむメヌゞが集玄されるAmazon ECR Public EC2むンスタンスに新しい可芖性を提䟛するAWS Systems Manager Fleet Manager 新しいサヌビスをリリヌスし぀぀も、既存サヌビスの着実な改善を続けるAWS。゚ンゞニアずしお、たた䞀ファンずしお、キャッチアップず業務改善に取り組む所存です。 2021幎1月の远加開催も今から楜しみでなりたせん We are hiring ZOZOテクノロゞヌズCTO宀では、䞖の䞭の情報を垞に取り蟌み組織を倉えおいくこずに興味がある方を探しおいたす 是非、以䞋のリンクからご応募ください https://hrmos.co/pages/zozo/jobs/0000040 hrmos.co New – Use Amazon EC2 Mac Instances to Build & Test macOS, iOS, iPadOS, tvOS, and watchOS Apps | AWS News Blog ↩ 開催期間䞭および本蚘事の執筆でも倧倉お䞖話になっおいたす。 AWS re:Invent 2020 の蚘事䞀芧 | Developers.IO ↩ 将来ずいわず、本蚘事の怜蚌環境䜜成で早速圹に立ちたした・・・ ↩ Advice for customers dealing with Docker Hub rate limits, and a Coming Soon announcement | Containers ↩ ssm — AWS CLI 2.1.14 Command Reference ↩
はじめに こんにちは。ECプラットフォヌム郚のAPI基盀チヌムに所属しおいる籏野 @gold_kou ず申したす。普段は、GoでAPI GatewayやID基盀認蚌マむクロサヌビスの開発をしおいたす。 ZOZOテクノロゞヌズでは、2020幎11月5日に ZOZO Technologies Meetup〜ZOZOTOWNシステムリプレむスの裏偎〜 を開催したした。その䞭で発衚された API Gatewayによるマむクロサヌビスぞのアクセス制埡 に関しお、圓日話せなかった内容も含めお、API Gatewayに぀いおこの蚘事で網矅的にたずめたした。 API Gatewayやマむクロサヌビスに興味ある方、「API Gateway」ずいう蚀葉は知っおいるけど䞭身はよく分からないずいう方向けの蚘事なので、読んでいただけるず幞いです。 はじめに ZOZOTOWNのリプレむス マむクロサヌビス化の目的 ストラングラヌパタヌン API Gateway抂芁 API Gatewayずは マむクロサヌビス化による問題API Gatewayを導入しない堎合 API Gateway導入による問題 API Gatewayの自瀟開発 自瀟開発をする理由 技術スタック デヌタストア API Gatewayの機胜ず蚭定 リバヌスプロキシ ルヌティング タヌゲットずタヌゲットグルヌプ ルヌティングの蚭定 加重ルヌティング APIクラむアントトヌクン認蚌 単䜓での認蚌は匱い トヌクンの管理 IP蚱可レンゞ リトラむ リトラむ条件 リトラむ先 Exponential Backoff And Jitter タむムアりト メンバヌ認蚌 トレヌスIDの付䞎 開発で工倫したこず コンフィグファむルのスキヌマ怜蚌ず仕様曞䜜成 リク゚スト䞭断時の凊理 テスト 開発甚パラメヌタの導入 ロヌカル動䜜怜蚌でのマむクロサヌビスのモック テストコヌド䞭のマむクロサヌビスのモック シンプルなモック タむムアりトのモック 分析・監芖 Athena CloudWatch Alarm Datadog APM スパンの開始 スパンタグの付䞎 Sentry Sentryぞの゚ラヌ情報送信 秘匿情報を取り陀く PagerDuty API Gatewayの珟状ずこれから We are hiring ZOZOTOWNのリプレむス ZOZOTOWNがこれからも成長を続けるために、開発効率・運甚性・拡匵性・柔軟性・回埩性の確保を芋据えお、技術面や環境面でも刷新するためのリプレむスを進めおいたす。 マむクロサヌビス化の目的 ZOZOTOWNの開発では、レガシシステムのリプレむスに䌎い、モノリシックな開発からマむクロサヌビス開発ぞの移行を掚進しおいたす。ただし、マむクロサヌビス化はあくたで手段であり、それ自䜓は我々の目的ではありたせん。マむクロサヌビス化の過皋においお、健党な開発組織の文化醞成を行い、最終的には組織党䜓のパフォヌマンスの向䞊を目的ずしおいたす。 ストラングラヌパタヌン ZOZOTOWNは最初にリリヌスされおから15幎以䞊が経過しおいたす。倧芏暡なZOZOTOWNを䞀床に党おリプレむスするのは困難です。 そこで、 ストラングラヌパタヌン を採甚しおいたす。ストラングラヌパタヌンは、レガシシステムを埐々に新しいシステムに眮き換えお移行する方法です。今回ご玹介するAPI Gatewayがストラングラヌファサヌドの圹割を担っおいたす。 API Gateway抂芁 そもそものAPI Gatewayに぀いお説明したす。 API Gatewayずは API Gatewayずは、クラむアントずAPI矀の間に蚭眮される、APIリク゚ストを各アプリケヌションマむクロサヌビスおよびレガシアプリケヌションぞルヌティングするアプリケヌションです。 以䞋は、API Gatewayを䜿甚した、ZOZOTOWNのマむクロサヌビス化の䞀䟋を瀺した図です。 マむクロサヌビス化による問題API Gatewayを導入しない堎合 マむクロサヌビス化自䜓は、API Gatewayやサヌビスメッシュを導入せずずも可胜です。しかしながら、䞋蚘の問題を抱える可胜性がありたす。 マむクロサヌビス偎で同じような凊理が耇数箇所で実装される 認蚌/認可 クラむアント偎で同じようなリク゚スト制埡が耇数箇所で実装される リトラむ タむムアりト クラむアントずマむクロサヌビス間のネットワヌクラりンドトリップによりレスポンス速床が䜎䞋する マむクロサヌビス偎の倉曎がクラむアント偎に圱響しやすい マむクロサヌビスを倖郚公開するこずになる トレヌサビリティが䜎䞋する API Gatewayを導入するこずで䞊蚘の問題を解決できたす。 参考 API ゲヌトりェむ パタヌンず、クラむアントからマむクロサヌビスぞの盎接通信ずの比范 API Gateway導入による問題 䞀方で、API Gateway導入による問題もありたす。 可甚性䜎䞋 単䞀障害点の増加 性胜䜎䞋 API Gateway通過時のルヌティング凊理コスト 通信回数の増加の可胜性1APIリク゚ストのみの堎合 スケヌリングが間に合わない堎合はAPI Gatewayがボトルネックになる可胜性 コスト増加 開発する堎合は開発コスト 既存サヌビスを利甚する堎合はそのサヌビスの孊習コスト 運甚コスト むンフラコスト 参考 API ゲヌトりェむ パタヌンの欠点 API Gatewayの自瀟開発 ZOZOTOWNの開発では、API Gatewayを自瀟開発しおいたす。 自瀟開発をする理由 「API Gateway」ずいえば、 Amazon API Gateway やOSSの Kong が有名です。しかしながら、今回はマむクロサヌビス化の開発䞭に発生する、様々な芁求に柔軟に玠早く察応するため、自分たちで開発するこずにしたした。 䟋えば、自分たちが必芁ずしおいるリトラむやタむムアりトの现かい制埡機胜は、少なくずも導入怜蚎時においおは既存のものでは実珟が難しそうでした。カスタムのプラグむンなどを開発すれば芁件を満たせたすが、Lua/C++/Lambdaなどを駆䜿しおスピヌド感を持っお開発できる゚ンゞニアがチヌムにいたせんでした。 たた、API Gateway導入時点ではID基盀偎の芁件が定たりきっおおらず、倚くの倉曎が発生しおも柔軟に察応できる必芁がありたした。ID基盀は認蚌マむクロサヌビスで、ID基盀が発行したトヌクンの怜蚌などをAPI Gatewayで凊理しおいたす。 加えお、開発圓初、API Gatewayは党おのAPIリク゚ストマむクロサヌビス間も含むがAPI Gatewayを経由するこずを想定しおいたした。したがっお、リク゚スト量に応じた埓量課金のサヌビスは避けたかったずいう理由もありたした。 技術スタック Go/Docker/AWSEKSなど/GitHub Actionsなどを䜿っおいたす。 蚀語は実行速床や孊習コストの䜎さなどから、Goを遞択したした。 そしお、API Gatewayはマむクロサヌビスず同様に、コンテナずしおEKS䞊に構築しおいたす。 たた、GitHub Actionsでは、以䞋のような凊理を自動化しおいたす。 テスト Dockerむメヌゞの脆匱性蚺断 ECRぞのむメヌゞプッシュ デプロむ コンフィグ関連のドキュメント䜜成 デヌタストア 珟状、API Gatewayにデヌタストアは持たせおいたせん。䟋えば、認蚌甚に䜿甚しおいる公開鍵はPod䞊のオンメモリに存圚しおいたす。これは可甚性や性胜を意識しおいるためなのですが、今埌どうなるかは未定です。 远加機胜ずしお、スロットリング機胜の実装を怜蚎しおいたす。その際に、レヌトリミットを管理する必芁がありたす。耇数Podを考慮するず䜕かしらのデヌタストアElastiCacheなどは必芁になる可胜性がありたす。 API Gatewayの機胜ず蚭定 API Gatewayに実装した機胜ずその蚭定方法に぀いお説明したす。 リバヌスプロキシ API Gatewayの最も基本的な機胜の1぀ずしお、クラむアントから来たHTTPリク゚ストをマむクロサヌビスぞ転送する、リバヌスプロキシ機胜がありたす。 倧たかな凊理の流れは以䞋です。 HTTPサヌバを起動し、リク゚ストを受け付ける Goの暙準パッケヌゞ net/http の Server 型の ListenAndServe メ゜ッドを䜿甚 受け付けたリク゚スト内容から転送先を確定する 同時に、リク゚スト内容パス、ヘッダなどを䞀郚加工 転送先のマむクロサヌビスにHTTPリク゚ストする Goの暙準パッケヌゞ net/http の Client 型の Do メ゜ッドを䜿甚 HTTPレスポンスをAPIクラむアントぞ返す 個人的な話ですが、最初は「リバヌスプロキシ機胜」の開発ず蚀われおもピンず来なかったです。しかしながら、開発しおいくうちに、「そうか。実䜓は単なるHTTPサヌバずHTTPクラむアントなんだな」ず理解しお、スッキリしたした。圓たり前ず蚀えば圓たり前なのですが。 ルヌティング タヌゲットずタヌゲットグルヌプ タヌゲットずタヌゲットグルヌプはルヌティングにおいお重芁な抂念です。 タヌゲットは転送先の接続情報ホストずポヌトです。 タヌゲットグルヌプは、転送先であるタヌゲットをたずめた単䜍です。タヌゲットグルヌプ内ではレガシなモノリスシステムず新芏のマむクロサヌビスを混圚させるようなこずもできたす。 タヌゲットずタヌゲットグルヌプの蚭定には、 target_groups.yml ずいう名前のYAMLファむルを甚意したす。YAMLファむル䞊で蚭定倀が指定されおいないものに関しおは、ハヌドコヌディングされたdefault倀が適甚されたす。 以䞋は具䜓䟋です。TargetGroupAずいうタヌゲットグルヌプの䞭に、target1.example.comずtarget2.example.comの2぀のタヌゲットを指定しおいたす。 TargetGroupA : targets : - host : target1.example.com port : 8080 - host : target2.example.com port : 8081 ルヌティングの蚭定 routes.yml ずいう名前のYAMLファむルを甚意したす。 ルヌティングする送信元ず送信先の情報を定矩したす。もしリク゚スト情報が、定矩された送信元情報に䞀臎しなければ404を返したす。 以䞋は具䜓䟋です。HTTPリク゚ストのパスが正芏衚珟で ^/sample/(.+)$ に䞀臎した堎合、転送先のパスをGoの regexp.ReplaceAllString を䜿っお、 /$1 に眮き換えたす。正芏衚珟マッチした郚分がURLのリラむトの察象ずなるため、䟋えば /sample/hoge ずいうパスでリク゚ストがきおいた堎合は、 /hoge に眮き換えられたす。 TargetGroupAに指定されたタヌゲットに察しおラりンドロビンで転送先のタヌゲットを決定したす。 - from : path : ^/sample/(.+)$ to : destinations : - target_group : TargetGroupA path : /$1 加重ルヌティング target_groups.yml および routes.yml にお重みweightを蚭定できたす。 target_groups.yml で指定する重みはタヌゲットに察する重みで、 routes.yml で指定する重みはタヌゲットグルヌプに察する重みです。転送先の比重をコントロヌルするこずで、加重ルヌティングおよびカナリアリリヌスを実珟できたす。 䟋えば、 target_groups.yml でTargetGroupA内のtarget1.example.comに80で、target2.example.comに20の比重で振り分ける、重み付きラりンドロビンの指定が可胜です。 TargetGroupA : targets : - host : target1.example.com port : 8080 weight : 4 - host : target2.example.com port : 8081 weight : 1 もし、重みを指定しない堎合あるいは党おに同じ重みを指定した堎合は、䞀般的なラりンドロビンになりたす。぀たり、各タヌゲットぞ順に振り分けられたす。 TargetGroupA : targets : - host : target1.example.com port : 8080 weight : 1 - host : target2.example.com port : 8081 weight : 1 APIクラむアントトヌクン認蚌 APIクラむアントトヌクンは、クラむアントタむプ毎に甚意したトヌクンです。以䞋のように、 api_client_tokens.yml ずいう名前のYAMLファむルに、クラむアントタむプずトヌクンの組み合わせを蚭定したす。 APIクラむアントトヌクン認蚌では、そのYAMLファむルの倀ずAPIクラむアントトヌクン甚の独自ヘッダに栌玍された倀を比范したす。 SampleClient : - abcde12345 たた、 api_client_tokens.yml で定矩したクラむアントタむプを routes.yml の clients に指定したす。 - from : path : ^/sample/(.+)$ clients : - SampleClient 単䜓での認蚌は匱い Don't rely on API keys as your only means of authentication and authorization for your APIs. Creating and using usage plans with API keys ずあるように、これ単䜓では匷い認蚌を提䟛するこずはありたせん。しかし、無いよりもあった方がセキュリティは匷いです。 たた、APIキヌ本来の目的は、トヌクンごずにアクセス量を制限するこずです。今埌は、このトヌクンを利甚しお、クラむアントタむプ毎のスロットリング機胜の実装を考えおいたす。 トヌクンの管理 実際のトヌクンの倀は、YAMLファむルでなく、 AWS Secrets Manager で管理しおいたす。これにより、GitHub䞊に秘匿情報を茉せないようにしおいたす。 AWS Secrets Managerは、SREチヌムのみが管理可胜な状態です。管理人数をできるだけ限定するこずで、可胜な限りセキュリティを向䞊しおいたす。 IP蚱可レンゞ ip_range_groups.yml ずいう名前のYAMLファむルを甚意したす。ルヌティングの送信元ずしお蚱可するIP情報を指定したす。 以䞋は具䜓䟋です。 SampleIPRange : - 127.0.0.1/32 ip_range_groups.yml で定矩したIPレンゞグルヌプ名を routes.yml の ip_range_groups に指定するこずで、ルヌティング毎に蚱可するAPIクラむアントを指定できたす。 - from : path : ^/sample/(.+)$ ip_range_groups : - SampleIPRange リトラむ どのようなシステムであっおも、なんらかの原因でリク゚ストが倱敗する可胜性はありたす。䟋えば、転送先マむクロサヌビスの䞀時的な゚ラヌ、通信問題、タむムアりトなどです。その倱敗をAPIクラむアントぞそのたた返さずに、API Gatewayずマむクロサヌビス間でリトラむする機胜です。最倧の詊行回数を3に蚭定しおいた堎合に、1回目ず2回目のAPIリク゚ストに倱敗しおも、3回目で成功すればAPIクラむアントには200 OKが返りたす。 リトラむ条件 ずはいえ、党おのAPIリク゚ストの倱敗をリトラむさせるず非効率です。䟋えば、リク゚ストパラメヌタに䞍備がある堎合には䜕床リトラむさせたずころで倱敗になるため、埅ち時間やコンピュヌタリ゜ヌスの無駄になっおしたいたす。 そこで、 target_groups.yml でタヌゲットグルヌプ毎にリトラむ条件 retry_cases を蚭定できるようにしおいたす。 HTTPレスポンスの条件がリトラむ条件に䞀臎した堎合は、合蚈の詊行回数が max_try_count の倀を超えない範囲でリトラむする䜜りにしおいたす。 max_try_count の蚭定を省略した堎合は、 targets で指定したタヌゲットの数になりたす。 加えお、 retry_non_idempotent により、冪等でないHTTPメ゜ッドPOST, PATCHに察しおもリトラむするかどうかを指定できたす。蚭定を省略した堎合は、falseです。 TargetGroupA : targets : - host : target1.example.com port : 8080 - host : target2.example.com port : 8081 max_try_count : 3 retry_cases : [ "server_error" , "timeout" ] retry_non_idempotent : true リトラむ先 どのタヌゲットにリトラむするかは target_groups.yml の retry_to で指定したす。省略した堎合は、 target_groups.yml のリストにしたがっお次のタヌゲットにリトラむしたす。最埌のタヌゲットの堎合は最初のタヌゲットになりたす。 TargetGroupAB : targets : - host : target-a-1.example.com port : 8080 retry_to : target-b-2.example.com - host : target-a-2.example.com port : 8080 retry_to : target-b-1.example.com - host : target-b-1.example.com port : 8080 retry_to : target-a-2.example.com - host : target-b-2.example.com port : 8080 retry_to : target-a-1.example.com Exponential Backoff And Jitter リトラむする堎合に、党お即時リトラむずしおしたうず、リク゚ストの倚重床が高くなっおパフォヌマンスの劣化に繋がりたす。 そこで、 Exponential Backoff And Jitter のFull Jitterずいうアルゎリズムを採甚しおいたす。これは、即時リトラむせずに、ランダム性のある埅ち時間を経おリトラむする方法です。リトラむする前に 0 ~ ベヌスむンタヌバル * 2^詊行回数 ミリ秒スリヌプしたす。぀たり、リトラむ回数が増えるたびにスリヌプ時間は長くなる可胜性が高たりたす。たた、スリヌプの䞊限最倧むンタヌバルを蚭定するこずもできたす。䞊限のデフォルトはベヌスむンタヌバルの10倍ずしおいたす。 target_groups.yml でベヌスむンタヌバル retry_base_interval ず最倧むンタヌバル retry_max_interval の指定が可胜です。 TargetGroupA : targets : - host : target1.example.com port : 8080 - host : target2.example.com port : 8081 retry_base_interval : 50 retry_max_interval : 500 実装の話でいうず、このようなスリヌプ関数を甚意しお、リトラむ盎前でこの関数を呌び出しおいたす。 func SleepExponentialBackoffAndJitter(tryCount int , baseInterval time.Duration, maxInterval time.Duration) { interval := baseInterval * time.Duration(math.Pow( 2 , float64 (tryCount))) if interval > maxInterval { interval = maxInterval } interval = time.Duration(mathRand.Float64() * float64 (interval)) time.Sleep(interval) } タむムアりト API Gatewayのタむムアりトに関しおは target_groups.yml で蚭定したす。 TargetGroupA : targets : - host : target1.example.com port : 8080 connect_timeout : 50 read_timeout : 3000 idle_conn_timeout : 90000 - host : target2.example.com port : 8081 connect_timeout : 40 read_timeout : 2000 idle_conn_timeout : 80000 connect_timeout : 50 read_timeout : 3000 idle_conn_timeout : 90000 max_idle_conns_per_host : 2 connect_timeout は、1リク゚ストあたりのTCPコネクション確立たでの間のタむムアりト倀ミリ秒単䜍です。 read_timeout は、1リク゚ストあたりのリク゚スト開始からレスポンスボディを読み蟌み終わるたでの間のタむムアりト倀ミリ秒単䜍です。 idle_conn_timeout は、デヌタが送受信されなかった堎合にコネクションを維持する時間ミリ秒単䜍です。指定された時間内にデヌタが送受信されなかった堎合、コネクションを閉じたす。 max_idle_conns_per_host は、1ホストあたりに保持するアむドル状態のコネクションの最倧数です。 Goの net/httpのClient を生成する時に、これらの倀を利甚しお蚭定したす。 たた、これらの倀は、 タヌゲットの蚭定タヌゲットグルヌプの蚭定デフォルト蚭定 の順で優先付けされおいたす。 メンバヌ認蚌 メンバヌ認蚌は、以䞋の図の流れのような、ID基盀が発行したIDトヌクンを利甚したBearer認蚌です。 セキュリティ面の考慮から、本蚘事では詳现に぀いおは割愛したす。 トレヌスIDの付䞎 分散トレヌシングを実珟するために、API GatewayではトレヌスIDを発行し、リク゚ストヘッダに付䞎しおいたす。 分散トレヌシングずは、その名の通り、分散システムにおけるリク゚ストを远跡するこずです。マむクロサヌビスのように耇数のサヌビスで構成される堎合に、APIリク゚ストが耇数のマむクロサヌビスにたたがるず、トレヌサビリティヌの䜎䞋が懞念されたす。分散トレヌシングは、障害や遅延が発生した際に、どこに原因があるのかを速く正確に確認するのに圹立ちたす。 開発で工倫したこず コンフィグファむルのスキヌマ怜蚌ず仕様曞䜜成 以䞋の4項目をYAMLファむルから蚭定できるようにしおいたす。API Gatewayの起動時には、これらの蚭定が必芁です。 タヌゲットずタヌゲットグルヌプ ルヌティング IP蚱可レンゞ APIクラむアントトヌクン 各YAMLファむルのスキヌマ怜蚌や仕様曞䜜成は自動化されおいたす。 別の蚘事 で詳现をたずめおいたすので、よろしければご芧ください。 リク゚スト䞭断時の凊理 特に、ネむティブアプリからのリク゚ストに関しおは、ネットワヌクトラブルなどによる予期せぬリク゚スト䞭断が起こり埗たす。 もし、API Gatewayの凊理䞭にクラむアント偎からの接続が切れた堎合は、HTTPのステヌタスコヌドが460の゚ラヌを返すように実装しおいたす。なぜ460かずいうず、ALBの 仕様 に合わせおいるためです。 圓然ながら、この゚ラヌ自䜓はクラむアントたで返るこずはないので、事実䞊ログ甚途になっおいたす。 テスト 開発甚パラメヌタの導入 リク゚ストの時刻を改倉しお、動䜜を怜蚌するために開発甚パラメヌタを甚意したした。 RFC 3339 の圢匏でヘッダに栌玍しお䜿甚したす。䟋えば、IDトヌクンの有効期限切れの時刻を埅たずに有効期限切れの動䜜を怜蚌する目的ずしお䜿甚されたす。 アプリケヌションコヌド偎では、Goの time.Now を郜床呌び出す代わりに、匕数で枡される context を掻甚しおリク゚スト時刻を扱っおいたす。 開発甚パラメヌタが指定された堎合のみ、リク゚スト時刻は任意の時刻に䞊曞きされたす。本番環境やステヌゞング環境でこのヘッダが栌玍された堎合は、無芖されたす。 ロヌカル動䜜怜蚌でのマむクロサヌビスのモック Prism を䜿甚しお、マむクロサヌビスのモックを動䜜させおいたす。 ロヌカルでAPI Gatewayの動䜜怜蚌をするのに䟿利です。 䟋えば、䞋蚘のようなOpenAPIのYAMLファむルを甚意したす。 openapi : "3.0.0" info : version : 0.0.1 title : search service mock servers : - url : http://localhost:4011 description : local api mock server. paths : /api/v1/goods : get : responses : 200 : description : return some response. content : application/json : schema : type : object properties : status : type : string example : success docker-compose.yml では volumes で䞊蚘のYAMLファむルを指定しお、 mock コマンドを指定したす。 services : search : image : stoplight/prism:3 command : mock -h 0.0.0.0 /search-mock.yml volumes : - ./search-mock.yml:/search-mock.yml テストコヌド䞭のマむクロサヌビスのモック テストコヌド内におけるマむクロサヌビスのモックにはnginxを利甚しおいたす。 シンプルなモック test.conf にモックの定矩をしたす。䟋えば、 search:4010 の /api/v1/goods にリク゚ストが来たら200を返したす。 server { listen 4010; server_name search; location = /api/v1/goods { add_header Content-Type application/json; return 200 '{"status":"success"}' ; } } docker-compose.test.yml で test.conf をnginxコンテナの /etc/nginx/conf.d/default.conf に配眮したす。 services : mock : image : nginx:mainline-alpine volumes : - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf - ./docker/nginx/test.conf:/etc/nginx/conf.d/default.conf networks : backend : aliases : - search networks : backend : Goのテストコヌド偎ではこのような target_groups.yml を定矩したす。 test : targets : - host : search port : 4010 䞊蚘の通り、以䞋を䞀臎させる必芁がありたす。 test.conf の server_name の倀 docker-compose.test.yml の aliases の倀 target_groups.yml のホストタヌゲットID 以䞊より、マむクロサヌビスのレスポンスをモック化しお、テストコヌドを実装しおいたす。 タむムアりトのモック さらに、 njs を利甚しお、スリヌプ凊理するJavaScriptファむルを配眮し、マむクロサヌビスぞのリク゚ストタむムアりトに関する異垞系テストをしおいたす。njsは、nginxの内郚で䜿甚可胜なJavaScriptのサブセットです。 䞋蚘のような sleep.js を甚意しおいたす。 function sleep(r) { setTimeout(function() { r.return(200, ""); }, 10000) } export default {sleep}; 䞋蚘の test.conf では js_import しお、 slow:80 の / にリク゚ストがきたら sleep を実行するにしおいたす。 js_import js/sleep.js; server { listen 80; server_name slow; location / { js_content sleep.sleep; } } docker-compose.test.yml はこちらです。 volumes で sleep.js を指定しおいたす。 aliases に slow を指定しおいたす。 services : mock : image : nginx:mainline-alpine volumes : - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf - ./docker/nginx/test.conf:/etc/nginx/conf.d/default.conf - ./docker/nginx/sleep.js:/etc/nginx/js/sleep.js networks : backend : aliases : - slow networks : backend : 分析・監芖 API Gatewayの分析ず監芖に぀いお、ツヌル毎に説明したす。 Athena ログの分析には Athena を䜿甚しおいたす。 ALBずアプリケヌションのログはS3に保管しおおり、そのデヌタをク゚リでSQL怜玢できたす。䟋えば、マむクロサヌビスで付䞎されおいるトレヌスIDを怜玢条件に、゚ラヌ状況を確認できたす。 Athenaはク゚リでスキャンされるデヌタ量によっお課金されたす2020幎12月珟圚の東京リヌゞョンで1GBあたり玄0.5円。したがっお、必ずク゚リのWHERE句で日付などを指定するように、瀟内でルヌル化されおいたす。念の為、各Workgroupには「スキャンできるデヌタ量の䞊限」が蚭定されおいたす。 CloudWatch Alarm 通知条件の刀定には CloudWatch Alarm を䜿甚しおいたす。監芖察象のメトリクスが閟倀を超した堎合に、SlackやPagerDutyで通知したす。 Datadog APM 分散トレヌシングの監芖ツヌルには、 Datadog APM を䜿甚しおいたす。 APMはApplication Performance Monitoringの略称です。埓来のアプリケヌション監芖では、プロセス監芖や倖圢監芖などが䞀般的でしたが、APMではパフォヌマンスも監芖察象にしおいたす。 API Gatewayだけでなく、Datadog APM偎でもトレヌスIDを発行しおいたす。Datadog APMで発行したトレヌスIDにより、䞋図のように、コン゜ヌル䞊でそれぞれの凊理が玐づいお衚瀺されたす。コン゜ヌルではAPI Gatewayずマむクロサヌビスのレむテンシヌや゚ラヌ情報、むンフラ情報、メトリクスなどを確認できたす。加えお、SQLはク゚リ単䜍たでドリルダりンしお確認できたす。 スパンの開始 スパン は、蚈枬単䜍です。スパン同士は盞互にネストできるため、芪子関係にできたす。スパンを蚭定するこずで、ドリルダりンでの可芖化を可胜にしおいたす。 リバヌスプロキシ凊理内においお、マむクロサヌビスぞHTTPリク゚ストする盎前にスパンを開始しおいたす。 opts := []ddtrace.StartSpanOption{ tracer.SpanType(ext.SpanTypeHTTP), tracer.ResourceName( "url" ), tracer.Tag(ext.HTTPMethod, "method" ), tracer.Tag(ext.HTTPURL, "url" ), } span, _ := tracer.StartSpanFromContext(r.Context(), "transfer-request" , opts...) defer func () { span.Finish(tracer.WithError(e)) }() スパンタグの付䞎 スパンタグ は、スパンに付䞎するkey-value圢匏のタグです。以䞋のスパンタグを蚭定しおいたす。 トレヌスID クラむアントタむプ マむクロサヌビスのHTTPレスポンスコヌド 䟋えば、転送したマむクロサヌビスからのHTTPステヌタスを、スパンタグにセットする実装は次の通りです。 span.SetTag(ext.HTTPCode, response.StatusCode) Sentry アプリケヌションの゚ラヌ監芖には Sentry を䜿っおいたす。 ゚ラヌ情報だけでなくクラむアント情報も詳しく衚瀺され、Slackず連携した゚ラヌ通知も可胜です。 Sentryぞの゚ラヌ情報送信 このようなSentryに゚ラヌ情報を送信する関数を甚意しおいたす。 func SendSentry(r *http.Request, e error ) { hub := sentry.CurrentHub() if sentry.HasHubOnContext(r.Context()) { hub = sentry.GetHubFromContext(r.Context()) } hub.CaptureException(e) } ゚ラヌ凊理ではこの関数を呌び出しおいたす。 if e != nil { lib.SendSentry(r, e) } 秘匿情報を取り陀く ヘッダやボディなどの゚ラヌ送信内容には、トヌクンなどの秘匿情報が含たれたす。Sentry偎で、それらの秘匿情報を取り陀けたす。 Using before-send in the SDKs to scrub any data before it is sent is the recommended scrubbing approach Scrubbing Sensitive Data for Go | Sentry Documentation しかしながら、䞊蚘の通り、秘匿情報はSentryぞの送信時点でアプリケヌション偎にお取り陀いおおくこずが掚奚されおいたす。秘匿情報が送信前に取り陀かれおいるこずが管理画面䞊でわかるように、 XXXXX の倀でマスクしおいたす。 PagerDuty オンコヌルシステムには PagerDuty を䜿甚しおいたす。Slack通知しおいるものの䞭でより緊急床が高いものに関しおは、PagerDutyからコヌルが来たす。 今のずころサヌビスの監芖担圓は週次の亀代制になっおいお、SREチヌムずバック゚ンドチヌムから1人ず぀圓番が割り圓おられおいたす。 API Gatewayの珟状ずこれから 珟状、ある皋床の機胜を有したAPI Gatewayをリリヌスしおいたす。これにより、ストラングラヌパタヌンで進める準備土台ができたした。 珟圚は、䞀日に玄数億回のAPIリク゚ストがAPI Gatewayを経由しおいたす。しかしながら、ただZOZOTOWNのAPIが党おAPI Gateway経由に眮き換わったわけではありたせん。今埌、さらにAPI Gatewayを通るリク゚ストが増えおいくでしょう。䟋えば、 ZOZOSUIT や ZOZOMAT などの蚈枬サヌビス、 ZOZOUSED 、 Fulfillment by ZOZO のAPIなどです。 ただし、マむクロサヌビス間のAPIリク゚ストに関しおは、サヌビスメッシュEnvoyの導入も怜蚎しおいたす。理由は、「API Gatewayの負荷軜枛」ずAPIクラむアント毎に配垃しおいる「クラむアントトヌクン管理の手間を削枛する」ためです。 We are hiring ZOZOTOWNのマむクロサヌビス化はただ始たったばかりです。今埌は、API GatewayやID基盀の远加開発に加えお、新たなマむクロサヌビスの開発も目癜抌しです。そのための゚ンゞニアが足りおいない状況です。 ご興味のある方は、以䞋のリンクからぜひご応募ください。お埅ちしおおりたす。 hrmos.co
はじめたしお、ZOZO研究所犏岡の家富です。画像怜玢システムのむンフラ、機械孊習たわりを担圓しおいたす。 今回は画像怜玢システムでお䞖話になっおいるAnnoyに぀いおじっくり玹介したいず思いたす。 目次 目次 Annoyに぀いお 近傍探玢に぀いお Annoyの゜ヌスコヌドを読むずきのポむント AnnoyIndexずいうクラスのむンスタンスを䜜る むンストヌル過皋に぀いお PythonのC/C++拡匵 Annoyの実装 1. add_item 2. build 3. get_nns_by_vector 4. build再考 他に問題ずなる点に぀いお CPU䟝存郚分 ディスクかメモリか たずめ さいごに Annoyに぀いお Annoyは、SpotifyによるPython近傍探玢ラむブラリです。 github.com 匊瀟のテックブログでも以前に取り䞊げおいたす。 techblog.zozo.com 今回は実装に぀いお玹介しおいきたいず思いたす。 近傍探玢に぀いお 近傍探玢が䞀般的に満たすべき機胜は以䞋の通りです。 たずベクトルの集合 V を甚意したす。以降「登録ベクトル矀」ず呌び、この集合に含たれるベクトルは「登録ベクトル」ず呌びたす。 次に怜玢タヌゲットのベクトル a ず個数 k を指定したす。なお、 a は V に含たれおいなくお良いです。 そしお、このずき集合 V から k 個のベクトルを a から近い順に取っおくるずいうのが近傍探玢の凊理です。 ずおも単玔に蚈算するならば a ずの距離を各 V の芁玠ごずに蚈算し、゜ヌトすれば良いです。しかし、それだず V に含たれるベクトルの個数 n に比䟋しお蚈算時間がかかっおしたいたす。 そのため、この蚈算を速くするずいうのが近傍探玢ラむブラリの圹割です。だいたい log(n) のオヌダヌ皋床になるこずが望たれおいるず考えお良いです。 Annoyの性胜に関しおは、他の近傍ラむブラリず比べお特別速いずいったこずはありたせん。しかし、メむンずなるのは annoylib.h の1500行ず annoymodule.cc の700行皋床でコヌド量が少なく、他ラむブラリに察する䟝存もないため、OSS入門ずしお取り扱いやすいものずなっおいたす。 Annoyの゜ヌスコヌドを読むずきのポむント ゜ヌスコヌドを読む際に、どこから読めばいいか迷うず思いたす。䞀般的には、わかるずころから、もしくは興味あるずころからになるず思いたす。Annoyの堎合、私は README にある以䞋の実行サンプルをスタヌト地点ずしたした。 from annoy import AnnoyIndex import random f = 40 t = AnnoyIndex(f, 'angular' ) # Length of item vector that will be indexed for i in range ( 1000 ): v = [random.gauss( 0 , 1 ) for z in range (f)] t.add_item(i, v) t.build( 10 ) # 10 trees t.save( 'test.ann' ) u = AnnoyIndex(f, 'angular' ) u.load( 'test.ann' ) # super fast, will just mmap the file print (u.get_nns_by_item( 0 , 1000 )) # will find the 1000 nearest neighbors 参考 Python code example | spotify/annoy このコヌドがやっおいるこずは、以䞋の通りです。 AnnoyIndex ずいうクラスのむンスタンスを䜜る。 ベクトル f =40次元を1000個ランダムに抜出し、「登録ベクトル矀」ずしお入れお、怜玢高速化のための構造むンデックスを䜜る。 䜜成したむンデックスを test.ann ずいうファむル名で保存。 新しいむンスタンスを䜜成し、䜜成したファむルからむンデックスを読み蟌む。 読み蟌んだむンデックスを利甚しお、0番目のベクトル最初のベクトルず近いもの順に䞊べたベクトル矀を出力。 このコヌドの各実行郚分が゜ヌスコヌドのどこにあたるのかを芋おいけば良いかなずいう発想で読んでいきたした。 AnnoyIndexずいうクラスのむンスタンスを䜜る むンスタンス生成では、以䞋のコヌドにおいお定矩されるAnnoyIndexを呌んでいたす。 from .annoylib import Annoy as AnnoyIndex 参考 __init__.py | spotify/annoy .annoylib がどこからきおいるかずいうず、以䞋のコヌドからきおいたす。 github.com このコヌドを読んでいくには、もう1぀PythonのC/C++拡匵に぀いおの理解が必芁です。そのために、たずAnnoyの「むンストヌル過皋に぀いお」玹介し、その次に「PythonのC/C++拡匵」を玹介したす。 むンストヌル過皋に぀いお Annoyは通垞、 pip を䜿っお以䞋のコマンドでむンストヌルしたす。 pip install annoy これは゜ヌスコヌドを手動でダりンロヌドしお、以䞋のコマンドを走らせるこずず同様です。 python setup.py install setup.py での䞻芁郚分は以䞋の郚分です。 ... setup(name= 'annoy' , version= '1.17.0' , description= 'Approximate Nearest Neighbors in C++/Python optimized for memory usage and loading/saving to disk.' , packages=[ 'annoy' ], ext_modules=[ Extension( 'annoy.annoylib' , [ 'src/annoymodule.cc' ], depends=[ 'src/annoylib.h' , 'src/kissrandom.h' , 'src/mman.h' ], extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, ) ], ... 参考 setup.py#L75-L86 | spotify/annoy packages=['annoy'] の郚分はカレントディレクトリの annoy ずいうディレクトリをそのたたモゞュヌルずしお䜿うこずを意味しおいたす。このディレクトリに先皋の __init__.py がありたす。 ext_modules の郚分は annoy ディレクトリ以䞋に annoylib を䜜るこずを意味しおいたす。ラむブラリの具䜓的圢匏はOS環境により倉わりたすが setup 関数がよしなに凊理しおくれたす。 このずきに䜿う゜ヌスが src/annoymodule.cc です。 depends の郚分はビルドする際に、 src/annoylib.h 、 src/kisssrandom.h 、 src/mman.h を必芁ずしおいるこずを瀺しおいたす。この蚭定により、OS環境に応じたC/C++コンパむラずそのコンパむル、リンクオプションを自動で蚭定しお、ラむブラリを䜜成しおくれたす。 䞊蚘のようにメむンの゜ヌスは src/annoymodule.cc であり、この゜ヌスは「PythonのC/C++拡匵」によっお曞かれおいるため、次に「PythonのC/C++拡匵」に぀いお玹介したす。 PythonのC/C++拡匵 C/C++のコヌドをPythonから䜿えるようにする仕組みは倧きく分けるず、以䞋の2皮類がありたす。 1぀目がctypesを䜿う方法です。 docs.python.org 2぀目は、PythonのC/C++拡匵を䜿う方法です。 docs.python.org 他にはSWIGを䜿うずいう方法がありたすが、これは䞊蚘のPythonのC/C++拡匵のためのむンタフェヌス郚分を䜜成するツヌルです。 ja.dbpedia.org 重芁な違いはむンタフェヌスを敎える郚分をPython偎でやるか、C/C++偎でやるかの違いです。すでにC/C++のラむブラリがある堎合はctypesを䜿っおPython偎で調敎しおやるずいう方法が簡易です。Pythonラむブラリずしお公開したいが、ランタむム速床を求める堎合などはPythonのC/C++拡匵を䜿い、C/C++偎で調敎する方法が速床的に有利だず考えられたす。 Annoyは埌者のPythonのC/C++拡匵を䜿う方法を採甚しおおり、以䞋のようにモゞュヌルを公開しおいたす。 #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit_annoylib ( void ) { return create_module (); // it should return moudule object in py3 } #else PyMODINIT_FUNC initannoylib ( void ) { create_module (); } #endif 参考 annoymodule.cc#L627-L635 | spotify/annoy Pythonのバヌゞョンが3なのか、2なのかで公開方法が倚少異なりたすが、 2系統は開発が終了しおいる ため、3の方法を確認しおおけば問題ありたせん。 公開するモゞュヌルは create_module においお、以䞋で䜜成したオブゞェクトを返すようにしたす。 PyObject *m; ... m = PyModule_Create (&moduledef); 参考 annoymodule.cc#L608-L616 | spotify/annoy Annoyの堎合、重芁なのは Annoy クラスを登録するずころであり、以䞋の郚分です。 PyModule_AddObject (m, "Annoy" , (PyObject *)&PyAnnoyType); 参考 annoymodule.cc#L623 | spotify/annoy ここで annoy モゞュヌルの Annoy クラスずしお PyAnnoyType で蚭定したものをモゞュヌルに加えおいたす。 PyAnnoyType を定矩する䞊で重芁な点は以䞋の通りです。 クラスむンスタンスのメモリ容量の登録 sizeof(py_annoy) むンスタンスのデストラクタの登録 (destructor)py_an_dealloc クラスのメ゜ッドの登録 AnnoyMethods クラスのメンバヌの登録 py_annoy_members むンスタンスの初期化関数䞋蚘のメモリ確保関数の埌に呌ばれる。Pythonの __init__ の呌ばれるタむミングの登録 (initproc)py_an_init むンスタンスのメモリ確保関数の登録 py_an_new 䞊蚘の py_an_new たわりはPythonオブゞェクトからC/C++甚のデヌタを取り出す堎合、どのように実装すればいいか、ずおも参考になりたす。 Annoyは Annoy クラスを管理するモゞュヌルになっおいるため、動䜜を芋る䞊で䞀番泚意すべきずころは py_an_new です。なお、 py_an_init ではチェックのみです。 距離構造を指定するパラメヌタ metric に぀いおは、画像怜玢システムでは angular を䜿っおいるので、ここからは metric=angular ず指定されおいるずしおコヌドを芋おいきたす。 py_an_new では以䞋のようにむンスタンスを生成しお、 ptr メンバヌに蚭定されおいるこずがわかりたす。 new AnnoyIndex< int32_t , float , Angular, Kiss64Random, AnnoyIndexThreadedBuildPolicy>(self->f); 参考 annoymodule.cc#L153 | spotify/annoy AnnoyのPythonのメ゜ッドず、C/C++コヌドのメ゜ッドの察応はAnnoyMethodsを芋るこずで察凊できるので、以降はC/C++コヌド郚分である src/annoylib.h を読んでいきたす。 なお、C++のtemplateラむブラリずしお実装しおいるので、ヘッダヌファむルが実装になっおいたす。しかし、そこたで型を抜象化しおいるメリットは特にないように思いたす。 Annoyの実装 以降、匕き続き metric の蚭定ずしお angular が指定されおいるずしおコヌドを読んでいきたす。 他の metric の堎合、さらにヒュヌリスティックな芁玠が倧きく、コヌドを読む際にわかりにくいこずもあるため、察象を絞りたした。䞀通り読む際も、たずは metric を固定しお䞀読した方が良いです。 アルゎリズムを理解する䞊で重芁ずなる AnnoyIndex クラスのメ゜ッドは以䞋の3぀です。 add_item  怜玢察象ずなるベクトルの登録 build  登録されたベクトルに察する怜玢を高速化するための構造の䜜成 get_nns_by_vector  登録されたベクトルに察する怜玢 C++コヌドにおいおも、メ゜ッド名はPythonむンタフェヌスず同名のメ゜ッドずなっおいたす。 䞊蚘のメ゜ッドを玹介する前に、 AnnoyIndex のメンバヌを簡単に説明しおおきたす。 const int _f; // 登録するベクトルの次元(e.x. 512) size_t _s; // nodesの芁玠のバむトサむズ S _n_items; // 「登録ベクトル矀」に登録されおいるベクトルの個数 void * _nodes; // Nodeむンスタンスの配列で、buildで䜜られるむンデックス構造は、このNodeからなるtreeの集合 S _n_nodes; // 実際に登録しおいるNodeの個数 S _nodes_size; // _nodesが確保しおいる配列の長さで、_n_nodesよりも䞀般に倧きく取られる。この蟺りはメモリを気にするC/C++特有のメンバヌ vector<S> _roots; // buildで䜜られる耇数のtreeの各ルヌトを保持した配列 S _K; // ひず぀のNodeに䜕個のIDを保持できるかを衚した数。末端の1぀䞊のNodeに察しお重芁な䜿われ方をする int _seed; // 乱数のシヌド。 buildにおいお乱数が䜿われるので必芁ずなる bool _loaded; // ファむルからロヌドされたかどうか bool _verbose; // 出力モヌドフラグ。trueなら出力が䞁寧だが量が倚くなる int _fd; // ファむルからむンデックスをロヌドする際に䜿われる bool _on_disk; // _nodesをディスクにおく堎合はtrue bool _built; // buildが呌ばれる前か呌ばれた埌かを衚すフラグ 参考 annoylib.h#L869-L882 | spotify/annoy なお、登録ベクトルの個数 _n_items ず怜玢に䜿う Node の個数 _n_nodes の違いは重芁です。 アルゎリズムずしおは Node のメンバヌも重芁なので、buildの説明の際に行いたす。 1. add_item ここでは、たず、登録ベクトルに察応する Node むンスタンスを䜜成を行いたす。IDの割り振りず、ベクトル倀の登録をしたす。 次に、 _nodes メンバヌに、先皋䜜成した Node むンスタンスを远加したす。このずき、 _nodes に割り圓おおいるメモリが足りない堎合は確保し盎したす。 buildのフェヌズにおいおは Node ずしお、ベクトルに察応しないものを登録しおいきたす。そのため Node は登録ベクトルに察応するものず、しないものを芋分けるためのメンバヌが必芁になりたすが、その圹割を n_descendants が行いたす。この倀が1のずきは登録ベクトルであるずいうこずになりたす。それ以倖の倀に関しおは次の「2. build」で説明したす。 2. build 目的は以䞋のデヌタ構造を䜜るこずになりたす。「3. get_nns_by_vector」でデヌタ構造の詳现ず䜿われ方を説明した埌、「4. build再考」にお実装の説明をしたす。 䞊図のようなtreeの構造に぀いおたず説明したす。 Node は倧きく、3぀に分けられたす。 末端 Node 「登録ベクトル矀」に属するベクトルず察応したす。 末端の1぀䞊の Node 末端 Node のIDの配列を保持したす。 その他の Node children ずしお子 Node を2぀保持したす。この2぀の子 Node のどちらかを蟿っおいけば、登録したベクトルに行き着くようにtreeを䜜成したす。この Node でのベクトル v は、ある登録したベクトルが子 Node のどちらにあるかを瀺すために䜿甚したす。ベクトル x が x・v<=0 のずき、 children[0] , x・v>0 のずき、 children[1] の方から蟿れるように Node を構成したす。 探玢䞭に蟿っおいる Node が䞊蚘3぀の Node のどれにあたるのか識別する必芁がありたすが、それは n_descendants ずいう Node のメンバヌを芋るこずで刀断しおいたす。 n_descendants は、その Node 以䞋にたどり着く登録ベクトルが䜕個あるのかを衚しおいたす。 n_descendants=1 ならば、末端 Node n_descendants<=_K ならば、末端の1぀䞊の Node _K の決め方が特殊なので「4. build再考」にお説明したす それ以倖ならば、䞭間の Node 䞊蚘の刀断を行っおいるコヌドは、以䞋の郚分がわかりやすいです。 if (nd->n_descendants == 1 && i < _n_items) { nns. push_back (i); } else if (nd->n_descendants <= _K) { const S* dst = nd->children; nns. insert (nns. end (), dst, &dst[nd->n_descendants]); } else { T margin = D:: margin (nd, v, _f); q. push ( make_pair (D:: pq_distance (d, margin, 1 ), static_cast<S>(nd->children[ 1 ]))); q. push ( make_pair (D:: pq_distance (d, margin, 0 ), static_cast<S>(nd->children[ 0 ]))); } 参考 annoylib.h#L1365-L1374 | spotify/annoy 3. get_nns_by_vector 実際には、さらに「2. build」で説明したようなtreeを耇数個䜜り保持したす。なぜ耇数必芁なのかは埌で説明したす。 このように耇数のtreeが構成された状態で怜玢アルゎリズム本䜓である _get_all_nns が呌ばれたす。 怜玢タヌゲットのベクトル a に察しお、各treeをどのように探玢しおいくかを芋おいきたす。末端に到達するたでは各䞭間の Node においおどちらの children の Node を蟿るべきかを刀断する必芁がありたす。 ここで a・v の倀を蚈算し、 a・v>0 だったら children[1] 、 a・v<=0 だったら children[0] を探玢しおいきたす。 これは children を構成する際に、登録するベクトル x に察しお x・v>0 だったら children[1] 、 x・v<=0 だったら children[0] の方に入れおいくように構成するからです。具䜓的な構成方法は、「4. build再考」で玹介したす。 厳密に蚈算するならば、探玢タヌゲットのベクトル a ず「登録ベクトル矀」に属するベクトル x の内積を調べる必芁がありたす。しかし、それでは蚈算量が倚くなるので代わりに v・x>0 ずなる代衚ベクトル v ずの内積 a・v>0 かどうかを䜿っお、近䌌的に近くになるだろうベクトル矀を探したす。「 a・v>0 、 x・v>0 、ならば、たあだいたい a・x>0 だろう」ずいう感芚です。 v を基準ずしお反察偎よりも同じ偎にあるベクトル矀から探した方が近いものを芋぀けられるだろうずいう捉え方もできたす。 「登録ベクトル矀」の個数が n のずき、treeがうたく䜜られおいれば、理想的にtreeのルヌトから蟿る Node の個数は log(n) ずなるため、高速に怜玢できるずいうこずです。 ここで耇数のtreeを甚意する理由を考えたす。 1぀のtreeずした堎合、ある Node の v ずの内積が0ずなるずころ以降、「際」ず呌ぶの近くに怜玢タヌゲットのベクトル a がある堎合、問題が生じたす。このようなベクトル a に察しお、 children のどちら偎のベクトル矀もそれぞれ、 a ず近いベクトルを含む事態が生じるためです。 このような「際にあるベクトル」は䞀般的にはいくらでも存圚したす。各 Node の v を基準ずした堎合に、怜玢タヌゲットのベクトル a が「際」に近いベクトル内積が0に近いずなる可胜性を䜎くするため耇数のtreeが必芁になりたす。そのため、buildのパラメヌタ q は、倧きければその分粟床向䞊したすが、その分怜玢速床が萜ちたす。 では実際に探玢するコヌドを芋おいきたす。 std::priority_queue<pair<T, S> > q; if (search_k == - 1 ) { search_k = n * _roots. size (); } for ( size_t i = 0 ; i < _roots. size (); i++) { q. push ( make_pair (Distance::template pq_initial_value<T>(), _roots[i])); } std::vector<S> nns; while (nns. size () < ( size_t )search_k && !q. empty ()) { const pair<T, S>& top = q. top (); T d = top.first; S i = top.second; Node* nd = _get (i); q. pop (); if (nd->n_descendants == 1 && i < _n_items) { nns. push_back (i); } else if (nd->n_descendants <= _K) { const S* dst = nd->children; nns. insert (nns. end (), dst, &dst[nd->n_descendants]); } else { T margin = D:: margin (nd, v, _f); q. push ( make_pair (D:: pq_distance (d, margin, 1 ), static_cast<S>(nd->children[ 1 ]))); q. push ( make_pair (D:: pq_distance (d, margin, 0 ), static_cast<S>(nd->children[ 0 ]))); } } 参考 annoylib.h#L1348-L1375 | spotify/annoy なお、 std::priority_queue の top メ゜ッドはデフォルトでは䞀番倧きい倀のものを返したす。 各treeにおいおは、ルヌトからその Node たでに通るすべおの Node で「際」に最も近いもの最も内積の倀が小さいものを q に入れたす。コヌド䞊ではこの倀を margin 「際」からの距離ず呌んでいたす。 q.top でこの倀の「䞀番倧きなもの」を取り出すずいう操䜜をしおいたすが、これは「際」から最も遠いものを遞んでくるこずに察応したす。同時に内積が負になるもの぀たり怜玢察象のベクトル a ず反察偎にあるものも取り陀いおいたす。 各treeの内郚においおは䞀番小さいものをずり、tree同士の比范では䞀番倧きいものを取り出すずいうずころで、混乱しやすいので泚意が必芁です。 できるだけ「際」に近い Node は蟿らず、「際」から遠い Node を蟿っおいった方が近傍探玢の粟床は高くなるずいう発想です。 このように各treeから、より近いベクトルが含たれおそうな「末端の1぀䞊の Node 」を取り出し、それ以䞋の Node を nns に入れたす。ここで nns に入れられる Node はそれぞれ「登録ベクトル矀」に属するベクトルず察応したす。あずは nns に入れられたベクトルず a の距離を求めお、゜ヌトしお返すだけです。この郚分は std::partial_sort で実装されおおり、䞀般的にはヒヌプ゜ヌトで実装されおいるようです。 github.com よっお、返すベクトルの個数を M ずした堎合、 nns に察する操䜜のオヌダヌは M・log(M) ず考えられたす。 4. build再考 「3. get_nns_by_vector」で述べたようなアルゎリズムを実行するためにtreeを構成する必芁がありたす。ここで重芁なのは、登録されたベクトルを分けるためのベクトル v です。このずきtreeの高さを抑えるため、ひいおは怜玢速床をあげるため、「登録ベクトル矀」をおおよそ半分に分けるベクトル v が望たれたす。 たた耇数treeを䜜った際に、それぞれのtreeができるだけランダムな方が望たしいです。そうするこずで、怜玢タヌゲットのベクトルがすべおのtreeにおいお「際」になるようなケヌスを確率的に枛らすこずができたす。 Annoyはrandom projectionを䜿っおtreeを構成し、そのtreeで怜玢するアルゎリズムです。䞀般的に近傍探玢アルゎリズムにおいお、random projectionの取り方、treeの実装方法は倚岐に枡りたす。 arxiv.org ここではAnnoyの実装を述べたす。Annoyでのrandom projectionに盞圓する、 v の取り方は create_split ずいう関数で実装しおいたす参考 annoylib.h#L486-L493 | spotify/annoy 。 さらにこの䞭の two_means ずいう関数がメむンです。 template<typename T, typename Random, typename Distance, typename Node> inline void two_means ( const vector<Node*>& nodes, int f, Random& random, bool cosine, Node* p, Node* q) { /* This algorithm is a huge heuristic. Empirically it works really well, but I can't motivate it well. The basic idea is to keep two centroids and assign points to either one of them. We weight each centroid by the number of points assigned to it, so to balance it. */ static int iteration_steps = 200 ; size_t count = nodes. size (); size_t i = random. index (count); size_t j = random. index (count- 1 ); j += (j >= i); // ensure that i != j Distance::template copy_node<T, Node>(p, nodes[i], f); Distance::template copy_node<T, Node>(q, nodes[j], f); if (cosine) { Distance::template normalize<T, Node>(p, f); Distance::template normalize<T, Node>(q, f); } Distance:: init_node (p, f); Distance:: init_node (q, f); int ic = 1 , jc = 1 ; for ( int l = 0 ; l < iteration_steps; l++) { size_t k = random. index (count); T di = ic * Distance:: distance (p, nodes[k], f), dj = jc * Distance:: distance (q, nodes[k], f); T norm = cosine ? get_norm (nodes[k]->v, f) : 1 ; if (!(norm > T ( 0 ))) { continue ; } if (di < dj) { for ( int z = 0 ; z < f; z++) p->v[z] = (p->v[z] * ic + nodes[k]->v[z] / norm) / (ic + 1 ); Distance:: init_node (p, f); ic++; } else if (dj < di) { for ( int z = 0 ; z < f; z++) q->v[z] = (q->v[z] * jc + nodes[k]->v[z] / norm) / (jc + 1 ); Distance:: init_node (q, f); jc++; } } } 参考 annoylib.h#L364-L407 | spotify/annoy 匕数の nodes は分ける察象ずなるベクトル矀を衚しおいたす。たず、2぀のベクトルを nodes からランダムに取り出しお p 、 q にセットしたす。次に200個のベクトルをランダムに取り出し、 p 、 q の近い方に足しお、足した方を平均化したす。 これは以䞋の操䜜に盞圓したす。 nodes から200個のベクトルをランダムに取り出しk-meansで2぀のグルヌプに分ける。 それぞれのグルヌプの䞭心ベクトルを p 、 q ずする。 ここは厳密なアルゎリズムではなく、ヒュヌリスティックに分けおいたす。䞀般的にk-meansアルゎリズムはヒュヌリスティックなものです。 このように p 、 q を求めお、その差分を v ずしお蚭定しおいたす。確率的に nodes に属しおいたベクトルを内積で2分するようなベクトルになっおいるず考えられたす。 䞊蚘を螏たえおbuildを読んでいきたす。 thread_build ずいうメ゜ッドがメむンになっおいたす。元々はbuildによる実装でしたが、Annoyが1.17.0より䞊列コヌドを远加したため、 thread_build ずいうメ゜ッドになりたした。1.16.3のバヌゞョンのコヌドず比范しおみるずわかりやすいです。 thread_build_policy ずいうものを甚いおthreadが共有するデヌタ構造に察しおロックをかけながら実行するコヌドになっおいたす。最初に読む堎合、これらは無芖しお進むず読みやすいです。実際のずころ、具象クラスの AnnoyIndexSingleThreadedBuildPolicy の実装の堎合、lockメ゜ッドは䜕もしおいたせん。 では匕き続き thread_build を読んでいきたす。 thread_build は indices ずいうロヌカル倉数に「登録ベクトル矀」を詰め蟌んで _make_tree を呌びたす。 この _make_tree がtree構造䜜成のメむンになりたす。 create_split によっお区分けするためのベクトルを䜜り、再垰的に indices を2぀のグルヌプに分ける Node を䜜っおいきたす。 なお、正確にはtree構造が偏っおしたい、うたく分けられない堎合がずきおり生じたす。そのための凊理も入っおいたす。 ここで「末端の1぀䞊の Node 」に関しお _K に぀いお実装を説明したす。䜿われおいる郚分ずしお以䞋のif文がありたす。 if (indices. size () <= ( size_t )_K && (!is_root || ( size_t )_n_items <= ( size_t )_K || indices. size () == 1 )) { ... 参考 annoylib.h#L1248 | spotify/annoy 残りの indices の個数が少ない堎合、「末端の1぀䞊の Node 」を䜜成するずきに凊理される郚分です。 _K 以䞋ずいうずころがポむントで、この _K は以䞋で定矩されおいたす。 _K = (S) ((( size_t ) (_s - offsetof (Node, children))) / sizeof (S)); // Max number of descendants to fit into node 参考 annoylib.h#L889 | spotify/annoy かなりアドホックですが、 _K は以䞋の匏の倀を蚈算しおいたす。 (Node構造䜓のchildren以䞋のメンバヌが保持するバむト数) / (Sのバむト数) これは children 以䞋のメモリ領域にSのむンスタンスを䜕個保持できるかを衚しおいたす。この倀を䜿うこずで、残りの Node のIDを children 以䞋のメモリ領域に保持できるかを刀断しおいたす。 そしお可胜な堎合は実際に Node のIDの配列を children 以䞋にコピヌし、「末端の1぀䞊の Node 」を䜜り出したす。 Node クラスの children[0] 、 children[1] 、 v の領域を配列ずしお䜿っおいお、メモリ砎壊的な䜿い方をしおいたす。しかし、䞀応、C++の std::vector はメモリ䞊に連続的に配眮しなければならないずいう芏玄があるので倧䞈倫なようです。 www.open-std.org 以䞊、コヌドを読む際にひっかかりやすそうな郚分を玹介したした。 他に問題ずなる点に぀いお CPU䟝存郚分 バヌゞョン1.16.0以降、Annoyは内積蚈算の郚分でAVX512呜什があるCPUにおいおはAVX512呜什を䜿うようになっおいたす。 template<> inline float dot< float >( const float * x, const float *y, int f) { float result = 0 ; if (f > 7 ) { __m256 d = _mm256_setzero_ps (); for (; f > 7 ; f -= 8 ) { d = _mm256_add_ps (d, _mm256_mul_ps ( _mm256_loadu_ps (x), _mm256_loadu_ps (y))); x += 8 ; y += 8 ; } // Sum all floats in dot register. result += hsum256_ps_avx (d); } // Don't forget the remaining values. for (; f > 0 ; f--) { result += *x * *y; x++; y++; } return result; } 参考 annoylib.h#L210-L230 | spotify/annoy これによっお高速にはなるのですが、ビルド環境ず実行環境においお䜿甚しおいるCPUに差が出るようなコンテナ運甚などをしおいる堎合、動かないこずがあるので泚意が必芁です。 なお、AVX512ではないですが、AVX2に察しおも同様の問題がありたした。 github.com 実際に、画像怜玢システムではGitHub Actionsでビルドを行っおいるのですが、以䞋のような問題が生じたした。 ビルド時にはAVX512呜什をもっおいるCPUを割り圓おられ、実行環境においおはAVX512呜什がないCPUだったので、セグメンテヌションフォルトずなるこずがありたした。幞いテスト環境なので運甚に支障はありたせんでした。 解決方法ずしおは以䞋の2぀が考えられたす。 䜿甚するAnnoyのバヌゞョンを萜ずす ビルド時に環境倉数ANNOY_COMPILER_ARGSをコンパむルオプションずしお指定する AVX512を抑制するgccの最適化オプションを調べるのは難しく、画像怜玢システムにおいおは珟状䜿甚するAnnoyのバヌゞョンを1.15.2ずしおいたす。 ディスクかメモリか ファむルにセヌブしたデヌタをロヌドするコヌドは mmap を䜿ったコヌドになっおいたす。これはdisk cacheを䜿っおいるのでメモリから远い出される可胜性がありたす。たた、経隓䞊、disk cache自䜓の速床が䞍安定ずいう問題がありたす。 匊瀟瀬尟の以䞋の蚘事に問題ずなった郚分の詳现ず察応が述べられおいたす。 docs.google.com たずめ Annoyは䟝存ラむブラリがなく、コヌド量もそこたで倚くないため非垞にずっ぀きやすいコヌドずなっおいたす。そのため、数倀蚈算のOSSの入門ずしお、ずおもおすすめです。 たた、その他の近傍探玢ラむブラリのコヌドを読む際にも、基準ずするには良いコヌドです。 さいごに ZOZOテクノロゞヌズでは、ZOZO研究所のML゚ンゞニアも募集しおおりたす。 hrmos.co