CARTA TECH BLOG 2024-03-18T17:53:14+09:00 voyagegroup_tech Hatena::Blog hatenablog://blog/8454420450079359178 Treasure・Sunrise経験者に聞く!CARTAインターンに参加して「進化」したこと【技育祭2024登壇サマリ】 hatenablog://entry/6801883189091741862 2024-03-18T17:53:14+09:00 2024-03-18T17:53:14+09:00 この記事ではCARTAのエンジニアサマーインターンTreasure・Sunriseをわかりやすくご紹介します!また、後半ではTreasure・Sunriseに参加し、「進化」したことを経験者に聞いてみました。 内容は2024/03/17(日)に技育祭2024(春)登壇で話された内容を一部編纂しています。 Treasure概要 Treasureは、3週間のエンジニア向けインターンシップです。 チームでWebアプリケーション開発に取り組み、もの創りの楽しさや難しさを体験できるプログラムとなっています。 前半は、CARTA HOLDINGSのエンジニアによる Webエンジニアリング講義 があります。… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318174541.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この記事ではCARTAのエンジニアサマーインターンTreasure・Sunriseをわかりやすくご紹介します!また、後半ではTreasure・Sunriseに参加し、「進化」したことを経験者に聞いてみました。</p> <p>内容は2024/03/17(日)に技育祭2024(春)登壇で話された内容を一部編纂しています。</p> <h2 id="Treasure概要">Treasure概要</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318160615.jpg" width="1200" height="750" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>Treasureは、3週間のエンジニア向けインターンシップです。</strong> チームでWebアプリケーション開発に取り組み、もの創りの楽しさや難しさを体験できるプログラムとなっています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318160652.jpg" width="1200" height="668" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>前半は、CARTA HOLDINGSのエンジニアによる <strong>Webエンジニアリング講義</strong> があります。技術的な内容だけでなく、ユーザーヒアリングやアイデア出し、データモデリングの方法などプロダクト開発に必要なスキルを学べます。</p> <p><strong>後半は4人1組のチームに分かれ、講義で学んだ内容を活かしながら実際にプロダクト開発を行います。</strong> 各チームにはエンジニアが2名サポートとしてつき、手厚いフォローを受けられます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318160712.jpg" width="1200" height="664" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>昨年は、バックエンドにGo言語、フロントエンドにTypeScriptとReactを使用していましたが、今年は技術スタックが変更になる可能性もあります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318161148.png" width="1200" height="672" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>3週間という期間を通して、エンジニアとして必要な考え方や、チーム開発の進め方を実践的に学ぶことができる、貴重な機会となっています。</p> <p>Treasureへの申込みはこちらから!<br /></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcarta-recruit.snar.jp%2Fentry.aspx%3Fentryid%3Dd77faab1-5cfa-480d-a7d3-90c2c6f79e3a" title="株式会社CARTA HOLDINGS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://carta-recruit.snar.jp/entry.aspx?entryid=d77faab1-5cfa-480d-a7d3-90c2c6f79e3a">carta-recruit.snar.jp</a></cite></p> <h2 id="Sunriseの概要">Sunriseの概要</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318160744.jpg" width="1200" height="750" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>※現在、インターン内容を企画・検討中のため内容が少し変更になることがあります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318160825.png" width="1200" height="672" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Sunriseは、5日間のインフラパフォーマンス改善インターンシップです。<strong>目標は、渡されたアプリケーションを改善し秒間30,000リクエストを処理できるようにすることです。</strong></p> <p>最初に参加者には非常に遅い実装のGoアプリケーションとAWSインフラ環境を渡されます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318160904.jpg" width="1200" height="669" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>5日間のプログラム中、チームは90分の開発と20分のナレッジシェアを繰り返し行います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318161048.png" width="1200" height="670" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>各チームが試行錯誤しながらパフォーマンス改善に取り組むことで、お互いの進捗やアイデアを共有し刺激し合える、ゲーム感覚で楽しめるインターンシップとなっています。</strong> 参加者はGoとAWSの実践的なスキルを身につけつつ、インフラ改善の面白さを体験できます。</p> <p>※Sunrise: 4/1にエントリーオープン予定</p> <h1 id="TreasureSunriseでどう進化した">Treasure・Sunriseでどう進化した?</h1> <p>ここからはTreasure・Sunriseに参加し、「進化」したことを経験者に聞いてみました。内容は2024/03/17(日)に技育祭2024(春)登壇で話された内容を一部編纂しています。</p> <h2 id="登場人物">登場人物</h2> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318161558.png" style="border-radius:50%;" width="20%"></img><br /> すずけん: CARTA HOLDINGS 執行役員CTO。CARTA Generative AI Lab長。Treasure経験者。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318160950.jpg" style="border-radius:50%;" width="20%"></img><br /></p> <p>りゅーや: 22新卒エンジニア。Treasure経験者。株式会社DIGITALIO所属。デジタルポイントサービスデジコにおいてフロント・サーバ・インフラを幅広く担当。CMS導入や懸賞システムの開発を主導。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318161004.png" style="border-radius:50%;" width="20%"></img><br /></p> <p>とよ: 23新卒エンジニア。Treasure・Sunrise経験者。株式会社CARTA MARKETING FIRM所属。広告配信事業Zucksにおいてサーバサイド・インフラを主に担当。AWSコスト最適化やリエンゲージメント機能を開発。</p> <h2 id="インターンの探し方">インターンの探し方</h2> <p><strong>すずけん</strong>: 実際、皆さんがサマーインターンを探している時に、どういう軸でサマーインターンを探したのか聞かせてください。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318161342.jpg" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong><span style="color: #dd830c">りゅーや:</span></strong> 面白そうなインターンを探していたのですが、<strong>1つのインターンの中で、アイデア出しからリリースまで1つのサービスを作れるようなインターン</strong> を探していました。Treasureはまさにそれだったので応募しました。</p> <p><strong><span style="color: #dd830c"></strong><span style="color: #2196f3">とよ:</span><strong></span></strong> 高専1年生のときに、 <strong>先輩が「Treasureというインターンはいいぞ」と教えてくれました。</strong> そのときに知って、自分が就活のときに行ってみたいなと思っていたため参加しました。</p> <h2 id="Treasureを選んだ理由と良さ">Treasureを選んだ理由と良さ</h2> <p><strong>すずけん</strong>: ありがとう。では、2人がTreasureを選んだ理由はなんだろう?</p> <p><strong><span style="color: #dd830c">りゅーや:</span></strong> <strong>TreasureはWeb技術について体系的に学べる講義</strong>があります。実際に僕みたいな文系の人で独学で学んでいくと、体系的に学べる場所がないんですよね。プロのエンジニアから体系的に教えてもらえる機会っていうのはすごい魅力的**に感じました。</p> <p><strong>すずけん</strong>: ありがとう。<strong>実際、Treasureの講義ってめちゃくちゃコストをかけて作ってる</strong>んだよね。実際どうなの?</p> <p><strong><span style="color: #dd830c">りゅーや:</span></strong> <strong>めちゃくちゃ良かったです。</strong>ただ、もうついていくのに必死でもう全力でやってましたね。</p> <p><strong><span style="color: #2196f3">とよ:</span></strong> Treasureは、講義で参加者の技術レベルがある程度揃った状態で開発に入れます。元々技術力がある<strong>いろんな背景を持った人と同じ知識を共有した状態で開発が始まるのですごく体験が良かった</strong>です。</p> <p><strong><span style="color: #dd830c">りゅーや:</span></strong> あと、Treasureでは、現場のプロのエンジニアから自分が作ったプルリクエストに対してレビューをもらえたのがとても新鮮で...良い経験になりました。</p> <h2 id="Sunriseを選んだ理由と良さ">Sunriseを選んだ理由と良さ</h2> <p><strong>すずけん</strong>: とよは、Sunriseも参加してるけどどうだろう?</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318161409.jpg" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong><span style="color: #2196f3">とよ:</span></strong> Sunriseにいった理由は、Treasureの体験がとても良かったのがきっかけです。<strong>Sunriseは、アプリケーションのログデータと結果を見て、それを元に改善していく</strong>みたいな形で進めます。自分は研究でもデータ分析をやっており、そそられる内容だったので行きたいなって思いました。</p> <p><strong>すずけん</strong>: そうなんだよね。Sunriseの流れって実はめっちゃ業務に似てるよね。まさにフルサイクル(開発)って感じで、結果から次の1手を決めて改善していく流れが体験できるよね。</p> <h2 id="TreasureSunriseに参加して実務に活きてる">Treasure・Sunriseに参加して実務に活きてる?</h2> <p><strong>すずけん</strong>: 今は事業の中で開発していると思いますが、その中でTreasureの経験は活きていますか?</p> <p><strong><span style="color: #dd830c">りゅーや:</span></strong> <strong>めちゃくちゃ活きてると思います。</strong>Treasureでは開発期間が限られた中で、いかに良いものを作るかが求められるので、<strong>作りたいものを時間内に間に合わせる力が明らかについた</strong>と思います。その場でプロのエンジニアがずっと話を聞いててくれるので、そこでアドバイスをもらってフィードバックを受けてよりやり方を高速で変えて改善してって締め切りに間に合わせるっていうのがいい体験でしたね。</p> <p><strong>すずけん</strong>: なるほどね。とよはどうだろう?</p> <p><strong><span style="color: #2196f3">とよ:</span></strong> 私はSunriseの経験がめちゃくちゃ活きてると思っています。Sunriseでは、データを見て次にどうするかをチームで意思決定していくのですが、そこで<strong>自分の意見を言語化して伝えていくことの重要性を学びました。</strong>これは業務でも本当に重要だと感じています。</p> <p><strong>すずけん</strong>: そうだよね。言わないと伝わらないもんね</p> <h2 id="TreasureSunriseに参加して進化したこと">Treasure・Sunriseに参加して進化したこと</h2> <p><strong>すずけん</strong>: インターンに参加した結果、どのように進化したと感じますか?</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240318/20240318161433.jpg" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong><span style="color: #dd830c">りゅーや:</span></strong> <strong>1つのサービスを完成させるまでの完成力や、予測しないことが起きた時の対応力がめちゃくちゃついた</strong>と思います。どういう姿を目指しているのかとかやってみて分かったことが出てきたあとにそれをどう調整していくのか。何か完成させるまでの完成力というか、何か予測しないことが起きた時の対応力とかが僕はめちゃめちゃついた気がします。</p> <p><strong><span style="color: #2196f3">とよ:</span></strong> Sunriseではデータを見て改善していくサイクルを回すことが、まさに業務でやっていることそのものだな、と思ってて。その改善サイクルを回すために、チームへ情報を共有する言語化力が進化したと感じています。</p> <h2 id="エンジニアに必要な資質は貪欲さと速さ">エンジニアに必要な資質は貪欲さと速さ</h2> <p><strong>すずけん</strong>: エンジニアとして進化するために必要だと思うことは何ですか?</p> <p><strong><span style="color: #dd830c">りゅーや:</span></strong> 何よりも必要なのは貪欲さだと思っています。 自分は何がやりたいのか、そのやりたい思いをどこまで形にできるのかは、強い気持ちが必要だと思います。その気持ちさえあれば、どんな困難があってもいい感じになって、やりたいことは達成できるんじゃないかなと思います。</p> <p><strong><span style="color: #2196f3">とよ:</span></strong> 速さがすごく重要だと思っています。1回やってみて、フィードバックをもらって、さらに改善して次にトライするというサイクルを速く回していくほど、すごく成長していることを感じます。そのサイクルを速くして回数を稼ぐことが重要だと思います。</p> <h2 id="エントリー募集中">エントリー募集中</h2> <p>CARTAのインターンであるTreasureとSunriseでは、プロのエンジニアからの指導や、チームでの開発を通して、エンジニアとしての成長を得ることができます。ぜひ、やりたいことへの強い思いを持ち、フィードバックを受けながら高速で改善していくことに挑戦する学生の皆さんの参加をお待ちしています!</p> <p>Treasure:</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcarta-recruit.snar.jp%2Fentry.aspx%3Fentryid%3Dd77faab1-5cfa-480d-a7d3-90c2c6f79e3a" title="株式会社CARTA HOLDINGS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://carta-recruit.snar.jp/entry.aspx?entryid=d77faab1-5cfa-480d-a7d3-90c2c6f79e3a">carta-recruit.snar.jp</a></cite></p> <p>Sunrise: 4/1にエントリーオープン予定</p> voyagegroup_tech SSMドキュメントでSSH不要のサーバ構築パイプラインを組む hatenablog://entry/6801883189076098219 2024-03-18T09:00:00+09:00 2024-03-18T09:00:00+09:00 こんにちは。CCI インフラ部のTADAです。 Amazon Linux 2023でホスト名・タイムゾーン・言語・nginx...などansible-playbookコマンドで行っていた設定作業をSSMドキュメント AWS-ApplyAnsiblePlaybooks を使って自動化してみました! 目次 困っていたこと SSMドキュメントとは 事前準備 パイプラインの流れ 1. ansible-playbookをzipファイルにまとめS3にアップロード 2. SSM-RunCommand を送信し、AWS-ApplyAnsiblePlaybooks を実行 3. S3に書き出された標準出力をダウ… <p><figure class="figure-image figure-image-fotolife" title="title"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cci_tada/20240304/20240304122807.jpg" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p> <p>こんにちは。CCI インフラ部のTADAです。</p> <p>Amazon Linux 2023でホスト名・タイムゾーン・言語・nginx...などansible-playbookコマンドで行っていた設定作業をSSMドキュメント AWS-ApplyAnsiblePlaybooks を使って自動化してみました!</p> <p><strong>目次</strong></p> <ul class="table-of-contents"> <li><a href="#困っていたこと">困っていたこと</a><ul> <li><a href="#SSMドキュメントとは">SSMドキュメントとは</a></li> </ul> </li> <li><a href="#事前準備">事前準備</a></li> <li><a href="#パイプラインの流れ">パイプラインの流れ</a><ul> <li><a href="#1-ansible-playbookをzipファイルにまとめS3にアップロード">1. ansible-playbookをzipファイルにまとめS3にアップロード</a></li> <li><a href="#2-SSM-RunCommand-を送信しAWS-ApplyAnsiblePlaybooks-を実行">2. SSM-RunCommand を送信し、AWS-ApplyAnsiblePlaybooks を実行</a></li> <li><a href="#3-S3に書き出された標準出力をダウンロード表示">3. S3に書き出された標準出力をダウンロード・表示</a></li> <li><a href="#ちょっと工夫">ちょっと工夫</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h4 id="困っていたこと">困っていたこと</h4> <p>これまでCCIではAmazon Linuxの設定作業をAnsibleでコード化していましたが<br> Ansible実行ホストからAmazon LinuxまではSSH接続できる必要があり下記の制約がありました</p> <ul> <li>Amazon LinuxはPublicサブネットに配置しなければならず、外部のどこからでも経路的に到達可能なリスクがある</li> <li>↑をケアするためにAnsible実行ホストに固定のグローバルIPを持たせてAmazon Linux側で受信を絞っていたがフィルタ追加が面倒 &amp; 固定のグローバルIP必須 <ul> <li>CI/CDパイプラインで固定のグローバルIPを使おうとするとself-hostedなRunnerを用意する必要がある</li> </ul> </li> </ul> <p>このような制約を解消するため AWS SSMドキュメントのAWS-ApplyAnsiblePlaybooksを試してみました</p> <p>試す中でコマンドを定型化した方が良いなと感じたのでCI/CDパイプライン(GitlabRunner)で自動化しています<br> ※shell scriptが書ければパイプライン実行環境はなんでもOK</p> <h5 id="SSMドキュメントとは">SSMドキュメントとは</h5> <ul> <li>SSM AgentがインストールされたOSはAWS Systems Manager (SSM)を通じてリモート操作が可能です <ul> <li>Amazon Linuxはデフォルトでamazon-ssm-agent が自動起動します</li> </ul> </li> <li>SSMで操作可能になったサーバをSSMマネージドノードと呼びます</li> <li>SSMマネージドノードに対し、実行する操作を定義したものがSSMドキュメントです</li> </ul> <h4 id="事前準備">事前準備</h4> <ul> <li>構築対象のAmazon LinuxはSSMマネージドノードである必要があります <ul> <li>AmazonSSMManagedInstanceCore ポリシーが付与されたIAM Roleをアタッチ</li> <li>インターネットへの疎通性 <ul> <li>Outbound通信ができればよいためPrivateサブネットでもOK</li> </ul> </li> </ul> </li> <li>今回はansible-playbookファイルの受け渡しにS3を利用しています。そのためS3バケットとPut/Get権限も必要です</li> <li>執筆時点で AWS-ApplyAnsiblePlaybooks は Amazon Linux 2023 に対応していません <ul> <li>そのため自作のSSMドキュメントで2023に対応させる必要がありますが、便宜上 AWS-ApplyAnsiblePlaybooks と呼称しています</li> <li>どのようなSSMドキュメントを自作すればよいかは下記を参考にさせて頂きました</li> <li>参考: <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fsakai00kou%2Fitems%2F2041ac2170faaa39a33b" title="AWS-ApplyAnsiblePlaybooksをAmazon Linux 2023でも使えるようにする。 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/sakai00kou/items/2041ac2170faaa39a33b">qiita.com</a></cite></li> </ul> </li> </ul> <h4 id="パイプラインの流れ">パイプラインの流れ</h4> <p><figure class="figure-image figure-image-fotolife" title="パイプライン"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/cci_tada/20240304/20240304161715.png" width="1200" height="159" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>パイプライン</figcaption></figure></p> <ol> <li>Runner上でansible-playbookをzipファイルにまとめS3にアップロードします</li> <li>Runner上でSSM-RunCommandを送信し、Amazon Linuxを対象に AWS-ApplyAnsiblePlaybooks を実行します<br> AWS-ApplyAnsiblePlaybooksは、実行するplaybookをS3から取得できます。ansible実行結果はAmazon Linux上の標準出力に出るため標準出力をS3に書き出すようにします</li> <li>最後にS3に書き出された標準出力をRunnerにダウンロード、Runnerのjob実行結果として表示します</li> </ol> <p>変数サンプル</p> <pre class="code lang-sh" data-lang="sh" data-unlink>variables: S3_URI: <span class="synStatement">&quot;</span><span class="synConstant">s3://</span><span class="synPreProc">${BUCKET_NAME}</span><span class="synStatement">&quot;</span> S3_VHOST_URI: <span class="synStatement">&quot;</span><span class="synConstant">https://</span><span class="synPreProc">${BUCKET_NAME}</span><span class="synConstant">.s3.ap-northeast-1.amazonaws.com</span><span class="synStatement">&quot;</span> COMMON_PREFIX: <span class="synStatement">&quot;</span><span class="synConstant">run-command</span><span class="synStatement">&quot;</span> ANSIBLE_DOCUMENT_NAME: <span class="synStatement">&quot;</span><span class="synConstant">Custom-ApplyAnsiblePlaybooksAL2023</span><span class="synStatement">&quot;</span> ANSIBLE_DOCUMENT_VERSION: <span class="synStatement">&quot;</span><span class="synPreProc">$$</span><span class="synConstant">DEFAULT</span><span class="synStatement">&quot;</span> ANSIBLE_S3_KEY: <span class="synStatement">&quot;</span><span class="synPreProc">${COMMON_PREFIX}</span><span class="synConstant">/</span><span class="synPreProc">${HOSTNAME}</span><span class="synConstant">.zip</span><span class="synStatement">&quot;</span> ANSIBLE_UPLOAD_PATH: <span class="synStatement">&quot;</span><span class="synPreProc">${S3_URI}</span><span class="synConstant">/</span><span class="synPreProc">${ANSIBLE_S3_KEY}</span><span class="synStatement">&quot;</span> ANSIBLE_SOURCE_PATH: <span class="synStatement">&quot;</span><span class="synPreProc">${S3_VHOST_URI}</span><span class="synConstant">/</span><span class="synPreProc">${ANSIBLE_S3_KEY}</span><span class="synStatement">&quot;</span> ANSIBLE_S3_OUTPUT: <span class="synStatement">&quot;</span><span class="synPreProc">${COMMON_PREFIX}</span><span class="synConstant">/</span><span class="synPreProc">${HOSTNAME}</span><span class="synConstant">/output/ansible</span><span class="synStatement">&quot;</span> </pre> <p>※${BUCKET_NAME}は事前に用意した自バケット名を指定<br> ※"Custom-ApplyAnsiblePlaybooksAL2023"は自作した2023対応のAWS-ApplyAnsiblePlaybooks<br></p> <h5 id="1-ansible-playbookをzipファイルにまとめS3にアップロード">1. ansible-playbookをzipファイルにまとめS3にアップロード</h5> <p>AWS-ApplyAnsiblePlaybooks はソースタイプとしてGithub/S3をサポートしており、S3を利用する場合は単一の .zip ファイルまたはディレクトリ構造を引数として渡します<br> 今回はzipファイルとして渡します<br></p> <pre class="code lang-sh" data-lang="sh" data-unlink>job: script: - zip <span class="synSpecial">-r</span> <span class="synPreProc">${HOSTNAME}</span>.zip . - aws s3 <span class="synStatement">cp</span> <span class="synPreProc">${HOSTNAME}</span>.zip <span class="synPreProc">${ANSIBLE_UPLOAD_PATH}</span> </pre> <h5 id="2-SSM-RunCommand-を送信しAWS-ApplyAnsiblePlaybooks-を実行">2. SSM-RunCommand を送信し、AWS-ApplyAnsiblePlaybooks を実行</h5> <p>aws ssm send-command で AWS-ApplyAnsiblePlaybooksを実行します<br> 実行は非同期になりますので、コマンド実行時に得られる CommandId を引数に aws ssm get-command-invocation で実行結果を確認します<br></p> <pre class="code lang-sh" data-lang="sh" data-unlink> <span class="synComment"># NameタグからINSTANCE_IDを取得</span> <span class="synComment"># 設定対象のNameタグはユニークなので1台分の値が返ることを期待</span> <span class="synComment"># instance-state-code=16 : running</span> - <span class="synStatement">&gt;</span> <span class="synIdentifier">INSTANCE_IDS</span>=<span class="synPreProc">$(</span><span class="synSpecial">aws ec2 describe-instances </span> <span class="synSpecial"> --query </span><span class="synStatement">&quot;</span><span class="synConstant">Reservations[].Instances[].{InstanceId:InstanceId}</span><span class="synStatement">&quot;</span> <span class="synSpecial"> --filters </span><span class="synStatement">&quot;</span><span class="synConstant">Name=tag:Name,Values=</span><span class="synPreProc">${HOSTNAME}</span><span class="synStatement">&quot;</span><span class="synSpecial"> </span><span class="synStatement">&quot;</span><span class="synConstant">Name=instance-state-code,Values=16</span><span class="synStatement">&quot;</span> <span class="synSpecial"> </span><span class="synPreProc">)</span> - <span class="synStatement">&gt;</span> <span class="synStatement">if [</span> <span class="synPreProc">$(</span><span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synPreProc">${INSTANCE_IDS}</span><span class="synStatement">&quot;</span><span class="synConstant"> </span><span class="synStatement">|</span><span class="synSpecial"> jq </span><span class="synStatement">'</span><span class="synConstant">. | length</span><span class="synStatement">'</span><span class="synPreProc">)</span> <span class="synStatement">-eq</span> <span class="synConstant">1</span> <span class="synStatement">];</span> <span class="synStatement">then</span> <span class="synIdentifier">INSTANCE_ID</span>=<span class="synPreProc">$(</span><span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synPreProc">${INSTANCE_IDS}</span><span class="synStatement">&quot;</span><span class="synConstant"> </span><span class="synStatement">|</span><span class="synSpecial"> jq -r </span><span class="synStatement">'</span><span class="synConstant">.[].InstanceId</span><span class="synStatement">'</span><span class="synPreProc">)</span> <span class="synStatement">else</span> <span class="synStatement">exit</span> <span class="synConstant">1</span> <span class="synStatement">fi</span> <span class="synComment"># ansible-playbook実行</span> <span class="synComment"># --check にしたいときは CHECK_MODE=True , 設定変更したいときは CHECK_MODE=False</span> <span class="synComment"># ${PLAYBOOK} にはzipファイル内のplaybookファイルのpathを指定</span> - <span class="synStatement">&gt;</span> <span class="synIdentifier">RESULT</span>=<span class="synPreProc">$(</span><span class="synSpecial">aws ssm send-command </span> <span class="synSpecial"> --document-name </span><span class="synStatement">&quot;</span><span class="synPreProc">${ANSIBLE_DOCUMENT_NAME}</span><span class="synStatement">&quot;</span> <span class="synSpecial"> --document-version </span><span class="synPreProc">${ANSIBLE_DOCUMENT_VERSION}</span> <span class="synSpecial"> --targets </span><span class="synStatement">&quot;</span><span class="synConstant">[{</span><span class="synSpecial">\&quot;</span><span class="synConstant">Key</span><span class="synSpecial">\&quot;</span><span class="synConstant">:</span><span class="synSpecial">\&quot;</span><span class="synConstant">InstanceIds</span><span class="synSpecial">\&quot;</span><span class="synConstant">, </span><span class="synSpecial">\&quot;</span><span class="synConstant">Values</span><span class="synSpecial">\&quot;</span><span class="synConstant">:[</span><span class="synSpecial">\&quot;</span><span class="synPreProc">${INSTANCE_ID}</span><span class="synSpecial">\&quot;</span><span class="synConstant">]}]</span><span class="synStatement">&quot;</span><span class="synSpecial"> </span> <span class="synSpecial"> --parameters </span> <span class="synSpecial"> </span><span class="synStatement">&quot;</span><span class="synConstant">{ </span><span class="synSpecial">\&quot;</span><span class="synConstant">SourceType</span><span class="synSpecial">\&quot;</span><span class="synConstant"> : [</span><span class="synSpecial">\&quot;</span><span class="synConstant">S3</span><span class="synSpecial">\&quot;</span><span class="synConstant">],</span> <span class="synConstant"> </span><span class="synSpecial">\&quot;</span><span class="synConstant">SourceInfo</span><span class="synSpecial">\&quot;</span><span class="synConstant"> : [</span><span class="synSpecial">\&quot;</span><span class="synConstant">{</span><span class="synSpecial">\\\&quot;</span><span class="synConstant">path</span><span class="synSpecial">\\\&quot;</span><span class="synConstant">:</span><span class="synSpecial">\\\&quot;</span><span class="synPreProc">${ANSIBLE_SOURCE_PATH}</span><span class="synSpecial">\\\&quot;</span><span class="synConstant">}</span><span class="synSpecial">\&quot;</span><span class="synConstant">],</span> <span class="synConstant"> </span><span class="synSpecial">\&quot;</span><span class="synConstant">InstallDependencies</span><span class="synSpecial">\&quot;</span><span class="synConstant"> : [</span><span class="synSpecial">\&quot;</span><span class="synConstant">True</span><span class="synSpecial">\&quot;</span><span class="synConstant">],</span> <span class="synConstant"> </span><span class="synSpecial">\&quot;</span><span class="synConstant">PlaybookFile</span><span class="synSpecial">\&quot;</span><span class="synConstant"> : [</span><span class="synSpecial">\&quot;</span><span class="synPreProc">${PLAYBOOK}</span><span class="synSpecial">\&quot;</span><span class="synConstant">],</span> <span class="synConstant"> </span><span class="synSpecial">\&quot;</span><span class="synConstant">ExtraVariables</span><span class="synSpecial">\&quot;</span><span class="synConstant"> : [</span><span class="synSpecial">\&quot;</span><span class="synConstant">SSM=True</span><span class="synSpecial">\&quot;</span><span class="synConstant">],</span> <span class="synConstant"> </span><span class="synSpecial">\&quot;</span><span class="synConstant">Check</span><span class="synSpecial">\&quot;</span><span class="synConstant"> : [</span><span class="synSpecial">\&quot;</span><span class="synPreProc">${CHECK_MODE}</span><span class="synSpecial">\&quot;</span><span class="synConstant">],</span> <span class="synConstant"> </span><span class="synSpecial">\&quot;</span><span class="synConstant">Verbose</span><span class="synSpecial">\&quot;</span><span class="synConstant"> : [</span><span class="synSpecial">\&quot;</span><span class="synConstant">-v</span><span class="synSpecial">\&quot;</span><span class="synConstant">],</span> <span class="synConstant"> </span><span class="synSpecial">\&quot;</span><span class="synConstant">TimeoutSeconds</span><span class="synSpecial">\&quot;</span><span class="synConstant"> : [</span><span class="synSpecial">\&quot;</span><span class="synConstant">3600</span><span class="synSpecial">\&quot;</span><span class="synConstant">]</span> <span class="synConstant"> }</span><span class="synStatement">&quot;</span><span class="synSpecial"> </span> <span class="synSpecial"> --timeout-seconds </span><span class="synConstant">600</span><span class="synSpecial"> </span> <span class="synSpecial"> --max-concurrency </span><span class="synStatement">&quot;</span><span class="synConstant">50</span><span class="synStatement">&quot;</span><span class="synSpecial"> </span> <span class="synSpecial"> --max-errors </span><span class="synStatement">&quot;</span><span class="synConstant">0</span><span class="synStatement">&quot;</span><span class="synSpecial"> </span> <span class="synSpecial"> --output-s3-bucket-name </span><span class="synStatement">&quot;</span><span class="synPreProc">${BUCKET_NAME}</span><span class="synStatement">&quot;</span><span class="synSpecial"> </span> <span class="synSpecial"> --output-s3-key-prefix </span><span class="synStatement">&quot;</span><span class="synPreProc">${ANSIBLE_S3_OUTPUT}</span><span class="synStatement">&quot;</span><span class="synPreProc">)</span> - <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synPreProc">${RESULT}</span> - <span class="synIdentifier">CommandId</span>=<span class="synPreProc">$(</span><span class="synStatement">echo</span><span class="synConstant"> </span><span class="synPreProc">${RESULT}</span><span class="synConstant"> </span><span class="synStatement">|</span><span class="synSpecial"> jq -r </span><span class="synStatement">'</span><span class="synConstant">.Command.CommandId</span><span class="synStatement">'</span><span class="synPreProc">)</span> <span class="synComment"># 実行結果取得: Pending/InProgress以外のステータスになるまで複数回Get</span> <span class="synComment"># Success以外になっても後続のstdoutダウンロードを行いたかったため Waiters(aws ssm wait command-executed) は使いませんでした</span> - <span class="synStatement">&gt;</span> <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synPreProc">$(</span><span class="synSpecial">seq </span><span class="synConstant">0</span><span class="synSpecial"> </span><span class="synConstant">9</span><span class="synPreProc">)</span>;<span class="synStatement">do</span> <span class="synStatement">sleep</span> <span class="synConstant">30</span> <span class="synIdentifier">COMMAND_RESULT</span>=<span class="synPreProc">$(</span><span class="synSpecial">aws ssm get-command-invocation --command-id </span><span class="synPreProc">${CommandId}</span><span class="synSpecial"> --instance-id </span><span class="synPreProc">${INSTANCE_ID})</span> <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synPreProc">${COMMAND_RESULT}</span> <span class="synStatement">if [</span> <span class="synPreProc">$(</span><span class="synStatement">echo</span><span class="synConstant"> </span><span class="synPreProc">${COMMAND_RESULT}</span><span class="synConstant"> </span><span class="synStatement">|</span><span class="synSpecial"> jq -r </span><span class="synStatement">'</span><span class="synConstant">.StatusDetails</span><span class="synStatement">'</span><span class="synPreProc">)</span> <span class="synStatement">!=</span> <span class="synStatement">&quot;</span><span class="synConstant">Pending</span><span class="synStatement">&quot;</span> <span class="synStatement">];then</span> <span class="synStatement">if [</span> <span class="synPreProc">$(</span><span class="synStatement">echo</span><span class="synConstant"> </span><span class="synPreProc">${COMMAND_RESULT}</span><span class="synConstant"> </span><span class="synStatement">|</span><span class="synSpecial"> jq -r </span><span class="synStatement">'</span><span class="synConstant">.StatusDetails</span><span class="synStatement">'</span><span class="synPreProc">)</span> <span class="synStatement">!=</span> <span class="synStatement">&quot;</span><span class="synConstant">InProgress</span><span class="synStatement">&quot;</span> <span class="synStatement">];then</span> <span class="synStatement">break</span> <span class="synStatement">fi</span> <span class="synStatement">fi</span> <span class="synStatement">done</span> </pre> <p>※ "${ANSIBLE_SOURCE_PATH}" = ""<a href="https://$">https://$</a>{BUCKET_NAME}.s3.${REGION}.amazonaws.com/${KEY}" の形式<br>  前出の${ANSIBLE_UPLOAD_PATH}とは形式が異なる点に注意<br></p> <h5 id="3-S3に書き出された標準出力をダウンロード表示">3. S3に書き出された標準出力をダウンロード・表示</h5> <pre class="code lang-sh" data-lang="sh" data-unlink> <span class="synComment"># stdout/stderrをS3からdownloadし出力</span> - <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synConstant">Download stdout/stderr...</span><span class="synStatement">&quot;</span> - aws s3 <span class="synStatement">cp</span> <span class="synPreProc">${S3_URI}</span>/<span class="synPreProc">${ANSIBLE_S3_OUTPUT}</span>/<span class="synPreProc">${CommandId}</span>/<span class="synPreProc">${INSTANCE_ID}</span>/awsrunShellScript/runShellScript/stdout ./stdout - aws s3 <span class="synStatement">cp</span> <span class="synPreProc">${S3_URI}</span>/<span class="synPreProc">${ANSIBLE_S3_OUTPUT}</span>/<span class="synPreProc">${CommandId}</span>/<span class="synPreProc">${INSTANCE_ID}</span>/awsrunShellScript/runShellScript/stderr ./stderr - <span class="synStatement">cat</span> stdout - <span class="synStatement">cat</span> stderr <span class="synComment"># 実行結果がSuccess以外のステータスの場合Runner JobもFailedとする</span> - <span class="synStatement">&gt;</span> <span class="synStatement">if [</span> <span class="synPreProc">$(</span><span class="synStatement">echo</span><span class="synConstant"> </span><span class="synPreProc">${COMMAND_RESULT}</span><span class="synConstant"> </span><span class="synStatement">|</span><span class="synSpecial"> jq -r </span><span class="synStatement">'</span><span class="synConstant">.StatusDetails</span><span class="synStatement">'</span><span class="synPreProc">)</span> <span class="synStatement">!=</span> <span class="synStatement">&quot;</span><span class="synConstant">Success</span><span class="synStatement">&quot;</span> <span class="synStatement">];</span> <span class="synStatement">then</span> <span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">&quot;</span><span class="synConstant">StatusDetails: </span><span class="synPreProc">$(</span><span class="synStatement">echo</span><span class="synConstant"> </span><span class="synPreProc">${COMMAND_RESULT}</span><span class="synConstant"> </span><span class="synStatement">|</span><span class="synSpecial"> jq -r </span><span class="synStatement">'</span><span class="synConstant">.StatusDetails</span><span class="synStatement">'</span><span class="synPreProc">)</span><span class="synStatement">&quot;</span> <span class="synStatement">exit</span> <span class="synConstant">1</span> <span class="synStatement">fi</span> </pre> <h5 id="ちょっと工夫">ちょっと工夫</h5> <p>s3からダウンロードした stdout にansibleの実行結果が出力されますが、デフォルトでは白黒で見づらくなってしまいました<br> ansible.cfg に下記のように付けることで変更箇所に色が付き、ssh実行しているときと同じような見え方になりました<br></p> <pre class="code" data-lang="" data-unlink># ansible.cfg [defaults] force_color = True</pre> <h4 id="最後に">最後に</h4> <p>aws ssm send-commandに渡す引数が多い・実行結果の取得を非同期に行う必要がある、という2点には気を付ける必要がありますが<br> 1度パイプラインを組んでしまえば非常に簡単にansible実行が行えるようになりました<br></p> <p>パイプラインの実行ホストはグローバルIPが変動する環境が多いと思いますが、SSM経由であればセキュアな通信経路を実現できそうです<br></p> <p>またSSHで初期構築していたころは、最初にec2-userで初期構築用のplaybook -> 別ユーザーに切り替えて ec2-user でログインできなくするためのplaybookと多段実行を行っていました<br> SSMは実行するplaybookをS3からダウンロード・localhostに対して適用する形(ansible-playbook -c local)になるため、初期構築~ec2-user無効化まで1回で行えるようになりました<br></p> <p>AWS-ApplyAnsiblePlaybooks 以外にも有用なSSMドキュメントは多く準備されていますので、いろいろ試して構築作業の質を高めていきたいと思います!</p> cci_tada CARTA の「技術力」はあなたのイメージする「技術力」ではないかも hatenablog://entry/6801883189087349301 2024-03-11T17:39:37+09:00 2024-03-11T17:43:17+09:00 CARTA における「技術力」というのは、一般的にイメージされているものよりも非常に広い意味を持っています。本エントリでは、こうした独特な「技術力」を少しでも感じていただけるようご案内いたします。 ナビゲーターは CARTA HOLDINGS CTO 室(兼 Lighthouse Studio CTO)の海老原 @co3k でお送りいたします。 まず入口としてCARTAのエンジニアが目指すVisionに触れていただきます。「フルサイクル開発」における日々の営みを想像していただきます。そして「CARTAにおける技術力」の、その特殊性について目の当たりにいたします。 TL;DR CARTA におけ… <p>CARTA における「技術力」というのは、一般的にイメージされているものよりも非常に広い意味を持っています。本エントリでは、こうした独特な「技術力」を少しでも感じていただけるようご案内いたします。 ナビゲーターは CARTA HOLDINGS CTO 室(兼 Lighthouse Studio CTO)の海老原 <a href="https://twitter.com/co3k">@co3k</a> でお送りいたします。</p> <p>まず入口としてCARTAのエンジニアが目指すVisionに触れていただきます。「フルサイクル開発」における日々の営みを想像していただきます。そして「CARTAにおける技術力」の、その特殊性について目の当たりにいたします。</p> <hr /> <h1 id="TLDR">TL;DR<a id="TL;DR"></a></h1> <p>CARTA における「技術力」は、専門的な技量にとどまらず問題の本質を見極め、価値を届けるために必要な技量を包含しています。</p> <ul> <li><strong>技術力</strong> <ul> <li>CARTA における技術力は、単なるコーディング能力や技術知識の蓄積を超えたもの</li> <li>問題の本質を見極め、持続可能な価値を提供するための広範な能力を指す</li> </ul> </li> <li><p><strong>CARTA Tech Vision</strong></p> <ul> <li>CARTA のエンジニア組織としての価値観や将来像の指針</li> <li>技術を使用して創造的な仕事に人々が取り組める世界を作ることを目指す</li> </ul> </li> <li><p><strong>フルサイクル開発</strong></p> <ul> <li>開発から運用までをエンジニア自身が担うプラクティス</li> </ul> </li> <li><p><strong>技術力評価会</strong></p> <ul> <li>エンジニアが相互に技術力を評価し合う制度</li> <li>「実現力」と「改善力」の2つの観点から評価</li> </ul> </li> <li><p><strong>まとめ</strong></p> <ul> <li>CARTA における「技術力」は、個々のエンジニアのスキルセットを超えた組織全体としての強みと成長の源泉である</li> </ul> </li> </ul> <hr /> <h1 id="CARTA-Tech-Vision">CARTA Tech Vision<a id="CARTA Tech Vision"></a></h1> <p>まず大前提として…… CARTA のエンジニア組織としての価値観や将来像の指針となる「CARTA Tech Vision」というものがあります。作成時の想いなどは、 <a href="https://techblog.cartaholdings.co.jp/entry/carta-tech-vision">CARTAのエンジニア組織、ひいてはテクノロジーに対する将来への指針として、CARTA Tech Visionを作成しました - CARTA TECH BLOG</a> をご覧ください。</p> <p>現時点の CARTA Tech Vision は以下の画像のとおりです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240305/20240305175942.png" width="1000" height="562" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>個々の未来像、価値観、習慣についての詳細な解説も公開されている(<a href="https://cartaholdings.co.jp/engineering/tech-vision">CARTA Tech Vision</a>)のですが、ここでは極力平易な文章で、かいつまんで説明していきますね!</p> <blockquote><p>未来像: 「人にもっと、創造的な仕事を」</p></blockquote> <p>人々が考えて物事を実現したり解決したりする能力は素晴らしいものです。そうした、ある意味では <strong>人にしかできない仕事に人々が取り組めるようにする世界を、技術の力でもって作り上げていこう</strong>、というわけですね。</p> <h2 id="価値観">価値観<a id="価値観"></a></h2> <p>がんばって一行解説していきます。</p> <ul> <li><p><strong>本質志向</strong></p> <ul> <li><strong>表面的な問題ではなく根本的な問題を特定</strong>し、徹底的に調査、議論、実行、そして振り返りをおこなうことで、表面的な解決策ではなく根本的な解決策を講じられていたかを自ら追求します</li> </ul> </li> <li><p><strong>共に信頼し、共に創る</strong></p> <ul> <li>ほとんどオリジナルの文の抜粋になりますが、 <strong>「セールスも、開発も、運用も、経営者も、全員一緒になって価値提供をするために信じ合い、任せ合い、協力し合う」</strong> ということに尽きます</li> </ul> </li> <li><p><strong>価値を届け続ける</strong></p> <ul> <li>価値は一度届ければそれで終わりではありません。 <strong>継続的に価値を届け続け、検証し続け、改善し続ける</strong> ことで、正解のわからない問題についてより最適なアプローチを図っていくということです</li> </ul> </li> </ul> <h2 id="習慣">習慣</h2> <p>引き続きがんばって一行解説していきます。</p> <ul> <li><p><strong>質は速さ</strong></p> <ul> <li><span style="color: #888888">質とスピードは相反するものと考えられがちですが、実は両立すること、そして、むしろ質の低さがスピードの低下を招くことも多いため、 <strong>質にもしっかり投資して、速さというリターンを手に入れよう</strong> ということです</span></li> </ul> </li> <li><p><strong>推測するな、計測せよ</strong></p> <ul> <li><span style="color: #888888">「原因はこれだ」「どうせこういう問題があるはずだ」と決めつけて解決に向かったものの、実際には見当外れだった、ということは少なくありません。 <strong>推測だけで解決にあたるのではなく、まずはその仮説を検証する</strong> というのが肝心です</span></li> </ul> </li> <li><p><strong>毎日試す</strong></p> <ul> <li><span style="color: #888888">日々少しずつ改善し、評価し……を繰り返していく、つまり、 <strong>毎日改善サイクルを回し続ける</strong> ということです。なかには失敗もありますが、このサイクルの下では <strong>小さく、早く失敗することができるため、次なる改善へと素早く繋げられます</strong></span></li> </ul> </li> <li><p><strong>先人に感謝し、還元する</strong></p> <ul> <li><span style="color: #888888">ソフトウェアエンジニアの世界は、 <strong>ノウハウやコードをオープンに共有し合う</strong> ことによって、業界全体として高められてきました。そのおかげで、数年前では困難だった課題も、今日では容易に解くことができるようになっています。この事実を忘れずに、私たちもまた事業活動のなかで得た知識や経験を業界に還元していきます</span></li> </ul> </li> <li><p><strong>最良のコードは、コードなし</strong></p> <ul> <li><span style="color: #888888">「コードを書く」ことは目的ではなく、あくまでも <strong>「問題を解決する」ことが目的</strong> です。問題を徹底的に分析し、根本的な解決を図った結果、表面的な問題解決のためのコードを書く必要がなくなった、ということがままあります。 <strong>必ずしも「コードを書く」という手段に固執しないように</strong> しましょう……という意なのですが、誤解されがちな項目であり、改善の余地ランキング堂々の 1 位 (海老原調べ) です</span></li> </ul> </li> </ul> <hr /> <h1 id="フルサイクル開発">フルサイクル開発<a id="フルサイクル開発"></a></h1> <p>CARTA のエンジニア組織は社外から「フルサイクル開発」というプラクティスを実践していることでも知られています。このプラクティスは <a href="https://netflixtechblog.com/full-cycle-developers-at-netflix-a08c31f83249">Netflix 社が提唱した Full Cycle Developers</a> がベースになっています。</p> <p>CARTAのフルサイクル開発に対する考え方は、CTO の鈴木 (<a href="https://twitter.com/suzu_v">@suzu_v</a>) がわかりやすいエントリ(<a href="https://techblog.cartaholdings.co.jp/entry/carta-full-cycle">CARTAとフルサイクル開発者 - CARTA TECH BLOG</a>)を公開しているのでそこから引用していきます (<strong>強調</strong>は引用者)。</p> <p><figure class="figure-image figure-image-fotolife" title="Netflix Full Cycle Developers"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240305/20240305180004.png" width="559" height="488" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Netflix Full Cycle Developers</figcaption></figure></p> <p>要約すると、以下のように示されています。</p> <ul> <li><p><strong>「開発したものが運用する」</strong> のがフルサイクル開発者。<strong>責任を外部化せず</strong>、直接のフィードバックループを開発チームに内在させる。ソフトウェア開発者が設計実装のみならず、 <strong>サポート、デプロイなどのイテレーションをすべて自分でこなしている</strong></p></li> <li><p>運用のペインは開発者自ら解消する</p></li> <li><p>ソフトウェア開発者はツールのちからにより、ライフサイクルを最適化させられている</p></li> <li><p>ライフサイクルを最適化することにより、 <strong>サイロを解消</strong> し、 <strong>学習とフィードバックを最適化</strong> して、 <strong>価値のデリバリー速度を高める</strong> のがフルサイクル開発である</p></li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240305/20240305180034.png" width="1000" height="542" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>フルサイクル開発者は、コードを書くこと(開発)だけでなく顧客への価値提供が仕事です。</strong> CARTAでは論理的に考え、あるべき仕組みを作れることがエンジニアの振る舞いとして求められています。</p> <hr /> <h1 id="技術力評価会において評価される技術力">技術力評価会において評価される「技術力」<a id="技術力評価会において評価される技術力"></a></h1> <p>この Tech Vison や「フルサイクル開発」という価値観を踏まえて、じゃあ個々人のスキル評価機会であるところの「技術力評価会」では、いったい何を評価されるのか、という話に移っていきます。</p> <h1 id="技術力評価会とは">技術力評価会とは<a id="技術力評価会とは"></a></h1> <p>ちょっと待って、「技術力評価会」ってそもそも何でしたっけ?</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240305/20240305180055.png" width="1184" height="1034" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>端的に言えば、以下のような評価制度です。</p> <ul> <li><p>他部署のエンジニア2名に90分で半期の仕事を説明</p></li> <li><p>なぜその仕事をやったのか・どのようにアプローチしたのかを共に議論する</p></li> <li><p>後日、フィードバックレポートが提出され、事業部評価を加味して評価確定</p></li> </ul> <p>フィードバックレポートは、GitHubで全社に公開され、学びが循環するようになっています。仕組みの詳細や狙いや前CTO makogaの<a href="https://speakerdeck.com/makoga/understanding-voyagegroups-technology-assessment-in-5-minites">5分でわかる技術力評価会</a> にまとまっています。</p> <p><br /></p> <h2 id="技術力評価会において意識される評価軸">技術力評価会において意識される「評価軸」<a id="技術力評価会において意識される評価軸"></a></h2> <p>さて、技術力評価会がどういう催しなのかご理解いただけたところで、本題です。</p> <p>評価者が評価をおこなうときに意識される指標として、「評価軸」というものがあります。これは一種のガイドラインのようなもので、ものすごく単純化してしまえば、この「評価軸」に合致していれば合致しているほど「技術力」は高く評価できる傾向にある、というものです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240305/20240305180115.png" width="1000" height="486" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この<strong>「評価軸」は、「実現力」と「改善力」のふたつの観点から成っています。</strong></p> <p>それぞれ詳細を見ていきましょう。</p> <p><br /></p> <h2 id="実現力">実現力<a id="実現力"></a></h2> <h4 id="何が課題で何が必要と考えたか">何が課題で何が必要と考えたか<a id="何が課題で何が必要と考えたか"></a></h4> <p>これは Tech Vision における「本質志向」と強くリンクしていますね。 -  <strong>課題をしっかり見つめ、精査する</strong> こと - <strong>表面的な課題の解決だけではなく、根本的な解決になるよう努力する</strong> こと - それらに囚われすぎないようにすること</p> <p>つまり、<strong>考えすぎもダメだし、考えなさすぎてもダメ</strong> ということです。</p> <h4 id="構築運用したシステムは妥当か">構築・運用したシステムは妥当か<a id="構築運用したシステムは妥当か"></a></h4> <p>端的に言えば「<strong>その場限りの誤魔化しではなく、必要な価値を届けられているか</strong> 」をみています。</p> <p>これは当たり前じゃない? って思われるかもしれないんですけれど</p> <p>「これそもそも要件満たしてなくないですか?」 「サービスイン当初の規模感なら耐えられるかもしれないけれど、すぐに破綻しますよね?」 「これは特定のユースケースしか対応できないですよね?」 「この辺セキュリティ脆弱性ないですか?」</p> <p>など、技術者視点からするとそれとわかるけど、できあがったものを表面的に見るだけでは見過ごされてしまう問題って数多くあるわけですね。 <strong>そうした問題に対して、見かけ倒しの解決策で逃げるのではなく、根本的な解決策を講じるなどして、しっかりモノを作れているか、必要な価値を届けられているか</strong> 、を見ています。</p> <h4 id="プロジェクトの進め方は妥当か">プロジェクトの進め方は妥当か<a id="プロジェクトの進め方は妥当か"></a></h4> <p>現実にはなかなか「当初の予定通りに作って終わり」というわけにはいきません。プロジェクトを進めていくうちに新たに明らかになる課題というのは出てくるものです。関係者やユーザーへのインタビューなどを通じて軌道修正することもあるでしょう。私たちはフルサイクル開発の実践者として、このような <strong>進行過程での課題発生や計画の変更に柔軟に対応するプロジェクトの進め方</strong> をすることをもまた求められています。</p> <p><br /></p> <h2 id="改善力">改善力<a id="改善力"></a></h2> <h4 id="システム的な改善は妥当か">システム的な改善は妥当か<a id="システム的な改善は妥当か"></a></h4> <p>システムを運用、改善していくにあたり、「スムーズな運用や改善を阻害するポイント」「オペレーションミスを誘発しやすいポイント」というのが明らかになってきます。 <strong>ひとつひとつは「頑張る」「気をつける」で済むような小さな痛みでも、積もり積もれば大きなビジネスインパクト</strong> に繋がりかねません。 <strong>目先の新しい施策のみにとらわれずに既存システムのこうした芽を丹念に摘んでいく</strong> ことも大事です。</p> <h4 id="ビジネス的な改善への貢献は妥当か">ビジネス的な改善への貢献は妥当か<a id="ビジネス的な改善への貢献は妥当か"></a></h4> <p>ただ闇雲にものづくりをするのではなくて、 <strong>論理的にビジネスをとらえ、自分が実行する施策をしっかりと実効性のある仮説検証のサイクルに組み込む</strong> ことが肝心です。こちらは Tech Vision における「価値を届け続ける」「価値を届け続ける」とリンクしていそうです。</p> <hr /> <h1 id="まとめCARTA-における技術力">まとめ:CARTA における「技術力」<a id="まとめcarta-における技術力"></a></h1> <p>これまでに見てきたように、 <strong>CARTA における「技術力」というのは、</strong>コーディング能力や技術知識の蓄積などエンジニア職としての専門的な技量(これを「狭義の技術力」などと呼んで区別することもあります)だけに留まらず、<strong>問題の本質を見極め、価値を届けるために必要な技量すべてを包含しています。</strong></p> <p>これらは「CARTA Tech Vision」において方向性が示され、「フルサイクル開発」の発想により具体化され、「技術力評価会」の機会において広く確認されています。</p> <p>チーム内外との信頼関係の構築、共同での創造、そして学び続ける姿勢によって醸成され続けてきた CARTA の「技術力」は、日々の業務だけでなく、CARTA の持続的な成長とイノベーションを支える根幹となっています。</p> <p>これは、<strong>もはや立派な文化資本であり、単に個々のエンジニアのスキルセットを超えた、組織全体としての強みと成長の源泉であると言っても決して言い過ぎではない</strong>でしょう。</p> <hr /> <p>合わせて読みたい</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fcarta-full-cycle" title="CARTAとフルサイクル開発者 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/carta-full-cycle">techblog.cartaholdings.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fcto_read_all_assessment" title="【80人17,949行】CTOがすべての評価資料を読む理由 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/cto_read_all_assessment">techblog.cartaholdings.co.jp</a></cite></p> co3k AWS x dentsu GenAI Hackathonに参加してきた hatenablog://entry/6801883189086290104 2024-03-08T09:00:00+09:00 2024-03-08T09:00:01+09:00 AWS x dentsu GenAI Hackathonとは CCIのkawak3nです。今回、AWSと電通グループが主催する生成AIをテーマにしたハッカソンに参加してきたので 3日のハッカソンでAWSの生成AI関連サービスを使ったチャットボットサービスを作ってみた Amazon Kendraを使うとチャットボットの回答に会社独自の情報を気軽に含ませることができた という話を書こうと思います。 AWS x dentsu GenAI Hackathonは現在成長の目まぐるしい生成AIをテーマにしたハッカソンです。 ハッカソンの流れは 2時間 x 5日間の事前講義でAWSの生成AI関連サービスにつ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kawak3n/20240227/20240227153918.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="AWS-x-dentsu-GenAI-Hackathonとは">AWS x dentsu GenAI Hackathonとは</h1> <p>CCIのkawak3nです。今回、AWSと電通グループが主催する生成AIをテーマにしたハッカソンに参加してきたので</p> <ul> <li>3日のハッカソンでAWSの生成AI関連サービスを使ったチャットボットサービスを作ってみた</li> <li>Amazon Kendraを使うとチャットボットの回答に会社独自の情報を気軽に含ませることができた</li> </ul> <p>という話を書こうと思います。</p> <p>AWS x dentsu GenAI Hackathonは現在成長の目まぐるしい生成AIをテーマにしたハッカソンです。 ハッカソンの流れは</p> <ul> <li>2時間 x 5日間の<strong>事前講義</strong>でAWSの生成AI関連サービスについて勉強</li> <li>3日間の開発期間でチームごとに<strong>サービス開発</strong></li> <li>最後に制作物の発表資料を提出</li> </ul> <p>という感じでした。AWSのサービスの中でも普段業務の中でなかなか触れることのない生成AIに関するサービスを色々と試しながら、自分たちのプロダクトを作ることができるいい機会でした。</p> <h1 id="5日間の事前講義">5日間の事前講義</h1> <p>事前講義では<a href="https://aws.amazon.com/jp/bedrock/">Amazon Bedrock</a>、<a href="https://aws.amazon.com/jp/sagemaker/">Amazon SageMaker</a>や<a href="https://aws.amazon.com/jp/kendra/">Amazon Kendra</a>などのサービスの説明とそれらのサービスを使った自然言語生成や画像生成などの解説をしていただきました。</p> <p>個人的に注目したのは、Kendraというサービスで、ExcelやPDFなどの<a href="https://docs.aws.amazon.com/kendra/latest/dg/index-document-types.html">さまざまなフォーマット</a>のデータを学習させて検索可能にすることができます。このKendraのサービスと大規模言語モデル(LLM)を組み合わせることで検索拡張生成(RAG)を実装することができます。</p> <p>RAGというのは、LLMに外部のデータを渡して検索可能にすることで、汎用的な知識しか持たないLLMにユーザー独自のデータを回答に含めさせる手法です。</p> <p><figure class="figure-image figure-image-fotolife" title="RAG概念図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kawak3n/20240301/20240301122519.png" alt="RAG&#x6982;&#x5FF5;&#x56F3;" width="705" height="184" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>RAG概念図</figcaption></figure></p> <p>例えば、</p> <ul> <li>会社で保有している大量のPDF資料を学ばせることで会社独自の情報を簡単に検索</li> <li>会社で提供しているサービスに関する過去の問い合わせ一覧が入っているExcelを学ばせることで、ユーザーの問い合わせに応対するチャットボットを構築</li> </ul> <p>とさまざまな応用例が考えられます。</p> <p>さらに、Kendraを使ってRAGを構築する場合には、LLMが回答をする際に参考にしたデータソースの場所も返すことができます!つまり、構築したRAGチャットが回答する際に、どのPDFのどのページを参考にしたというリンク付きのリファレンスリストを回答と一緒にユーザーに返すことができるのです!これはかなり便利そう。</p> <p>こんなことを考えながら、5日間の事前講義が終わりました。</p> <h1 id="3日間のハッカソン開始">3日間のハッカソン開始!</h1> <p>いよいよハッカソンの開始です。</p> <p>僕たちのチームは様々ある自社の広告に関連するサービスの案内ができるチャットボットを作ろうという話になりました。</p> <p>会社名は知っているけど、実際自分たちの商品をどうやって広告してくれるのかイメージが湧かないユーザーがチャットに自分たちの商品に関する情報を入力することによって、適切なサービスを提案して、かつ、そのサービスの資料へのリンクが提案されるようなイメージです。</p> <p>アーキテクチャとしてはまず、S3に設置した弊社サービスを概説した資料をKendraに学習させます。資料をインデックスしたKendraにBedrockを通して問い合わせるような構成でいこうと考えました。Bedrockは学習済みモデルを基盤モデルとして選択し、簡単に生成AIアプリケーションのAPIを作成できます。使えるモデルには、Claudeのようなテキスト生成モデルやStable Diffusionのような画像生成モデルなどがあります。</p> <p><figure class="figure-image figure-image-fotolife" title="アーキテクチャ図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kawak3n/20240301/20240301104445.png" alt="&#x30A2;&#x30FC;&#x30AD;&#x30C6;&#x30AF;&#x30C1;&#x30E3;&#x56F3;" width="804" height="382" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>アーキテクチャ図</figcaption></figure></p> <h2 id="まずは触って検証してみる">まずは触って検証してみる</h2> <p>チーム全員がAWSの生成AI系のサービスについて無知な状態から始まったので、まずは実際にAWSのサービスを触ってみて、どれくらいの精度が出るものなのか確かめるところから始めました。僕たちの考えたサービスは</p> <ul> <li>ユーザーが訴求したい商品に合ったサービスを提案</li> <li>サービスを提案した根拠の資料のリンクを表示</li> </ul> <p>が根幹の機能なので、この2つの機能が問題なく実現できそうかを検証してみました。</p> <p>Kendraの自社サービスに関するデータを与えるところから始めます。</p> <p>使うデータはいろいろ模索しましたが、結果的に会社のサービス概要を説明したPDF資料を使用しました。使いたいと思っていたサービス概要を説明した資料はPPTX形式で作られており、Kendraに対応している形式であるPPT形式にエクスポートして学習しようと試みたのですが、なぜかうまくいかず。原因を特定するのに時間がかかりそうだったので、PPTX形式の資料をPDF形式へエクスポートして対応しました。</p> <p>30分程度でKendraへのインデックスが終わり、実際に弊社サービスに多少関連のあるような単語を入力してみると資料の内、関連のある資料のページリンクを返してくれました。KendraはCLIからリクエストをしなくとも、AWS Console上からリクエストをして動作確認をするインターフェースが備わっているので、その機能を使って学習したKendraの動作確認をします。</p> <p><figure class="figure-image figure-image-fotolife" title="know-how-now.cci.co.jpをクローリングしたKendraのレスポンス"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kawak3n/20240227/20240227111156.png" width="1200" height="730" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>know-how-now.cci.co.jpをクローリングしたKendraのレスポンス</figcaption></figure></p> <p>弊社サービスの概要PDFを学習して動作確認した時のスクリーンショットを撮り忘れてしまったので、参考画像↑に載せたのは、その前に試しで学習させていた弊社の運営しているWebサイトの<a href="https://know-how-now.cci.co.jp">KnowHowNow</a>を学習させた際のスクリーンショットになります。Kendraのデータ参照先はS3やGoogle DriveだけでなくウェブサイトのURLを指定してクローリングができるのが便利だなと感じました。</p> <p>これで資料のリンクを表示させる能力は十分かなと感じたので、次にユーザーが訴求したい商品を問い合わせたときに適切なサービスが提案されるか検証していきます。</p> <p>「日本酒を売りたいです。販売支援してほしい。」みたいに問い合わせを投げた際に、いい感じにサービスが提案されて欲しいので、Bedrockには</p> <pre class="code" data-lang="" data-unlink>あなたはお客さんの悩みに応じてCCIという会社のサービスを適切に提案するAIアシスタントです。 CCIには&lt;cci-services&gt;&lt;/cci-services&gt;のサービスが存在しています。 &lt;cci-services&gt; * DataDig * KNOTBOX * Commerce Container * Social AdTrim * Lakebi * CCI ANALYTICS * Media Digital Connect * Z世代研究会 * Lifestyle Digital Connect &lt;/cci-services&gt; あなたは&lt;cci-services&gt;&lt;/cci-services&gt;のサービスの中から一つ以上のサービスを選択して、お客さんに提案する必要があります。</pre> <p>のような感じにプロンプトを与えてあげます。その上でBedrockとKendraを繋げてあげると、問い合わせに応じていい感じにサービスを提案してくれるようになりました!</p> <p>あとはハッカソンの時間ギリギリまでサービスを調整したり資料を作っていたりしていたら、あっという間に制限時間になってしまいました。</p> <h1 id="成果物">成果物</h1> <p>最終的に次のようなサービスを作ることができました!</p> <p><figure class="figure-image figure-image-fotolife" title="サービス画面 (1) 質問をする様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kawak3n/20240227/20240227142829.png" width="1200" height="574" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>サービス画面 (1) 質問をする様子</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="サービス画面 (2) サービスを提案している様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kawak3n/20240227/20240227142849.png" width="1200" height="568" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>サービス画面 (2) サービスを提案している様子</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="サービス画面 (3) 提案したサービスに関連する資料のリンクを提示している様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kawak3n/20240227/20240227142906.png" width="1200" height="575" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>サービス画面 (3) 提案したサービスに関連する資料のリンクを提示している様子</figcaption></figure></p> <p>「日本酒を売りたいです。販売支援してほしい。」という問い合わせに対してCommerce Containerという弊社のサービスを提案しながらユーザーの問い合わせに回答することができていそうです。</p> <h1 id="所感">所感</h1> <p>生成AIをプロダクトに組み込むことは結構手間がかかると考えていたのですが、AWSのサービスを利用することにより(技術的な意味で)簡単に組み込むことができるんだなと感じました。 また、LLMだけだとビジネスへの応用が限られてきますが、AWS Kendraを使うことによって気軽にLLMに自社独自のデータを組み込めることが可能になり、様々な応用例が生まれるかなと考えています。</p> kawak3n 2024 Snowflake Data Superheroes に CARTAの @pei0804 が選出されました!❄️ hatenablog://entry/6801883189081745163 2024-02-09T17:37:15+09:00 2024-02-27T11:21:59+09:00 技術広報しゅーぞーです。今回は CARTAにとってのGood Newsをお知らせします!❄️ 2024 Snowflake Data Superheroes に @pei0804 が選出! 2024/02/08 に 2024 Snowflake Data Superheroes が発表されました。 今回は、世界で80名、日本人は11名が選出 ❄️ なんと今回は、我らが CARTA から @pei0804 が選出されました! CARTAからのリリースはこちらをご参照ください: 「2024 Snowflake Data Superheroes!」にCARTA MARKETING FIRMのエンジニ… <p>技術広報しゅーぞーです。今回は CARTAにとってのGood Newsをお知らせします!❄️</p> <h2 id="2024-Snowflake-Data-Superheroes-に-pei0804-が選出">2024 Snowflake Data Superheroes に @pei0804 が選出!</h2> <p>2024/02/08 に <a href="https://medium.com/snowflake/introducing-the-2024-snowflake-data-superheroes-1962bc20079f">2024 Snowflake Data Superheroes</a> が発表されました。<br/> 今回は、<strong>世界で80名、日本人は11名が選出</strong> ❄️</p> <p>なんと今回は、我らが CARTA から <strong><a href="https://twitter.com/pei0804">@pei0804</a> が選出</strong>されました!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240209/20240209152137.png" width="1200" height="628" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>CARTAからのリリースはこちらをご参照ください: <a href="https://cartaholdings.co.jp/news/20240209_1/">&#x300C;2024 Snowflake Data Superheroes!&#x300D;&#x306B;CARTA MARKETING FIRM&#x306E;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2; &#x8FD1;&#x68EE; &#x6DF3;&#x5E73;&#x304C;&#x9078;&#x51FA; &#xFF5C; &#x682A;&#x5F0F;&#x4F1A;&#x793E;CARTA HOLDINGS</a></p> <h2 id="Snowflake-Data-Superheroes-とは">Snowflake Data Superheroes とは</h2> <p>そもそもSnowflake Data Superheroes Programとはなんでしょうか?</p> <p><a href="https://medium.com/snowflake/introducing-the-2024-snowflake-data-superheroes-1962bc20079f">Introducing the 2024 Snowflake Data Superheroes! | by Howard Lio | Snowflake | Feb, 2024 | Medium</a> 曰く、</p> <blockquote><p>データ スーパーヒーローは、データ クラウドでのユースケースや専門知識の共有を楽しむコンテンツ作成者、講演者、エバンジェリストです。</p></blockquote> <p><a href="https://medium.com/snowflake/2022%E5%B9%B4snowflake%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B9%E3%83%BC%E3%83%91%E3%83%BC%E3%83%92%E3%83%BC%E3%83%AD%E3%83%BC%E3%81%AE%E3%81%94%E7%B4%B9%E4%BB%8B-28d2ef0765f6">2022&#x5E74;Snowflake&#x30C7;&#x30FC;&#x30BF;&#x30B9;&#x30FC;&#x30D1;&#x30FC;&#x30D2;&#x30FC;&#x30ED;&#x30FC;&#x306E;&#x3054;&#x7D39;&#x4ECB; - Snowflake - Medium</a> 曰く、</p> <blockquote><p>Snowflakeコミュニティのメンバーは誰もがすでにデータヒーローですが、データスーパーヒーローとはその中でも特にコミュニティ内で積極的に活動している、まさにSnowflakeエキスパートの中のエキスパートと言えるでしょう</p></blockquote> <p>と、 <strong>Snowflakeに専門的な知見を持ち、コミュニティに貢献するSnowflakeのエキスパート中のエキスパート</strong> に与えられる称号だそう。すごい...。</p> <h2 id="pei0804のアウトプットをご紹介">@pei0804のアウトプットをご紹介</h2> <p>そんな @pei0804 の アウトプットの一部をご紹介!2022 - 2023年で取り組んだデータエンジニアリングに関する知見が濃縮されています。ぜひご一読ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fsnowflake-data-platform-vision" title="Snowflakeと共に過ごした一年間。その進化過程と未来へのVision - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/snowflake-data-platform-vision">techblog.cartaholdings.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fsnowflake-dbt-data-platform-vision" title="Snowflakeの力を引き出すためのdbtを活用したデータ基盤開発の全貌 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/snowflake-dbt-data-platform-vision">techblog.cartaholdings.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fselect-cloud-cost-optimize-cmf" title="SELECTを使った手間なしSnowflakeコスト最適化 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/select-cloud-cost-optimize-cmf">techblog.cartaholdings.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2F2022%2F12%2F22%2F130000" title="データをモデリングしていたら、組織をモデリングし始めた話 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/2022/12/22/130000">techblog.cartaholdings.co.jp</a></cite></p> <h2 id="pei0804-が所属する事業会社をご紹介">@pei0804 が所属する事業会社をご紹介</h2> <p>世界的にデータエンジニアの重要性が注目される中、データウェアハウスとしての本流を担うSnowflake。CARTAも重要領域として注力を行っています。</p> <p>CARTAには20近い事業があり、@pei0804 が所属する CARTA MARKETING FIRM はマーケティングファームとして、クライアントのマーケティング課題解決に努めるべく、デジタルマーケティングを中心に価値を提供しています。</p> <p>CARTA MARKETING FIRM: <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcarta-marketing-firm.co.jp%2F" title="株式会社CARTA MARKETING FIRM(カルタマーケティングファーム)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://carta-marketing-firm.co.jp/">carta-marketing-firm.co.jp</a></cite></p> <p>CARTAのエンジニア文化: <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcartaholdings.co.jp%2Fengineering%2F" title="エンジニアリング | 株式会社CARTA HOLDINGS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cartaholdings.co.jp/engineering/">cartaholdings.co.jp</a></cite></p> voyagegroup_tech RLoginが使い勝手良くて周りに仲間が増えて欲しい人の叫び hatenablog://entry/6801883189065067898 2024-02-05T09:00:00+09:00 2024-02-05T09:00:03+09:00 3行まとめ RLoginをざっくばらんにお勧めする記事だよ TeraTermやPoderosaと一部比較するよ RLoginはタブ管理・画面分割ができてプロトコルハンドラ登録、SFTP可能と痒い所に手が届くよ ご挨拶 どうも、CCITechのまっつんです。自動化大好き人間です。この辺の記事の変なタイトルつけてる人は私です。 突然ですがWindowsユーザのエンジニアさん、ターミナルソフトは何を使っていますか? TeraTerm? Poderosa? WSL経由のターミナル? いやいや断然「RLogin」を推させてくださいッ!!!!!!!! Macユーザさんの事を全然考慮してなくてすみません。お… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/htn6/20240129/20240129172219.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="3行まとめ">3行まとめ</h3> <ul> <li>RLoginをざっくばらんにお勧めする記事だよ</li> <li>TeraTermやPoderosaと一部比較するよ</li> <li>RLoginはタブ管理・画面分割ができてプロトコルハンドラ登録、SFTP可能と痒い所に手が届くよ</li> </ul> <h4 id="ご挨拶">ご挨拶</h4> <p>どうも、CCITechのまっつんです。自動化大好き人間です。<a href="https://techblog.cartaholdings.co.jp/archive/category/%E8%87%AA%E5%8B%95%E5%8C%96">この辺の記事</a>の変なタイトルつけてる人は私です。<br/> 突然ですがWindowsユーザのエンジニアさん、ターミナルソフトは何を使っていますか?<br/> TeraTerm?<br/> Poderosa?<br/> WSL経由のターミナル?<br/> いやいや断然「RLogin」を推させてくださいッ!!!!!!!!<br/> Macユーザさんの事を全然考慮してなくてすみません。<s>お仕事でMac使っていないんだもの…</s><br/> <s>MobaXterm?使ってないです。</s></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkmiya-culti.github.io%2FRLogin%2F" title="rlogin/telnet/ssh(クライアント)ターミナルソフト" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://kmiya-culti.github.io/RLogin/">kmiya-culti.github.io</a></cite></p> <h4 id="突然どうした">突然どうした?</h4> <p>社内では結構サーバログインにTeraTermとかpowershell(MS-DOS)を使ったsshとかを見かけるんですよね。<br/> でもそれだと、そのターミナル(ウィンドウ)がどこに接続されているか一発で分からない。<br/> そもそもサーバログインする時点でヒューマンエラーのフラグが立っているようなものなので、なるべくなら見た目でどのサーバで作業をしているかを判断したい訳です。<br/> そんな時、MS-DOSでもなくPowerShellでもなくTeraTermでもなく、RLoginを使うのがお勧めってわけです。</p> <h3 id="RLoginと他ターミナルとの比較">RLoginと他ターミナルとの比較</h3> <h4 id="ざっくり比較表">ざっくり比較表</h4> <p>体感、以下のような差があると思いました。</p> <table> <thead> <tr> <th>項目</th> <th>RLogin</th> <th>TeraTerm</th> <th>Poderosa</th> </tr> </thead> <tbody> <tr> <td>設定値共有</td> <td>◎<br/>最低exeとiniのみ!</td> <td>○</td> <td>○</td> </tr> <tr> <td>タブ管理・画面分割</td> <td>○</td> <td>×</td> <td>○</td> </tr> <tr> <td>踏み台経由の自動ログイン</td> <td>○</td> <td>△<br/>※マクロを使えば可</td> <td>○</td> </tr> <tr> <td>プロトコルハンドラ登録</td> <td>○</td> <td>×</td> <td>×</td> </tr> <tr> <td>GUIベースのファイル転送</td> <td>○</td> <td>×</td> <td>×</td> </tr> <tr> <td>遊び心</td> <td>○</td> <td>×</td> <td>△<br/>※Poderosa Terminal 5なら某SW風エフェクトがありますが公開停止?</td> </tr> </tbody> </table> <h4 id="軽いのに設定値の共有がラク">軽いのに設定値の共有がラク</h4> <ol> <li><a href="https://github.com/kmiya-culti/RLogin/releases/">RLoginをダウンロード</a>して</li> <li>iniファイル(空ファイル)を作成して</li> <li>exeファイルをクリックして</li> <li>各種設定した後、iniファイルを共有する</li> </ol> <p>これだけで、同じ環境を使うユーザは鍵ファイル情報以外の全ての設定を共通化できるわけです。<br/> exeとiniだけあれば同じ設定・情報で動かせる、何て便利!!<br/> 例えば私は本番環境アクセス時の外観と検証環境アクセス時の外観を変えています。<br/> これは設定ファイルで管理できるので、「この色は本番だ」「この色は検証だ」と一発で分かるようになります。<br/> この内容、exeファイルとiniファイルがあれば同じチームの他のメンバーにも簡単に共有できます。<br/> イメージ的には、こんな感じ。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/htn6/20240118/20240118171404.png" width="1200" height="784" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><br/> ※↑のイメージは検証用のコンソールと本番用のコンソールが同居していて非常に危険なので、良い子の皆はこんな画面構成にしちゃ、絶対ダメだぞ!</p> <h4 id="タブ管理画面分割できる">タブ管理・画面分割できる</h4> <p>TeraTermで出来ないところです。Poderosaでは出来ます。<br/> これ、何が良いかっていうと、例えば踏み台と踏み台先、あるいは複数サーバ接続が必要な時にウィンドウを開かなくて済むんですね。<br/> タブ管理が出来ないと、あっちこっちターミナルコンソールのウィンドウを切り替えたりしていて、思いがけず違うサーバ上でコマンド打とうとしていた、というヒヤリハットを経験した人もいるはず。<br/> 私はそのヒヤリハットを受けて早い段階でTeraTermから脱却しました。<br/> 2号機構成+踏み台サーバで作業をすることが多いので、大体こんな風に画面分割して作業しています。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/htn6/20231208/20231208130232.png" width="1200" height="780" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="プロトコルハンドラ登録が出来る">プロトコルハンドラ登録が出来る</h4> <p><code>ssh://IPアドレス</code>のアレです。<br/> RLogin側で作った接続設定に対して右クリック>「プロトコルハンドラに登録する」を選んでおくと、<code>ssh://</code>から始まるリンクをRLoginで開く候補に出来ます。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/htn6/20240119/20240119134831.png" width="311" height="395" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>例えば、HTMLファイルにサーバ一覧的なものを用意して、それぞれ<code>ssh://xxx.xxx.xxx.xxx</code>のリンクを設定しておけば、そのリンクをクリックした時にRLoginが起動して接続しに行ってくれるわけです。<br/> IPアドレスさえ把握して羅列してしまえば、1回1回サーバIPを調べてRLoginを立ち上げてIPアドレス入力してサーバログインなんてしなくても良いんです。<br/> (1サーバずつRLogin側に定義を登録しておけばいいじゃないか、と言う皆さん、まあちょっとお待ちください)<br/> こんな感じ。</p> <pre class="code" data-lang="" data-unlink>&lt;html&gt; &lt;body&gt; &lt;table&gt; &lt;tr&gt; &lt;td&gt;対象&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;a href=&#34;ssh://xxx.xxx.xx1&#34;&gt;XX1サーバログイン&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;a href=&#34;ssh://xxx.xxx.xx2&#34;&gt;XX2サーバログイン&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/table&gt; &lt;/body&gt; &lt;/html&gt;</pre> <table> <tr> <td>対象</td> </tr> <tr> <td><a>XX1サーバログイン</a></td> </tr> <tr> <td><a>XX2サーバログイン</a></td> </tr> </table> <p>※クリックするとRLoginが開いてxxx.xxx.xxxのサーバログインが行われるイメージ</p> <h4 id="踏み台経由のログインが簡単">踏み台経由のログインが簡単</h4> <p>例えば踏み台→本番サーバにログインと言うのは多々あると思います。<br/> そんな時、別設定で踏み台サーバにログインする設定を作っておき、本番サーバ接続の定義側に設定すれば踏み台サーバでsshコマンドを打たなくてもOKなわけです。<br/> たった一行、されど一行、一行コマンドが減るだけでヒューマンエラーは回避できます。<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/htn6/20240119/20240119141037.png" width="1200" height="778" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><br/> 下領域が踏み台サーバのターミナルコンソールなのですが、自動で踏み台サーバにログイン&本番サーバへSSH接続ログインしているポートフォワード扱いになっているため、タブ選択を間違えて本番サーバでコマンド実行しているつもりで間違えて踏み台サーバでコマンドを実行していた!と言う事もありません。</p> <h4 id="他プログラムを立ち上げなくてもGUIベースでファイル転送が出来る">他プログラムを立ち上げなくてもGUIベースでファイル転送が出来る</h4> <p>何といってもこれでしょう。<br/> RLoginはTeraTerm、Poderosaと違い、GUIベースで実施できます。<br/> TeraTermもPoderosaも簡易的なSCPは可能ですが、パスを知らないとファイル転送できません。一括選択以外のSCPがものすごく面倒です。<br/> ましてや存在しない・権限が不足しているパスのファイルを指定してSCPしようとする、なんて状況もあり得ます。<br/> でもRLoginはGUIベースと言うか、SSH接続(22番ポートを利用)しているので、SFTPが可能です。<br/> フォルダパス手動入力箇所に変なパスを入力しない限り、GUIベースなので存在しないパスにアクセスがそもそもできませんし、権限も見れるので変なファイルのやり取りも未然に防げます。<br/> 例えるなら、省機能版WinSCPが備わっているようなものです。<br/> まあWinSCPで出来るようなことがRLoginのSFTPで出来るかと言うと、全てが出来るわけじゃない(所有者変更とかエクスプローラツリー表示とか)ですが、ターミナルソフトを起動しつつWinSCPを起動しつつ、複数プロセスを視認しながら作業をするよりかはよっぽどスリムですよね!<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/htn6/20240119/20240119150648.png" width="1200" height="645" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="おまけ-SSMセッションマネージメントアクセスもこれ一本">おまけ. SSM(セッションマネージメント)アクセスもこれ一本</h4> <p>弊社ではAWS EC2サーバへはSSM接続経由でのログインを採用しています。 <br/> RLoginではプロキシ設定画面でProxy Commandが設定できるので、以下をパイプするだけで慣れ親しんだターミナルソフト(つまりRLogin)で操作できることになります!<br/> <code>powershell.exe "aws-vault exec ${PROFILE_NAME} -- aws ssm start-session --target ${TARGETID} --document-name AWS-StartSSHSession --parameters portNumber=22"</code><br/> ※${PROFILE_NAME}:<ユーザディレクトリ>/.aws/configで設定しているprofile値、${TARGETID}:EC2のインスタンスID</p> <h4 id="おまけ-おさかなが飼えるよ">おまけ. おさかなが飼えるよ</h4> <p>いつのバージョンからかは覚えていないのですが、気が付いたらおさかなが飼えるようになっていました!<br/> 最初からだったりして……<br/> 見たところカクレクマノミかな?<br/> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/htn6/20240118/20240118172852.png" width="200" height="200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="まとめの時間">まとめの時間</h4> <p>RLoginのイイ所についておさらいです。<br/> 1. 軽いのに設定値の共有がラクラク<br/> 2. タブ管理・画面分割ができる<br/> 3. プロトコルハンドラ登録が出来る<br/> 4. 踏み台経由のログインが簡単<br/> 5. SFTPができる</p> <h4 id="おわりに">おわりに</h4> <p>他にも勧めたいポイントは沢山ある(複数タブに同コマンドを発行するとか)んですが、とにかく軽くて自分好みにカスタマイズ出来て、設定の共有や別PCへの移行が簡単で、WinSCPライクにファイル転送が出来る。<br/> 今まで使ってきたターミナルソフトの中で最高!って思ってます。<br/> これを見た方でちょっとでもRLoginに興味を持った方がいたら、是非使ってみてください。</p> <h4 id="おわった後のちょっとした自慢">おわった後のちょっとした自慢</h4> <p>ちなみに本人が把握していなかっただけで、<s>私が小うるさく「(゚益)o彡゜RLogin!RLogin!」と言って</s>RLoginを勧めていたら同じチームの人でRLogin使うようになった人がちらほらいたみたいです。やったね!!<br/> とっても嬉しいです。皆さんも良かったら是非。</p> htn6 非同期コラボレーション手段としてのコードレビュー hatenablog://entry/6801883189079483321 2024-02-02T11:29:00+09:00 2024-02-27T11:15:16+09:00 CARTA HOLDINGS アドベントカレンダー 2023 20日目の記事です。 Lighthouse Studio の海老原 (@co3k) です。あけましておめでとうございます。あけましておめでとうって時期でももうないか。。。えっと、とにかく何かしらおめでとうございます! さて、コードレビューの話です(が、考え方のエッセンスはコード以外のレビューにも適用できるはずです)。別に示し合わせたとかではないのですが、 10 日目を担当してくれた ryu さんが既にレビューに関する記事を書いてくれています。 zenn.dev 即返すプラクティスは僕も実践しています。「もうレビューされたw」みたいな… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/co3k/20240201/20240201134416.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTA HOLDINGS アドベントカレンダー 2023 20日目</a>の記事です。</p> <p>Lighthouse Studio の海老原 (<a href="https://twitter.com/co3k">@co3k</a>) です。あけましておめでとうございます。あけましておめでとうって時期でももうないか。。。えっと、とにかく何かしらおめでとうございます!</p> <p>さて、コードレビューの話です(が、考え方のエッセンスはコード以外のレビューにも適用できるはずです)。別に示し合わせたとかではないのですが、 10 日目を担当してくれた ryu さんが既にレビューに関する記事を書いてくれています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fzenn.dev%2Fryu955%2Farticles%2F81d20a2669c6ed" title="プルリクのレビューは即返す" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://zenn.dev/ryu955/articles/81d20a2669c6ed">zenn.dev</a></cite></p> <p>即返すプラクティスは僕も実践しています。「もうレビューされたw」みたいなお声をいただけたりして、とても嬉しいです。(誰ですか「レビューは即返すのにアドベントカレンダーは即書かないんですね」なんて言っているのは)</p> <p>ご多分に漏れず弊チームでもレビュー待ちによって生じるリードタイムの長期化は課題として上がっており、チームの方針として「朝会でレビュー待ち状態の pull request がないかを確認する」「レビュワーになった人は午前中のうちにレビューする」を定めつつ、特定の人にレビュー負担が偏らないように週次でレビュー担当数を可視化しておく、という運用をしています。</p> <p>そうはいってもユーザに即座に価値を届けたいじゃないですか。という欲求があまりにも強すぎるので、このフローに先んじて「即返す」を発動してしまいがちです。圧倒的スピード大事ですからね。</p> <h2 id="そもそも我々は何のためにコードレビューをしているのか">そもそも……我々は何のためにコードレビューをしているのか?</h2> <p>アジャイル開発が一般化し、小さくて頻繁なリリースが求められるようになりました。それに呼応するようにバグトラッキングシステムやバージョン管理システム等の各種周辺ツールも進化を続け、チケットやユーザーストーリーなどの単位でのコードレビューが自然な形でおこなえるようになってきました。</p> <p>また、 OSS 開発の文脈においては、「(時として顔も知らない)コントリビュータから受け取ったパッチを自分たちのソフトウェアの一部として取り込むかどうか」という観点でのレビューが必要とされていたため、 GitHub などのバージョン管理システムのフロントエンドではコードレビューのための機能等を備えているのが一般的でした。これらのシステムが徐々に OSS 開発に限らない領域まで広がり、ツールとしても文化としても合流した結果、コードレビューというプラクティスがいまではほとんど当たり前と言える形で浸透しているように感じます。</p> <p>一方で、最近では、先述したようなコードレビューの引き起こすリードタイム等への影響などに対する怨嗟の声もぽつぽつと聞こえてくるようになりました。</p> <p>ここで、「当たり前だから当たり前なのだ」で終わらせないのが我々 CARTA HOLDINGS のエンジニアの<a href="https://techblog.cartaholdings.co.jp/tech-vision">「本質志向」</a>です。このタイミングであらためて、コードレビューの意義について再確認していきましょう。……と言いたいところなのですが、ありがたいことに、<a href="https://abseil.io/resources/swe-book">『Software Engineering at Google』</a> (2020 年) の 9 章 "Code Review" 内、 "Code Review Benefits" にて概ね以下のようにまとめられています。</p> <ul> <li><strong>コードが正しいことの確認</strong> (Checks code correctness)</li> <li><strong>他のエンジニアにとって理解が容易な変更であることの担保</strong> (Ensures the code change is comprehensible to other engineers)</li> <li><strong>コードベースにおける一貫性を強制</strong> (Enforces consistency across the codebase)</li> <li><strong>チーム内のオーナーシップの心理的促進</strong> (Psychologically promotes team ownership)</li> <li><strong>知識共有</strong> (Enables knowledge sharing)</li> <li><strong>コードレビュー自体の歴史的記録の提供</strong> (Provides a historical record of the code review itself) <ul> <li>コードを調査する際に歴史を辿っていくことがあると思いますが、そうした際にレビューの記録がインサイトとなるよ、ということですね</li> </ul> </li> </ul> <p>これらのベネフィットを享受しうるということには、皆さんも同意されるところなのではないでしょうか。僕も同意します。</p> <p>個人的には、ここにひとつ、 <strong>「内部不正の防止」</strong> を付け加えたいです。いわゆる Dev と Ops を兼任する我々フルサイクルエンジニアはソフトウェアに対する絶大な権限を有しており、やろうと思えばどこまでも凶悪なことができてしまいます。安定した事業運用のうえでは、こうした脅威に対して自らで制約を課す必要があり、その制約の一つとしてコードレビューにおける相互確認が有効であろうと考えられます。</p> <h2 id="For-us-なコードレビューについて考える">For us なコードレビューについて考える</h2> <p>ということで、 "Code Review Benefits" に「内部不正の防止」を付け加えた 7 項目について検討していきます。ここでは一例として弊チームにおける "for us" なコードレビューについて考えてみます。みなさんもよければ真似してみてください。</p> <h3 id="For-us-な点">For us な点</h3> <p>弊チームにとっても嬉しそうな恩恵としては以下があげられそうです。</p> <ul> <li><strong>チーム内のオーナーシップの心理的促進</strong></li> <li><strong>知識共有</strong></li> <li><strong>コードレビュー自体の歴史的記録の提供</strong></li> <li><strong>内部不正の防止</strong></li> </ul> <h4 id="オーナーシップを持ち続けるために">オーナーシップを持ち続けるために</h4> <p>プロダクトに対するオーナーシップを持つフルサイクルエンジニアである以上、コードに対するオーナーシップを全員が持っていることはもはや当然の前提です。コードを書いた人だけではなく、レビューすることによってオーナーシップを持つ人が広がるのであれば、まさに一粒で二度おいしいと言えそうです。</p> <p>また、我々はプロダクト内の各マイクロサービスにおいてあえて固定の担当者を設けずに、全員が全体を見るスタイルでやっています。つまり、「いま自分が書いているコードは他の誰かがメンテするかもしれない」可能性が、「いつか」ではなく「常に」あるのです。そのため、同期的な知識共有の機会はもちろん、非同期的な歴史的記録の提供も言わずもがな重要です。コードレビューにおいておこなわれた疑問点の解消や議論が、将来コードをメンテする他の人々にとって有益となることは間違いありません。</p> <h4 id="オーナーシップを持つからこそ">オーナーシップを持つからこそ</h4> <p>また、先述したように DevOps を兼任する以上、内部不正への抑止力としての効果も見過ごせません。弊チームにおいては開発者がプロダクション環境に対して直接的にデプロイすることはできず、必ず CI / CD を経由する必要があります。こうした前提があるため、プロダクション環境で不正なコードをデプロイするとしたら、通常の pull request に分からないように混ぜ込むというのがもっとも現実的な手段であると言えます。この脆弱性に対して、コードレビューというパッチを当ててリスクを軽減するというわけです。もちろん手の込んだ不正を防げるような完璧なものではありませんが、こうした相互確認のステップを設け、「不正のトライアングル」における「機会」を提供しないことで、心理的なハードルを上げることは内部不正対策として有効であることが知られています。</p> <h3 id="Not-for-us-な点">Not for us な点</h3> <p>一方、我々にとってはあまり嬉しくなさそうな恩恵としては以下があげられそうです。これは、もう少し正確に言えば、「もうすでに他の要因によって解決可能な課題が解消していたり、リスクヘッジができている」ので、「コードレビューでの解決を期待しなくてもよい」というようなニュアンスであるとするほうが実情に近そうです。</p> <ul> <li><strong>コードが正しいことの確認</strong></li> <li><strong>他のエンジニアにとって理解が容易な変更であることの担保</strong></li> <li><strong>コードベースにおける一貫性を強制</strong> (ちょっと嬉しい)</li> </ul> <p>コードが正しいことの確認に関していえば、原初よりコードレビューの目的として理解されているところだと思います。 <a href="https://www.amazon.co.jp/dp/0735619670">『CODE COMPLETE 2nd Edition』</a> (2004 年)において "Code Reading" として紹介されている (Chapter 21. Collaborative Construction) ものの亜種が現在の我々にとっての「コードレビュー」に相当するものと思われますが、ここでは <code>also comment on qualitative aspects of the code</code> としながらも、「不具合検出率」がテスト等と比べて 20% から 60% ほど優位であるとするなど、明らかにバグの発見を主目的であると捉えていることがわかります。</p> <h4 id="現代的なウェブサービスの自社開発におけるコードの正しさを目的としたレビューの意義">現代的なウェブサービスの自社開発における「コードの正しさ」を目的としたレビューの意義</h4> <p>ただし反復的かつ短いイテレーションのなかで継続的にデリバリ、検証、改善のサイクルを回し続けていくアジャイル開発のうえでは、このメリットは以前と比べてグッと小さくなりつつあると言えるでしょう。ましてや我々の場合はウェブサービスを自社開発しているわけで、顧客に価値を届けるためのリードタイムは限りなく小さく抑えられます。また、ブルーグリーンデプロイをしているため切り戻しも容易かつ即座におこなえます。であれば少ない人数で時間を掛けてバグを精査するよりも、致命的な問題さえなければ即座にデリバリーしてしまい、問題があれば改善するもしくは切り戻しするのが合理的です。まさに <code>Given enough eyeballs, all bugs are shallow</code> (充分な目玉の数があればどんなバグも洗い出せる; リーナスの法則) というわけです。</p> <h4 id="コードレビューにおける自転車置き場の議論">コードレビューにおける「自転車置き場の議論」</h4> <p>こうなってくると、コードレビューの意義というのはバグのような外形的に発露しうるものを発見するというよりもむしろ、いわゆる内部品質というところに重きを置かれつつあることがあります。内部品質の向上——自転車置き場の議論を誘発しかねない甘美かつ危険な香りがしますね。</p> <p>危険なので対策を考えたいところです。自転車置き場の議論を避けるうえで有効な手立てとして、意思決定者を設ける、というアプローチがあります。個人の好みや意見に左右されない絶対的な存在というわけですね。</p> <p>静的解析技術が発展し広まった現代的なプログラミング環境においては、こうした絶対的な存在としてフォーマッタや lint ツールが据えられがちです。我々とて例外ではなく、 gofmt のようなスタンダードなフォーマットツールがある Go などを好んで使うことによってこうした問題を最小限に抑えています。また、好むと好まざるとに関わらず、可能な限り言語の標準的な書き方に従うこと、既存のコードにあわせること(それが難しいようなら局所最適に終始するのではなく全体を変えること)を求めています。</p> <p>コードの書き方の問題について人間の好みが介入する余地を減らせるとして、終わりない議論を生じさせる他のトピックといえばアーキテクチャがありそうです。とはいえこれは当該リポジトリの first author が design doc 等で定義したものを起点とすることでそうズレなくなるのではないでしょうか。また、特に、我々がおこなっているコードレビューはあくまで pull request ベースでのコードレビューであって、コードセット全体に対するものではありません。このレベルの議論をコードレビューという狭い場でおこなうことは、まさに木を見て森を見ずです。弊チームでは、こうした議論の機会を週次で設けており(議題がない場合は開催せず)、コードレビューでこみ入った議論をすることを、むしろ極力回避しています。</p> <h2 id="非同期コラボレーション手段としてのコードレビュー">非同期コラボレーション手段としてのコードレビュー</h2> <p>タイトル回収のある作品は名作であると風の噂で聞いたため、そろそろこのあたりでタイトル回収をしていきます。</p> <p>さて、ここまで考えてみると、むしろ我々がコードレビューに期待していることは、フォーマットこそ原初からのコードレビューを踏襲しているものの、求める役割はそれとはまったくかけ離れていることがわかります。</p> <p>先ほど言及した『CODE COMPLETE 2nd Edition』にも見られたように、コードレビューは監査やテストなどの工程を補完ないし代替しうるものとして考えられてきました。これは言うなれば第三者的な視点の獲得です。</p> <p>一方で、我々が期待するコードレビューの役割は、第三者というよりむしろ当事者を増やすことを目的としていると言えます。これは先述したような監査やテストというよりむしろペアプログラミングなどのコラボレーション行為との類似性が強いように思います。ペアプログラミングが同期的なコラボレーションとするならば、我々のコードレビューは非同期的なコラボレーションであると位置づけることができるかもしれません。</p> <p>そこで、あらためてペアプログラミングの利点について整理してみましょう。</p> <ul> <li>主眼ではないながらも、品質向上の効果があります。既に触れたとおり、『CODE COMPLETE 2nd Edition』において「不具合検出率」の優位性が確認されています</li> <li>生産性向上の効果が知られています。この理由として、『Extreme Programming Installed』では「飽きにくい (work longer without getting tired)」、思考作業の分担によって「頭の中で生じるスワップ (mental swap)」が生じなくなること、『Joel on Software』の<a href="https://www.joelonsoftware.com/2002/01/06/fire-and-motion/">「射撃しつつ前進 (Fire And Motion)」</a> では、簡単なように思えてとても難しい「ただはじめること (just getting started)」ことが生産性における鍵だとしたうえで、「ペアが互いに『はじめること』を強いる (you force each other to get started)」ためにペアプログラミングはうまく機能するのだ、としています</li> <li>知識共有 (コードベースはもちろん、考え方、プロセス、ツールの使い方など) の機会となります。自分とは異なる人の手を通じて、自分とは違う道を進みうるその過程をリアルタイムで体験できるわけです。知的好奇心が刺激されることは間違いないでしょう</li> <li>言わずもがな、コミュニケーション、チームワークの向上に繋がります。同じ問題に一緒に取り組んだバディとの間に信頼関係が築かれることは想像に難くありません</li> </ul> <p>こうした利点を非同期的なコラボレーションとしてのコードレビューにおいても享受するにはどうすればいいでしょう。同期性を求められる取り組みと異なり、非同期的な取り組みには時間の都合を合わせる必要がないというメリットもありますが、フィードバックが即座におこなえないというデメリットもあります。</p> <p>特に、ツールの使い方などのコーディングプロセスに関わる知識共有などは強い同期性を要求します。しかし、さほどの同期性を要求しない要素であれば、いくつかの工夫によって非同期の元でもメリットが享受できるかもしれません。</p> <h3 id="生煮え段階のコードレビューおよびマージを許容する">生煮え段階のコードレビューおよびマージを許容する</h3> <p>原初からのコードレビューのコンセプトに従えば、コードレビューの提出はあるひとまとまりが完成した「区切り」のタイミングで提出したくなるかもしれません。私たちはそこに期待しないと決めたわけですから、必ずしもコードレビューのタイミングは完成時に限定しなくてもいいわけです。</p> <p>GitHub には draft pull request もありますし、途中だけどこんな感じでいいかちょっと見てみてーって相談しつつ、並行して作業を進められるのはいいですね。まあ全然 draft とかに頼らなくても普通に pull request 出してわーってコメントもらって、いったん仕切り直しますわーって言ってもう一回 pull request 出し直してとかでも全然いいわけですが。</p> <p>また、海老原自身は完成系を待たずにマージすることも推奨、実践しています。これはたとえば、いわゆるステルスリリース(「既存機能の導線を断ったうえでリリースする」、「特定のクエリパラメータを付与しないと機能が発現しないようにする」など)であったり、 MVP (Minimum Viable Product) の段階に達した時点でリリースしてしまって、あとは走りながら改善する、といったプラクティスを、レビュー目的においてもおこなうということです。もちろん、他の機能に影響を与えないとか、ユーザに混乱を与えないとか、ブランドイメージを損なわないとか、諸々の条件が整ったうえでということになりますが、フィーチャーブランチの機能を手元で動かすように準備する手間を掛けることなく、ふたりが一緒の環境であーだこーだ言えるのでこれはめちゃくちゃアリな感じあります。</p> <p>もはや死語かもしれませんが、 Web 2.0 黎明期ではよく「永遠のβ版」という表現が使われていました。これは、サービスは「完成」することなく、継続的に改善、進化し続けるということをセンセーショナルに謳った表現です。リードタイムが限りなく抑えられ、トライアンドエラーがしやすい Web というプラットフォーム特性を的確に捉えた表現であるように思います。開発環境が整備され強化された現在において、あらためてこのコンセプトに立ち返り、「Web 2.0 をちゃんとやる」、つまり、「Web 2.0 2.0」をやっていく、というのも乙なものかもしれません。</p> <h3 id="即返すプラクティスを実践する">「即返す」プラクティスを実践する</h3> <p>ペアプロといえばやはり同期的な対話による迅速なフィードバックが魅力です。非同期的なアプローチでも少しでもこの恩恵を受けるためにはやはり「即返す」しかありません。しかし愚直に「即返す」だけではレビュワー側のコスト爆上がりですし、中途半端なことをせずに素直にペアプロをしたほうがいいまであります。</p> <p>「即返す」を無理なく実践しやすくするためには、月並みですが、タスクの細分化をめっちゃ頑張るとか、フェーズ分けをするなどして、「pull request のサイズを小さくする」というのが効きます。</p> <p>ここで問題となってくるのは、「pull request のサイズが小さければ小さいほどめっちゃコメントしたくなってしまう」という謎の衝動でして、どうやらこれは万国共通のようです。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">10 lines of code = 10 issues.<br><br>500 lines of code = &quot;looks fine.&quot;<br><br>Code reviews.</p>&mdash; I Am Devloper (@iamdevloper) <a href="https://twitter.com/iamdevloper/status/397664295875805184?ref_src=twsrc%5Etfw">2013年11月5日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>「即返す」をチームとして実践するためにも、レビュワー側ではこうした重箱の隅をつつきたくなる誘惑に打ち勝つ自制心が必要になります。頑張ってあらがいましょう。僕も頑張ってあらがいます。</p> <h3 id="あえて特定の担当者をつけないようにする">あえて特定の担当者をつけないようにする</h3> <p>特定の担当者をつけずにプルリクエストをレビューすることで、全員がコードベース全体に対してオーナーシップを持つことができます。これは全員がコードの品質保持に貢献する体制を作るうえで非常に有効です。</p> <p>この取り組みは Lighthouse Studio 開発チームのスタンダードなスタイルとして従来より採り入れていましたが、そうはいっても、「この領域はこの人が詳しいから」が積み重なって、気がつけば特定の人にレビューが集中してしまいやすくなるような力学がどうしても働いてしまっていました。これは組織や事業規模の拡大に伴う、プロダクトバリエーションの増大も背景にありますし、「監査」としてのレビューへの期待がどうしても捨てきれていないことへの証左とも言えます。</p> <p>我々としては、先述したような「レビュー担当数の可視化」に加えて、特定の人に知識が偏っていそうなプロダクトについては、一定期間で交代する補助的な「壁打ち相手」を設けるようにし、少しずつチーム全体に知識を伝搬していくような施策をはじめました。このあたりも今後どうなったか触れていけるといいですね。</p> <h3 id="すべてのレビューコメントは暗黙-nits-あるいは暗黙-imo">すべてのレビューコメントは暗黙 nits あるいは暗黙 imo</h3> <p>レビューコメントに <code>[nits]</code> とかのラベリングをつけていくようなスタイルも最近ではごくごく一般的となってきました。これは極めて合理的というか、フレームを避けたりチーム内でのいい感じのコミュニケーションを築いていくいい施策であることは確かです。</p> <p>しかしここにはひとつ落とし穴があって、裏を返すと「何もついていない」コメント、つまりフラットな状態のコメントの強制感を相対的に暗示してしまう、ということでもあります。</p> <p>私たちはレビューに監査的な役割を期待しないのですから、よっぽどのことがない限り全部 nits であり imo をつけていくべきです。……というのはなかなかまどろっこしいですね。なのでデフォルトで <code>[nits]</code> もしくは <code>[imo]</code> がついている、つまり暗黙 nits あるいは暗黙 imo なわけです。</p> <h3 id="レビューコメントでは別にレビューしなくていい">レビューコメントでは別にレビューしなくていい</h3> <p>レビューに監査的な役割を期待しないということは、レビューも別に指摘とかだけじゃなくていいってことですね。「え、この書き方オシャレくない?」とか、「このバグどうやって見つけました?」とか、ガンガンコミュニケーション取っていっていいわけです。非同期ながらもペアプログラミングのようなコードを通した会話ができるのが、 pull request 等のコメント機能の最大の魅力であると思っています。</p> <p>しかもペアプログラミングとは違って、しっかり記録に残るのが非同期コミュニケーションの最大の利点です。何気ない会話が、しかも発言した当人としては大したことだと思っていなかったようなものが、時を越えていつかチームの誰かの役に立ったりすることもままあります。</p> <h2 id="まとめ">まとめ</h2> <p>本エントリでは、現代の開発現場において、ある意味では盲目的に取り入れられている「コードレビュー」というプラクティスに対して、改めて立ち止まって利点を分解し、「for us なコードレビュー」として再定義を試みてみました。</p> <p>その結果、リモートワークが中心な開発チームである私たちとしては「コードレビュー」を「非同期コラボレーション手段」を最適なプラクティスとして位置づけているわけですが、みなさんの現場ではどういった「コードレビュー」が最適なスタイルとなるのでしょうか。是非お考えをいろいろお聞きしたいです。</p> <p>以上、 <a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTA HOLDINGS アドベントカレンダー 2023 20日目</a>の記事でした。次の 21 日目の記事は株式会社DIGITALIOの ぐり による <a href="https://techblog.cartaholdings.co.jp/entry/guri-or-ogurimon-quiz">「ぐり」か「おぐりもん」かクイズ</a> でした。楽しみですね!</p> co3k エラー検知からJiraチケット作成まで自動で!CloudWatch+Lambda+Jira+Slackの連携ツール hatenablog://entry/6801883189058642742 2024-02-01T09:30:00+09:00 2024-02-01T09:30:00+09:00 こんにちは。CCIのJ.YAMAGUCHIです。 今回は以前少し担当したプロダクトでCloudWatch + Lambda + jira + slackを連携して、アプリケーションのエラー検知からjiraチケットを自動で作成するツールを作成した話をしたいと思います。 背景 導入する前はCloudWatch => SNS => ChatBot => slackで仕組みを作ってslackにエラーを通知していましたが、以下課題がありました。 どこでエラーが起こったかはわかるが、そこから対象のログを探し出して、調査を開始する際にひと手間かかっていた。 slackなので埋もれることもあるので、定期的にC… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jun_work/20240109/20240109115208.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。CCIのJ.YAMAGUCHIです。</p> <p>今回は以前少し担当したプロダクトで<strong>CloudWatch</strong> + <strong>Lambda</strong> + <strong>jira</strong> + <strong>slack</strong>を連携して、アプリケーションのエラー検知からjiraチケットを自動で作成するツールを作成した話をしたいと思います。</p> <h3 id="背景">背景</h3> <p>導入する前はCloudWatch => SNS => ChatBot => slackで仕組みを作ってslackにエラーを通知していましたが、以下課題がありました。</p> <ul> <li>どこでエラーが起こったかはわかるが、そこから対象のログを探し出して、調査を開始する際にひと手間かかっていた。</li> <li>slackなので埋もれることもあるので、定期的にCloudWatch insightを使用してエラーログの棚卸しをしていた。(特定文字列で引っ掛けていた)</li> </ul> <p><figure class="figure-image figure-image-fotolife" title="CCI山口純"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jun_work/20231114/20231114105352.png" width="775" height="319" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure> ※slackに飛んできているCloudWatchのアラートの通知</p> <p>担当プロダクトではプロジェクト管理でjiraを導入していたため、直接エラー内容をjiraチケットで起票できるように構築しました。</p> <h3 id="本題">本題</h3> <p>今回は下記のような形でCloudWatchでエラーを検知して、jiraチケットを作成する構成で構築してみました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jun_work/20231207/20231207203500.png" width="1200" height="220" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>詳細を書いてみます。</p> <h5 id="1-CloudWatch--Lambdaの部分">1. CloudWatch =&gt; Lambdaの部分</h5> <p>Lambdaのコンソール画面からトリガーを設定します。</p> <p>担当プロダクトではjson形式でlogを出力しているので、ERRORログを引っ張るようにFilter patternに下記を設定します。</p> <p><code>{ $.level = "ERROR" }</code></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jun_work/20231127/20231127112137.png" width="874" height="398" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>※画像のようにトリガーを設定しました。</p> <h5 id="2-Lambda--Jiraの部分">2. Lambda =&gt; Jiraの部分</h5> <p>最終的なアウトプットとして下記のようなチケットを作成するように実装しました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jun_work/20231127/20231127124430.png" width="1200" height="545" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>※タイトルの構成要素:【ログレベル(今回はERROR)/どの環境で起こっているか/どこのソースで起こっているか】ログストリーム名</p> <p>チケット内容:テーブル形式でログに出力されている内容を記載</p> <h5 id="Lambda関数を作成し下記処理の流れで実装しています">Lambda関数を作成し、下記処理の流れで実装しています。</h5> <ul> <li>トリガーで設定した、CloudWatchlogsから渡ってくるイベントを解析し、対象のロググループからERRORログを抽出する。</li> </ul> <p> ※Lambdaに渡ってくるイベント情報はbase64でエンコードされているため、下記を参考にデコードしてから情報を加工しました。</p> <blockquote><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.aws.amazon.com%2Fja_jp%2FAmazonCloudWatch%2Flatest%2Flogs%2FSubscriptionFilters.html%23LambdaFunctionExample" title="CloudWatch Logs サブスクリプションフィルターの使用 - Amazon CloudWatch Logs" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe></p></blockquote> <ul> <li>Jira APIを使用するための、tokenを取得する</li> </ul> <p> ※tokenを取得する方法は下記3パターンあり、今回はツール目的でもあっためお手軽な「Basic HTTP」を使用しました。</p> <blockquote><p>If you are integrating directly with the REST APIs, rather than via an Atlassian Connect add-on, use one of the authentication methods listed below:</p> <ul> <li><p>OAuth 2.0 - This token-based method is the recommended method. It is more flexible and secure than other options.</p></li> <li><p>OAuth 1.0a - This is a legacy authentication method and, therefore, isn't recommended. Instead use OAuth 2.0.</p></li> <li><p>Basic HTTP - This method is only recommended for tools like scripts or bots. It is easier to implement, but much less secure.</p></li> </ul> <p><a href="https://developer.atlassian.com/cloud/jira/software/rest/intro/#introduction">https://developer.atlassian.com/cloud/jira/software/rest/intro/#introduction</a></p></blockquote> <p>tokenの作成方法としてはメールアドレスとJiraから作成できるAPI tokenをbase64でエンコードすることでtokenを作成することができます。</p> <p>※このような形でtokenは作れます(Node.js)</p> <pre class="code" data-lang="" data-unlink>/** * ================================================================================ * アクセストークン取得 * ================================================================================ */ function getAccessToken() { const buffer = new Buffer.from(JIRA_EMAIL_ADDRESS + &#39;:&#39; + JIRA_ACCESS_TOKEN); return buffer.toString(&#39;base64&#39;); }</pre> <p>JiraのAPI tokenに関しては、下記を参考にしながらAPI tokenを発行しました。</p> <blockquote><p><a href="https://developer.atlassian.com/cloud/jira/platform/basic-auth-for-rest-apis/">https://developer.atlassian.com/cloud/jira/platform/basic-auth-for-rest-apis/</a></p></blockquote> <ul> <li><p>Jira APIを使用して、特定の課題エピック配下のチケットの一覧を取得し、タイトルの【】内の文字列で重複チェックを実施(同じ個所で起きていたら同じエラーと定義)</p></li> <li><p>タイトルが重複していた場合、</p></li> </ul> <p>  Slackにエラーが起こったが重複した旨のメッセージを通知</p> <p> タイトルが重複していなかった場合、</p> <p>  Jira APIを使用してチケットを作成</p> <p>Jira APIは下記ドキュメントを参照し、実装しました。</p> <blockquote><p><a href="https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#about">https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#about</a></p></blockquote> <h5 id="3-Jira--Slackの部分">3. Jira =&gt; Slackの部分</h5> <p>こちらはJiraとSlackでそれぞれ設定するだけです(お手軽ですね)</p> <blockquote><p><a href="https://support.atlassian.com/ja/jira-software-cloud/docs/use-jira-cloud-for-slack/">https://support.atlassian.com/ja/jira-software-cloud/docs/use-jira-cloud-for-slack/</a></p> <p><a href="https://teamccihq.slack.com/intl/ja-jp/apps/collection/atlassian-for-slack">https://teamccihq.slack.com/intl/ja-jp/apps/collection/atlassian-for-slack</a></p></blockquote> <p>これで一連の開発&設定は完了です。無事アプリケーションでエラーが発生してから自動的にJiraチケットが作成できるようになりました。</p> <h3 id="締め">締め</h3> <p>今回の対応でエラー調査にひと手間かかっていたところが、チケットにわかりやすくまとめることができました。</p> <p>情報にはロググループも載せているため、エラーの前後を見たいときにもすぐに対象のロググループを探すことができます。</p> <p>この仕組みを作った際は1つのプロダクトに導入していましたが、現在はプロダクトが増えるたびに導入し、4つ導入できているので、結果汎用性高く作れたなと思いました。</p> <p>※他のプロダクトに導入する際はLambdaの環境変数を変える程度</p> <p>最後までお読みいただきありがとうございました!!</p> jun_work dbt x snowflakeで使っていないテーブルとビューを安全に一括で削除する hatenablog://entry/6801883189074401550 2024-01-15T11:30:00+09:00 2024-02-27T11:22:17+09:00 概要 こんにちは、4月に新卒で株式会社CARTA HOLDINGSに入社し、現在はCARTA MARKETING FIRMのデータエンジニアをやっているharukiです。 私たちのチームでは、dbtとsnowflakeを使ってデータ基盤を構築しています。 データ基盤を使うエンジニアが増え、dbtのモデル数が増えてきたのですが、その中には使わなくなり削除したdbtモデルもありました。 dbtモデルを削除しても、Snowflake上の対応するテーブルやビューは自動的には消えないため、使われないsnowflake上のテーブルやビューが増えて目立つようになってきました。 そこで、dbtモデルとしては削… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/haruki0415/20240116/20240116100505.jpg" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="概要">概要</h1> <p>こんにちは、4月に新卒で株式会社CARTA HOLDINGSに入社し、現在はCARTA MARKETING FIRMのデータエンジニアをやっている<a href="https://twitter.com/_haruki0415">haruki</a>です。</p> <p>私たちのチームでは、dbtとsnowflakeを使ってデータ基盤を構築しています。</p> <p>データ基盤を使うエンジニアが増え、dbtのモデル数が増えてきたのですが、その中には使わなくなり削除したdbtモデルもありました。 dbtモデルを削除しても、Snowflake上の対応するテーブルやビューは自動的には消えないため、使われないsnowflake上のテーブルやビューが増えて目立つようになってきました。</p> <p>そこで、<strong>dbtモデルとしては削除されているが、snowflake上に残ってしまっているテーブルやビューを一括削除できる処理を考えました</strong>。</p> <p>想定読者</p> <ul> <li>dbtとsnowflakeを使ってデータ基盤を開発している方</li> </ul> <p>この記事を読んでわかること</p> <ul> <li>使っていないsnowflakeのテーブル、ビューを見つける方法</li> <li>それに対して私たちのチームがどういう運用をしているか</li> </ul> <h1 id="開発体制">開発体制</h1> <p>まず私たちのチームの開発体制を簡単に紹介します。</p> <p>データ基盤を使うのは二つのチームに分かれています。</p> <ul> <li>dataチーム <ul> <li>データ基盤の安定稼働・機能開発を行うのがメイン業務ですが、時にはプロダクト開発チームのサポートも行い、基盤の活用を促します。</li> <li>チームトポロジーで言うところのPlatformチームであり、Enablingチームです。</li> </ul> </li> <li>プロダクト開発チーム <ul> <li>自分たちのプロダクトのニーズに合わせたdbtモデル開発をします。</li> <li>チームトポロジーで言うところのStream alignedチームです。</li> </ul> </li> </ul> <p>そしてdbtをシングルプロジェクトで運用し、プロダクト開発チームはそのプロジェクトをプロダクトごとに区切って利用しています。dataチームはその管理者になっています。</p> <p>システム構成は以下のようになっています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/haruki0415/20240112/20240112122351.jpg" width="981" height="570" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>開発環境とCI環境と本番環境は以下のように分けて運用しています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/haruki0415/20240112/20240112122354.jpg" width="1200" height="1020" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>使っているdbtパッケージ</p> <ul> <li><a href="https://github.com/elementary-data/dbt-data-reliability">elementary</a> <ul> <li>dbtネイティブなデータオブザーバビリティツールElementaryが提供するパッケージ。</li> <li>内容としては、dbtの振る舞いに関するメタデータ生成をしているパッケージです。弊社では、Elementary Cloudを使っているので、その連携にも利用しています。</li> </ul> </li> <li><a href="https://github.com/get-select/dbt-snowflake-monitoring">dbt-snowflake-monitoring</a> <ul> <li><a href="https://techblog.cartaholdings.co.jp/entry/select-cloud-cost-optimize-cmf">SELECT</a>が提供するSnowflakeのコストとパフォーマンスを可視化するために必要なモデルを構築してくれるパッケージです。 より詳しく知りたい方は私の先輩<a href="https://twitter.com/pei0804">@pei</a>が書いた以下の二つの記事をご覧ください。</li> </ul> </li> <li><a href="https://techblog.cartaholdings.co.jp/entry/snowflake-dbt-data-platform-vision">Snowflakeの力を引き出すためのdbtを活用したデータ基盤開発の全貌</a></li> <li><a href="https://techblog.cartaholdings.co.jp/entry/snowflake-data-platform-vision">Snowflakeと共に過ごした一年間。その進化過程と未来へのVision</a></li> </ul> <h1 id="生じた問題">生じた問題</h1> <p>dbtモデルをコード上から削除すると、そのモデルに対してdbt buildが走らなくなり更新が止まります。 更新がなくても使う可能性があるものなら良いのですが、削除しなければ、snowflake上に使わないテーブルやビューは残ったままになってしまいます。</p> <p>プロダクト開発チームがdbtモデルを削除したらsnowflake上のリソースを消すようにお願いするような運用も考えられます。しかし、容易に消し忘れが生じることが考えられ現実的ではありません。</p> <p><strong>データ基盤を利用するユーザーが増えてきてこのようなゾンビモデルが目立つようになってきました。</strong></p> <p>残っているとどうなるのか、立場ごとにリスト化します。</p> <p><strong>プロダクト開発チーム側</strong></p> <ul> <li>データ基盤を使う <ul> <li>クエリしてみたら最新のデータがない。どうなってんの?って話が始まる。</li> <li>テーブルにクエリしてみたがデータが取れないので調べて見てほしいという依頼を受けたが、そのテーブルはしばらくの間更新されておらず、新しいモデルに作り変わっていたということがあった。</li> </ul> </li> </ul> <p><strong>dataチーム側</strong></p> <ul> <li>データ基盤を管理する</li> <li>使っていないテーブルは削除してストレージコストを減らしたい。</li> </ul> <p>少人数のチームで管理しているのですが、dbtモデル数が500近くあり、全てを把握するのは難しい状態になっています。さらにその中に使っていないものがあると、その確認作業が必要になります。</p> <p>snowflakeから見れば、モデルがdbtで管理されて、更新されるものなのかはわかりません。dbtを使っているエンジニアならわかりますが、dbtを使わない人にとっては確認が不可能になります。<strong>snowflake上には管理され更新されるテーブル、ビューのみがあるのが理想でその状態を保ちたい</strong>というモチベーションがあります。</p> <h1 id="解決方法">解決方法</h1> <p><a href="https://github.com/get-select/dbt-snowflake-monitoring">elementary</a>が作ってくれる<a href="https://docs.elementary-data.com/dbt/dbt-artifacts">dbt_artifacts</a>を利用します。</p> <p>dbt_artifactsはmanifest.jsonなどの成果物から取れるdbtのメタデータをわかりやすいように加工したものです。elementary packageをdbtのプロジェクトで使っていると、dbtモデルの実行後に作られます。</p> <p>利用するテーブル</p> <ul> <li><a href="https://docs.snowflake.com/ja/sql-reference/info-schema/tables">snowflake tableビュー</a> <ul> <li>データベース内にあるテーブルのスキーマ名、モデル名、テーブル型などを見ることができます。</li> </ul> </li> <li><a href="https://docs.elementary-data.com/dbt/dbt-artifacts#dbt-models">dbt_models</a> <ul> <li>各dbtモデルのデータベース名、スキーマ名、モデル名、マテリアライゼーションなどを見ることができます。</li> <li>各dbtモデルのモデル名とsnowflake上に作られるテーブル名が一致することを前提としています。</li> </ul> </li> </ul> <p>私たちのチームではRAW、PREP、PRODの3つのデータベースを使ってレイヤーごとに分けて運用しています。</p> <p>RAWに対してはdbtを使って書き込みをしない方針なので、今回の対象はPREPとPRODデータベースです。</p> <p>この二つのデータベースの<strong>information_schema.tablesビューに対してクエリした結果とelementaryが作ってくれるdbt_modelsに対してクエリした結果の差</strong>を見ることで使っていないテーブル、ビューをシンプルに取得することができます。</p> <p>テーブルとビューではデータが物理的に存在するかしないかの違いがあり、テーブルの削除とビューの削除では間違って消してしまった際の復旧の大変さが異なります。(snowflakeにはTimeTravelという便利な機能がありますが、、)</p> <p>そのため、テーブルとビューで処理を分けてます。</p> <h3 id="ビューの場合">ビューの場合</h3> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synSpecial">with</span> prod <span class="synSpecial">as</span> ( <span class="synStatement">select</span> table_catalog <span class="synSpecial">as</span> database_name, table_schema <span class="synSpecial">as</span> schema_name, table_name <span class="synSpecial">from</span> PREP.INFORMATION_SCHEMA.TABLES <span class="synSpecial">where</span> table_schema != <span class="synSpecial">'</span><span class="synConstant">INFORMATION_SCHEMA</span><span class="synSpecial">'</span> <span class="synStatement">and</span> table_type = <span class="synSpecial">'</span><span class="synConstant">VIEW</span><span class="synSpecial">'</span> ), prep <span class="synSpecial">as</span> ( <span class="synStatement">select</span> table_catalog <span class="synSpecial">as</span> database_name, table_schema <span class="synSpecial">as</span> schema_name, table_name <span class="synSpecial">from</span> PROD.INFORMATION_SCHEMA.TABLES <span class="synSpecial">where</span> table_schema != <span class="synSpecial">'</span><span class="synConstant">INFORMATION_SCHEMA</span><span class="synSpecial">'</span> <span class="synStatement">and</span> table_type = <span class="synSpecial">'</span><span class="synConstant">VIEW</span><span class="synSpecial">'</span> ), <span class="synComment">-- snowflake上に存在するビュー</span> snowflake_views <span class="synSpecial">as</span> ( <span class="synStatement">select</span> * <span class="synSpecial">from</span> prod <span class="synStatement">union</span> <span class="synStatement">all</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> prep ), <span class="synComment">-- dbtモデルとして管理されているビュー</span> dbt_models <span class="synSpecial">as</span> ( <span class="synStatement">select</span> <span class="synIdentifier">upper</span>(database_name) <span class="synSpecial">as</span> database_name, <span class="synIdentifier">upper</span>(schema_name) <span class="synSpecial">as</span> schema_name, <span class="synIdentifier">upper</span>(name) <span class="synSpecial">as</span> table_name <span class="synSpecial">from</span> ELEMENTARY.ELEMENTARY.DBT_MODELS <span class="synSpecial">where</span> package_name = <span class="synSpecial">'</span><span class="synConstant">your package name</span><span class="synSpecial">'</span> <span class="synStatement">and</span> materialization = <span class="synSpecial">'</span><span class="synConstant">view</span><span class="synSpecial">'</span> ) <span class="synComment">-- 最後に差分をみる</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> snowflake_views <span class="synStatement">minus</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> dbt_models; </pre> <h3 id="テーブルの場合">テーブルの場合</h3> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synSpecial">with</span> prod <span class="synSpecial">as</span> ( <span class="synStatement">select</span> table_catalog <span class="synSpecial">as</span> database_name, table_schema <span class="synSpecial">as</span> schema_name, table_name <span class="synSpecial">from</span> PREP.INFORMATION_SCHEMA.TABLES <span class="synSpecial">where</span> table_schema != <span class="synSpecial">'</span><span class="synConstant">INFORMATION_SCHEMA</span><span class="synSpecial">'</span> <span class="synStatement">and</span> table_type = <span class="synSpecial">'</span><span class="synConstant">BASE TABLE</span><span class="synSpecial">'</span> ), prep <span class="synSpecial">as</span> ( <span class="synStatement">select</span> table_catalog <span class="synSpecial">as</span> database_name, table_schema <span class="synSpecial">as</span> schema_name, table_name <span class="synSpecial">from</span> PROD.INFORMATION_SCHEMA.TABLES <span class="synSpecial">where</span> table_schema != <span class="synSpecial">'</span><span class="synConstant">INFORMATION_SCHEMA</span><span class="synSpecial">'</span> <span class="synStatement">and</span> table_type = <span class="synSpecial">'</span><span class="synConstant">BASE TABLE</span><span class="synSpecial">'</span> ), <span class="synComment">-- snowflake上に存在するテーブル</span> snowflake_tables <span class="synSpecial">as</span> ( <span class="synStatement">select</span> * <span class="synSpecial">from</span> prod <span class="synStatement">union</span> <span class="synStatement">all</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> prep ), dbt_models <span class="synSpecial">as</span> ( <span class="synStatement">select</span> <span class="synIdentifier">upper</span>(database_name) <span class="synSpecial">as</span> database_name, <span class="synIdentifier">upper</span>(schema_name) <span class="synSpecial">as</span> schema_name, <span class="synIdentifier">upper</span>(name) <span class="synSpecial">as</span> table_name <span class="synSpecial">from</span> ELEMENTARY.ELEMENTARY.DBT_MODELS <span class="synSpecial">where</span> package_name = <span class="synSpecial">'</span><span class="synConstant">your package name</span><span class="synSpecial">'</span> <span class="synStatement">and</span> materialization <span class="synStatement">in</span> (<span class="synSpecial">'</span><span class="synConstant">table</span><span class="synSpecial">'</span>, <span class="synSpecial">'</span><span class="synConstant">incremental</span><span class="synSpecial">'</span>) ), dbt_seeds <span class="synSpecial">as</span> ( <span class="synStatement">select</span> <span class="synIdentifier">upper</span>(database_name) <span class="synSpecial">as</span> database_name, <span class="synIdentifier">upper</span>(schema_name) <span class="synSpecial">as</span> schema_name, <span class="synIdentifier">upper</span>(name) <span class="synSpecial">as</span> table_name <span class="synSpecial">from</span> ELEMENTARY.ELEMENTARY.DBT_SEEDS <span class="synSpecial">where</span> package_name = <span class="synSpecial">'</span><span class="synConstant">your package name</span><span class="synSpecial">'</span> ), <span class="synComment">-- dbtモデルとして管理されているテーブル</span> dbt_objects <span class="synSpecial">as</span> ( <span class="synStatement">select</span> * <span class="synSpecial">from</span> dbt_models <span class="synStatement">union</span> <span class="synStatement">all</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> dbt_seeds ) <span class="synComment">-- 最後に差分をみる</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> snowflake_tables <span class="synStatement">minus</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> dbt_objects; </pre> <p>このクエリを使って不要なテーブルやビューを削除するクエリを発行してくれる処理が以下になります。</p> <h3 id="テーブルの場合-1">テーブルの場合</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> argparse <span class="synPreProc">import</span> snowflake.connector <span class="synPreProc">from</span> datetime <span class="synPreProc">import</span> datetime, timezone parser = argparse.ArgumentParser(description=<span class="synConstant">&quot;開発用DBを削除するためのツール&quot;</span>) parser.add_argument( <span class="synConstant">&quot;--snowflake_user_name&quot;</span>, required=<span class="synIdentifier">True</span>, <span class="synIdentifier">type</span>=<span class="synIdentifier">str</span>, <span class="synIdentifier">help</span>=<span class="synConstant">&quot;このツールを実行するsnowflakeのユーザー名&quot;</span>, ) parser.add_argument( <span class="synConstant">&quot;--snowflake_account&quot;</span>, required=<span class="synIdentifier">True</span>, <span class="synIdentifier">type</span>=<span class="synIdentifier">str</span>, <span class="synIdentifier">help</span>=<span class="synConstant">&quot;このツールを実行するsnowflakeのアカウント&quot;</span>, ) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;run_at:</span><span class="synSpecial">\n</span><span class="synConstant">{datetime.now(timezone.utc)}&quot;</span>) args = parser.parse_args() <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;args:</span><span class="synSpecial">\n</span><span class="synConstant">{args}&quot;</span>) ctx = snowflake.connector.connect( account=args.snowflake_account, user=args.snowflake_user_name, authenticator=<span class="synConstant">&quot;externalbrowser&quot;</span>, ) cs = ctx.cursor() query = f<span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> with prod as (</span> <span class="synConstant"> select</span> <span class="synConstant"> table_catalog as database_name,</span> <span class="synConstant"> table_schema as schema_name,</span> <span class="synConstant"> table_name</span> <span class="synConstant"> from</span> <span class="synConstant"> PREP.INFORMATION_SCHEMA.TABLES</span> <span class="synConstant"> where</span> <span class="synConstant"> table_schema != 'INFORMATION_SCHEMA'</span> <span class="synConstant"> and table_type = 'BASE TABLE'</span> <span class="synConstant"> ),</span> <span class="synConstant"> prep as (</span> <span class="synConstant"> select</span> <span class="synConstant"> table_catalog as database_name,</span> <span class="synConstant"> table_schema as schema_name,</span> <span class="synConstant"> table_name</span> <span class="synConstant"> from</span> <span class="synConstant"> PROD.INFORMATION_SCHEMA.TABLES</span> <span class="synConstant"> where</span> <span class="synConstant"> table_schema != 'INFORMATION_SCHEMA'</span> <span class="synConstant"> and table_type = 'BASE TABLE'</span> <span class="synConstant"> ),</span> <span class="synConstant"> -- snowflake上に存在するテーブル</span> <span class="synConstant"> snowflake_tables as (</span> <span class="synConstant"> select</span> <span class="synConstant"> *</span> <span class="synConstant"> from</span> <span class="synConstant"> prod</span> <span class="synConstant"> union all</span> <span class="synConstant"> select</span> <span class="synConstant"> *</span> <span class="synConstant"> from</span> <span class="synConstant"> prep</span> <span class="synConstant"> ),</span> <span class="synConstant"> dbt_models as (</span> <span class="synConstant"> select</span> <span class="synConstant"> upper(database_name) as database_name,</span> <span class="synConstant"> upper(schema_name) as schema_name,</span> <span class="synConstant"> upper(name) as table_name</span> <span class="synConstant"> from</span> <span class="synConstant"> ELEMENTARY.ELEMENTARY.DBT_MODELS</span> <span class="synConstant"> where</span> <span class="synConstant"> package_name = 'your package name'</span> <span class="synConstant"> and materialization in ('table', 'incremental')</span> <span class="synConstant"> ),</span> <span class="synConstant"> dbt_seeds as (</span> <span class="synConstant"> select</span> <span class="synConstant"> upper(database_name) as database_name,</span> <span class="synConstant"> upper(schema_name) as schema_name, </span> <span class="synConstant">  upper(name) as table_name</span> <span class="synConstant"> from</span> <span class="synConstant"> ELEMENTARY.ELEMENTARY.DBT_SEEDS</span> <span class="synConstant"> where</span> <span class="synConstant"> package_name = 'your package name'</span> <span class="synConstant"> ),</span> <span class="synConstant"> -- dbtモデルとして管理されているテーブル</span> <span class="synConstant"> dbt_objects as (</span> <span class="synConstant"> select</span> <span class="synConstant"> *</span> <span class="synConstant"> from</span> <span class="synConstant"> dbt_models</span> <span class="synConstant"> union all</span> <span class="synConstant"> select</span> <span class="synConstant"> *</span> <span class="synConstant"> from</span> <span class="synConstant"> dbt_seeds</span> <span class="synConstant"> ) </span> <span class="synConstant"> -- 最後に差分をみる</span> <span class="synConstant"> select</span> <span class="synConstant"> *</span> <span class="synConstant"> from</span> <span class="synConstant"> snowflake_tables minus</span> <span class="synConstant"> select</span> <span class="synConstant"> *</span> <span class="synConstant"> from</span> <span class="synConstant"> dbt_objects;</span> <span class="synConstant"> &quot;&quot;&quot;</span> <span class="synIdentifier">print</span>(<span class="synConstant">&quot;----------------------------------------&quot;</span>) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;drop table queries&quot;</span>) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;----------------------------------------&quot;</span>) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;use role {dbtモデルを実行するロール};&quot;</span>) <span class="synStatement">for</span> row <span class="synStatement">in</span> cs.execute(query): database_name = row[<span class="synConstant">0</span>] schema_name = row[<span class="synConstant">1</span>] table_name = row[<span class="synConstant">2</span>] query = f<span class="synConstant">&quot;drop table if exists {database_name}.{schema_name}.{table_name};&quot;</span> <span class="synIdentifier">print</span>(query) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;----------------------------------------&quot;</span>) </pre> <h3 id="ビューの場合-1">ビューの場合</h3> <p>クエリを置き換えて同じように実行すればクエリを発行できます。</p> <h2 id="実際に実行してみた結果">実際に実行してみた結果</h2> <p>ビュー 47件、テーブル19件でした。</p> <p>思っていたよりありました。</p> <p>(本番DBで実行しているため、開発用DBの結果は含まれていません)</p> <p>処理を実行すると以下のようなスクリプトが発行されます。</p> <pre class="code console" data-lang="console" data-unlink>--------------------------------------- drop table queries ---------------------------------------- use role dbt; drop view if exists PREP.TEST_SCHEMA.TEST_VIEW; ----------------------------------------</pre> <p>これらの処理を自動で走らせることも検討しました。</p> <ul> <li>現状どんどん増えていくものではないので<strong>頻繁に実行する必要がない</strong>こと</li> <li><strong>削除処理が知らないところで走っていて、気づかないうちに消えていることを防ぎたい</strong></li> </ul> <p>上記の理由から手動で実行して発行されたクエリを月1で走らせるという運用をしています。</p> <p>クエリを走らせるのは以下のコストを見る会で行なっています。</p> <h3 id="コストを見る会について">コストを見る会について</h3> <p>私たちのチームでは、主にAWS、GCP、Snowflakeの3つのクラウドサービスといくつかのSaaSを利用しています。</p> <p>各種サービスのコストをチーム全員が把握し、コスト削減に繋げるために月1でチーム全員でコスト確認会を30分程度で行っています。</p> <p>やっていることとしては、</p> <ul> <li>各種クラウドサービスの月のコストの確認</li> <li>利用しているSaaSの月のコストの確認</li> <li>クエリコストが高いクエリパターンの確認 <ul> <li>select devを使ってやっています。</li> <li>詳しい話は<a href="https://techblog.cartaholdings.co.jp/entry/select-cloud-cost-optimize-cmf">この記事</a>で紹介しています。</li> </ul> </li> <li>snowflakeの使っていないデータの削除 <ul> <li>ストレージコストが増えていく一方なので月一で使わないデータを削除しています。今回の内容と同様に手動で行っています。</li> </ul> </li> <li>使っていないsnowflakeのテーブル、ビューの削除 <ul> <li>今回紹介している内容です</li> </ul> </li> <li>開発用データベースの削除 <ul> <li>基盤ユーザーは本番データベースをcloneして開発環境を作ってそこで作業しています。本番データベースからデータを削除しても、<strong>snowflakeではcloneされたテーブルからの参照が残っていると、物理的なストレージからはデータは消えない</strong>ため、ストレージコストが減らない仕様になっています。そのため毎回データを削除するたびに開発用データベースを削除しています。</li> </ul> </li> </ul> <h1 id="補足">補足 </h1> <h2 id="より安全に削除するためには">より安全に削除するためには</h2> <p><a href="https://github.com/get-select/dbt-snowflake-monitoring">dbt-snowflake-monitoring</a> が作ってくれる<a href="https://github.com/get-select/dbt-snowflake-monitoring/blob/main/models/query_base_table_access.sql">query_base_table_access</a>を使います。</p> <p>このモデルはテーブルへのアクセス履歴をクエリしやすいように加工したものです。dbt-snowflake-monitoringのモデルをビルドした際に作られます。</p> <p>私たちのチームでは他のプロダクト開発チームが作ったdbtモデルとは別でビルドするフローを用意していて1日1回実行されています。</p> <p><strong>テーブルごとに最後にアクセスされた日時がいつなのかを集計し、一定期間クエリされていないテーブルに絞り込みます</strong>。</p> <p>以下の例では1週間クエリされていないテーブルに絞り込んでいます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synSpecial">with</span> table_access_summary <span class="synSpecial">as</span> ( <span class="synStatement">select</span> full_table_name, <span class="synIdentifier">max</span>(query_start_time) <span class="synSpecial">as</span> last_accessed_at, max_by(user_name, query_start_time) <span class="synSpecial">as</span> last_accessed_by <span class="synSpecial">from</span> DBT_SNOWFLAKE_MONITORING.DBT_SNOWFLAKE_MONITORING.query_base_table_access <span class="synSpecial">group</span> <span class="synSpecial">by</span> <span class="synConstant">1</span> ), unused_snowflake_tables <span class="synSpecial">as</span> ( <span class="synStatement">select</span> full_table_name <span class="synSpecial">from</span> table_access_summary <span class="synSpecial">where</span> last_accessed_at &lt;= dateadd(<span class="synSpecial">'</span><span class="synConstant">day</span><span class="synSpecial">'</span>, <span class="synConstant">-7</span>, <span class="synIdentifier">current_date</span>) ), <span class="synComment">-- 削除忘れモデル すでに紹介したので省略しています</span> undeleted_snowflake_table <span class="synSpecial">as</span> ( <span class="synStatement">select</span> * <span class="synSpecial">from</span> snowflake_tables <span class="synStatement">minus</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> dbt_objects ) </pre> <p>削除忘れモデルの結果との共通部分を考えることで、削除忘れかつクエリされていないモデルを取得することができます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">select</span> database_name || <span class="synSpecial">'</span><span class="synConstant">.</span><span class="synSpecial">'</span> || schema_name || <span class="synSpecial">'</span><span class="synConstant">.</span><span class="synSpecial">'</span> || table_name <span class="synSpecial">as</span> full_table_name <span class="synSpecial">from</span> undeleted_snowflake_tables <span class="synStatement">intersect</span> <span class="synStatement">select</span> * <span class="synSpecial">from</span> unused_snowflake_tables; </pre> <p>以下の記事を参考にしました</p> <ul> <li><a href="https://select.dev/posts/snowflake-unused-tables">Identifying unused tables in Snowflake</a></li> </ul> <h2 id="もしも使っているテーブルやビューだったら">もしも使っているテーブルやビューだったら</h2> <p>undropしてください</p> <p><a href="https://docs.snowflake.com/ja/sql-reference/sql/undrop-table">https://docs.snowflake.com/ja/sql-reference/sql/undrop-table</a></p> <h1 id="おまけ">おまけ</h1> <p>elementaryがつくるdbt_artifactはめっちゃ便利です。</p> <p>私たちのチームはデータの品質管理を目的にelementaryを導入していますが、</p> <p>データの品質管理を目的としていなくても、dbt_artifactを使ってメタデータへのクエリがしやすくなるのでぜひ使ってみてください。</p> haruki0415 【Whyを追い求める】マネージャ経験で視座が高まる 【事業をエンジニアリングするラジオ #4:(テレシー後編)】 hatenablog://entry/6801883189067782531 2024-01-11T08:00:00+09:00 2024-02-27T10:14:07+09:00 チャンネル紹介 このシリーズは、よく何をやっているかわからない会社と言われるCARTA HOLDINGS(以下、CARTA)の技術や組織の中の雰囲気を、ゲストを交えてカジュアルに対談する企画。 第3回はテレシーのエンジニアにフォーカスした内容となっています。今回は起業から3年たったテレシーの激動期と開発本部長大竹のマネージャー経験での成長についての内容となっています。 自己紹介・経歴 曽根壮大(以下、そーだい) 合同会社 Have Fun Tech 代表社員。株式会社 Linkage のCTO。CARTA HOLDINGSとはVOYAGE GROUP(CARTAの前身)の時代からの6、7年の関… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/s_abe_1109/20231219/20231219172743.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1 id="チャンネル紹介">チャンネル紹介</h1> <p>このシリーズは、よく何をやっているかわからない会社と言われるCARTA HOLDINGS(以下、CARTA)の技術や組織の中の雰囲気を、ゲストを交えてカジュアルに対談する企画。</p> <p><a href="https://techblog.cartaholdings.co.jp/entry/carta-engineering-radio-3">第3回</a>はテレシーのエンジニアにフォーカスした内容となっています。今回は起業から3年たったテレシーの激動期と開発本部長大竹のマネージャー経験での成長についての内容となっています。</p> <h1 id="自己紹介経歴">自己紹介・経歴</h1> <div style="width: 100px; height: 100px; overflow: hidden; border-radius: 50%; position: relative;"> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/s_abe_1109/20230906/20230906195429.png " style="object-fit: cover; object-position: center; width: 100%; height: 100%; position: absolute;"> </div> <p>曽根壮大(以下、<span style="color: #1464b3"><strong>そーだい</strong></span>)<br/> 合同会社 Have Fun Tech 代表社員。株式会社 Linkage のCTO。CARTA HOLDINGSとはVOYAGE GROUP(CARTAの前身)の時代からの6、7年の関わり。Zucks事業部への技術顧問やデータベース設計のサポート、広報活動への協力。他社との業務委託も並行しており技術部での多岐にわたる経験がある。</p> <div style="width: 100px; height: 100px; overflow: hidden; border-radius: 50%; position: relative;"> <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/s_abe_1109/20231121/20231121131221.jpg " style="object-fit: cover; object-position: center; width: 100%; height: 100%; position: absolute;"> </div> <p>大竹 聡子(以下、<span style="color: #DD8800"><strong>大竹</strong></span>)<br/> 株式会社テレシー開発本部部長。2003年にCARTAの前身のVOYAGE GROUPへ入社し、アクシブドットコムで活動を開始。CARTA入社前は2、3年で転職を繰り返していたが、CARTAのカルチャーにフィットし勤続20年。CARTAのエンジニアから慕われる。</p> <h1 id="テレビ広告の裏側を知れるテレシーの面白さ">テレビ広告の裏側を知れるテレシーの面白さ</h1> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> はい。じゃあ、後半戦テレシーの紹介を引き続き、そーだいと大竹さんとでやっていきたいと思います。</p> <p><a href="https://techblog.cartaholdings.co.jp/entry/carta-engineering-radio-3">前半</a>は、テレシーがどんな会社かとか、大竹さんがCARTAの中でどんなことをしてきたかをお話しました。</p> <p>後編では、テレシーの事業詳細と、今大竹さんがテレシーの中でどんなお仕事をされているのか、今後どのような方向に向かうのかを聞けると良いなと思います。</p> <p><a href="https://techblog.cartaholdings.co.jp/entry/carta-engineering-radio-3">前半</a>でも話しましたけど、デジタルマーケティングと違うところや、テレビ業界とかって僕らからするとだいぶ遠い世界という印象があります。</p> <p>それの窓口としていろんな仕事ができると面白そうだし、そういう意味では、<strong>スタートアップだけどお客さんが大手というB2Bの面白さというところが詰まっている</strong>なという印象があります。</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> 私もずっとデジタルマーケティング、デジタル広告の領域しか携わっていなかったのでマス広告は、勉強になりますね。特にテレビとかね、配信するまでのステップがものすごく多かったりして違いが多いです。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> その裏側に関われるとか見れるというのは、ソフトウェアの面白さとは違った、事業の面白さだと思いますね。</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> 事業の面白さ以外にも、<strong>テレシーのいいところって、電通協業なので、今までCARTAにないカルチャーというのがあります。</strong></p> <p><strong>これまで見たことがない強いビジネスプロデュース的な戦略的なやり方、コンサル的な手法というのを見せてもらって「これすごいな」と思うこともあります。その中で、CARTAの強みであるエンジニアリングで一番必要なものを生み出していきたい</strong>です。</p> <h1 id="スタートアップ3年間でやってきたこと">スタートアップ3年間でやってきたこと</h1> <h3 id="スタートアップ激動の3年間">スタートアップ 激動の3年間</h3> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> この3年のスタートアップの激動期をどのように乗り越えてきましたか?</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> 私はテレシーにテレビCMの効果分析手法を作るためのデータサイエンティストとしてジョインしました。プロダクトも何もできていない状態だったので、案件ベースでアドホックに分析し、フィードバックレポートを作って返す、というのが最初でした。なかなかプロダクトを作る時間も取れず大変な時期が約1年間ほど続きました。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> なるほど。大変な1年間ですね。</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> 現在は、「テレシーアナリティクス」プロダクトが完成し、そこでCM効果の分析などができるようになっています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/s_abe_1109/20231218/20231218110807.png" width="1200" height="647" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftelecy.tv%2Fcheck%2F" title="効果測定【テレシーアナリティクス】 | 運用型テレビCMサービス | テレシー(TELECY)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://telecy.tv/check/">telecy.tv</a></cite></p> <p>その中で私は、約1年前にマネージャーになり、最近は幅広いことをしています。プロダクト開発の品質管理的なことや、データフローの検証も行っています。</p> <p>データサイエンティスト目線だと、分析データの一般性や数値の整合性が取れているかといったデータの品質が重要視されます。そのための品質保証的なチェックもしています。</p> <p>あと、テレシーは電通との共同事業なので、プロダクト面でも電通のグローバルポリシーに合わせたセキュリティ対策を行うなど、そちらと連携したセキュリティ強化も行っています。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> なるほど、プログラマやデータサイエンティストの仕事から、CISO(最高情報セキュリティ責任者)やCIO(最高情報責任者)的なセキュリティ管理の仕事とか、スタートアップに必要なものは幅広く、何でもやる、という形で力を発揮されているんですね。</p> <h3 id="マネージャー経験で視座が上がる養われる判断力">マネージャー経験で視座が上がる&養われる判断力</h3> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> いろんなことを経験するメリットは何だったでしょうか。キャリアとしてどのように成長できたと感じますか?</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> <strong>視点や視座が変わりましたね。今までプレイヤーとして仕事をしていたのが、マネージャーになることで視点が上がる</strong>というか、見なければならないことや気を付けなければならないことが一段上のレベルになります。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> <strong>マネージャーを経験することで視座が上がる</strong>というのも経験上その通りだと思います。僕もCTOを経験してからプレイヤーに戻った時、2歩3歩先のことが読めるようになっていたり、判断がうまくなったりしていましたから。</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> あと、<strong>マネージャーは次に何をすべきかを考えることも重要</strong>だと思います。もちろん1人で考えるのではなく、みんなと事業の成長や要求に合わせて検討していきます。<strong>需要と供給のバランスを見極めるようにもなりましたし、必要ないと判断した際にはドライに切る、ということも普通にできるようになりました。</strong></p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> おっしゃる通り、<strong>自分で判断していく力はマネージャーを経験することで身についていくと思います。自分の決断に責任を持つことでバランス感覚も養われていくと思います。</strong></p> <p>結果としてビジネスを前に進めることができるので、<strong>マネジメント経験はプレイヤーに戻った後でも非常に役立つ</strong>ので色々経験してみるのは良いことかな、と思います。</p> <h3 id="WhyとWhatに立ち戻って判断する">WhyとWhatに立ち戻って判断する</h3> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> テックリードや事業責任者になると技術選定や事業判断を迫られますよね。僕も3回くらいCTOをやって、ビジネスを含めた判断力が上がっていった感覚があります。大竹さんはどのような点を重視して判断されているのでしょうか。</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> <strong>「今やってることが最終的に何のためになるのか」を意識して判断しています。</strong></p> <p><strong>CARTAのエンジニアに共通しているのは、何が目的なのか、何をしたいのか、という原点に立ち返ることだと思います。「開発する目的は何か」を忘れずにいることが大切だと思います。</strong></p> <p>みんな、Howに目を向けすぎて、本来の目的を忘れちゃうんですよね。そんなときは一旦リセットして、<strong>WhyとWhatに立ち返る必要があります。これは自問自答していくことが重要だと考えていて、自分の作業が最終的に何のためかを意識することで、自然と判断ができる</strong>のではないかと思っています。それは個人でもビジネスでも同じですね。</p> <p><strong>それはマネージャーだけがするべきことではなく、新卒1年目のエンジニアでも2年目でも、その視点を持っていれば素晴らしい判断ができる</strong>と思っています。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> なるほど、CARTAのテックビジョンにある「<strong>本質志向</strong>」が強く浸透していることが伺えますね。<strong>目的を忘れずに仕事をすることが強み</strong>になっているんですね。</p> <p><figure class="figure-image figure-image-fotolife" title="https://techblog.cartaholdings.co.jp/tech-vision"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/s_abe_1109/20231218/20231218110950.png" width="1200" height="681" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption><a href="https://techblog.cartaholdings.co.jp/tech-vision">https://techblog.cartaholdings.co.jp/tech-vision</a></figcaption></figure></p> <blockquote><p><strong>本質志向</strong><br/> 何が本質的な課題であり、何が解くべき問題なのか。何をすべきで何をやらないべきか。私たちは徹底的に考え、判断し、前進します。 役割により、着想が制約されるべきではありません。問題を解くために私たちはとことん調査し、議論し、自ら実行し、振り返ります。 すべての時間は事業を進め、価値を生み出すためにあります。 <a href="https://techblog.cartaholdings.co.jp/tech-vision">https://techblog.cartaholdings.co.jp/tech-vision</a></p></blockquote> <h1 id="強い駒で将棋を打てる環境がある">「強い駒で将棋を打てる」環境がある</h1> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> エンジニア13人で逆に言うとビジネスとして成功していて、売上も伸びていて事業としても成長している印象があって。</p> <blockquote><p><strong>テレシーの構成</strong><br/> 事業会社全体の人数 :37人程度<br/> 開発チームの人数:13人</p></blockquote> <p>事業の困難さや悩みどころもあると思います。どのようなところが課題だと感じていらっしゃいますか?</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> テレシーはエンジニア13人でやっているスタートアップです。メンバーが若く、事業戦略は決まっていますが、市場動向に合わせて組織は拡大していき戦略も変化します。やりたいことはたくさんあるのですが、具体的に言うと人が足りないのが課題です。</p> <p>本当に実装面でやりたいことがいっぱいあるので...。客観的に見ると、自分を最大限生かすには駒になるのが一番だと思う時があります。需要があれば上手く自分を駒として使っていただければと思ってます。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> 今、大竹さんに代わってプロジェクトを回してくれるマネージャーがテレシーに入ってきたらガツガツコードを書いていきたい感じなんですか?</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> そうですね。データやプロダクトの品質を気にしながらマネジメントできる人がほしいです。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> CARTAには最強のプレイヤーが多くいて、それはプロジェクトマネージャーにとって非常に恵まれた環境なんですよね。CARTAはスタートアップに強力なプレイヤーをバンッと置いてものすごく突破力を上げることがあるのですが、それはCARTAのカラーだな、と思います。</p> <p>僕はよくマネージャーを「将棋」で例えますが、大体のケースで「リソースが足りない」という場合、「飛車がいればここを突破できる」「角がいればここを固められる」と”タラレバ”が多いのですが...。<strong>テレシーには大竹さんという最強の竜(飛車:成)がいらっしゃるので、マネジメントとして将棋を指すのが楽になりますし挑戦しやすい環境ですね。</strong></p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> ありがとうございます。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> 他社でテックリードやチームリーダーなどを経験し、次はマネジメントやプロジェクトリードにガッツリ関わりたい人が入って、最強のプレイヤーである大竹さんに支えてもらいながらプロジェクト推進できるのは成長できるチャンスだと思います。</p> <p>大竹さんはマネージャーとしてもプレイヤーとしても活躍できるからこそ、守りの駒にも攻めの駒にもなれるわけですね。20代前半の若い子は香車のように一方向にガッと進みますが、大竹さんは酸いも甘いも経験した上での判断ができる方だと思います。そんな大竹さんにマネージャーとしても学ぶことがたくさんあると思います。</p> <p>若いマネージャーを目指す人にとって、テレシーで大竹さんと一緒に仕事をするのは非常に良い成長のチャンスになるのではないでしょうか。</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> うちに来たら上手く私を使ってください。</p> <h1 id="テレシーの目指す方向性">テレシーの目指す方向性</h1> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> そんなCARTAの中でもテレシーというカラーなんですけども、最後に今後どういうふうなテレシーを目指していくかとか、ミッションの話というので締めていこうと思うんですけど、大竹さんどうでしょう。</p> <p><span style="color: #DD8800"><strong>大竹:</strong></span> そうですね、<strong>最高峰のビジネスサイドとエンジニアを掛け合わせて、最適なプロダクトを提供し、事業を成長させていくというところが今後の目指している方向性</strong>ですかね。</p> <p><span style="color: #1464b3"><strong>そーだい:</strong></span> いやーいいですね。かっこいい感じでしまったので、第4回はこれでおわりたいと思います。ご活躍を応援しております。ぜひ皆さんも興味があったらテレシー調べてみてください。</p> <h3 id="今までの記事にご興味ありましたらこちら">▶今までの記事にご興味ありましたらこちら</h3> <ul> <li><a href="https://techblog.cartaholdings.co.jp/entry/carta-engineering-radio-1">#1 技術的なビジョンがある会社は珍しい?Tech Visionと「CARTAっぽさ」とは【事業をエンジニアリングするラジオ】</a></li> <li><a href="https://techblog.cartaholdings.co.jp/entry/carta-engineering-radio-2">【フィードバックはエンジニアの財産】CARTAは学びを大切にする会社【事業をエンジニアリングするラジオ #2】</a></li> <li><a href="https://techblog.cartaholdings.co.jp/entry/carta-engineering-radio-3">【MVPを3日で作って試す】運用型テレビCMを手がける株式会社テレシー【事業をエンジニアリングするラジオ #3】</a></li> </ul> <p><a href="https://cartaholdings.co.jp/engineering/">CARTAエンジニアの価値観と制度についてはこちらもご覧ください</a></p> <h3 id="PR">PR</h3> <p>株式会社テレシーはエンジニアを募集中です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhrmos.co%2Fpages%2Fcartaholdings%2Fjobs%2Ftelecy-e10" title="【シニアアプリケーションエンジニア】運用型テレビCM分析サービスの開発をリードする | 株式会社CARTA HOLDINGS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://hrmos.co/pages/cartaholdings/jobs/telecy-e10">hrmos.co</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhrmos.co%2Fpages%2Fcartaholdings%2Fjobs%2Ftelecy-e14" title="【プロジェクトマネージャー(PjM)】運用型テレビCM分析プロダクトのデータ品質管理・戦略策定等のプロジェクトマネジメント業務をお任せ! | 株式会社CARTA HOLDINGS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://hrmos.co/pages/cartaholdings/jobs/telecy-e14">hrmos.co</a></cite></p> <p>CARTA HOLDINGSの他事業部もエンジニアを積極大募集中です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhrmos.co%2Fpages%2Fcartaholdings%2Fjobs%3Fcategory%3D1260098130969743360" title="株式会社CARTA HOLDINGS エンジニア職 の求人一覧" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://hrmos.co/pages/cartaholdings/jobs?category=1260098130969743360">hrmos.co</a></cite></p> <p>まずはカジュアルにお話してみませんか? <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhrmos.co%2Fpages%2Fcartaholdings%2Fjobs%2F0-casual-e001" title="【エンジニア】CARTA HOLDINGSのエンジニアたちとカジュアル面談する | 株式会社CARTA HOLDINGS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://hrmos.co/pages/cartaholdings/jobs/0-casual-e001">hrmos.co</a></cite></p> s_abe_1109 SendGrid利用時のメモリ消費問題を改修した話:メール送信におけるパフォーマンス最適化の事例 hatenablog://entry/6801883189053105621 2024-01-10T13:07:21+09:00 2024-02-27T11:22:32+09:00 まえがき はじめてのかたははじめましてhaya_timeです。 ブログを書く機会が全然なく、大昔に何回か書いたっきりだったので、ニックネームも変わりました。 新しいニックネームの由来は小学校で履修してるはずなので省略しますが、そもそも正解が分からないと思うので悶々としたままブログを読んで頂ければと思いますww さて、むかーしに書いたSendGridの記事の続きっぽいことを最近実務で行ったので、せっかくなのでブログにしておこうと思います。 内容としては、現在の運用しているシステムでSendGridを使ってメールを送信しているのですが、宛先1件1件ごとに処理を実施していたため、 件数が多くなるとメ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231129/20231129083424.jpg" width="800" height="533" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h4 id="まえがき">まえがき</h4> <p>はじめてのかたははじめましてhaya_timeです。</p> <p>ブログを書く機会が全然なく、大昔に何回か書いたっきりだったので、ニックネームも変わりました。</p> <p>新しいニックネームの由来は小学校で履修してるはずなので省略しますが、そもそも正解が分からないと思うので悶々としたままブログを読んで頂ければと思いますww</p> <p>さて、むかーしに書いたSendGridの記事の続きっぽいことを最近実務で行ったので、せっかくなのでブログにしておこうと思います。</p> <p>内容としては、現在の運用しているシステムでSendGridを使ってメールを送信しているのですが、<strong>宛先1件1件ごとに処理を実施していたため、 件数が多くなるとメモリが肥大化しOut Of Memory(OOM)になってしまったので、 今回はその原因を解決するための改修した内容をまとめました。</strong></p> <p>ざっくりまとめると、今までは1件1件Promiseオブジェクトを作成し1000件ごとに実行を、<strong>Promiseオブジェクトを作成する過程でメモリが枯渇しする問題が発生したため、 その対応としてメールアドレスを「personalizations」配列に追加して送信するように改修しました。</strong></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Farchives%2F7746" title="SendGridのアクティビティログを収集してみた - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/archives/7746">techblog.cartaholdings.co.jp</a></cite></p> <h4 id="SendGridとは">SendGridとは?</h4> <p>おさらいとして、まずはSendGridとは?をもう一度説明していきたいと思います。</p> <p>SendGridとはメール配信サービスです。</p> <p>クラウドサービスなので登録すればすぐに使えてライブラリも豊富なので実装しやすくお値段も結構お手軽な感じかなと思います。</p> <p>また、公式ブログもあり導入手順など色々な情報が上がっているので便利なので、システムのメールサービスとして使用させてもらっています。</p> <p>興味のある方はぜひHPのブログも参照してみて下さい。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsendgrid.kke.co.jp%2F" title="SendGrid | クラウドメール配信サービス・メルマガ配信システム" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://sendgrid.kke.co.jp/">sendgrid.kke.co.jp</a></cite></p> <h4 id="動作環境">動作環境</h4> <p>SendGridを使っているサービスの運用環境は下記の通りです。</p> <ul> <li>サーバ:AWS EC2 <ul> <li>nodejs(v18.x)</li> <li>SendGridライブラリ:Mail Service for the SendGrid v3 Web API(v7.7.x)</li> </ul> </li> </ul> <h4 id="ソース改修前">ソース改修前</h4> <p>実際のソースをそのまま転記出来ないので、一部処理を省いたり変更していますが、大体はこんな感じ実装していました。</p> <p>ざっくり解説すると1件ずつ送信処理のPromiseオブジェクトを作成し、1000件ごとに処理を一括実行しています。</p> <p>リリース当時は問題なく動作していたのですが、この処理だと例えばメール本文にBase64形式の画像が貼り付けられて容量が大きくなった場合、 1000件のオブジェクトを作成する過程でメモリが枯渇してしまいます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>■ループ処理 <span class="synComment">// 送信を行う</span> <span class="synIdentifier">let</span> emailAccounts = toAccounts.filter((x) =&gt; x.isEmailReceive) applogger.info(<span class="synIdentifier">{</span> msg: emailAccounts.length + <span class="synConstant">'件送信'</span> <span class="synIdentifier">}</span>) <span class="synIdentifier">let</span> cnt = 0 <span class="synIdentifier">let</span> promiseList = <span class="synIdentifier">[]</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> toObj of emailAccounts) <span class="synIdentifier">{</span> <span class="synComment">// 配列に処理を追加</span> promiseList.push(<span class="synIdentifier">this</span>.sendEmail(message, toObj)) cnt++ <span class="synStatement">if</span> (cnt % 1000 === 0) <span class="synIdentifier">{</span> <span class="synComment">// 1000件ごとに送信処理を実行</span> await Promise.allSettled(promiseList) applogger.info(<span class="synIdentifier">{</span> msg: cnt + <span class="synConstant">'/'</span> + emailAccounts.length + <span class="synConstant">'件送信完了'</span> <span class="synIdentifier">}</span>) promiseList = <span class="synIdentifier">[]</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synComment">// 残りがあれば送信</span> <span class="synStatement">if</span> (promiseList.length &gt; 0) <span class="synIdentifier">{</span> await Promise.allSettled(promiseList) applogger.info(<span class="synIdentifier">{</span> msg: cnt + <span class="synConstant">'/'</span> + emailAccounts.length + <span class="synConstant">'件送信完了'</span>, <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> </pre> <p>下記のソースは今回あまり変更対象になっていないのですが、処理の流れが分かりやすいかなぁと思い添付しました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>■メール送信呼出し元の処理 <span class="synComment">/**</span> <span class="synComment"> * 宛先に対してメールを送信する</span> <span class="synComment"> * @param {Object} message メッセージデータ</span> <span class="synComment"> * @param {Object} toObj 宛先オブジェクト</span> <span class="synComment"> * @return {Promise}</span> <span class="synComment"> */</span> sendEmail: <span class="synIdentifier">function</span> (message, toObj) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise((resolve, reject) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">try</span> <span class="synIdentifier">{</span> applogger.info(<span class="synIdentifier">{</span> msg: <span class="synConstant">'メール送信処理 開始'</span> <span class="synIdentifier">}</span>) <span class="synComment">// メール内容を結合する</span> <span class="synIdentifier">let</span> contentHtml = message.content + message.contentAttachmentURI + message.contentFooter <span class="synComment">// メール用のパラメータを設定</span> <span class="synStatement">const</span> params = <span class="synIdentifier">{</span> to: toObj.email, cc: <span class="synIdentifier">[]</span>, bcc: <span class="synIdentifier">[]</span>, subject: message.subject, accountId: toObj.accountId ? toObj.accountId : <span class="synConstant">''</span>, content: contentHtml, <span class="synIdentifier">}</span> <span class="synComment">// メール送信</span> htmlMail.sendCompileHtmlMail( commonConst.MAIL.NFO_MAIL, params, (err) =&gt; <span class="synIdentifier">{</span> <span class="synStatement">if</span> (err) <span class="synIdentifier">{</span> reject( commonUtil.createErrorObject( commonConst.HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR, commonMessage.ERR_0010 ) ) <span class="synIdentifier">}</span> <span class="synStatement">else</span> <span class="synIdentifier">{</span> resolve() <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> ) <span class="synIdentifier">}</span> <span class="synStatement">catch</span> (err) <span class="synIdentifier">{</span> reject(err) <span class="synIdentifier">}</span> <span class="synStatement">finally</span> <span class="synIdentifier">{</span> applogger.info(<span class="synIdentifier">{</span> msg: <span class="synConstant">'送信処理 終了'</span> <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span>, </pre> <pre class="code lang-javascript" data-lang="javascript" data-unlink>■メール送信処理のメイン <span class="synComment">// メールの送信先リスト作成</span> <span class="synIdentifier">let</span> personalizations = <span class="synIdentifier">[]</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> toObj of emailAccounts) <span class="synIdentifier">{</span> personalizations.push(<span class="synIdentifier">{</span> to: toObj.email, custom_args: <span class="synIdentifier">{</span> accountId: toObj.accountId ? toObj.accountId : <span class="synConstant">''</span>, <span class="synIdentifier">}</span>, <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> <span class="synIdentifier">let</span> request = <span class="synIdentifier">{</span> personalizations: personalizations, from: config.get(<span class="synConstant">'mailConfig.mediaInfoFrom'</span>), reply_to: mediaInfoReply_to, subject: _s.unescapeHTML(subjectCompiler(data)), content: <span class="synIdentifier">[</span> <span class="synIdentifier">{</span> type: <span class="synConstant">'text/html'</span>, value: htmlCompiler(data), <span class="synIdentifier">}</span>, <span class="synIdentifier">]</span> attachments: inlineAttachments, <span class="synIdentifier">}</span> </pre> <h4 id="改修結果">改修結果</h4> <p>改修方法として、1000件分のメールアドレスが入った「personalizations」配列と本文を入れたPromiseオブジェクトを作成するようにしました。</p> <p>ちなみにSendGridの仕様として一度に設定出来る件数は1000件なので、1000件ごとにPromiseオブジェクトを作成するループ処理はそのままとなっています。それでも今まで数千件オブジェクト作成していたのが、数件になるんでメモリに余裕が出来ますね。</p> <p>同じように1000件溜まったらPromiseオブジェクトを実行というようにしています。</p> <p>こんな感じでソースを修正しました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>■ループ処理 <span class="synComment">// メール送信</span> applogger.info(<span class="synIdentifier">{</span> msg: emailAccounts.length + <span class="synConstant">'件送信開始'</span> <span class="synIdentifier">}</span>) <span class="synIdentifier">let</span> cnt = 0 <span class="synIdentifier">let</span> emailList = <span class="synIdentifier">[]</span> <span class="synIdentifier">let</span> promiseList = <span class="synIdentifier">[]</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> toObj of emailAccounts) <span class="synIdentifier">{</span> emailList.push(toObj) cnt++ <span class="synStatement">if</span> (cnt % 1000 === 0) <span class="synIdentifier">{</span> <span class="synComment">// 1000件ごとに送信処理を作成する(SendGridのMultipleの上限が1000件のため)</span> promiseList.push(sendEmail(message, emailList)) <span class="synComment">// await sendEmail(message, emailList)</span> applogger.info(<span class="synIdentifier">{</span> msg: cnt + <span class="synConstant">'/'</span> + emailAccounts.length + <span class="synConstant">'件処理完了'</span>, <span class="synIdentifier">}</span>) emailList = <span class="synIdentifier">[]</span> <span class="synIdentifier">}</span> <span class="synIdentifier">}</span> <span class="synComment">// 残りがあれば送信処理追加</span> <span class="synStatement">if</span> (emailList.length &gt; 0) <span class="synIdentifier">{</span> promiseList.push(sendEmail(message, emailList)) applogger.info(<span class="synIdentifier">{</span> msg: cnt + <span class="synConstant">'/'</span> + emailAccounts.length + <span class="synConstant">'件処理完了'</span>, <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> <span class="synComment">// 送信処理実施</span> await Promise.allSettled(promiseList) applogger.info(<span class="synIdentifier">{</span> msg: cnt + <span class="synConstant">'/'</span> + emailAccounts.length + <span class="synConstant">'件送信完了'</span>, <span class="synIdentifier">}</span>) </pre> <p>呼出し元を修正したのでメール送信処理のメイン部分も少し変更しています。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>■メール送信処理のメイン <span class="synComment">// メールの送信先リスト作成</span> <span class="synIdentifier">let</span> personalizations = <span class="synIdentifier">[]</span> <span class="synStatement">for</span> (<span class="synIdentifier">let</span> toObj of emailAccounts) <span class="synIdentifier">{</span> personalizations.push(<span class="synIdentifier">{</span> to: toObj.email, custom_args: <span class="synIdentifier">{</span> accountId: toObj.accountId ? toObj.accountId : <span class="synConstant">''</span>, <span class="synIdentifier">}</span>, <span class="synIdentifier">}</span>) <span class="synIdentifier">}</span> <span class="synIdentifier">let</span> request = <span class="synIdentifier">{</span> personalizations: personalizations, from: config.get(<span class="synConstant">'mailConfig.mediaInfoFrom'</span>), reply_to: mediaInfoReply_to, subject: _s.unescapeHTML(subjectCompiler(data)), content: <span class="synIdentifier">[</span> <span class="synIdentifier">{</span> type: <span class="synConstant">'text/html'</span>, value: htmlCompiler(data), <span class="synIdentifier">}</span>, <span class="synIdentifier">]</span> attachments: inlineAttachments, <span class="synIdentifier">}</span> </pre> <h4 id="動作確認">動作確認</h4> <p>動作確認してみました。</p> <p>本文が422200文字あるメールを4252件送信したときのログです。</p> <p>これもログをそのまま載せれないので省略していますが、無事1000件ごとにメール処理を実行されメモリが枯渇することなく全件送信出来ました。</p> <p>リリース後も本文の容量が大きいメールがいくつか送信されていますが、とくにOOMが発生することなく運用しています。</p> <pre class="code" data-lang="" data-unlink>2023-11-01T17:04:41.509 メッセージ送信 メッセージ件名: xxxxxxxx メッセージ本文の文字数: 422200 2023-11-01T17:04:41.727 4252件送信開始 2023-11-01T17:04:41.728 情報発信メール送信処理 開始 2023-11-01T17:04:41.801 1000/4252件処理完了 2023-11-01T17:04:41.801 情報発信メール送信処理 開始 2023-11-01T17:04:41.872 2000/4252件処理完了 2023-11-01T17:04:41.872 情報発信メール送信処理 開始 2023-11-01T17:04:41.914 3000/4252件処理完了 2023-11-01T17:04:41.914 情報発信メール送信処理 開始 2023-11-01T17:04:41.954 4000/4252件処理完了 2023-11-01T17:04:41.954 情報発信メール送信処理 開始 2023-11-01T17:04:41.975 4252/4252件処理完了</pre> <h4 id="まとめ">まとめ</h4> <p>今回はOOMが発生していた問題を改修する内容を書きました。 事前に実際に送信される件数とか確認して、テストも実施してるのですが・・・やはり本番稼働すると色々と問題って発生しますね。。。</p> <p>前のブログに書いているのですが、このシステムはメールの開封数や、本文内のリンクのクリック数などを計測しています。 開封率は2~3割と低いといえば低いのですが、ダイレクトメールとしては一般的な開封率らしいですね。</p> <p>まずまずの開封率で、結構見てくれている人はいるんだなぁーって思いながら今回の改修で安定してメールを提供出来るようになってホッと一息つけたかなと思います。</p> <p>またブログのネタが出来たら書いていきたいと思いますので、それまで皆様お達者で!!</p> <h4 id="参考文献">参考文献</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsendgrid.kke.co.jp%2Fblog%2F%3Fp%3D1300" title="SendGridを使って短時間に大量のメールを送るための方法 | SendGridブログ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://sendgrid.kke.co.jp/blog/?p=1300">sendgrid.kke.co.jp</a></cite> <a href="https://www.npmjs.com/package/@sendgrid/mail">https://www.npmjs.com/package/@sendgrid/mail</a></p> haya_time 新卒でサポーターズに配属されて強いエンジニア像が変わった hatenablog://entry/6801883189069640348 2023-12-25T16:52:54+09:00 2024-02-27T11:22:51+09:00 こんにちは!今年CARTAに新卒で入社し、現在はサポーターズでエンジニアをやっているほりです。 この記事は、CARTA TECH BLOG アドベントカレンダー 2023の12/23の記事です。 サポーターズと開発チームについて 私はサポーターズの開発チームに所属しており、新卒エンジニアの就職支援サービスの開発を行っています。CARTAでは珍しくtoC色強めだけどtoBとしての側面もあったり、正社員エンジニア5人で構成される小さなチームでビジネスサイドの距離も近かったりという特徴があります。 チームの体制について チームが小さいので、何らかのテーマに向かって全員で開発に取り組んで戦力を集中させ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/horiy0125/20231225/20231225145029.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!今年CARTAに新卒で入社し、現在はサポーターズでエンジニアをやっている<a href="https://twitter.com/horiy0125">ほり</a>です。</p> <p>この記事は、CARTA TECH BLOG アドベントカレンダー 2023の12/23の記事です。</p> <h2 id="サポーターズと開発チームについて">サポーターズと開発チームについて</h2> <p>私はサポーターズの開発チームに所属しており、新卒エンジニアの就職支援サービスの開発を行っています。CARTAでは珍しくtoC色強めだけどtoBとしての側面もあったり、正社員エンジニア5人で構成される小さなチームでビジネスサイドの距離も近かったりという特徴があります。</p> <h3 id="チームの体制について">チームの体制について</h3> <p>チームが小さいので、<strong>何らかのテーマに向かって全員で開発に取り組んで戦力を集中させる</strong>ことが多いです。</p> <p>サポーターズにはイベント、メディア・紹介、技育と複数の事業があります。今年の夏は、イベントや紹介にあたる部分を集中的に開発していました。</p> <p>そのような時には、</p> <ul> <li>定例MTGとは別に週次でMTGが行われ、ビジネスサイドからの要望を拾う <ul> <li>今後何をやっていくかの目線を短いスパンで合わせる</li> </ul> </li> <li>サポーターズにとってより良い判断が取れるよう話し合う <ul> <li>そもそもなんでそれをやりたいのか</li> <li>他にもっと良い方法がありそうなのか</li> </ul> </li> </ul> <p>その後、開発チームとしても技術的にどうやって解決するかを話し合い、小さなタスクに落とし込むところまでやっています。</p> <h2 id="サポーターズでの業務">サポーターズでの業務</h2> <p>私は、内定者バイト時代も含めるとサポーターズで1年ほど業務に取り組んでいます。</p> <p>内定者バイトの頃から単独で動いていた時期が長く、正式に配属された後も任されたタスクが終わるまでは一人で動いていました。</p> <h3 id="働き方の変化">働き方の変化</h3> <p>任されていたタスクを無事に終え、今年の夏くらいから<strong>チームと連携して開発</strong>に取り組むようになりました。</p> <p>ジョインしてすぐ、今後何をやっていくのかビジネスサイドと目線を合わせるところから始まっていきました。</p> <p>自分としては正直、目の前で物事がものすごいスピードで決まっていく状況についていくことができていませんでした。</p> <p>特に紹介事業にあたる部分は完全な新規開発であったうえに、わりとタイトなスケジュール。</p> <ul> <li>そもそも何でシステム化をしていきたいのか</li> <li>この期間内にやるとしたらどこまで小さくはじめられそうか</li> </ul> <p>など、プロジェクトの初期状況から話し合っていきました。</p> <p>今でもそのあたりの話について自分一人で満足な判断ができるとは思っていませんが、当時は<strong>ただただ話を聞いて理解していくだけでも精一杯</strong>になってしまっていました。</p> <p>「このままじゃまずいな」という自覚も焦りもありましたが、そのMTGの後にチームメンバーから言われたことで最も衝撃を受けることになります。</p> <blockquote><p>「ついて来れてる?」「かなり頑張らないとまずいと思うよ」</p></blockquote> <p>ここまで突き放すような雰囲気ではなく、その後現状をより良くしていくためにどうするかなど相談にも乗ってくれました。</p> <p><strong>が、かなり悔しかったしヘコみました。</strong></p> <p>それでも、当時の自分が客観的に見てもMTGで価値を発揮できていなかったんですよね。</p> <blockquote><p>少しでも良い動きができるようになりたいし、早くこの状態を脱却したい!</p></blockquote> <p>と思い、できそうなことを少しずつ行動に移してみるところからはじめました。</p> <ul> <li><p><strong>議事録をとってみる</strong></p> <ul> <li>文字に起こし、整理することで理解を深める</li> </ul> </li> <li><p><strong>わからないことはその場で聞いてみる</strong></p> <ul> <li>チーム雑談の場や取締役がいるMTGの場でも恐れずに質問する</li> </ul> </li> <li><p><strong>少しでも思ったことがあればみんなに共有する</strong></p> <ul> <li>極論、Meetのチャットとかdocsのコメントとかでも良い</li> <li>議論している内容に対して「良さそう」「なんか違うと思う」(+ 理由)レベルのレスポンスをするだけでも、そこから話が広がっていくこともある</li> </ul> </li> <li><p><strong>動く画面ができたら、ビジネスサイドに見せる</strong></p> <ul> <li>文言を見てほしいレベルから、全体的な挙動こんな感じだけどどうですか?という温度感でも見せてフィードバックをもらう</li> </ul> </li> <li><p><strong>差し込みタスクに自分から手を挙げにいく</strong></p> <ul> <li>わからないことを減らすのに繋がるし、今後も拾いに行く抵抗感がなくなる</li> </ul> </li> </ul> <p>主に意識して動いたのはこの辺りです。</p> <p>いろいろ行動を変えてみて、<strong>普段から発言量が増えたり、コードを書く以外の仕事が広がったり</strong>という変化があったことで、より業務領域の理解が深まったなと実感しています。</p> <p>追い込みすぎはよくないけど、ある程度日々の業務やMTGなどでいい動きができているかどうかは気にするようにしていて、</p> <blockquote><p>以前の自分より価値を発揮することができてきているな</p></blockquote> <p>と自信を持てるようになってきました。</p> <p>最近の1on1で、チームのマネージャーや取締役から</p> <blockquote><p>「ほりの動き良くなったね」 「いい波に乗っている」</p></blockquote> <p>というフィードバックをもらえたので、チームから見てもプラスに働いていそうです。</p> <h2 id="強いエンジニアってなんだっけ">「強いエンジニア」ってなんだっけ</h2> <p>このような環境で開発業務にあたっていく中で、これまでひたすら「強いエンジニアになりたい」と思っていた<strong>「強い」の意味が変わってきている</strong>ことに気づきました。</p> <h3 id="技術的に強いエンジニアでありたかった">技術的に「強いエンジニア」でありたかった</h3> <p>過去の自分は「強いエンジニア」と聞くと、特に<strong>技術的な強さをイメージ</strong>する傾向がありました。技術的に強ければやりたいことをなんでも実現できて潰しが効きそうだなと。</p> <p>技術は引き出しのようなもので、何かを実現させるための手段だという捉え方はしていたものの、それを踏まえた上で<strong>技術的解決の選択肢が広がるように技術的にめちゃくちゃ強くありたい</strong>と思い、そこにこだわっていました。</p> <h3 id="CARTAのエンジニアになって気づいた強いエンジニアのあり方">CARTAのエンジニアになって気づいた「強いエンジニア」のあり方</h3> <p>しかし現在では、<strong>技術的な強さというものは「強いエンジニア」の一構成要素にすぎない</strong>と考えています。</p> <p>今の「強いエンジニア」のイメージは</p> <blockquote><p>いま事業がどのような状態で、どこを改善すればより良くなるか判断することができ、それを実現していける</p></blockquote> <p>エンジニアです。</p> <h3 id="なぜそう思ったか">なぜそう思ったか</h3> <p><strong>高い技術力の先輩たちが、ビジネスの本質的な課題を捉えて技術以外の方法でも課題解決する様子を見たからです。</strong>実際にCARTAで働いてみて、技術的に尖っている以外の軸でも価値を発揮している先輩エンジニアがたくさんいると感じました。なによりビジネス的な課題をどのように解決・改善していくか判断し、周りを巻き込んで押し進めていくことのできる人ばかりだなと思いました。</p> <p>CARTAでは<strong>ビジネスの課題を技術的に解決する上でどう進めていけるか</strong>が重要視されるため、重宝されるエンジニアとなるためにはその過程で数字や反応を追ったり、意見やフィードバックをもらったりしてそれをもとに改善していくことが求められます。</p> <p>それを体現する人たちに囲まれて「自分もそうなりたいな」と思い、徐々にイメージが変わっていきました。</p> <h2 id="まとめ">まとめ</h2> <p><strong>ユーザーや顧客に価値提供していく手段として技術を用いている以上、事業やビジネスそのものに対する理解は欠かせない</strong>ものなのだなと身に染みた1年でした。</p> <p>自分も「強いエンジニア」に近づくために</p> <ul> <li>今後よりビジネスサイドと近い距離で仕事をし、事業についてもっと詳しくなる</li> <li>触ったことのないものや抽象的な物事に躊躇せず、周りを巻き込みながら改善を押し進めていく</li> </ul> <p>ということを意識し、次の1年もさらなる成長を目指していきます。</p> horiy0125 数字で一喜一憂せず、人の声から学ぶフルサイクル技術広報のススメ hatenablog://entry/6801883189069142518 2023-12-25T16:51:20+09:00 2024-02-27T11:15:31+09:00 この記事は CARTA アドベントカレンダー 12/25 担当です🎄 CARTA 技術広報 しゅーぞーです。 今年から専任の技術広報になりました。 この記事は、 広範な「技術広報の仕事」をCARTAとしての視点からまとめる試み です。 CARTAの技術的な魅力を伝えるために、フルサイクル的に領域を限定せず「技術を広めるための仕事は全部やる」スタイルで1年ほど奮闘 しておりました。 この記事では、その過程で見つけ出した発見を「コツ」としてまとめます。 広報はよく 成果が分かりづらい 成果を定量的に測りづらい と言われます。なので、 フィードバックサイクルを回すために「どうやってフィードバックをも… <p>この記事は CARTA アドベントカレンダー 12/25 担当です🎄</p> <p>CARTA 技術広報 <a href="https://twitter.com/ShuzoN__">しゅーぞー</a>です。 今年から専任の技術広報になりました。</p> <p>この記事は、 <strong>広範な「技術広報の仕事」をCARTAとしての視点からまとめる試み</strong> です。 <strong> CARTAの技術的な魅力を伝えるために、フルサイクル的に領域を限定せず「技術を広めるための仕事は全部やる」スタイルで1年ほど奮闘</strong> しておりました。</p> <p>この記事では、その過程で見つけ出した発見を「コツ」としてまとめます。</p> <p>広報はよく</p> <ul> <li>成果が分かりづらい</li> <li>成果を定量的に測りづらい</li> </ul> <p>と言われます。なので、 <strong>フィードバックサイクルを回すために「どうやってフィードバックをもらうか」もまとめています。</strong></p> <center><figure class="figure-image figure-image-fotolife" title="数字で一喜一憂せず、人の声から学ぶ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231225/20231225172816.png" width="1200" height="702" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>数字で一喜一憂せず、人の声から学ぶ</figcaption></figure></center> <p>その仕事の広さゆえに記事自体も長いです...が、1年目の振り返りも込めて書いていきます。</p> <h3 id="この記事で一番伝えたいこと">この記事で一番伝えたいこと</h3> <ul> <li>CARTA技術広報のざっくりした担当範囲</li> <li>仕事の範囲広いけど、価値提供フローを全担当すると段違いに解像度上がる</li> <li>数字も大事。だけど量をこなして、振り返りで「周りの声」からフィードバックを得るとよさそう</li> </ul> <h3 id="読んでほしい人">読んでほしい人</h3> <ul> <li>今から技術広報やろう・なろうとしてる人</li> <li>すでに技術広報やってて、他社の技術広報がやってることを知りたい人</li> </ul> <h3 id="前提">前提</h3> <ul> <li>CARTA技術広報は専任1名(私) + 兼務 数名でやっています</li> <li>全てを1人でやってるわけではなく同僚と上司とワイワイやっております</li> </ul> <hr /> <h1 id="そもそもフルサイクルってなに">そもそもフルサイクルってなに?</h1> <p><figure class="figure-image figure-image-fotolife" title="ライフサイクルのすべてを担当"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225114933.png" width="601" height="641" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>ライフサイクルのすべてを担当</figcaption></figure></p> <p><a href="https://techblog.cartaholdings.co.jp/entry/carta-full-cycle">CARTAはフルサイクル開発を体現</a>しています。経験値の蓄積を促進し、顧客への価値提供を最大化するために採用しているプラクティスです。</p> <p>そもそも 「フルサイクル開発って何?」って話ですが、原典である Netflixの <a href="https://netflixtechblog.com/full-cycle-developers-at-netflix-a08c31f83249">「Full Cycle Developers at Netflix — Operate What You Build」</a> によると</p> <blockquote><p>Full cycle developers are expected to be knowledgeable and effective in all areas of the software life cycle. But he “real job” of full cycle developers is to use their software development expertise to solve problems across the full life cycle.</p> <p>フルサイクル開発者は、ソフトウェア ライフ サイクルのすべての分野について知識があり、効果的であることが期待されます。 フルサイクル開発者の「本当の仕事」は、ソフトウェア開発の専門知識を活用してライフサイクル全体にわたって問題を解決することです。</p></blockquote> <p>つまり <strong>「開発者はプロダクトのライフサイクルに関することを全部やろう」</strong> って話ですね(強い)。</p> <p>詳細を読んでいくと <strong>「全体に関わってちゃんとフィードバックから学習サイクル回そう」と読めます。</strong></p> <p><figure class="figure-image figure-image-fotolife" title="FBサイクルはどの職種でも大事"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231225/20231225145552.png" width="358" height="379" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>FBサイクルはどの職種でも大事</figcaption></figure></p> <p>それだけ聞くとPDCAやOODAに近く、 <strong>職種に関係ないプラクティス</strong>として受け止められます。ってことで <strong>広報でありながらフルサイクルっぽくやることも出来る</strong> んですよね。</p> <p>ってことでやったことの詳細を書いてみます。</p> <hr /> <h2 id="Dev-Rel技術広報の仕事を知ろう">Dev Rel・技術広報の仕事を知ろう</h2> <p>その前に。</p> <p><strong>きっとみなさん、Dev Rel・技術広報の仕事ってあまり馴染みがないですよね?</strong> それについては、smartHRさんが素晴らしい記事を書いているのでご一読ください。 めちゃくちゃ参考になりました!ありがとうございます!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.smarthr.jp%2Fentry%2F2023%2F11%2F28%2F115542" title="教えて先輩! DevRelの立ち上げ方(前編)活動の成果と計測、体制、予算 - SmartHR Tech Blog" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.smarthr.jp/entry/2023/11/28/115542">tech.smarthr.jp</a></cite></p> <h3 id="一般的なDev-Rel技術広報の仕事">一般的なDev Rel・技術広報の仕事</h3> <p>元記事いわく、大きく分けて4つあるらしいです。詳細は元記事を頼るとして、簡単化して言うと以下の図のように言えます。</p> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225121717.png" width="1200" height="706" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></center> <h3 id="CARTA技術広報の仕事">CARTA技術広報の仕事</h3> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225121810.png" width="1200" height="700" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></center> <p>特に <strong>CARTA技術広報は「テックブランディング」「インターナルサポート」が主軸</strong> です。</p> <p>語弊を恐れず言い換えると、CARTA技術広報は</p> <blockquote><p><strong>「CARTAの外にも中にもCARTAのエンジニアリングすげー!」</strong></p></blockquote> <p>って思ってもらう仕事をしています。最終的な目的はエンジニアの採用強化であり、フルサイクルもそのために採用しています。</p> <p>ざっくりこんな感じの流れで、組織中を温めて、会社の外に熱🔥を伝える仕事ですね。</p> <center><figure class="figure-image figure-image-fotolife" title="方向性を図示するとこんな感じのイメージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225120110.png" width="634" height="674" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>方向性を図示するとこんな感じのイメージ</figcaption></figure></center> <hr /> <h2 id="CARTA技術広報の具体的な仕事">CARTA技術広報の具体的な仕事</h2> <center><figure class="figure-image figure-image-fotolife" title="テックブランディング・インターナルサポートの中身"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231225/20231225175337.png" width="1200" height="713" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>テックブランディング・インターナルサポートの中身</figcaption></figure></center> <p>おそらくDev Rel・技術広報の仕事についてなんとなくわかってもらえたような気がします。では、 <strong>実際にCARTA技術広報はどんな仕事をしているのか?</strong> 具体的に見ていきましょう。</p> <h2 id="テックブランディング">テックブランディング</h2> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225120637.png" width="604" height="644" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></center> <p>CARTAの技術力を社外に知ってもらい「スゲー!」って思ってもらうために以下を行っています</p> <ul> <li>メディア運営</li> <li>イベント</li> <li>カンファレンススポンサー・登壇</li> </ul> <h3 id="ブログ">ブログ</h3> <p>2023年は約80本のテックブログをリリースしました。ざっくりカテゴリ分けしてみると、 <strong>大きく分けて2種類あり「エンジニア執筆」と「広報企画」に分かれます。</strong></p> <h4 id="エンジニア執筆">エンジニア執筆</h4> <center><figure class="figure-image figure-image-fotolife" title="ブログ ーエンジニア執筆ー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225120717.png" width="601" height="638" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>ブログ ーエンジニア執筆ー</figcaption></figure></center> <p>エンジニアが自ら執筆してくれる記事の場合は、レビューをしたり煮詰まったときの壁打ちをやっています。<strong>一緒に話しながら書くことで主旨が整理されまとまりやすくなるようです。</strong> 直近だと <a href="https://techblog.cartaholdings.co.jp/entry/snowflake-dbt-data-platform-vision">CMF テックリード</a>, <a href="https://techblog.cartaholdings.co.jp/entry/carta-full-cycle">CTO</a>と共に記事を執筆しています。</p> <h4 id="広報企画">広報企画</h4> <p><figure class="figure-image figure-image-fotolife" title="ブログ ー広報企画ー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225120748.png" width="604" height="642" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>ブログ ー広報企画ー</figcaption></figure></p> <p>広報企画ネタは<strong>新卒・中途インタビューや「文化・福利厚生」ネタが代表的なもの</strong>です。また、<a href="https://techblog.cartaholdings.co.jp/entry/treasure2023-data-modeling">イベントや日常に起きる出来事をレポート</a>したり、人事から来た依頼を倒すのも仕事です。</p> <p>具体的には <a href="https://techblog.cartaholdings.co.jp/entry/sabbatical_bonus_vacation">長期休暇</a> や <a href="https://techblog.cartaholdings.co.jp/entry/lunch-box-in-toranomon">ランチのお弁当制度</a> といったゆるいものや <a href="https://techblog.cartaholdings.co.jp/entry/test-driven-development-training-2023">新卒研修ネタ</a> など様々です。</p> <h4 id="テックブログのコツは誰よりも熱くなること">テックブログのコツは「誰よりも熱くなる」こと</h4> <p><strong>盛り上げるコツは「担当が誰よりも熱くなる」こと</strong> です🔥</p> <ul> <li>とにかくやってくれたことを褒める</li> <li>いいネタだと自分が信じて、エンジニアを応援する</li> <li>鉄は熱いうちに打ち切る。<a href="https://zenn.dev/ryu955/articles/81d20a2669c6ed">レビューは最速</a> で</li> <li>エンジニアが書く社内ブログは基本全部読む</li> </ul> <p>日常に転がっているいいネタを出すために <strong>一番必要なのはKIAI</strong> (気合)だったりします...がんばれ...私...。</p> <p><strong>それらを大切にすべきと気づいたのは「仕掛りのままだと、人のコストを無碍に奪ってしまう」と気づいたからです。</strong></p> <p>せっかく書いてくれたものへのレビューが遅れたり、リソースが取れなかったりして世に出ないものもいくつか生みました。それらも素敵なものだったからとても悔やまれますし、申し訳ない気持ちでいっぱいです。やはり <strong>最後は「出し切る気合」が必要</strong> だと気づきました。</p> <center><figure class="figure-image figure-image-fotolife" title="今年つくったブログのアイキャッチたち"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225122319.png" width="1200" height="669" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>今年つくったブログのアイキャッチたち</figcaption></figure></center> <p>アイキャッチも多分30-40枚くらいつくりました。こればっかりは楽しい。</p> <center><figure class="figure-image figure-image-fotolife" title="何を考えてコンテンツを創っているか"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231223/20231223210600.png" width="1200" height="667" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>何を考えてコンテンツを創っているか</figcaption></figure></center> <p>ブログ・アイキャッチ制作で気をつけてることをまとめて登壇してきました。<a href="https://speakerdeck.com/carta_engineering/how-to-create-deliver-content-catches-the-eye">思わず目にとまるコンテンツの作り方、届け方</a></p> <h3 id="マルチメディア企画">マルチメディア企画</h3> <p>今年は新たに<strong> 同じネタをマルチメディアで展開する企画</strong> をやっていました。</p> <center><figure class="figure-image figure-image-fotolife" title="一つのネタを複数のメディアで扱う"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225122935.png" width="602" height="644" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>社内配信したネタを複数メディアで扱う</figcaption></figure></center> <p>ざっくり流れとしては</p> <ul> <li>社内向けにエンジニア対談を生配信する <ul> <li>録画したものを元に<a href="https://techblog.cartaholdings.co.jp/entry/carta-engineering-radio-1">ブログ</a>、<a href="https://youtu.be/JpQ2ofmyVVo">動画</a>、<a href="https://open.spotify.com/episode/77yrIS7dEwBB3T9GK3GBMR?si=uZXku21dTqSXIB_Q6-kzOg">podcast</a>を制作</li> </ul> </li> <li>制作 <ul> <li>AIを活用し作業分担</li> </ul> </li> </ul> <p><figure class="figure-image figure-image-fotolife" title="制作はチーム内で手分け"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225124556.png" width="375" height="396" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>制作はチーム内で手分け</figcaption></figure></p> <p>CARTA技術広報チームは専任の私 + 兼務メンバー数名。チーム全員で手分けして制作を行っていました。</p> <p><strong>コツは...「コンテンツの型を決める」「可能な限りAIを頼る」でした。</strong>制作周りはたたき台を私が作ってチーム展開する形に。制作フローも物によっては半分くらいAIを使っています。どこにも知見がない中、一緒に作り上げていたチームメイトの皆さんが本当にすごい。</p> <p><strong>制作自体は超大変だったけど、チームのコラボレーション度がかなり上がりました</strong>。配信企画は社内エンジニアからも好評でした。ほんとにやってよかった。数字や現場FBはこれからですね。やっていき。</p> <h3 id="イベント">イベント</h3> <p>広報活動においてイベントは欠かせない存在です。CARTAも2023年で8回行いました。</p> <center><figure class="figure-image figure-image-fotolife" title="全てハイブリッド開催"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225122656.png" width="1152" height="777" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>全てハイブリッド開催</figcaption></figure></center> <p>CARTAが行っているイベントは、 <strong>大別すると「主催イベント」と「会場スポンサー」に分けれます。</strong> <strong>大きな違いは「企画をCARTAが考えるか、社外からもってきていただくか」の違い</strong>です。ややこしくなるので詳細は省きます。</p> <center> <figure class="figure-image figure-image-fotolife" title="主催イベントはCARTAが企画・実行する"> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231225/20231225155101.png" width="639" height="685" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>主催イベントはCARTAが企画・実行する</figcaption></figure></center> <center> <figure class="figure-image figure-image-fotolife" title="会場スポンサーは外部の方に場所と配信を提供する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231225/20231225155214.png" width="626" height="640" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>会場スポンサーは外部の方に場所と配信を提供する</figcaption></figure></center> <h4 id="イベントのコツは違和感の少ない環境を作ること">イベントのコツは「違和感の少ない環境を作る」こと</h4> <p>イベントのコツは <strong>「違和感の少ない環境を作る」ことを意識する</strong> ことでした。</p> <p>登壇者の方々が素晴らしいコンテンツを持ってきてくれます。来てくれた方々にそのコンテンツから学びを得て帰ってもらうために、ノイズとなる違和感はできるだけなくしたい。</p> <p>だから <strong>イベントを提供する立場である広報は「体験の質を損なわない」ことに全力で注力していれば大丈夫</strong> なんだと、何度目かで気づきました。</p> <ul> <li>イベント会場への導線が分からず迷わせたり</li> <li>途中で配信が止まってしまったり</li> <li>懇親会の始まりが分からず混乱させたり...</li> </ul> <p>たくさんの失敗をして気づきました。</p> <p>そのノウハウもまとめて登壇しております。こちらから参照ください: <a href="https://speakerdeck.com/carta_engineering/how-to-make-hybrid-event-in-carta">内製ハイブリッドイベントの創り方</a></p> <center> <figure class="figure-image figure-image-fotolife" title="オンライン配信も質を損なわないことが最も大事"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231223/20231223210140.png" width="1200" height="671" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>オンライン配信も質を損なわないことが最も大事</figcaption></figure></center> <p>人数に関しても、リーンに削ればスタッフが3名ほどいれば多分なんとか回ります。 ですが、人多いほどワイワイ感が出て良いイベントになりやすくシンプルに楽しいですね!</p> <p>(会場スポンサーの提供は公募しておらず、紹介の方に限定させてもらっています。すみません🙏 )</p> <h3 id="カンファレンススポンサー登壇サポート">カンファレンススポンサー・登壇サポート</h3> <p><strong>広報といえばカンファレンス!</strong></p> <p>CARTAも何度かカンファレンススポンサーをやっておりました。2023年はブース出展を行わなかったため「登壇サポート」「カンファレンスにまつわる制作・イベント」を主に担当しました。</p> <h4 id="登壇サポート">登壇サポート</h4> <center><figure class="figure-image figure-image-fotolife" title="登壇サポート"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225131958.png" width="350" height="371" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>登壇サポート</figcaption></figure></center> <p><strong>カンファレンススポンサーはプランによって登壇枠をいただけます。</strong> そこで、今年は多くの登壇資料作成サポートをやっていました。登壇者といっしょに話したいことをブレイクダウンしていきます。</p> <p><strong>コツは2つあって「主軸を決めること」と「MTGで話しているその場で図示していく」こと</strong> でした。</p> <p>長いセッションは軸がぶれて発散しがちですし、なんだかんだ同じものを見て同じ言葉で話すとすんなりと物事は決まっていくものです。</p> <p>いつもの流れは以下。</p> <ul> <li>ネタぎめ</li> <li>3回くらいMTG</li> <li>登壇練習してちょっと修正</li> <li>本番</li> </ul> <p>MTGで主軸となるコンセプトと図を作り切ってしまい、後はマイナーチェンジで当日まで一緒に詰めていく。そんな感じで進めるとイイカンジに進むことがわかりました。</p> <h4 id="CM動画">CM動画</h4> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231225/20231225132214.png" width="362" height="376" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></center> <p>今年はISUCONで利用するCM動画を新調しました。 <strong>実は企画・撮影〜編集まで全て広報チーム内製で作っています。</strong></p> <p><strong>コツは 「撮影前にコンセプトと荒い完成形を作り、関係者と合意形成しておく」ことでした。</strong> これは大失敗から学んだ教訓です。</p> <p>素材を撮った後からコンセプトを変えようとしたり、再撮影しようとするとエンジニアに迷惑がかかります。シンプルにスケジュールが押し、現場に混乱を招き、関係者に迷惑をかけます。実際にそれをやろうとしてめちゃくちゃ迷惑をかけました...すみませんでした...orz</p> <p>さて、実は撮影に使用したカメラはiPhoneです。まじですごい、iPhone。</p> <h4 id="派生イベント">派生イベント</h4> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231223/20231223214932.png" width="660" height="270" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></center> <p>カンファレンスやコンテストに出ただけではもったいない!</p> <p>ということで、その <a href="https://cartaholdings.connpass.com/event/302226/">派生イベント</a> を行いました。これはCARTAのISUCON参加エンジニアをあつめて、<a href="https://x.com/soudai1025">@soudai1025</a> さんとISUCON13を振り返るイベントを作りました(当イベントは、大本の運営の方に連絡した上で実施しております)。</p> <p>コツは... <strong>「事前に何を話すか軸をちゃんと決めておいて、参加者に事前に思い描いてもらうこと」</strong> でした。しっかりと要点を洗い出しておくことで初めて登壇したエンジニアたちも、そーだいさんのファシリテーションに支えられスムーズに話せていました。</p> <p>これは裏話ですが、ISUCONの中の人が視聴者としてプライベート参加してくださり、コメントで大いに盛り上げていただきました。その節はありがとうございました!</p> <h2 id="インターナルサポート">インターナルサポート</h2> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231223/20231223214607.png" width="1200" height="700" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></center> <p>さぁ <strong>やっと外向けの話が終わりました。</strong> ながい...w ということでお気づきなように、広報の仕事範囲は広大です。次は内向きの仕事について書きます。あと一息!</p> <p>ここまで読んだ人は最後まで読んでいってくださいね!</p> <h3 id="ナレッジシェア交流会">ナレッジシェア・交流会</h3> <p><figure class="figure-image figure-image-fotolife" title="事業間でのコラボレーションを生む"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231225/20231225170710.png" width="634" height="758" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>事業間でのコラボレーションを生む</figcaption></figure></center></p> <p>CARTAは20近い事業体があり、エンジニア組織がある事業だけでも10近くあります。このため、 <strong>社内ナレッジに分断が起きやすい</strong> わけです。ジュニア〜ミドルエンジニアまでの半期の仕事内容を全部読める <a href="https://cartaholdings.co.jp/engineering/tech-assessment/">技術力評価会</a> という制度もありますが、それでもナレッジシェアには限度があります。</p> <p>ということで</p> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231223/20231223215838.png" width="808" height="342" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></center> <ul> <li>技術責任者が登壇する会</li> <li>若手エンジニアが登壇する会</li> </ul> <p>をCTO室が企画し社内イベント化しました。 <strong>結果、めちゃくちゃ盛り上がりました...!! </strong></p> <p><strong>実際に運営として関わっていて思ったのは「シニアエンジニアの仕事ぶりがわかる、若手のいい仕事がわかる組織はめちゃくちゃ見通しが良い」ということ。</strong> 横と縦が融解して一体感が生まれた組織は強いです。</p> <p>ちなみに若手エンジニアの発表内容はこちらにまとまっています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2F2023%2F08%2F15%2F163000" title="1リリース6,108行から18行へ。ビッグバンリリースを改善した話 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/2023/08/15/163000">techblog.cartaholdings.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fkamigame-wwb-core-vitals-improve" title="神ゲー攻略の Core Web Vitals を大幅改善した話: CrUX API による計測基盤と施策 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/kamigame-wwb-core-vitals-improve">techblog.cartaholdings.co.jp</a></cite></p> <h3 id="新卒研修振り返り">新卒研修・振り返り</h3> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231223/20231223220139.png" width="805" height="374" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span></center> <p>あとは、細かい仕事として新卒の育成制度・企画を支える仕事もやっています。</p> <p><strong>新卒エンジニアからすれば「入ったらどういう感じでオンボーディングされるのか」は気になるところでしょう。</strong> そういうイベントを支えて、ついでに外からも見えるように可視化していくのも広報の仕事でした。</p> <p><strong>社内イベントレポートのコツは「サクッと撮ってサクッと書く」こと。</strong> その場で書いてしまってレビューを投げてその場でリリースすることを目指します。レポートは忘れちゃうので、手が早いのが一番大事です。</p> <p>仕事内容については以上です。</p> <hr /> <h2 id="どこがフルサイクルなの">どこがフルサイクルなの?</h2> <p>冒頭で、 <strong>フルサイクルは「全体に関わってちゃんとフィードバックから学習サイクル回そう」といってる</strong> と伝えました。</p> <p>これらのどこがフルサイクルなのか。実は<strong>コツと書いたことは全て「この1年で失敗から学んだこと」でした。</strong></p> <p><strong>私にとっては、広報の仕事はほとんどが新しいこと・やったことがないことでした。</strong> 趣味でやってたことも一部あるけど、仕事でやるのは趣味と訳が違います。だから、めちゃくちゃ失敗したし周りの人にも迷惑をかけました、本当に。だからこそ、 <strong>失敗に真摯に向き合った結果、得たものが多くあった</strong> んですよね。</p> <p>広報はよく「成果が分かりづらい」や「成果を定量的に測りづらい」と言われます。だから <strong>効果的なフィードバックサイクルを作りづらい</strong> んですよね。なので <strong>「フィードバックサイクルを回すこと」に一番苦心</strong> しました。</p> <h3 id="フィードバックは人の声を信じよう">フィードバックは「人の声」を信じよう</h3> <p>では、どこからフィードバックを得ればよいのでしょうか。</p> <center><figure class="figure-image figure-image-fotolife" title="フィードバックは人の声を信じていく"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231225/20231225153603.png" width="531" height="559" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>フィードバックは人の声を信じていく</figcaption></figure></center> <p>自分にとっては、よく言われる「数字」からのFBだけではなく、 <strong>「人の声や体感」からのFBがとても大切</strong> でした。</p> <ul> <li>社内からの反応の声</li> <li>エンジニア友達からの反応</li> <li>社外からのイベントリピートの声がけ</li> <li>イベントのアンケート結果・満足度</li> <li>インターンに来ている学生にヒアリング</li> <li>社内エンジニアからの壁打ち相談やレビュー依頼</li> <li>面接に立つ人事からみたコンテンツの使いやすさ</li> </ul> <p>こういった「数字に現れないフィードバックポイント」を貪欲に拾う姿勢が必要でした。</p> <p>数字的なデータは例えば</p> <ul> <li>ブログならGA4の数字</li> <li>コンテンツの再生数</li> <li>イベント来客数や満足度</li> </ul> <p>などで計測可能です。しかし、多くの場合、<strong>1回1回の仕掛けはそこまで大きく跳躍しません。</strong> たまにとても伸びる記事があってもすぐにしぼんでいきます。</p> <p>だから <strong> 「数字で一喜一憂しない」姿勢が常に求められる</strong> ことがわかってきました。</p> <h3 id="数字はあとからついてくる">数字はあとからついてくる</h3> <p><strong> 1年くらい数字をあまり気にせずにとにかく量をやってみると「徐々に右肩上がりで推移する」ことが見えてきました。</strong></p> <center><figure class="figure-image figure-image-fotolife" title="数字はあとからついてくる。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231225/20231225153750.png" width="527" height="560" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>数字はあとからついてくる。</figcaption></figure></center> <p><strong>また、貪欲にFBもらって改善していくと、数字的な成果がついてきて頑張ってる姿勢を応援してくれるエンジニアが増えて、どんどん頼ってくれる</strong> ようになってきました。</p> <p>でも、それらが体感できるのは社内や社外の「定性的な評価がついてきたあと」だったりします。体感としても、<strong>評判や数字がついてくるのは3~6ヶ月くらいズレる</strong> 気がしています。</p> <p>だから <strong>目の前の人に向かって全力で頑張ってコミットしたほうが、広報は数字が伸びやすい気がしますし何よりその方が楽しい!</strong></p> <p>ということで2024年も頑張っていこうかな、という所存です。ちなみに、僕が担当した中で2023年で一番伸びた記事は「お弁当」の記事でした!技術関係ないじゃん!w</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Flunch-box-in-toranomon" title="🍴虎ノ門ヒルズ36Fで500円で弁当が食べれる🍴 ランチの弁当が来た! - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/lunch-box-in-toranomon">techblog.cartaholdings.co.jp</a></cite></p> <p>ってことで、長くなったので以上です!! 読んでいただいてありがとうございます!</p> namu_r21 Snowflakeと共に過ごした一年間。その進化過程と未来へのVision hatenablog://entry/6801883189068793337 2023-12-24T12:04:55+09:00 2024-02-27T11:16:26+09:00 こんにちは、株式会社CARTA MARKETING FIRMのデータエンジニア、@pei0804です。 昨年、私たちはSnowflakeを中心に据えた「Vision」という新データ基盤構想を立ち上げました。 それから1年、多くの変化がありました。この記事では、Snowflake導入によって現場に起きた変化、直面した挑戦、そして見出した可能性について、率直に語ります。 当記事は、話が膨らみすぎるため、データ利用部分については深く触れませんが、別の機会にどういうことを行っているかを紹介したいと思います。 Snowflake導入の経緯と初期の構想 そもそも、Snowflakeを、なんで導入するんだっ… <p>こんにちは、株式会社CARTA MARKETING FIRMのデータエンジニア、<a href="https://twitter.com/pei0804">@pei0804</a>です。</p> <p>昨年、私たちはSnowflakeを中心に据えた「Vision」という新データ基盤構想を立ち上げました。 それから1年、多くの変化がありました。この記事では、Snowflake導入によって現場に起きた変化、直面した挑戦、そして見出した可能性について、率直に語ります。</p> <p>当記事は、話が膨らみすぎるため、データ利用部分については深く触れませんが、別の機会にどういうことを行っているかを紹介したいと思います。</p> <h1 id="Snowflake導入の経緯と初期の構想">Snowflake導入の経緯と初期の構想</h1> <p>そもそも、Snowflakeを、なんで導入するんだっけ?については、以下のスライドで詳しくまとめていますが、簡潔に説明すると、色んなクラウドにあるデータを簡単に集約できて、かつ、スケーラビリティに問題がなく、枯れた技術をうまく使ってそうで安心感があったからです。</p> <p><a href="https://speakerdeck.com/pei0804/strongest-data-architecture-discussion">ぼくのかんがえる最高のデータ分析基盤 / strongest-data-architecture-discussion - Speaker Deck</a></p> <h2 id="全体から見た変化">全体から見た変化</h2> <p>図の読み方は、赤枠が変化があった部分で、青枠が変化後の状態を表しています。</p> <p>2022年11月 運用開始時点</p> <h1 id=""><img alt="" src="https://lh7-us.googleusercontent.com/lhuWkUZ6bAaHp4Bj3x8a09ky83vEO_xC8VGATz3iOIi88B2Ga5vuxvgXHUD1AyK_-P9FfPQttWhXq5FxUGS_FsExVKbBSYUUzl89iGETRh2gSP84eLkoogI2CLEGmRMbLrN4T8CYWl8XmPPwWE8kqwQ" /></h1> <p>2023年12月 現在</p> <p><img src="https://lh7-us.googleusercontent.com/mr0tRcIw-C4oZojp0Q8QC32AKhTZnOryVT2Tk7pAX9TpQYUH_i2swWqwJEggikjWjZ_gRi3-gHca-5mycB4VIawPM7jD2X4Z1MUClQk5V4bCyEQsORvlGGoVAhChlEqwHbZbkv4ev8dwhIfry3sQb7M" alt="" /></p> <h2 id="Snowpipeの活用拡大">Snowpipeの活用拡大</h2> <p>Before</p> <ul> <li>データを比較的早い段階で参照する必要な場合のみSnowpipeを使用していました。 それ以外の場合は、External Stageとdbtを組み合わせたバッチロード。 <ul> <li><a href="https://speakerdeck.com/pei0804/strongest-data-architecture-discussion?slide=41">参考資料</a></li> </ul> </li> </ul> <p>After</p> <ul> <li><strong>9割近くのワークロードをSnowpipeでカバー。</strong> 元々全面的に採用できなかった理由は、ログファイルの要件とSnowpipeが合っていなかったことが原因でしたが、それらを解消した結果、全面的に採用してもコストパフォーマンスが合うようになりました。</li> </ul> <h2 id="External-Tableを活用したアドホックなデータ要件の対応">External Tableを活用したアドホックなデータ要件の対応</h2> <p>Before</p> <ul> <li>初期は利用していなかった。理由としては、External Tableを使用するには、直接クエリされることをある程度想定したログ設計になってる必要があったため。 初期段階では、使おうとすると、パーティション作成時にオブジェクト数が多すぎることでエラーが発生してしまっていた。</li> </ul> <p>After</p> <ul> <li>Snowpipeの全面適用の一貫で、<strong>ログが扱いやすい形になったことで、External Tableにとっても扱いやすい状態になり、使える状態になりました。</strong> 主なユースケースは、アドホックな分析で、常にSnowflakeに永続化するほどでもないけど、たまにクエリしたいやつに、バッチリとハマっています。 他にも、外部から提供されたレポートデータを参照する時に使っているケースもあります。</li> </ul> <h2 id="dbtモデル数が10倍">dbtモデル数が10倍</h2> <p>Before</p> <ul> <li>Snowflakeにロードされたデータを、変換する。</li> <li>External Stageを用いたデータ取り込み。</li> <li>モデル数50個</li> </ul> <p>After</p> <ul> <li>データ取り込みは行わなくなった。データ変換のみ。</li> <li>Backfill Botを使ったBackfill業務の一般化。</li> <li>Elementary Cloudの導入</li> <li><strong>モデル数500個</strong></li> </ul> <p>dbtに関して、役割は大きく変化していません。ですが、モデル数は10倍近くになり、それに伴って開発フローやレイヤリングは、継続的に見直しがされています。 2023年末時点のdbtの全貌については、以下の記事でまとめているので、そちらを御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fsnowflake-dbt-data-platform-vision" title="Snowflakeの力を引き出すためのdbtを活用したデータ基盤開発の全貌 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/snowflake-dbt-data-platform-vision">techblog.cartaholdings.co.jp</a></cite></p> <h2 id="開発体制が中央集権からDataOpsへ">開発体制が中央集権からDataOpsへ</h2> <p>Before</p> <ul> <li>データ基盤チームが、各プロダクトデータ取り込みから、dbtモデル実装から運用まで行う。</li> </ul> <p>After</p> <ul> <li>各プロダクトがデータ取り込みから、dbtモデル実装・運用は、プロダクトチームが管理する。</li> </ul> <p>運用開始直後は、dbtプロジェクトがどのように変化していくのか、どういうニーズがあるのか、また、社内でそこまでdbtが認知されていなかったフェーズだったのもあり、データ基盤チームによる中央集権的な体制で管理をしていました。</p> <p>しかし、段々と扱っているプロダクトのデータの数が増えてくると、ドメイン知識のキャッチアップにコストがかかり、管理ができているとはいい難い状態になってきました。</p> <p>例えば、dbt testが落ちたという通知が来た時に、これがどれくらい緊急度の高いものかは、モデルの作成者じゃないと判断つかないなど。それを都度エスカレーションしたり、それっぽい対応入れてみたり、<strong>これだといずれ回らなくなると判断し、<a href="https://www.splunk.com/ja_jp/data-insider/data-ops.html">DataOps</a>を明示的に導入しました。</strong></p> <p>DataOps導入には、データオブザーバビリティが必須です。 ツールとしては、<a href="https://docs.elementary-data.com/cloud/introduction">Elementary Cloud</a>を利用しています。どのようにDataOpsを実現してるかについては、また別の記事で取り上げたいと思います。</p> <h2 id="Fivetranの一貫した利用価値と拡張可能性">Fivetranの一貫した利用価値と拡張可能性</h2> <p><a href="https://fivetran.com/">Fivetran</a>は導入以来、変化することなく、<strong>我々のデータエンジニアリング業務にとって重要な役割を果たし続けています。</strong></p> <p>役割は導入当初から変わらず、Snowflake単体だと、データ連携するのが困難なユースケースに積極的に採用しています。 多くのデータソースに対応してくれているので、今後も活躍場面は広がるだろと考えています。</p> <h2 id="コストパフォーマンス最適化の効率化">コストパフォーマンス最適化の効率化</h2> <p>Before</p> <ul> <li>Snowflakeのコンソール画面。</li> <li><a href="https://github.com/get-select/dbt-snowflake-monitoring">SELECT OSSパッケージ</a></li> </ul> <p>After</p> <ul> <li><a href="https://select.dev/">SELECT SaaS版</a> を使った可視化と最適化</li> </ul> <p>導入初期は、優先度の問題で、コストは度返しで進めていました。特に可視化や最適化に関しては、リソースを割いてる余裕がなかったです。 しかし、皮肉なことに、コスト面で問題が起き始めても、対策を打とうとすると想像以上に手間暇がかかり、継続して行えていませんでした。</p> <p><strong>現在では、<a href="https://select.dev/">SELECT</a>を導入したことで、短時間で現状把握と対策案まで出せる状態になりました。本当に便利なので、是非チェックしてみてください。</strong></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fselect-cloud-cost-optimize-cmf" title="SELECTを使った手間なしSnowflakeコスト最適化 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/select-cloud-cost-optimize-cmf">techblog.cartaholdings.co.jp</a></cite></p> <h2 id="データ利用の変遷と進化">データ利用の変遷と進化</h2> <p>Before</p> <ul> <li>特定のプロダクトのためのレポーティング。</li> </ul> <p>After</p> <ul> <li>多数のプロダクトに対するレポーティング、分析、機械学習モデルの訓練データ生成</li> </ul> <p><strong>「とりあえず、このデータ基盤を使っとけば、いい感じにデータが入り、変換され、使える</strong>」という印象が社内で広まったこともあり、導入が加速したと考えられます。初めから意識していたデータ基盤の扱いやすさが、その普及と使われるデータ基盤への成長において、重要な要素となりました。</p> <h1 id="やって良かったこと">やって良かったこと</h1> <p>この1年弱、色々な取り組みをやってきましたが、これはやってよかったなというのを、いくつか紹介したいと思います。</p> <h2 id="運用">運用</h2> <h3 id="Snowflakeを導入したこと">Snowflakeを導入したこと</h3> <p>Snowflakeの導入は重要な一歩でした。導入前はRedshiftとBigQueryを併用していましたが、理想的な状態には至っていませんでした。Snowflakeを選択したことで、私たちの事業が直面するデータ課題を包括的に解決できるようになりました。</p> <p>導入後は、事業の性質とマッチしていたので、便利なことは想定済みでしたが、予想以上に迅速にデータ活用を進めることができました。これは、<strong>Snowflakeの安定性と優れた機能、そしてサポートの賜物です。今後も、Snowflakeを活用してデータからさらなる価値を生み出していきたいと考えています。詳細な導入ストーリーは、以下のスライドでまとめています。</strong></p> <p><a href="https://speakerdeck.com/pei0804/data-cloud-world-tour-tokyo-2023">&#x30A2;&#x30C8;&#x3099;&#x30C6;&#x30AF;&#x306E;&#x30D2;&#x3099;&#x30C3;&#x30AF;&#x3099;&#x30C6;&#x3099;&#x30FC;&#x30BF;&#x3092;&#x5236;&#x3059;&#x308B;Snowflake&#x306E;&#x529B; / data-cloud-world-tour-tokyo-2023 - Speaker Deck</a></p> <h3 id="多様性と均質性のバランス">多様性と均質性のバランス</h3> <p>経験上、データロードと変換プロセスにおいて均質性を保つことがデータ基盤の複雑性を低減します。 現実はデータ基盤に適合しない要件が存在します。短期的にはデータを急いで基盤に取り込みたくなりますが、長期的には基盤側を変更するのではなく、コントロール可能な範囲で要件を見直し、基盤のベストプラクティスに適合させることが望ましいです。</p> <p>データロードには普遍的なベストプラクティスが存在し、これを周知し、新しく生まれるデータを扱いやすい形になる当たり前を作ることは重要です。 また、複雑すぎる要件は、その複雑性に見合った成果を出していないことが多く、本質を見極めて簡素化することが効果的です。 このような変更は手間がかかり困難かもしれませんが、<strong>長期的に見るとデータの複雑性を基盤に持ち込まないことが利益につながります。</strong></p> <p>これらの地道な取り組みもあって、記事の冒頭でも触れましたが、Snowpipeがワークロードの大部分を効率的に処理できるようになりました。これはベストプラクティスの導入と徹底の成果です。現在では、新しいデータの取り込みは誰でもできる状態にまで一般化されました。</p> <p><strong>一方で、データが最大の価値を発揮する瞬間や利用場面では多様なニーズを受け入れることが重要です。</strong></p> <h2 id="チームマネジメント">チームマネジメント</h2> <h3 id="基盤チームとプロダクトチームの責任範囲の明確化">基盤チームとプロダクトチームの責任範囲の明確化</h3> <p>基盤の活用範囲が広がるにつれ、データ基盤チームの責任範囲に関する問題が浮上しました。</p> <p>初期の導入段階では、データ基盤チームがデータロード、モデリング、レポーティング設計の全工程を担当していました。 しかし、ビジネスの進化と共に新たな要件が生まれ、都度、誰がこれらの要件を担うべきかという問題が生じました。 また、ドメインに詳しくないと実装が難しい要件も増えてきたことで、徐々にプロダクトチームがこれらのタスクを引き受けるようになりました。</p> <p>ですが、事あるごとに「基盤チームとプロダクトチームどっちがやる?」とコミュニケーションが発生していました。 そこで、責任範囲を明確に定義しました。このアプローチは一定の効果がありました。 <strong>データ基盤チームが対応すべき事項とプロダクトチームで完結できる事項が明確にしたことで、結果として良い意味で、コミュニケーションの回数が減少しました。</strong></p> <p>ただし、この責任分担は時間と共に変化していくものであるため、継続的な見直しが必要であると感じています。 <strong>既に責任範囲を定めた時から現在に至るまで、微妙な変化がありました。これからも、状況に応じて責任範囲のアップデートを行うことの重要性を感じています。</strong></p> <p>実際の責任範囲はこちらです。</p> <pre class="code" data-lang="" data-unlink># 基盤 データ基盤チームの責任範囲は、データ基盤の保護と安定性を確保するため、バッチやアプリケーションの安定稼働や監視を行います。具体的には、ソフトウェア、ネットワーキングなどのインフラストラクチャの保護、基盤となるサービスの保守やアップデート、セキュリティ対策などを実行します。また、基盤で動作する各バッチやアプリケーションの適切な稼働状況を監視し、障害発生時には迅速な対応を行います。 さらに、利用者からのコードレビューの依頼を受け、必要に応じてコードの改善を行います。基盤に足りない機能が明らかになった場合には、その開発も行います。 また、データを扱う上でのベストプラクティスを定義し、それを利用者に伝え、適用を促すことも責任の一部とします。 タスク例 * 基盤で扱っているライブラリのアップデート * Snowflake、 dbtの新しい機能の基盤への取り込み。 * データカタログの提供。 * コスト可視化 * 監視ツールの提供。 * 全体の権限管理の設計。 * 設計指針の定義。 # 利用者 Visionの利用者は、データの取り込み設定の追加やビジネスロジックの実装(例えば、dbtモデルの作成など)を行います。また、タスクの冪等性を保証し、必要に応じてデータの再取り込み(バックフィル)を行うなどの責任も負います。さらに、データの管理(マスキングや暗号化オプションを含む)、適切なアクセス権限の設定なども利用者の役割となります。 必要に応じて、ライブラリの追加やパッケージの追加なども行い、システムに組み込むことができます。基盤に触れることは逆に推奨しており、より効果的な利用を促しています。 タスク例 * dbt modelの作成。 * 連携先AWSアカウントの追加。 * Snowpipe、 External TableなどのData Ingestionの設定追加。 * ビジネスロジックの誤りを修正するために、コードリバートした上で、バックフィルする。 # 大事なこと データ基盤チームとして、我々はユーザーの責任範囲を明確に定義していますが、絶対厳守なルールではありません。初めてデータ基盤を利用する際のサポートはしっかりと提供し、スムーズなスタートが切れるようにします。 さらに、日常的な疑問やデータに関する相談なども随時受け付けています。上記の責任範囲は我々の基本的なスタンスを示すものであり、具体的な対応は状況により柔軟に変わります。場合によっては、例外的に積極的に手を動かしてサポートすることも想定しています。 </pre> <h3 id="新卒採用">新卒採用</h3> <p>2023年4月、私たちのデータ基盤チームは新卒社員を迎え入れました。 当時、チームは私と業務委託の方の2名体制で、大変ながらも順調に進行している段階でした。 しかし、この少人数体制は、長期的に見た時に体制として脆弱なのは明らかでした。そこで、新卒を採用し今からチーム体制強化の仕込みを行うことにしました。 正直、データエンジニアの新卒採用は挑戦的な試みでした。恐らく私だけではなく、新卒社員も不安はあったと思います。</p> <p>チームに配属してからは、実務と座学をひたすら行ってもらいました。 データパイプラインの設計、ディメンショナルモデリング、Snowflakeのアーキテクチャ、dbtの実装プラクティス、DesignDocの書き方、そして、もちろん実践も。 入社から1年も経たずに、新卒社員は顕著な成果を上げ、データ基盤の強化に大きく貢献しました。</p> <p>特に大きな変化があったのは、データ基盤のミスが発生しにくい仕組みの強化です。 元々はシニアクラスのエンジニア2名で運営していたチームでしたが、<strong>新卒社員の参加により、良い意味で、不十分なシステムやプロセスが明らかにされ、より堅牢でミスの少ないデータ基盤へと進化しました</strong>。実際、事故発生する回数が激減して、圧倒的な安定性を保っています。</p> <p>また、私が腰重くて試せていなかったやりたいことシリーズにも積極的に取り組んでくれました。 <strong>SELECTとElementary Cloudの導入は、新卒社員が率先して行ってくれたことで、本番で試せる環境が構築され、導入まで一気に進めることができました。</strong></p> <p>やはり、関わる人が増えると、面白いことが起きますね。</p> <h1 id="今後の展望">今後の展望</h1> <p>今後は、データ基盤のさらなる活用範囲の拡大を図ります。</p> <p>また、せっかく一箇所にデータが集約されているので、複数プロダクト間のデータ利用と統合も模索したいところです。 加えて、チームの拡大と人材育成にも力を入れ、データ駆動型の未来を実現するための基盤を固めていきます。</p> tikasan0804 Hono + htmx + Cloudflare Workersでちょっとしたアプリケーションを作ってみた hatenablog://entry/6801883189068979406 2023-12-24T10:00:00+09:00 2024-02-27T10:16:12+09:00 こんにちは!@jewel_x12 です。CARTA TechBlog アドベントカレンダーの12/24の記事です。 うおお!もうクリスマスですね。みなさんはクリスマスプレゼントに何かもらえそうですか?自分はちょっと前にMeta Quest3を衝動買いしたので、それを自分へのクリスマスプレゼントということにしています。ちなみに先週、NuPhy Air75 V2も買いました。物欲が留まることを知らないですね。机の上にあるでっかい眼鏡がもったいないので、アドベントカレンダーネタにMRアプリでも作る予定だったのだけど、ストリートファイターをやったりグダグダしていると、MRアプリを作るためのインプット時… <p>こんにちは!<a href="https://twitter.com/jewel_x12">@jewel_x12</a> です。<a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTA TechBlog アドベントカレンダー</a>の12/24の記事です。 うおお!もうクリスマスですね。みなさんはクリスマスプレゼントに何かもらえそうですか?自分はちょっと前に<a href="https://www.meta.com/jp/quest/quest-3/">Meta Quest3</a>を衝動買いしたので、それを自分へのクリスマスプレゼントということにしています。ちなみに先週、<a href="https://sanyoshop.jp/products/air75-v2">NuPhy Air75 V2</a>も買いました。物欲が留まることを知らないですね。机の上にあるでっかい眼鏡がもったいないので、アドベントカレンダーネタにMRアプリでも作る予定だったのだけど、ストリートファイターをやったりグダグダしていると、MRアプリを作るためのインプット時間など全くなくなってしまっていたのでした。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2F2023%2F12%2F14%2F110000" title="確かみてみろ!社内ゲームサークル活動報告とストリートファイター6練習会のお知らせ - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/2023/12/14/110000">techblog.cartaholdings.co.jp</a></cite></p> <p>残る期間でなんかネタはないものかと焦りがつのる中、そういや今年は <a href="https://htmx.org/">htmx</a> とやらを聞いたのでそれを試してみた話を記事にしてみようとなったのです。</p> <h3 id="TLDR">TL;DR</h3> <p>前置きが長くなりましたがTL;DRです。</p> <ul> <li>こんな記事よりも <a href="https://zenn.dev/yusukebe/articles/e8ff26c8507799">Hono + htmx + Cloudflare&#x306F;&#x65B0;&#x3057;&#x3044;&#x30B9;&#x30BF;&#x30C3;&#x30AF;</a> を読んでください</li> <li>ちょっとしたアプリを作りやすいスタックでとても気に入りました</li> </ul> <p>作ったアプリのリポジトリはこちらです。</p> <p><a href="https://github.com/jewel12/shokuyov2">GitHub - jewel12/shokuyov2: &#x98DF;&#x7528; v2</a></p> <h3 id="おわり">おわり</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jewel12/20231223/20231223010730.png" width="371" height="571" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <hr /> <p>こっから先は習作で作ったちょっとしたアプリの説明なので、興味のある暇な人だけ読んでください。</p> <h3 id="食用とは">食用とは</h3> <p><a href="https://shokuyo.jewelve.dev/">https://shokuyo.jewelve.dev/</a></p> <p>入力されたものが食用かどうか判断してくれる便利なアプリです。このアプリを作ったのは2回目で、最初は学生の頃にPerlで作ったアプリでした。今回はそれをある程度再現しています。どなたでも使えますが、OpenAIのAPIを使っているので$10くらいのCredit balanceが尽きたら動かなくなります。お布施のクリスマスプレゼントはいつでもウェルカムです。</p> <h4 id="初代食用はこんな感じ">初代食用はこんな感じ</h4> <ul> <li>入力されたものが食用かどうか判定して、関連画像も表示してくれる</li> <li>食用の場合、画像を激しくアニメーションさせて活きの良さを表現する <ul> <li>SNSに上げる食事の写真を加工してバエで美味そうに仕上げる風潮に対し、より美味そうに見せる本質的な方法を提示できました</li> </ul> </li> <li>判定のデータソースにはWikipediaを利用していて、入力された単語の説明に「食用」と書いてあれば食用であるということにした <ul> <li>非常にガバかったのが可愛いくて「アメリカ合衆国」も食用であった</li> </ul> </li> <li>弊社入社後に食用をドヤ顔で紹介していたら、社員に<a href="https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0">XSS</a>を指摘された</li> </ul> <h4 id="食用-V2">食用 V2</h4> <ul> <li>食用かどうかの判定にOpenAIの<a href="https://platform.openai.com/docs/guides/text-generation">Text generation API</a>を使うようになった <ul> <li>食用判定の精度があがってつまらなくなった</li> </ul> </li> <li>画像の生成にも<a href="https://platform.openai.com/docs/guides/moderation">Image generation API</a>を使うように</li> </ul> <h3 id="出力結果例">出力結果例</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jewel12/20231223/20231223014833.gif" width="353" height="567" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>いちごのショートケーキは食べられます。</p> <hr /> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jewel12/20231223/20231223014703.gif" width="353" height="567" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ロボットレストランは食べられます。</p> <hr /> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jewel12/20231223/20231223015122.gif" width="353" height="567" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>スベスベマンジュウガニは食べられます。 (※ 有毒です)</p> <hr /> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jewel12/20231223/20231223015333.gif" width="353" height="567" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>二郎は食べられます。</p> <hr /> <p>もうすぐ正月ですが、くれぐれも有毒のものを食べたり、餅を喉に詰まらせないようにしないでくださいね!</p> jewel12 Snowflakeの力を引き出すためのdbtを活用したデータ基盤開発の全貌 hatenablog://entry/6801883189068590548 2023-12-23T09:33:02+09:00 2024-02-27T11:17:03+09:00 当記事は、dbtのカレンダー | Advent Calendar 2023 - Qiita の23日目の記事です。 こんにちは、株式会社CARTA MARKETING FIRMのデータエンジニア、@pei0804です。データエンジニアリングのほか、組織運営やデータエンジニア育成にも携わっています。 本記事では、Snowflakeを中心とした当社のデータ基盤「Vision」と、その中核であるdbtの利用について深掘りします。dbtを活用することで、SQLのみでデータパイプラインを効率的に構築し、作業の効率化を図っています。 dbt導入の詳しい導入背景は以下のスライドでご覧いただけます:広告レポー… <p>当記事は、<a href="https://qiita.com/advent-calendar/2023/dbt">dbt&#x306E;&#x30AB;&#x30EC;&#x30F3;&#x30C0;&#x30FC; | Advent Calendar 2023 - Qiita</a> の23日目の記事です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231221/20231221162848.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、株式会社CARTA MARKETING FIRMのデータエンジニア、<a href="https://twitter.com/pei0804">@pei0804</a>です。データエンジニアリングのほか、組織運営やデータエンジニア育成にも携わっています。</p> <p>本記事では、Snowflakeを中心とした当社のデータ基盤「Vision」と、その中核であるdbtの利用について深掘りします。dbtを活用することで、SQLのみでデータパイプラインを効率的に構築し、作業の効率化を図っています。</p> <p>dbt導入の詳しい導入背景は以下のスライドでご覧いただけます:<a href="https://speakerdeck.com/pei0804/tokyo-dbt-meetup-4">広告レポーティング基盤に、dbtを導入したら別物になった話 / tokyo-dbt-meetup-4 - Speaker Deck</a>。</p> <p>私たちのチームでは、ビジネスに直接価値を提供しているdbtモデルの開発はプロダクトチームのエンジニアによって担当されています。 これは彼らの主業務ではなく、幅広いプロダクト開発の一環です。そのため、dbtやSnowflakeの専門知識を持つ人だけでなく、様々なスキルレベルを持つ人々がアドホックに開発に関わっています。この多様なスキルセットを持つチームに対応するためには、どのような基盤が必要で、どのように構築し、運用しているのか、そして直面した課題とその解決策に焦点を当てています。</p> <p>具体的には、以下のトピックについて取り上げます</p> <ul> <li>開発環境の構成</li> <li>CIの範囲と実装</li> <li>本番環境の運用</li> <li>環境間の差分管理</li> <li>ドキュメンテーション</li> </ul> <p>登壇や懇親会では語りきれない細かすぎるデータ基盤の話を盛り込んでますので、ぜひご一読ください。 別途Snowflakeにフォーカスした記事を書いておりますので、興味あれば御覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fsnowflake-data-platform-vision" title="Snowflakeと共に過ごした一年間。その進化過程と未来へのVision - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/snowflake-data-platform-vision">techblog.cartaholdings.co.jp</a></cite></p> <h1 id="全体の概要">全体の概要</h1> <p>CI/CDパイプライン <img src="https://lh7-us.googleusercontent.com/Lw2s23h3oLYQuq8D_jmu-PTWK4ryV8QDV2T28KfA734PR6Yc_xsODZs6FjREK1M1L7v1pLKwr6tqngsbBbjGqqnXu_y49EPrmcyqHd80EaoV5-2Bi4vtjezbFzKyo8VmArs1ItKT8obeqS1aCVUlqNM" alt="" /></p> <p>主に3つの環境で機能しています。</p> <p>開発、CI(Continuous Integration)、そして本番。これらの環境は、同一のコードベースを共有しながらも、それぞれ特有の役割と振る舞いを持っています。dbtマクロを用いることで、これらの環境間の差異を効果的に管理し、各環境での要求に対応しています。</p> <p>環境差分を埋めるマクロについては、後の章で取り上げます。</p> <h2 id="シングルプロジェクトアプローチ">シングルプロジェクトアプローチ</h2> <p>dbtプロジェクトはシングルプロジェクト方式で運営されており、複数のプロダクトにまたがるデータを一元管理しています。</p> <p>異なるライフサイクルを持つデータも、一元管理された形式で扱うことで、管理の複雑さを軽減しています。これにより、基盤チームは効率的な運用が可能です。</p> <p>また、モデル開発者にとっても、メリットがあります。後述する開発体制で紹介しますが、弊社では<a href="https://www.splunk.com/ja_jp/data-insider/data-ops.html">DataOps</a>を採用しているので、それぞれのデータを、それぞれのデータオーナーが管理しています。</p> <p>それぞれのドメインに集中して開発できることを重視しつつも、近くに似たドメインの問題を解決してるモデルがあることで、集合知でも戦える体制を築いています。</p> <p>新たに登場した<a href="https://www.getdbt.com/product/dbt-mesh">dbt Mesh</a>のようなツールによってマルチプロジェクトアプローチが可能になりましたが、当社では過去の経験からモノリシックなアプローチを選択しています。これは、一箇所での管理のシンプルさと、運用コストの低さから、現時点では最適な選択だと考えています。</p> <h2 id="利用しているdbtパッケージ">利用しているdbtパッケージ</h2> <ul> <li><a href="https://hub.getdbt.com/dbt-labs/dbt_utils/latest/">dbt-labs/dbt_utils</a> <ul> <li>多様な汎用的なマクロを提供。データ変換や集計作業を簡素化するために使用している。</li> </ul> </li> <li><a href="https://hub.getdbt.com/dbt-labs/codegen/latest/">dbt-labs/codegen</a> <ul> <li>SQLやモデルのコードを自動生成するマクロ。コーディングの効率化をしてくれる。</li> </ul> </li> <li><p><a href="https://hub.getdbt.com/elementary-data/elementary/latest/">elementary-data/elementary</a></p> <ul> <li>dbtネイティブなデータオブザーバビリティツールElementaryが提供するパッケージ。</li> <li>内容としては、dbtの振る舞いに関するメタデータ生成をしているパッケージです。弊社では、<a href="https://docs.elementary-data.com/cloud/introduction">Elementary Cloud</a>を使っているので、その連携にも利用している。</li> </ul> </li> <li><p><a href="https://hub.getdbt.com/dbt-labs/dbt_project_evaluator/latest/">dbt-labs/dbt_project_evaluator</a></p> <ul> <li>dbtプロジェクトのベストプラクティスを確実に遵守するためのツール。プロジェクトの品質向上に役立つ。</li> </ul> </li> <li><a href="https://hub.getdbt.com/yu-iskw/dbt_unittest/latest/">yu-iskw/dbt_unittest</a> <ul> <li>マクロのユニットテストを容易にするパッケージ。信頼性の高いマクロ開発を支援。</li> </ul> </li> <li><a href="https://hub.getdbt.com/get-select/dbt_snowflake_monitoring/latest/">get-select/dbt_snowflake_monitoring</a> <ul> <li><a href="https://techblog.cartaholdings.co.jp/entry/select-cloud-cost-optimize-cmf">SELECT</a> が提供するSnowflakeのコストを可視化するために必要なモデルを構築してくれるパッケージ。</li> </ul> </li> </ul> <h2 id="開発環境">開発環境</h2> <h3 id="開発体制">開発体制</h3> <p>データ基盤開発・活用推進には、3名のデータエンジニアが専念しています。</p> <p>データ基盤チームは、<a href="https://bliki-ja.github.io/TeamTopologies">チームトポロジー</a>でいうところのプラットフォームチーム兼イネイブリングチームとして機能しています。</p> <p>基盤の安定稼働・機能開発を行うのがメイン業務ですが、時にはストリームアラインドチーム(プロダクトチーム)のデータに関する技術的サポートと知識の提供にも努めています。 プロダクトチームは、自分たちのニーズに合わせたdbtモデル開発を担当し、より具体的なサービスや機能に集中しています。リリースしたdbtモデルを、それぞれのチームに運用してもらっています。このような開発スタイルは、一般的には、<a href="https://www.splunk.com/ja_jp/data-insider/data-ops.html">DataOps</a>と呼ばれているもので、弊社では明示的に導入をしています。</p> <h3 id="本番同様の開発データベース">本番同様の開発データベース</h3> <p>各開発者が本番データベースをクローンし、開発者専用のデータベースを構築しています。これはdbt cloneコマンドを用いて実装されています。</p> <p>この方式により、開発中の変更が他の開発者や本番環境に不意に影響を及ぼすリスクがなくなり、開発のためにダミーデータを用意する必要がなく、最小限の工数で、本番同様の環境下で開発ができます。</p> <h3 id="Visual-Studio-Code-VS-Codeのプラグイン">Visual Studio Code (VS Code)のプラグイン</h3> <p>dbt開発では、VS Codeの利用を推奨しています。その理由は、dbtと密接に連携する豊富なプラグインが利用可能であるためです。弊社では以下のプラグインを使っています。</p> <ul> <li>dbt <ul> <li><a href="https://marketplace.visualstudio.com/items?itemName=bastienboutonnet.vscode-dbt">vscode-dbt - Visual Studio Marketplace</a></li> <li><a href="https://marketplace.visualstudio.com/items?itemName=Fivetran.dbt-language-server">Wizard for dbt Core (TM) - Visual Studio Marketplace</a></li> </ul> </li> <li>Snowflake <ul> <li><a href="https://marketplace.visualstudio.com/items?itemName=snowflake.snowflake-vsc">Snowflake - Visual Studio Marketplace</a></li> </ul> </li> <li>地味便利 <ul> <li><a href="https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml">YAML - Visual Studio Marketplace</a> <ul> <li><a href="https://github.com/dbt-labs/dbt-jsonschema/tree/main/schemas">dbtスキーマ</a>読み込ませて使ってます。</li> </ul> </li> <li><a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker">Code Spell Checker - Visual Studio Marketplace</a> <ul> <li>カラム、モデル名のtypoを防ぐ。</li> </ul> </li> <li><a href="https://marketplace.visualstudio.com/items?itemName=nemesv.copy-file-name">Copy file name - Visual Studio Marketplace</a> <ul> <li>モデル名のコピペに地味便利です。</li> </ul> </li> </ul> </li> </ul> <h1 id="CI環境">CI環境</h1> <p>開発者がdbtモデルを作成する際、GitHubでプルリクエストを作成します。これをトリガーとして、自動化されたCIプロセスが開始され、コードの品質と実行可能性を検証します。</p> <p>CIの役割は、dbtを熟知していなくても、CIのガイドラインに沿ってコーディングするだけで高い品質が担保されるようにすることです。これにより、基盤チームの詳細なレビューにかかる時間と労力を削減し、効率的な開発プロセスを実現しています。</p> <p>ちなみに、レビュープロセスは、プロダクトチーム内で回すことが基本で、エッジケースに当たった時に相談を受けるという状態です。基盤チームとプロダクトチームは、物理的にも独立した存在ですが、業務フロー的にも、独立して価値を出すことに集中しています。</p> <h2 id="コード品質チェック">コード品質チェック</h2> <h3 id="マクロ名とファイル名が一致するか">マクロ名とファイル名が一致するか</h3> <p>マクロの追加は基本的に自由なのですが、マクロ名とファイル名が1:1になるようにしています。</p> <p>例えば、<code>filter_partition</code> というマクロを作ったら、<code>filter_partition.sql</code> というファイルを作ることを強制しています。 これはファイル名検索でマクロが発見できたり、エディタで見た時に、どういうマクロがあるか一覧しやすいなどの理由で強制しているルールです。</p> <p>命名規則のチェックに関しては、Pythonでスクリプトを組んで、GitHubActionでチェックしています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> logging <span class="synPreProc">from</span> typing <span class="synPreProc">import</span> List <span class="synPreProc">from</span> glob <span class="synPreProc">import</span> glob check_skip_files = () <span class="synStatement">def</span> <span class="synIdentifier">check_macro_name_to_file_name</span>(files: List):     <span class="synStatement">for</span> filename <span class="synStatement">in</span> files:         <span class="synStatement">with</span> <span class="synIdentifier">open</span>(filename, <span class="synConstant">&quot;r&quot;</span>) <span class="synStatement">as</span> stream:             contents = stream.read()             realfilename = filename.split(<span class="synConstant">&quot;/&quot;</span>)[-<span class="synConstant">1</span>].split(<span class="synConstant">&quot;.sql&quot;</span>)[<span class="synConstant">0</span>].strip()             logging.info(f<span class="synConstant">&quot;Processing {realfilename}.&quot;</span>)             <span class="synStatement">if</span> realfilename <span class="synStatement">in</span> check_skip_files:                 logging.info(f<span class="synConstant">&quot;Skipping {filename} 👀&quot;</span>)                 <span class="synStatement">continue</span>             <span class="synStatement">if</span> has_materialization(contents):                 isolating_materialization = contents.split(<span class="synConstant">&quot;materialization &quot;</span>)[<span class="synConstant">1</span>].split(<span class="synConstant">&quot;,&quot;</span>)[<span class="synConstant">0</span>].strip()                 <span class="synStatement">if</span> realfilename == isolating_materialization <span class="synStatement">in</span> contents:                     logging.info(f<span class="synConstant">&quot;{filename} passes! 🎉&quot;</span>)                     <span class="synStatement">continue</span>                 <span class="synStatement">raise</span> <span class="synType">ValueError</span>(<span class="synConstant">&quot;Materialization naming error in &quot;</span> + filename)             <span class="synStatement">if</span> has_macros(contents):                 isolating_macro = contents.split(<span class="synConstant">&quot;macro &quot;</span>)[<span class="synConstant">1</span>].split(<span class="synConstant">&quot;(&quot;</span>)[<span class="synConstant">0</span>].strip()                 <span class="synStatement">if</span> realfilename == isolating_macro <span class="synStatement">in</span> contents:                     logging.info(f<span class="synConstant">&quot;{filename} passes! 🎉&quot;</span>)                     <span class="synStatement">continue</span>                 <span class="synStatement">raise</span> <span class="synType">ValueError</span>(<span class="synConstant">&quot;Macro naming error in &quot;</span> + filename)             <span class="synStatement">if</span> has_tests(contents):                 isolating_test = contents.split(<span class="synConstant">&quot;test &quot;</span>)[<span class="synConstant">1</span>].split(<span class="synConstant">&quot;(&quot;</span>)[<span class="synConstant">0</span>].strip()                 <span class="synStatement">if</span> realfilename == isolating_test <span class="synStatement">in</span> contents:                     logging.info(f<span class="synConstant">&quot;{filename} passes! 🎉&quot;</span>)                     <span class="synStatement">continue</span>                 <span class="synStatement">raise</span> <span class="synType">ValueError</span>(<span class="synConstant">&quot;Test naming error in &quot;</span> + filename)             <span class="synStatement">raise</span> <span class="synType">ValueError</span>(<span class="synConstant">&quot;Has no macro/materialization/test in &quot;</span> + filename) <span class="synStatement">def</span> <span class="synIdentifier">has_macros</span>(contents):     <span class="synStatement">return</span> <span class="synIdentifier">len</span>(contents.split(<span class="synConstant">&quot;macro &quot;</span>)) &gt; <span class="synConstant">1</span> <span class="synStatement">def</span> <span class="synIdentifier">has_materialization</span>(contents):     <span class="synStatement">return</span> <span class="synIdentifier">len</span>(contents.split(<span class="synConstant">&quot;materialization &quot;</span>)) &gt; <span class="synConstant">1</span> <span class="synStatement">def</span> <span class="synIdentifier">has_tests</span>(contents):     <span class="synStatement">return</span> <span class="synIdentifier">len</span>(contents.split(<span class="synConstant">&quot;test &quot;</span>)) &gt; <span class="synConstant">1</span> <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>:     logging.basicConfig(level=<span class="synConstant">20</span>)     files = glob(<span class="synConstant">&quot;macros/**/*.sql&quot;</span>, recursive=<span class="synIdentifier">True</span>)     check_macro_name_to_file_name(files)     logging.info(<span class="synConstant">&quot;全てのマクロが、ファイル名と一致しています 🥂&quot;</span>) </pre> <h3 id="モデル名に対応するyamlドキュメントが存在するか">モデル名に対応するyamlドキュメントが存在するか</h3> <p>dbtモデルを作成する際、そのドキュメンテーションはyamlで記述されます。 当初は、複数のモデルのドキュメントを、フォルダごとに一つの<code>models.yml</code>で管理していましたが、これは可読性とコンフリクトの観点から問題がありました。</p> <p>そこで、各モデルに個別のyamlドキュメントを割り当てる方法に切り替えました。例えば、<code>hoge_report.sql</code>モデルに対しては、対応する<code>hoge_report.yml</code>ドキュメントを作成します。この方法は、<strong>エディタ等で見た時に、モデルに対応するドキュメントの探しやすさ、ファイル名検索での見つけやすさ、コンフリクトを避けるのにも効果的でした。</strong></p> <p>このルールを強制するために、Pythonスクリプトでチェックをしています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> os <span class="synPreProc">import</span> glob <span class="synPreProc">import</span> logging <span class="synStatement">def</span> <span class="synIdentifier">check_doc_exists</span>(model_files, ignore_folders): missing_yml = [] <span class="synStatement">for</span> model_file <span class="synStatement">in</span> model_files: <span class="synStatement">if</span> <span class="synIdentifier">any</span>(ignore_folder <span class="synStatement">in</span> model_file <span class="synStatement">for</span> ignore_folder <span class="synStatement">in</span> ignore_folders): <span class="synStatement">continue</span> yml_file = os.path.splitext(model_file)[<span class="synConstant">0</span>] + <span class="synConstant">'.yml'</span> <span class="synStatement">if</span> <span class="synStatement">not</span> os.path.exists(yml_file): missing_yml.append(yml_file) <span class="synStatement">if</span> missing_yml: logging.error(<span class="synConstant">'モデルファイルに対応するymlファイルがありません。'</span>) <span class="synStatement">for</span> <span class="synIdentifier">file</span> <span class="synStatement">in</span> missing_yml: <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;touch {file}&quot;</span>) <span class="synStatement">raise</span> <span class="synType">SystemExit</span>(<span class="synConstant">'上記のコマンドでymlファイルを作成し、ドキュメントを作成してください。'</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: logging.basicConfig(level=<span class="synConstant">20</span>) model_files = glob.glob(<span class="synConstant">&quot;models/**/*.sql&quot;</span>, recursive=<span class="synIdentifier">True</span>) <span class="synComment"># ここにチェックを無視するフォルダをリスト形式で指定します</span> ignore_folders = [ <span class="synConstant">&quot;models/dbt_snowflake_monitoring&quot;</span>, ] logging.info(<span class="synConstant">&quot;チェック対象外: &quot;</span> + <span class="synIdentifier">str</span>(ignore_folders)) check_doc_exists(model_files, ignore_folders) logging.info(<span class="synConstant">&quot;ドキュメントが全て揃っています🥂&quot;</span>) </pre> <h3 id="dbtがベストプラクティスに沿った実装になっているか">dbtがベストプラクティスに沿った実装になっているか</h3> <p><a href="https://github.com/dbt-labs/dbt-project-evaluator">github.com/dbt-labs/dbt-project-evaluator</a> を使って、dbtの実装がベストプラクティスに則っているかをチェックしています。このライブラリは非常に便利で、dbtで複数人開発をしている方は絶対に入れた方がいいレベルです。</p> <p>どういったことをチェックできるか抜粋して紹介します。</p> <ul> <li>特定のフォルダ以下のモデル名に、決まったprefixがついているか? <ul> <li><a href="https://dbt-labs.github.io/dbt-project-evaluator/0.8/rules/structure/#model-naming-conventions">Structure - dbt_project_evaluator</a></li> </ul> </li> <li>sourceモデルと直接Joinしてないか? <ul> <li><a href="https://dbt-labs.github.io/dbt-project-evaluator/0.8/rules/modeling/#direct-join-to-source">Modeling - dbt_project_evaluator</a></li> </ul> </li> </ul> <p>設定を書き換えることで、それぞれのプロジェクトのプラクティスに<a href="https://dbt-labs.github.io/dbt-project-evaluator/0.8/customization/overriding-variables/">カスタマイズも可能です。</a>しかし、後から入れるとほとんどの場合は違反だらけで直すのが大変になるので、プロダクト当初からの導入や早めの導入がおすすめです。</p> <h3 id="SQLフォーマットチェック">SQLフォーマットチェック</h3> <p>弊社では、SQLのコーディングスタイルを、<a href="http://github.com/tconbeer/sqlfmt">sqlfmt</a>を使って統一しています。</p> <p>当初は、<a href="https://github.com/sqlfluff/sqlfluff">sqlfluff</a>も導入していた時期があったのですが、<code>sqlfluff lint</code>が通らないことに関するコミュニケーションコストが非常に高くつきました。有無も言わせず、書き方を強制するsqlfmtの方が一定の品質を担保しつつ、ストレスなく開発ができました。 さらに、sqlfmtの実行速度が非常に速いことも、切り替える大きな理由の一つでした。sqlfluffに比べて、その速さはストレスを微塵も感じさせないレベルです。</p> <p>但し、sqlfmtとSnowflakeの組み合わせで起きる問題が、いくつかあるので、導入時に気をつけたい点があります。</p> <h4 id="半構造化データ走査のドット表記">⚠半構造化データ走査のドット表記</h4> <p>Snowflakeで半構造化データ走査には、<a href="https://docs.snowflake.com/ja/user-guide/querying-semistructured#dot-notation">ドット表記</a>と<a href="https://docs.snowflake.com/ja/user-guide/querying-semistructured#bracket-notation">かっこ表記</a>があります。</p> <p>CMFでは、かっこ表記を採用しています。</p> <p>sqlfluffを使っていた時はドット表記を利用しており、<code>raw_data:requestTime::timestamp_tz</code> の様に走査をしていました。</p> <p>しかし、このSQLに対して、sqlfmtをかけると、<code>raw_data:requesttime::timestamp_tz</code> の様に全てが小文字に変換されてしまいます。つまり、全く違う意味になるので、SQLが意図しない状態になります。</p> <p>これについては以下のissueでも議論されていますが、現状も同じ仕様のままなので、注意が必要です。私はこれでデータが壊れry</p> <p><a href="https://github.com/tconbeer/sqlfmt/issues/269">https://github.com/tconbeer/sqlfmt/issues/269</a></p> <p>これに対して弊社では、ドット表記が、そもそも型変換の <code>::</code> と見分けがつきにくいということもあり、かっこ表記に統一することで、この問題は再発していません。</p> <h4 id="メタデータカラム">⚠メタデータカラム</h4> <p><a href="https://docs.snowflake.com/ja/user-guide/querying-metadata#metadata-columns">ステージングされたファイルのメタデータ</a> へのアクセスは、<code>metadata$filename</code> の様なカラムを指定します。これをsqlfmtかけると、<code>metadata $filename</code> の様に、スペースが作られてしまいます。これもまた意味が変わってしまいます。</p> <p>これに対する対応は、<a href="https://docs.sqlfmt.com/getting-started/disabling-sqlfmt">Disabling sqlfmt</a> を使うことで対処できます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synComment">-- fmt: off</span> metadata$filename::<span class="synType">varchar</span> <span class="synSpecial">as</span> _metadata_filename, metadata$file_row_number::bigint <span class="synSpecial">as</span> _metadata_file_row_number, <span class="synComment">-- fmt: on</span> </pre> <h3 id="yamlフォーマットチェック">yamlフォーマットチェック</h3> <p><a href="https://github.com/lyz-code/yamlfix">github.com/lyz-code/yamlfix</a></p> <p>dbtを開発してると、yamlをそれなりに書くことになるのですが、これも色んな書き方が発生しがちで、収集つかなくなるので、フォーマットするのがおすすめです。</p> <h3 id="warningチェック">warningチェック</h3> <p><code>dbt --warn-error ls</code> を流すことで、dbtの実装でwarning判定されているコードがあるかを検知しています。経験的に、dbtのwarningは出てる状態を放置しててもメリットがないので、基本的に許さないスタイルでやった方が時間の節約になります。</p> <p><a href="https://docs.getdbt.com/reference/global-configs/warnings">Warnings | dbt Developer Hub</a></p> <h2 id="実行可能性の検証">実行可能性の検証</h2> <h4 id="新しく追加されたモデルのbuild">新しく追加されたモデルのbuild</h4> <p>追加されたdbtモデルを <a href="https://docs.getdbt.com/reference/node-selection/methods#the-state-method">state</a>を使って検知して、buildします。</p> <p>この時に、実データを使って実行していますが、全量のデータは使わないようにしています。 理由としては、全量データでテストすると処理が終わらなくなるからです。</p> <p>そのかわりに少量のテストを流して、最低限の挙動チェックを行うようしています。以下のようなマクロを組んでいます。 dbtのtargetがciの時に、limitをかけるマクロ。</p> <pre class="code lang-python" data-lang="python" data-unlink>{%- macro limit_ci(rows_limit=<span class="synConstant">10</span>) -%} {%- <span class="synStatement">if</span> target.name == <span class="synConstant">'ci'</span> %} limit {{ rows_limit }} {%- endif -%} {%- endmacro -%} </pre> <p>時系列系のデータで、limitをかけるマクロ。 最近のパーティションで絞り込んで、limitすることで、最近のデータで試すことを実現する。 <a href="https://zenn.dev/pei0804/articles/data-partitioning-in-dbt">データパーティショニング</a> について気になる方は、別途記事を書いてるので、そちらを御覧ください。</p> <pre class="code lang-python" data-lang="python" data-unlink>{%- macro filter_partition_and_limit_ci(rows_limit=<span class="synConstant">10</span>) -%} {%- <span class="synStatement">if</span> target.name == <span class="synConstant">'ci'</span> %} {{ filter_partition() }} {{ limit_ci(rows_limit) }} {%- <span class="synStatement">else</span> -%} <span class="synConstant">1</span>=<span class="synConstant">1</span> {%- endif -%} {%- endmacro -%} </pre> <p>実行時に使うSnowflakeデータベースは、CI用のものでスキーマは動的にコミット単位で作っています。</p> <p><img src="https://lh7-us.googleusercontent.com/pJPcOumqzfkusDL3RJCf7svi5MO5L9fwoS39cGxT_qQkE17XCLGcijvoQSZVQPR8OTz8FIQowOxCe4kBYfnUMsLNnITF1ybEtcdRtiGxTHxQ1nEhTK6ZbQcBX02cm5bjvFC2hI9AKULZYLD3D3pOtPs" alt="" /></p> <p>プルリクエストにプッシュしたコミットごとに作られるので、結構な数が生成されます。そして、実はスキーマには上限数があるので、放置しているとエラーになります。そのため、タスクとストアドプロシージャで定期的に消すようにしています。</p> <h4 id="dbt-macroのユニットテスト">dbt macroのユニットテスト</h4> <p><a href="https://github.com/yu-iskw/dbt-unittest">github.com/yu-iskw/dbt-unittest</a></p> <p>dbt-unittestという便利なライブラリがあるので、こちらを使ってマクロをユニットテストできるようにしています。全てのマクロにテスト追加させるようなルールはなく、適宜追加するスタイルでやっています。仮にテストしないと不安になるような場合、マクロが複雑すぎる可能性があるのでシンプルな方向に落とすように促しています。</p> <p><a href="https://docs.getdbt.com/blog/unit-testing-dbt-packages">An introduction to unit testing your dbt Packages | dbt Developer Blog</a></p> <h4 id="シンギュラーデータテスト">シンギュラーデータテスト</h4> <p><a href="https://docs.getdbt.com/docs/build/data-tests#singular-data-tests">Add data tests to your DAG | dbt Developer Hub</a></p> <p>dbt modelに対するtestとは別で、データを使ったテストをしています。</p> <p>いくつかの例を紹介すると、少し込み入ったロジックのマクロを、実際にSnowflake上で、実行した結果をテストしたいケースに使っています。</p> <p>弊社特有のものだと、dbt projectの設定をテストをするのにも使っています。弊社では、dbtのレイヤリングをSnowflakeスキーマに反映しています。例えば、warehouseレイヤーなら、Snowflakeのwarehouseスキーマで作られるイメージです。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">warehouse</span><span class="synSpecial">:</span> <span class="synIdentifier">+schema</span><span class="synSpecial">:</span> warehouse </pre> <p>仮に<code>+schema</code>が設定漏れしていると、profileのdefault schemaにモデルが作られます。これは意図してない設定なので、CIで検知して修正を促しています。 手法としては、elementaryのdbt artifactを使ったスキーマが設定されてないモデルを抽出して、default schemaのままのモデルがあったらテストを落としています。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">select</span> * <span class="synSpecial">from</span> {{ ref(<span class="synSpecial">&quot;</span><span class="synConstant">elementary</span><span class="synSpecial">&quot;</span>, <span class="synSpecial">&quot;</span><span class="synConstant">dbt_models</span><span class="synSpecial">&quot;</span>) }} <span class="synSpecial">where</span> schema_name = <span class="synIdentifier">lower</span>(<span class="synSpecial">'</span><span class="synConstant">{{ target.schema }}</span><span class="synSpecial">'</span>) </pre> <p><a href="https://docs.elementary-data.com/dbt/dbt-artifacts">dbt artifacts - Elementary</a></p> <p>非常に便利なモデルなので、結構重宝しています。 なお、elementaryのartifactsを利用するためには、elementaryのモデルをビルドする必要があります。CIプロセスにこれらのモデルを組み込む場合、事前にモデルのビルドを行うことが必須となります。</p> <h4 id="dbt実行制御タグチェック">dbt実行制御タグチェック</h4> <p>dbtモデルの実行制御にタグを使っています。</p> <ul> <li><code>build__every_20_minutes</code> <ul> <li>このタグは20分ごとの実行に用いられ、主にディメンションの更新など、アプリケーション設定の迅速な反映が必要な場合に使用されます。</li> </ul> </li> <li><code>build__every_hour</code> <ul> <li>1時間ごとの実行に用いられるこのタグは、ビジネスの核となるモデルに属しており、レポーティング、分析、機械学習モデルの訓練データ生成などに使用されます。</li> </ul> </li> <li><code>build__every_day</code> <ul> <li>1日に1回実行されるモデルにこのタグが付与され、主に分析用のモデルに利用されます。ここでは、データ確認までの若干のタイムラグが許容されるケースが多いです。</li> </ul> </li> </ul> <p>これはTIPSなのですが、tagは一定の命名規則がある方が検索性も上がりますし、コンフィグバリデーションなども仕込みやすいです。</p> <p>ですが、正しくタグがついてるか、外れたかの判断が、パッと見ではわかりにくいという問題があります。そこで、diffコマンドを使って、変更をわかりやすく確認できる仕組みを用意しています。</p> <p>CIが実際に変更を検知した時のコメント例。</p> <p><img src="https://lh7-us.googleusercontent.com/uD05Xk21yHKyUxdk8DV692VzRNOt7qsRuVOOttRrahUtkTm9YKKnG3I8jYdyqJy13VsRyy0zvSYugUc1N-XY8LGmFW3AmEHOotiu6ijyKPSNCjNKEzA3pWD0JzKoieJfGIxl5PmSstBuoepfiDgL27I" alt="" /></p> <p>弊社では、500個以上のモデルを管理していて、その半数以上が1時間ごとに動作するincrementalモデルです。間違った設定による事業へのインパクトが大きいため、機械的に変更を検知して、チェックできる仕組みを用意することは重要でした。</p> <h2 id="同時実行の制御">同時実行の制御</h2> <p>これらのCIの一部は、Snowflakeでウェアハウスを使って実際に実行されるため、クレジットを消費します。<strong>当然、実行したとしても無駄になる可能性があれば、CIの実行を止めたいわけです。</strong></p> <p>例えば、短時間に複数回コミットとプッシュされた場合、最初のコミットは無駄な実行になる可能性が高いです。</p> <p>そこで使っているのが、GitHubActionのconcurrencyを使った制御です。<strong>簡単にプルリクエストごとの実行重複を制限できるので便利</strong>です。</p> <p><a href="https://docs.github.com/en/actions/using-jobs/using-concurrency">https://docs.github.com/en/actions/using-jobs/using-concurrency</a></p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">concurrency</span><span class="synSpecial">:</span> <span class="synIdentifier">group</span><span class="synSpecial">:</span> ${{ github.workflow }}-${{ github.head_ref || github.run_id }} <span class="synIdentifier">cancel-in-progress</span><span class="synSpecial">:</span> <span class="synConstant">true</span> </pre> <h1 id="本番環境">本番環境</h1> <h2 id="CDContinuous-Delivery">CD(Continuous Delivery)</h2> <p>mainブランチへのpushをトリガーにCDプロセスを実行しています。このプロセスではAWS CDK PipelinesとGitHub Actionsを使用した自動化されたデプロイメントを実現しています。</p> <p>CDプロセスにより、以下のタスクが自動的に行われます。</p> <ul> <li><a href="https://docs.elementary-data.com/release-notes/upgrading-elementary#upgrade-elementary-dbt-package">elementary modelの更新。</a></li> <li>mainブランチのモデルからstateを生成し、S3にPutObjectとして保存。 <ul> <li>CIで使用される差分テストや、開発環境構築用のdbt cloneに利用される。</li> </ul> </li> <li>StepFunctionsやECSなどのデータパイプライン関連リソースのデプロイ。</li> </ul> <h2 id="オーケストレーション">オーケストレーション</h2> <p>本番環境では、AWS Step FunctionsとAmazon ECSを使って、dbt-coreを稼働させています。 dbtで表現されるDAGをStep Functionsに完全に反映させるのは技術的に難しいため、現在はこの部分については簡素化しています。</p> <p>定期的に他のオーケストレーションツールへの切り替えも検討していますが、<strong>現状ではStep Functionsの低コストが魅力的であり、変更の必要性が勝るケースに遭遇していません。</strong></p> <p>dbt-coreを動かしてるAWS Step Functionsの具体的なコスト。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tikasan0804/20231223/20231223104009.png" width="664" height="782" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="Backfill-Bot">Backfill Bot</h2> <p>データ基盤の運用においては、Backfill(再集計)作業が頻繁に発生します。 これに対応するため、Backfillを容易に実行できるSlackBotを導入しています。このツールにより、修正が必要なdbtモデルの再計算や、新たにリリースされたモデルの過去データに対する再集計が簡単に実施できます。また、<strong>スマートフォンがあればどこからでも操作可能なため、問題が生じた場合の迅速な対応が可能です。</strong></p> <p><img src="https://lh7-us.googleusercontent.com/qoyKJ1qMKmII2EuLSw_6WtAXls4FVeWHgzkkemEHJNCXpJPJLrpG7Ti-9zQz4vha97IhlwV7as2Y6FJv1fUyEifORi-raBza3jE-yMbGitXDwIKCpXCeyEKyt5fEWBi_Tse3vsv8vBE-ISDwiy_6psE" alt="" /></p> <p>入力項目は以下の通りです。</p> <ul> <li>Backfill実行の理由 <ul> <li>理由がわからないと、実行内容の妥当性わからないのと、これがログになるので、地味に大事です。</li> </ul> </li> <li>集計するモデル名 <ul> <li><a href="https://docs.getdbt.com/reference/node-selection/putting-it-together">select</a>で指定するのと同じ。</li> </ul> </li> <li>集計しないモデル名 (任意) <ul> <li><a href="https://docs.getdbt.com/reference/node-selection/exclude">exclude</a>で指定するのと同じ。</li> </ul> </li> <li>開始日 <ul> <li>集計開始日を選択する。</li> </ul> </li> <li>開始時間 <ul> <li>集計開始時間を選択する。</li> </ul> </li> <li>終了日(任意) <ul> <li>集計終了日を選択する。</li> <li>設定されていない場合は、デフォルトの最大値(9999-12-31)が適用されます。</li> </ul> </li> <li>終了時間(任意) <ul> <li>集計終了時間を選択する。</li> <li>設定されていない場合は、最大値(23時)が適用されます。</li> </ul> </li> </ul> <p>このBackfill Botは非常に便利ですが、便利が故に誤った使用が起きる潜在的なリスク(例えば、過去のレポートデータの改変)もあるため、セーフティ機能の強化に向けた取り組みを検討中です。</p> <h1 id="環境差分を管理するマクロ">環境差分を管理するマクロ</h1> <p>ここまで、3つの環境を紹介してきました。</p> <p>再掲:CI/CDパイプライン</p> <p><img src="https://lh7-us.googleusercontent.com/Lw2s23h3oLYQuq8D_jmu-PTWK4ryV8QDV2T28KfA734PR6Yc_xsODZs6FjREK1M1L7v1pLKwr6tqngsbBbjGqqnXu_y49EPrmcyqHd80EaoV5-2Bi4vtjezbFzKyo8VmArs1ItKT8obeqS1aCVUlqNM" alt="" /></p> <p>それぞれの環境でのデータベースやスキーマの扱い方には微妙な違いがあります。これらの差分は、dbtのマクロを使用して効果的に管理しています。</p> <h2 id="データベーススキーマ切り替え">データベース、スキーマ切り替え</h2> <p>dbtのカスタムデータベースとスキーマ機能を利用して、環境に応じてデータベースやスキーマを動的に切り替えます(参照:<a href="https://docs.getdbt.com/docs/build/custom-databases">Custom databases | dbt Developer Hub</a>、<a href="https://docs.getdbt.com/docs/build/custom-schemas">Custom schemas | dbt Developer Hub</a>)。これにより、各環境のデータ分離と整合性を確保しつつ、効率的な運用が可能となります。</p> <pre class="code lang-python" data-lang="python" data-unlink>{% macro generate_database_name(custom_database_name, node) -%} {%- <span class="synIdentifier">set</span> production_targets = production_targets() -%} {<span class="synComment">#</span> 定義: - custom_database_name: dbt_project.ymlまたはmodel configで設定されたdatabase name。 - target.name: target名 (devはローカル開発用、 prodは本番用など) - target.database: profiles.ymlに設定されているdatabase名 前提条件: - dbtを使った開発をする人は、USERNAME_PROD, USERNAME_PREPが既に作成されていることを前提としています このマクロの挙動のサンプルは以下の通りです。 (custom_database_name, target.name, target.database) = &lt;output&gt; (prod, prod, prep) = prod (prod, ci, prep) = prod (prod, dev, pei) = pei_prod (prep, prod, prep) = prep (prep, ci, prep) = prep (prep, dev, pei) = pei_prep <span class="synComment">#}</span> {%- <span class="synStatement">if</span> target.name <span class="synStatement">in</span> production_targets -%} {%- <span class="synStatement">if</span> custom_database_name <span class="synStatement">is</span> none -%} {{ target.database | trim }} {%- <span class="synStatement">else</span> -%} {{ custom_database_name | trim }} {%- endif -%} {%- <span class="synStatement">else</span> -%} {%- <span class="synStatement">if</span> custom_database_name <span class="synStatement">is</span> none -%} {<span class="synComment"># ここに通ることは、通常ありえない #}</span> {{ target.database | trim }} {%- <span class="synStatement">else</span> -%} {{ target.database }}_{{ custom_database_name | trim }} {%- endif -%} {%- endif -%} {%- endmacro %} </pre> <pre class="code lang-python" data-lang="python" data-unlink>{% macro generate_schema_name(custom_schema_name, node) -%} {<span class="synComment">#</span> 定義: - custom_schema_name: dbt_project.ymlまたはmodel configで設定されたdatabase name。 - target.name: target名 (devはローカル開発用、 prodは本番用など) - target.schema: profiles.ymlに設定されているschema名 このマクロの挙動のサンプルは以下の通りです。 (custom_schema_name, target.name, target.schema) = &lt;output&gt; (hoge_logs, prod, public) = hoge_logs (hoge_logs, ci, public) = hoge_logs_public (hoge_logs, dev, public) = hoge_logs <span class="synComment">#}</span> {%- <span class="synStatement">if</span> target.name == <span class="synConstant">&quot;ci&quot;</span> -%} {%- <span class="synStatement">if</span> custom_schema_name <span class="synStatement">is</span> none -%} {{ target.schema.lower() | trim }} {%- <span class="synStatement">else</span> -%} {{ custom_schema_name.lower() | trim }}_{{ target.schema.lower() | trim }} {%- endif -%} {%- <span class="synStatement">else</span> -%} {%- <span class="synStatement">if</span> custom_schema_name <span class="synStatement">is</span> none -%} {{ target.schema.lower() | trim }} {%- <span class="synStatement">else</span> -%} {{ custom_schema_name.lower() | trim }} {%- endif -%} {%- endif -%} {%- endmacro %} </pre> <h2 id="ウェアハウス切り替え">ウェアハウス切り替え</h2> <p>dbtマクロを使用して、必要に応じて異なるウェアハウス間での切り替えを行います。これにより、リソースの最適化とコスト管理を行いながら、各環境でのデータ処理を行います。</p> <p><a href="https://zenn.dev/pei0804/articles/dbt-snowflake-dynamic-warehouse">dbt Snowflakeでウェアハウスをモデルごとに切り替える</a> で使い方について解説しているので、興味のある方はこちらを御覧ください。</p> <pre class="code lang-python" data-lang="python" data-unlink>{% macro get_warehouse(size) %} {% <span class="synIdentifier">set</span> available_sizes = [<span class="synConstant">&quot;XS&quot;</span>, <span class="synConstant">&quot;S&quot;</span>, <span class="synConstant">&quot;M&quot;</span>, <span class="synConstant">&quot;L&quot;</span>, <span class="synConstant">&quot;XL&quot;</span>] %} {% <span class="synStatement">if</span> size <span class="synStatement">not</span> <span class="synStatement">in</span> available_sizes %} {{ exceptions.raise_compiler_error( <span class="synConstant">&quot;Warehouse size not one of &quot;</span> ~ valid_warehouse_sizes ) }} {% endif %} {% <span class="synStatement">if</span> target.name <span class="synStatement">in</span> (<span class="synConstant">&quot;prod&quot;</span>) %} {<span class="synComment"># backfill時は、モデルに設定されたウェアハウスサイズだと処理しきれないため、強めのウェアハウスを設定する #}</span> {%- <span class="synStatement">if</span> var(<span class="synConstant">&quot;is_transform_backfill&quot;</span>) -%} {% do <span class="synStatement">return</span>(<span class="synConstant">&quot;DBT_XL_WH&quot;</span>) %} {% <span class="synStatement">else</span> %} {% do <span class="synStatement">return</span>(<span class="synConstant">&quot;DBT_&quot;</span> ~ size + <span class="synConstant">&quot;_WH&quot;</span>) %} {% endif %} {% <span class="synStatement">elif</span> target.name <span class="synStatement">in</span> (<span class="synConstant">&quot;ci&quot;</span>) %} {% do <span class="synStatement">return</span>(<span class="synConstant">&quot;DEV_XS_WH&quot;</span>) %} {% <span class="synStatement">elif</span> target.name == <span class="synConstant">&quot;dev_d&quot;</span> %} {<span class="synComment"># ローカルでの動作チェック用 #}</span> {% do <span class="synStatement">return</span>(<span class="synConstant">&quot;DEV_&quot;</span> ~ size + <span class="synConstant">&quot;_WH&quot;</span>) %} {% <span class="synStatement">else</span> %} {% do <span class="synStatement">return</span>(<span class="synIdentifier">None</span>) %} {% endif %} {% endmacro %} </pre> <h2 id="環境ごとのprofile">環境ごとのprofile</h2> <p>紹介したマクロでは、実行環境に応じて振る舞いを変える判断軸に、<a href="https://docs.getdbt.com/reference/dbt-jinja-functions/target">target variable</a>を使っています。これは、profileに設定されてる値を、変数として扱うことができ非常に便利です。弊社では、以下のようなprofileを使っているので、参考までに紹介します。</p> <h3 id="開発環境-1">開発環境</h3> <p>開発環境では、個々の開発者が<code>~/.dbt/profiles.yml</code>を使用してプロファイルを管理しています。</p> <p>一方、CIおよび本番環境では、リポジトリ内の<code>dbt/profile/profiles.yml</code>を用いてプロファイルが設定されています。これにより、<strong>開発環境では個別の設定が可能となり、CIおよび本番環境では一貫したプロファイル管理が実現されています。</strong></p> <p>開発用プロファイル</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">config</span><span class="synSpecial">:</span> <span class="synIdentifier">partial_parse</span><span class="synSpecial">:</span> <span class="synConstant">true</span> <span class="synIdentifier">vision_dbt</span><span class="synSpecial">:</span> <span class="synIdentifier">target</span><span class="synSpecial">:</span> dev <span class="synIdentifier">outputs</span><span class="synSpecial">:</span> <span class="synIdentifier">dev</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> snowflake <span class="synIdentifier">threads</span><span class="synSpecial">:</span> <span class="synConstant">8</span> <span class="synIdentifier">account</span><span class="synSpecial">:</span> account_id <span class="synIdentifier">user</span><span class="synSpecial">:</span> メールアドレス <span class="synIdentifier">role</span><span class="synSpecial">:</span> DBT_DEV <span class="synIdentifier">database</span><span class="synSpecial">:</span> J_CHIKAMORI<span class="synComment"> # &lt;-- Jumpei chikamori なら J_CHIKAMORI</span> <span class="synIdentifier">warehouse</span><span class="synSpecial">:</span> DEV_XS_WH <span class="synIdentifier">schema</span><span class="synSpecial">:</span> public <span class="synIdentifier">authenticator</span><span class="synSpecial">:</span> externalbrowser <span class="synIdentifier">dev_d</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> snowflake <span class="synIdentifier">threads</span><span class="synSpecial">:</span> <span class="synConstant">8</span> <span class="synIdentifier">account</span><span class="synSpecial">:</span> account_id <span class="synIdentifier">user</span><span class="synSpecial">:</span> メールアドレス <span class="synIdentifier">role</span><span class="synSpecial">:</span> DBT_DEV <span class="synIdentifier">database</span><span class="synSpecial">:</span> J_CHIKAMORI<span class="synComment"> # &lt;-- Jumpei chikamori なら J_CHIKAMORI</span> <span class="synIdentifier">warehouse</span><span class="synSpecial">:</span> DEV_L_WH <span class="synIdentifier">schema</span><span class="synSpecial">:</span> public <span class="synIdentifier">authenticator</span><span class="synSpecial">:</span> externalbrowser </pre> <p>CI、本番プロファイル</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">vision_dbt</span><span class="synSpecial">:</span> <span class="synIdentifier">outputs</span><span class="synSpecial">:</span> <span class="synIdentifier">prod</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> snowflake <span class="synIdentifier">threads</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('DBT_THREADS', '16') | as_number }}&quot;</span> <span class="synIdentifier">account</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_ACCOUNT') }}&quot;</span> <span class="synIdentifier">user</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_USER') }}&quot;</span> <span class="synIdentifier">role</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_ROLE') }}&quot;</span> <span class="synIdentifier">warehouse</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_WAREHOUSE') }}&quot;</span> <span class="synIdentifier">database</span><span class="synSpecial">:</span> PREP <span class="synIdentifier">schema</span><span class="synSpecial">:</span> public <span class="synIdentifier">private_key_path</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('PRIVATE_KEY_PATH', '') }}&quot;</span> <span class="synIdentifier">private_key_passphrase</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('PRIVATE_KEY_PASSPHRASE', '') }}&quot;</span> <span class="synIdentifier">query_tag</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_QUERY_TAG', 'prod') }}&quot;</span> <span class="synIdentifier">connect_retries</span><span class="synSpecial">:</span> <span class="synConstant">3</span> <span class="synIdentifier">connect_timeout</span><span class="synSpecial">:</span> <span class="synConstant">10</span> <span class="synIdentifier">use_colors</span><span class="synSpecial">:</span> <span class="synConstant">False</span> <span class="synIdentifier">ci</span><span class="synSpecial">:</span> <span class="synIdentifier">type</span><span class="synSpecial">:</span> snowflake <span class="synIdentifier">threads</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('DBT_THREADS', '16') | as_number }}&quot;</span> <span class="synIdentifier">account</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_ACCOUNT') }}&quot;</span> <span class="synIdentifier">user</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_USER') }}&quot;</span> <span class="synIdentifier">role</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_ROLE') }}&quot;</span> <span class="synIdentifier">warehouse</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_WAREHOUSE') }}&quot;</span> <span class="synIdentifier">database</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_PREP_DATABASE') }}&quot;</span> <span class="synIdentifier">schema</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_SCHEMA') }}&quot;</span> <span class="synIdentifier">private_key_path</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('PRIVATE_KEY_PATH', '') }}&quot;</span> <span class="synIdentifier">private_key_passphrase</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('PRIVATE_KEY_PASSPHRASE', '') }}&quot;</span> <span class="synIdentifier">query_tag</span><span class="synSpecial">:</span> <span class="synConstant">&quot;{{ env_var('SNOWFLAKE_QUERY_TAG', 'ci') }}&quot;</span> <span class="synIdentifier">connect_retries</span><span class="synSpecial">:</span> <span class="synConstant">3</span> <span class="synIdentifier">connect_timeout</span><span class="synSpecial">:</span> <span class="synConstant">10</span> <span class="synIdentifier">use_colors</span><span class="synSpecial">:</span> <span class="synConstant">False</span> </pre> <h1 id="ドキュメンテーション">ドキュメンテーション</h1> <p>体制のところでも説明した通り、dbtモデルの開発は、基盤チームではなく各プロダクトチームのエンジニアによって担当されています。</p> <p>ですが、dbtモデルの開発はプロダクトチームのエンジニアにとっての主業務ではなく、幅広いプロダクト開発の一部として行われます。そのため、dbtやSnowflakeに詳しい人だけでなく、様々なスキルレベルを持つ人々がアドホックに開発に関わっています。</p> <p>このような多様なスキルセットを持つチームに対応するためには、自己完結可能なドキュメンテーションの提供が不可欠です。</p> <p>そのため、以下のようなドキュメントをGitHub wikiで管理し、開発チームが必要な情報に素早くアクセスできるようにしています。</p> <p>※抜粋</p> <ul> <li>CIのエラー解消方法</li> <li>ETLパターン選択ガイド</li> <li>カラムの命名規則</li> <li>クエリパフォーマンス改善</li> <li>パフォーマンス基盤改善</li> <li>リリース手順</li> <li>ログ設計ベストプラクティス</li> <li>基盤の責任範囲</li> <li>障害対応TIPS</li> <li>オンボーディング <ul> <li>dbt</li> <li>Fivetran</li> <li>Terraform</li> <li>CDK</li> <li>Snowflake</li> </ul> </li> </ul> <p>現在、GitHub wikiの検索性や情報探索の難しさに直面しているため、ドキュメンテーションをより利用しやすくするためのサービスへの移行を検討しています。</p> <h1 id="モニタリング">モニタリング</h1> <p>モニタリングに関しては、これだけで一つの記事が書けるような内容なので、別の機会に取り上げますが、何を使っているかを簡単に紹介します。</p> <ul> <li>基盤監視 <ul> <li>DataDog</li> <li>Snowsight</li> </ul> </li> <li>データオブザーバビリティ <ul> <li>elementary Cloud</li> </ul> </li> <li>コスト監視 <ul> <li><a href="https://techblog.cartaholdings.co.jp/entry/select-cloud-cost-optimize-cmf">SELECT</a></li> </ul> </li> </ul> <h1 id="まとめ変化を恐れず進化を続けるデータ基盤">まとめ:変化を恐れず進化を続けるデータ基盤</h1> <p>この記事を通じて、私たちCARTA MARKETING FIRMがどのようにデータ基盤「Vision」を構築し、運用してきたかをご紹介しました。</p> <p>現在の業務フローに合わせたアプローチを取り、dbtとSnowflakeを中心にした柔軟なアーキテクチャを実現しています。</p> <p>しかし、技術は常に進化しており、ビジネスの要求も変わり続けます。過去に作成したデータ基盤が完璧であったとしても、時代の変化とともにそのアーキテクチャは必然的に変容していく必要があります。</p> <p><strong>私たちの目指すのは、ビジネスの変化に迅速に対応できる俊敏性と、同時に業務の邪魔をしない安定性を持ったデータ基盤です。これを実現するためには、継続的な学習と改善、新しい技術への適応が不可欠です。</strong></p> <p>今後も変化を恐れず、データ基盤の進化を続けていく所存です。変革の旅は終わりなく、常に次の一歩を模索し続けることが、私たちの持続的な成長の鍵となると考えています。</p> tikasan0804 サポーターズで1on1イベントをフルサイクル開発した話 hatenablog://entry/6801883189068845275 2023-12-22T16:00:00+09:00 2024-02-27T11:17:24+09:00 この記事は CARTA TECH BLOGアドベントカレンダー 12/22の記事です。 こんにちは、スプラトゥーンをする傍ら猫の下僕をしつつ、サポーターズというところエンジニアマネージャーをやっていますara_ta3です。 サポーターズ はCARTAの子会社で新卒採用支援を行っている会社です。 CARTAではフルサイクル開発を謳っており、サポーターズの開発チームでもその良さを感じながら実際にフルサイクル開発を行っています。 今回はその一例として、1on1イベントと呼ばれる、1日で最大8社の企業と面談できるイベントをここ1・2年でシステムに落とし込んで行った事例をお話できればと思います。 サポー… <p>この記事は <a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTA TECH BLOGアドベントカレンダー</a> 12/22の記事です。</p> <p>こんにちは、スプラトゥーンをする傍ら猫の下僕をしつつ、サポーターズというところエンジニアマネージャーをやっています<a href="https://twitter.com/ara_ta3">ara_ta3</a>です。  </p> <p><a href="https://techblog.cartaholdings.co.jp/supporterz">サポーターズ</a> はCARTAの子会社で新卒採用支援を行っている会社です。</p> <p>CARTAでは<a href="https://techblog.cartaholdings.co.jp/entry/carta-full-cycle">フルサイクル開発</a>を謳っており、サポーターズの開発チームでもその良さを感じながら実際にフルサイクル開発を行っています。</p> <p>今回はその一例として、1on1イベントと呼ばれる、1日で最大8社の企業と面談できるイベントをここ1・2年でシステムに落とし込んで行った事例をお話できればと思います。  </p> <h2 id="サポーターズ1on1イベント開発の背景">サポーターズ1on1イベント開発の背景</h2> <p>サポーターズでは主に学生の方、企業の人事担当者の方を対象とした自社開発のシステムを提供しつつも、<strong>1on1のイベントに関してはサイボウズさんのkintoneを駆使</strong>してこなしていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/arata3da4/20231222/20231222153148.png" width="1200" height="701" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>kintoneはいわゆるノーコードツールで、エンジニア以外の人でも比較的容易にプロトタイプを作って、業務に適用させることが出来るので非常に便利です。</p> <p><strong>なにかやってみたいことを小さく試しPDCAを回すという点で我々はkintoneを利用</strong>しており、今回テーマの1on1イベント以外でも非常に有意義に使わせていただいております。</p> <p>参考 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2F2020%2F04%2F01%2F135743" title="なぜオンライン移行に爆速対応できたのか?決まり手はkintone×Zoomにあった! サポーターズ、フルオンライン1on1面談イベントの裏側に迫る - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/2020/04/01/135743">techblog.cartaholdings.co.jp</a></cite></p> <p>しかし、<strong>kintoneもそれなりに構築に知識が必要となり、業務知識(ドメイン)が属人化し、一人に集中する状況</strong>になっていました。システムの改修を行うにも一人がコストを全て背負う形にもなっており、<strong>それ自体が事業リスク</strong>なのではないかという懸念もありました。</p> <p>また、3rd party pluginで提供しているような簡素なUIではなく、リッチなUIを提供したいというニーズもあったところから今回の話が始まります。</p> <hr /> <h2 id="提案フェーズ">提案フェーズ</h2> <h3 id="プロダクトオーナーと徹底的に議論する">プロダクトオーナーと徹底的に議論する</h3> <p>まず初めに、プロダクトオーナーから</p> <ul> <li>ドメイン知識の属人化のリスク</li> <li>改修コストを全て一人が背負うような状態</li> <li>リッチなUIがほしい</li> </ul> <p>と話があがります。</p> <p>ここで我々エンジニアチームは</p> <ul> <li>そもそもやる必要があるのか</li> <li>既存のプロトタイプでも十分な価値提供ができているのではないか</li> <li><strong>なぜこの開発を行う</strong>のか</li> </ul> <p>を理解するための<strong>議論を徹底的に行います。</strong></p> <h3 id="本質を追求しどんな価値を提供をするか言葉にする">本質を追求し、どんな価値を提供をするか言葉にする</h3> <p>これがなぜかと言うと、CARTA Tech Visionに「本質志向」があり、本質を追求し<strong>課題を解決した先にどういう価値を提供できるのかを判断することが大事</strong>だと考えているからです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Ftech-vision" title="CARTA Tech Vision - CARTA TECH BLOG" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/tech-vision">techblog.cartaholdings.co.jp</a></cite></p> <p>なので、ビジネスに詳しい人がやりたいと言ったから開発を行うのではなく、<strong>エンジニアそれぞれが「なぜやるべきなのか」「解くべき課題とそれを解決した先の価値はなんなのか」を理解した上で開発を行います。</strong></p> <p>議論の結果、以下の点から開発は行ったほうがいいだろうという話にまとまりました。</p> <ul> <li>ドメイン知識の属人化に関して <ul> <li>ドメイン知識をコードに落とし込むことで、コードと人が行うオペレーション操作が一致している状況を作り、システムの理解がある人を増やせるだろうという点。</li> </ul> </li> <li>UIに関して <ul> <li>既存のシステムでそれなりのUIを提供しているので、そこに乗せていけば思った通りの効果は期待できるだろうという点。</li> </ul> </li> </ul> <h4 id="恐れを生まず本質的な議論のためにお互いにリスペクトを持つ">恐れを生まず、本質的な議論のためにお互いにリスペクトを持つ</h4> <p>少し話がそれますが、この時、エンジニアがただただ「なんでそんなことやらなきゃいけないの?」と雑なコミュニケーションを取ってしまうと、ビジネスサイドを怖がらせてしまいます。</p> <p>例えば、エンジニアの人々は怖いと無駄に恐れさせてしまったり、エンジニアは何を言ってもやってくれないから話しても無駄だというように感じさせてしまう可能性があります。</p> <p>なので、<strong>コミュニケーションは互いにリスペクトを持った形でコミュニケーションを取る必要があることは念頭に置いておきたい</strong>です。</p> <hr /> <h2 id="実装フェーズ">実装フェーズ</h2> <p>やると決まったなら顧客の本当に欲しかったものをしっかりと定義します。</p> <p><strong>大事なのは、今の運用をすべて捨てたとしたときにどういう形が最も良いのか(理想)を考え、現在地点とその理想の点を結んだ間にあるものを実装していくこと</strong>だと私は考えています。</p> <h3 id="理想を描いてまっすぐに向かう">理想を描いて、まっすぐに向かう</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/arata3da4/20231222/20231222154107.png" width="1200" height="552" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>今回の<strong>サポーターズの1on1での理想形としてはkintoneをやめ、サポーターズのシステムを使えば学生・企業、サポーターズ内の運用者全てが1on1面談イベントを行える状態</strong>でした。</p> <p>元々の運用がkintoneの仕組みに乗っかっていたので一度kintoneから離れ、プロダクトとして本当に欲しいものを精査して作って行く必要があります。</p> <p>というのも、<strong>なぜいまある運用の在り方を辿ったとき、システムや過去の経緯などの制約に依るもので、実は必要が無かったりもっとシンプルなやり方が隠れていたりします。</strong></p> <p>例えば</p> <ul> <li>1on1面談イベント当日の面談を行う部分に関する機能</li> <li>イベント当日より前の学生による申し込みから参加に至るまでの機能</li> <li>企業が対象学生のプロフィールを閲覧する機能</li> </ul> <p>だったり様々なものが含まれていました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/arata3da4/20231222/20231222154116.png" width="1200" height="677" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>次に、<strong>理想の地点が見えたなら現実的な実装</strong>を考えていきます。</p> <p><strong>価値の提供を行うには全てが揃っている必要はなく、一部の機能だけでも価値の提供が出来るはず</strong>です。</p> <p>ただし関わるユーザ、ここでは学生や企業、さらにはサポーターズ内部の運用者のことも考えるべきです。ユーザへの価値、運用者の負担、実装にかかる時間などを考えて価値提供が出来る部分を模索していきます。</p> <p>実際のこのときは、2022/3月頃に始まるインターン期に一部機能をリリースしようと初め考えていました。</p> <p>※サポーターズの1on1面談イベントは時期が2つあり、インターン期(3月〜)と本選考期(9月〜)があります。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/arata3da4/20231222/20231222154130.png" width="1200" height="673" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>しかし、その実装はkintoneの機能を一部残し、その部分をユーザに使ってもらうという前提のもとでした。</p> <p>その場合、kintone以外の部分で一定の価値提供が出来るとは言いつつも、複数のシステム(kintoneとサポーターズのシステム)を利用して貰う必要があり、ユーザの手間が増えてしまい総じてプラスの価値提供ができてるのか怪しいと考え、9月の本選考期まで機能提供を見送りました。</p> <hr /> <h2 id="運用フェーズ--FBフェーズ">運用フェーズ  / FBフェーズ</h2> <p>実際に実装が進んだ先ではサポーターズ内部でフィードバックサイクルを早くするべく定期的に触ってもらって目線を合わせていきました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/a/arata3da4/20231222/20231222154152.png" width="1200" height="689" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="実際の運用者に使ってもらいフィードバックを得る">実際の運用者に使ってもらい、フィードバックを得る</h3> <p>具体的には、<strong>実戦投入までに細かくリリースを行い、実際に使ってもらうことを繰り返しました。</strong></p> <p>仕様策定のために話してるだけだと重要なことを引き出しきれないことがあります。なので、<strong>実際に使ってもらい困ってもらうことで「これも必要なんです!」と声を上げてもらいました。</strong></p> <p>最終的には実戦投入の1週間前ほどのタイミングで運用者とエンジニアでダミーの1on1イベントを実施し、最終チェックを行いました。</p> <p>個々まで細かくやっていても気づけなかったエラーケースに遭遇し、本番に発覚して困ったりはしたのですが...イベント自体は成立してよかったです。</p> <h2 id="まとめ">まとめ</h2> <p>以上がフルサイクル開発で1on1イベントを実装していった際のサポーターズ内での事例紹介でした。</p> <p>個人的にはフェーズの前半が大事だと思っています。</p> <p>「なぜやるのか」「ビジネス的にどうしていきたいのか」をいわゆるプロダクトオーナーと徹底的に議論し、<strong>自らの言葉で語れるまで開発を行うエンジニアが理解し開発を進めていくことでより良いプロダクトが作れるだろう</strong>と思っています。</p> <p>これからもサポーターズは改善していきたい部分がたくさんありますし、していきたいにとどまらず改善していくので応援よろしくお願いします。</p> arata3da4 CARTAとフルサイクル開発者 hatenablog://entry/6801883189068132220 2023-12-22T14:14:57+09:00 2024-02-27T11:23:11+09:00 こんにちは!CTOのsuzukenです。 CARTAの開発スタイルは「フルサイクル開発」として説明しています。 これについてまとめておきます。 「フルサイクル開発」とは 「フルサイクル開発」という言葉は、2018年に公開されたNetflixのブログ記事にある「Full Cycle Developers(フルサイクル開発者)」に由来しています。 Full Cycle Developers at Netflix — Operate What You Build 要約すると、以下のように示されています。 「開発したものが運用する」のがフルサイクル開発者。責任を外部化せず、直接のフィードバックループを… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231221/20231221210218.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!CTOのsuzukenです。</p> <p>CARTAの開発スタイルは「フルサイクル開発」として説明しています。 これについてまとめておきます。</p> <h2 id="フルサイクル開発とは">「フルサイクル開発」とは<a id="フルサイクル開発とは"></a></h2> <p>「フルサイクル開発」という言葉は、2018年に公開されたNetflixのブログ記事にある「Full Cycle Developers(フルサイクル開発者)」に由来しています。</p> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231117/20231117153034.png" width="559" height="488" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span></center> <p><a href="https://netflixtechblog.com/full-cycle-developers-at-netflix-a08c31f83249">Full Cycle Developers at Netflix — Operate What You Build</a> </p> <p>要約すると、以下のように示されています。</p> <ul> <li><p>「開発したものが運用する」のがフルサイクル開発者。責任を外部化せず、直接のフィードバックループを開発チームに内在させる。ソフトウェア開発者が設計実装のみならず、サポート、デプロイなどのイテレーションをすべて自分でこなしている。</p></li> <li><p>運用のペインは開発者自ら解消する。</p></li> <li>ソフトウェア開発者はツールのちからにより、ライフサイクルを最適化させられている。</li> <li>ライフサイクルを最適化することにより、サイロを解消し、学習とフィードバックを最適化して、価値のデリバリー速度を高めるのがフルサイクル開発である。</li> </ul> <p>当初この記事がでたとき、fluct(CARTAの事業子会社の一つ)の有村さんが日本語訳をしてCARTA TECH BLOGに投稿してくれました。</p> <p><a href="https://techblog.cartaholdings.co.jp/entry/2019/02/04/171325">Netflixにおけるフルサイクル開発者―開発したものが運用する - CARTA TECH BLOG</a> </p> <p>これが社外にCARTA(当時VOYAGE GROUP)はフルサイクル開発を志向していると認識されるきっかけとなったように思います。</p> <p>この記事を多くのCARTAエンジニアが見た当初、</p> <blockquote><p>「ああ、自分たちの仕事を表しているなあ」</p></blockquote> <p>と感じたのでした。</p> <hr /> <h2 id="CARTAもフルサイクル開発の実践者である">CARTAもフルサイクル開発の実践者である<a id="cartaもフルサイクル開発の実践者である"></a></h2> <p>CARTAに浸透しているフルサイクル開発の在り方は、Netflixの「Full Cycle Developers」を参考にしつつ、独自のエッセンスを加えたものです。</p> <p>世界的企業であるNetflixとCARTAが同じプラクティスを実践しても、どこまで行っても同じものにはなりません。CARTAもフルサイクル開発の実践者であると言えるでしょう。</p> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231117/20231117145106.png" width="1114" height="1106" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span></center> <p><strong>フルサイクル開発者は、コードを書くこと(開発)だけでなく顧客への価値提供が仕事です。</strong>CARTAでは論理的に考え、あるべき仕組みを作れることがエンジニアの振る舞いとして求められています。</p> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231221/20231221143216.png" width="1200" height="651" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span></center> <p>たとえば、次の役割を担います。</p> <ul> <li>ビジネス的な要件を整理する。営業から依頼されているものは、実は本当に顧客が求めているものではないかもしれない。「なぜ」を明らかにし、どう進めるかを判断する。</li> <li>テストをし、自らリリースする。自分のパッチは自分の手でリリースする。モニタリング基盤を通じてプロダクトKPIへの影響を確認する。</li> <li>そしてつくったソフトウェア・プロダクトを自ら運用し、改善しつづける。</li> </ul> <p>フロントエンド、バックエンド、クラウド等、<strong>役割に囚われず、また場所や範囲を問わずに自ら手をいれて変えられることを善しとしています。</strong></p> <h2 id="経験値の最大化を重視する">経験値の最大化を重視する<a id="経験値の最大化を重視する"></a></h2> <p><strong>CARTAの開発の現場では複数のプログラミング言語や環境のあるプロダクトがほとんど</strong> です(TypeScript, Go, PHP, Kotlin, Pythonなど)。</p> <p>ただし全てに習熟している必要はありません。チームでお互いに高めあい、支援しあいながら開発を進めています。</p> <blockquote><p>言語が複数あることが採用難易度をあげ、オンボーディングや<strong>育成上の問題</strong>にならないのか</p></blockquote> <p>と聞かれることもあります。</p> <p>私たちは、「基礎のあるエンジニアなら、新しい技術を学ぶことは問題にならない」と考えています。むしろ新たな仕組みを学ぶことは楽しく、刺激的なことであり、<a href="https://speakerdeck.com/twada/understanding-the-spiral-of-technologies-2023-edition">技術選定の審美眼</a>を養う上で欠かせない営みだと考えています。</p> <p>新たなビジネス的要望のため、未知の言語や仕組みに関する<strong>学習は主要な仕事の一部</strong>です。こうした環境に身をおいていると、<strong>新しいものを学ぶことが日常になり、チーム及び個人としてのキャッチアップする力が向上していきます。</strong></p> <blockquote><p>新しいものを覚えるのが億劫になってしまうより、それが当たり前になっているチームのほうがいい</p> <p><a href="https://www.lambdanote.com/products/carta">「事業を開発する技術者たち」</a> 第2章Zucks p.62 より</p></blockquote> <p><strong>フルサイクル開発者は経験値の最大化を重視します。</strong>わたしたちの事業においては<strong>経験を増やすことが事業の成功にとって重要です。</strong></p> <p>なぜなら環境は変化し、技術は進歩するものであり、顧客の求めるものも常に要望のレベルが高くなるからです。<strong>事業モデルとしても変化に追随し、それにともなってプロダクトもなめらかに変わり続ける。これがCARTAにおける理想的なプロダクト開発です。</strong></p> <hr /> <h2 id="戻れる決断戻れない決断">戻れる決断、戻れない決断<a id="戻れる決断戻れない決断"></a></h2> <p><strong>プロダクトを開発していると、多くの場面で決断する必要があります。</strong></p> <ul> <li>「この決済機能はいまつくるべきか」</li> <li>「リファクタリングを先にしておくべきか、あとにすべきか」</li> <li>「どのように新しい配信モジュールを日本向けに追加すべきか」</li> </ul> <p>さまざまな問いを立て、決定し続けるのが開発の現場です。</p> <p><strong>プロダクト開発において重要なのは、「戻れる決断」を素早く重ね、小さく速く失敗し、成功に近づくことです。</strong>例えばデータを消してしまっては元に戻せないかもしれませんが、データを別の場所に移せば状態をもとに戻すことができます。「元に戻せる」のは強力な選択肢で、意思決定を速やかにするメリットがあります。個人や組織において<strong>小さく挑戦し、失敗から学ぶことを重視すれば経験を最大化できます。</strong></p> <p> Amazonでは「戻れる決断」のことをTwo-way doors(双方向に行き来できるドア)と呼んでいます。</p> <p>参考:<a href="https://aws.amazon.com/executive-insights/content/how-amazon-defines-and-operationalizes-a-day-1-culture/?nc1=h_ls">Elements of Amazon’s Day 1 Culture | AWS Executive Insights</a> </p> <blockquote><p>Amazon で高品質で高速な意思決定を支援するために使用しているもう 1 つのツールは、一方向と双方向と呼ばれるメンタルモデルです。一方向の意思決定は、重大でしばしば取り返しのつかない結果をもたらす決定です。フルフィルメントまたはデータセンターの構築は、多くの設備投資、計画、リソースを必要とする決定であるため、注意深い分析が必要となります。一方、双方向の意思決定は、限定的で可逆的な結果をもたらすものです。サイトの詳細ページまたはモバイルアプリで機能の A/B テストを実施することは、可逆的な決定において基本的だがエレガントな例です。</p></blockquote> <p>では、<strong>逆に「戻れない決断」とはなんでしょうか?</strong></p> <p><strong>元に戻すコストが高いものは「戻れない決断」と考えるとよいでしょう。</strong></p> <p>例えばデータベースの選定やモノリス or マイクロサービスの選定、採用が含まれます。もちろん長い時間軸で考えれば元に戻れますが、より慎重に意思決定をするのが合理的です。</p> <p>一度決めた技術選定は、開発チームの文化にも影響を与えます。早い段階であれば元に戻すコストは低いですが、時間が経つにつれて戻すことが難しくなっていきます。</p> <p>人の採用などは「戻れない決断」になりやすいです。一度雇用すると元に戻すコストは高くつきます。「戻れる決断」か「戻れない決断」かは環境、チーム、国における法律などによって変わってきます。</p> <p>1つ言えることは、その<strong>意思決定について「元に戻せる」環境をつくることができれば、より早く意思決定ができる</strong>ということです。「元に戻せる」なら、挑戦とそれに伴う失敗もしやすくなります。</p> <h3 id="元に戻せる環境が優れたフルサイクル開発を実現する素地になる">「元に戻せる」環境が優れたフルサイクル開発を実現する素地になる<a id="元に戻せる環境が優れたフルサイクル開発を実現する素地になる"></a></h3> <p><strong>フルサイクル開発者を支える技術として、クラウド、ビルド・デプロイに関するツールやサービス、エディタによるコーディング補助などがあります。</strong></p> <p>また、CARTAで実施しているプラクティスには以下のものがあります。</p> <ul> <li>リリースおよび修復の高速化</li> <li>リバート可能なリリース、カナリアリリースで戻すことのコストを下げる</li> <li>オブザーバビリティへの投資: 何かあったらすぐ気づけるようにする</li> </ul> <p>多くの場合はクラウドに標準で搭載されたり、ベンダーがサービスとして提供されているものを活用しています。たとえばNewRelic / Datadog / SplunkなどのAPMツールをつかえば監視が容易になります。これらは事業部によっては専任のチームが提供することもあります。</p> <p>GitHub Actionはほぼすべてのチームがワークフローの自動化のために採用しています。自前でこれらを用意するのはとても手間がかかります。</p> <p><strong>優れたフルサイクルを実現するチームは高速に実験を積み重ね、多くの経験を得られるようにする。これを支えるのが「元に戻せる」環境づくりです。</strong></p> <h3 id="状況判断力を向上させるには何度もループを回す必要がある">状況判断力を向上させるには、何度もループを回す必要がある。<a id="状況判断力を向上させるには何度もループを回す必要がある"></a></h3> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231117/20231117153221.png" width="1200" height="491" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span></center> <p>図: OODAループ (<a href="https://ja.wikipedia.org/wiki/OODA%E3%83%AB%E3%83%BC%E3%83%97">https://ja.wikipedia.org/wiki/OODA%E3%83%AB%E3%83%BC%E3%83%97</a> )</p> <p>OODAは、「観察・指向・決定・行動」の4つのフェーズからなる意思決定フレームワークです。</p> <p>図は意思決定のメカニズムを示しています。左から観察、状況判断、意志決定、行動と並んでいます。経過観察をしたり、行動からフィードバックを得てさらに観察を重ねる等の構造がループとして書かれています。</p> <p><strong>この図において重要なのは状況判断です。</strong>状況判断は「今何をすべきか」を分析し、過去の傾向と照らし合わせ、新しい情報を組み合わせることで判断を組み上げます。<strong>経験のない人には状況判断ができません。状況判断力を増やすには、何度もループを回す必要があります。</strong></p> <p>行動を増やすには状況判断力を高める必要があります。熟練したエンジニアは観察から行動までが短時間で実現できます。熟練したエンジニアはそれに至るまでに数多くの経験を積んでいます。</p> <p><strong>経験を積むには「元に戻せる」環境によって支援された早い意思決定を積み重ね、実際に自分で考えた機能を多く作るとよいでしょう。</strong></p> <p>ここまでみてきたように、<strong>フルサイクル開発はツール及び環境に支援され、意思決定を早め、試行と失敗の回数を増やすことで経験の最大化を重視しています。</strong>全員がフルサイクル開発者であるチームは、新しく入ったチームメンバーの経験獲得を支援します。チームとして安全な挑戦が行えるようになっていると、そのチームでは人の成長速度が上がります。</p> <h2 id="組織を越えて知見を共有することで共に創る">組織を越えて知見を共有することで、共に創る<a id="組織を越えて知見を共有することで共に創る"></a></h2> <p>CARTAにはフルサイクル開発者によって構成されたチームが複数あります。事業の数だけ、自律し、プロダクトにオーナーシップを持ち、事業を開発する技術者たちがいます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fcarta_award_2022_best_engineer" title=" プロダクトは継続的に改善するもの。それができてはじめて &quot;CARTA っぽい&quot; 【ベストエンジニアインタビュー】 - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/carta_award_2022_best_engineer">techblog.cartaholdings.co.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftechblog.cartaholdings.co.jp%2Fentry%2Fcto-interview9" title="日本最大級SSP fluctが誇る会計システム「backbone」 開発の現場 | CTOが聞く!Vol.9 fluct なっかー - CARTA TECH BLOG" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://techblog.cartaholdings.co.jp/entry/cto-interview9">techblog.cartaholdings.co.jp</a></cite></p> <p>社内では毎日、多くのツールやサービスをつかった実験が繰り返されています。モニタリングツール、テストツール、エディタの機能、デプロイのプラクティスに至るまで、各チームが自律的に実験を繰り返しています。</p> <p>これらの実験と多くの失敗は、以下の環境を通じて個人や事業部を越えて共有されます。</p> <ul> <li>Slackやオフィスでの雑談の場で</li> <li>社内勉強会(dev_urandom day, Tech Garden..等)</li> <li>社内ブログ</li> <li>GitHub</li> <li><a href="https://cartaholdings.co.jp/engineering/tech-assessment/">技術力評価会</a></li> </ul> <p>社内における制度や雑談を通じて、個々人や事業部の挑戦と学び(実験)について参照できます。社内のエンジニアは過去すべての実験の情報を検索し、学ぶことができます。</p> <p>他のプロダクト開発チームから知識を移し、それを足場に新たな挑戦をスタートすることができます。</p> <p>事業によってフェーズも異なります。20年もののメディア事業もあれば、まだ立ち上げて1年ほどの初期フェーズにあるプロダクトもあります。</p> <center><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231117/20231117153244.png" width="1200" height="678" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span></center> <p>事業<strong>フェーズが異なれば、プロダクトのサイズ・複雑性もまた異なります。</strong>まだPMF(Product Market Fit)が済んでいないサービスもあれば、安定して収益をあげているサービスもあります。</p> <p>先に実験をし、経験を積んだ事業やチームから、新しいサービスを開発するメンバーに経験が伝達される。こうして<strong>開発組織のフルサイクル開発者は、状況判断のための知識を異なるチームから得ることができます。</strong></p> <h2 id="まとめ-実行に価値がある">まとめ: 実行に価値がある<a id="まとめ-実行に価値がある"></a></h2> <p>いくら考えても、<strong>ある程度まで考えたら、あとはやってみなければわかりません。</strong></p> <p><strong>考えて、考えて、手を動かせないのでは結局経験も増えず、新たな観察も生まれず、何も進みません。方法はいろいろあれど、ある程度考えたならやってみるしかない。</strong>「投資しない」という判断はあり得えますが、「何にも投資しない」という判断はありません。別の機能やコンポーネントを改善したり、事業展開より先回りしてリアーキテクチャする等、なにをすべきかの判断は常になされるべきです。</p> <p><strong>一番よくないのは、考えて、何もしないことです。</strong></p> <p>チームで行動していると、他者の振る舞いを見て個人としては「それはうまい打ち手ではないなあ」と思うこともあるでしょう。けれど、やると決めたならやる。やってみれば自分も経験を得て、次の状況判断では次のレベルで見ることが可能になっているでしょう。</p> <p><strong>決めたらやる。それによって、あなただけではなく、チーム全体が経験を最大化できることにつながります。</strong></p> suzu_v Webアプリの技術的負債を少しずつ減らす取り組み hatenablog://entry/6801883189068146231 2023-12-21T11:18:49+09:00 2024-02-27T11:15:16+09:00 こんにちは、CCIのくっちーです。 広告案件管理システムのWebアプリの開発、運用を担当していました。 (現在は別プロダクトを担当しています。) 今回は設計を見直して、技術的負債を減らす取り組みについて書いていきます。 この記事はCARTA TECH BLOGアドベントカレンダー 12/18の記事です。 背景 担当システムではコードのレガシー化が進んでおり、技術的負債をどのように減らすかという課題に直面していました。ソースの変更が少なければ問題無いですが、大幅改修依頼があったため改善が必要でした。 私は改修機能を担当していたため、設計、実装を行うことになりました。 改修内容 ユーザ同士がやり取… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231221/20231221100732.png" width="1200" height="668" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、CCIのくっちーです。 広告案件管理システムのWebアプリの開発、運用を担当していました。 (現在は別プロダクトを担当しています。)</p> <p>今回は設計を見直して、技術的負債を減らす取り組みについて書いていきます。 この記事はCARTA TECH BLOGアドベントカレンダー 12/18の記事です。</p> <h2 id="背景">背景</h2> <p>担当システムではコードのレガシー化が進んでおり、技術的負債をどのように減らすかという課題に直面していました。ソースの変更が少なければ問題無いですが、大幅改修依頼があったため改善が必要でした。</p> <p>私は改修機能を担当していたため、設計、実装を行うことになりました。</p> <h3 id="改修内容">改修内容</h3> <p>ユーザ同士がやり取りするメッセージ機能を改修しました。</p> <p>以下のユーザー要望を実現することが目的です。</p> <ul> <li>複数メッセージを同時に確認できる画面が欲しい <ul> <li>既存では1メッセージしか確認できないため、新規画面が必要</li> </ul> </li> <li>メッセージ機能を1対多ではなく、1対1でやりとりできるようにしたい <ul> <li>既存では1対多でのやりとりのみ可能</li> </ul> </li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kuchiishi-a/20231220/20231220143806.png" width="956" height="662" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="進め方">進め方</h2> <p>システム全体に影響する課題のため事業部外の知見を得たいと思い、CTO相談室にアドバイザリを依頼しました。</p> <p>今回は、少しずつ再設計を進めます。</p> <p>既存のアーキテクチャは既存設計と新設計が共存する構造になっています。<br/> まずはメッセージ機能から再設計を行い、それ以外の機能は順次対応するようにしました。</p> <h3 id="現状の把握">現状の把握</h3> <p>まずはレガシー化している原因を調査しました。</p> <h4 id="メソッドの依存関係が多く修正時に影響が大きくなる">メソッドの依存関係が多く、修正時に影響が大きくなる</h4> <p>共通処理以外のメソッドが複数箇所から呼び出されていました。<br/> 例えばAメソッドを修正した場合、呼び出している5箇所全てに動作確認が必要になることもあります。<br/> また依存関係が多いと単体テストが書きにくいため、既存実装では単体テストの作成が難しくなっていました。</p> <h4 id="権限処理が散らばっている">権限処理が散らばっている</h4> <p>権限処理がフロントエンド、バックエンド両方に分散して書かれている状況でした。 <br/> 処理分岐、ループ処理が多くなり複雑で認知負荷が高い実装になっていました。</p> <h2 id="再設計について">再設計について</h2> <p>チームでルールを決めて進めていきました。<br/> ルールだけだと共通認識を持つことが難しかったので、 CTO室にソースレビューを依頼しました。設計方針に沿った実装になっているか見て頂きました。</p> <h3 id="1権限ごとにエンドポイントを分ける">1.権限ごとにエンドポイントを分ける</h3> <p>クライアント、バックエンド両方でエンドポイントを分けるようにしました。</p> <p><strong>before: 全権限の処理が1つにまとまっているので処理が複雑になりやすい</strong></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kuchiishi-a/20231220/20231220183543.png" width="960" height="138" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>after: 権限ごとにエンドポイントを分けると、分岐処理が減るのでシンプルになる</strong></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kuchiishi-a/20231220/20231220183623.png" width="960" height="254" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="2バリデーションと権限制御を切り出す">2.バリデーションと権限制御を切り出す</h3> <p>今回はバックエンドで権限制御を行うので、 httpリクエストを受け付けた時にバリデーションと権限制御を行うようにします。</p> <p><strong>before: 認可処理とvalidationが分散しており、ビジネスロジックに絡まっておりテスタブルでない</strong></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kuchiishi-a/20231220/20231220183648.png" width="960" height="172" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><strong>after : 認可処理とvalidationをまとめ、ビジネスロジックと分けたことで個々がテスタブルになった</strong></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kuchiishi-a/20231220/20231220183716.png" width="901" height="332" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="3責務を明確にする">3.責務を明確にする</h3> <p>各レイヤーごとの責務を明確にしました。</p> <p><strong>before: 明確に決まっていなかった</strong></p> <p>部品によって各レイヤーの責務が異なっていました。</p> <p><strong>after: チームで責務を統一</strong></p> <p>各レイヤーの責務は以下になります。</p> <h5 id="バックエンド">バックエンド</h5> <table> <thead> <tr> <th> 各レイヤー </th> <th> 責務 </th> </tr> </thead> <tbody> <tr> <td> バリデーション </td> <td> 必須チェック、入力項目のチェック等を行う </td> </tr> <tr> <td> 認可処理 </td> <td> 権限制御を行う </td> </tr> <tr> <td> Controller層 </td> <td> httpトランザクションのみ行う </td> </tr> <tr> <td> Converter層 </td> <td> Service層から受け取った値を、レスポンスの形式に成形する処理 </td> </tr> <tr> <td> Service層 </td> <td> ビジネスロジック </td> </tr> <tr> <td> Model層 </td> <td> DBのCRUD処理を行う </td> </tr> </tbody> </table> <h5 id="フロントエンド">フロントエンド</h5> <p>再設計した機能はAPIで取得した値を表示するシンプルな機能だったので、<br/> 責務は画面描画のみで、ロジック、データ整形は行わないようにしました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kuchiishi-a/20231220/20231220183749.png" width="960" height="335" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kuchiishi-a/20231220/20231220183820.png" width="960" height="287" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="よかったこと">よかったこと</h2> <p>複数の課題を解決することが出来ました。</p> <ul> <li>条件分岐やループ処理が大幅に減ったため、可読性が向上</li> <li>テスタブルな実装になり、単体テストが書きやすくなった</li> <li>今後の改修コストの削減</li> <li>手動テストの工数を削減 <ul> <li>修正時の影響範囲を小さくすることが出来た</li> </ul> </li> </ul> <h2 id="課題">課題</h2> <ul> <li>クライアントの権限制御 <ul> <li>画面のパスではなく、権限制御の共通ロジックにまとめた方がよいかも</li> </ul> </li> <li>バックエンドのService層の責務が大きすぎる <ul> <li>責務を細かく分けた方が良さそう</li> </ul> </li> <li>複数の設計が共存しているので、新規参画者は把握しづらい</li> </ul> <h2 id="感想">感想</h2> <p>CARTAは横のつながりがあるので、有識者と気軽に相談出来るのでありがたいです。<br/> これからもレガシーコードを改善する機会はあると思うので、積極的に改善していきます。</p> kuchiishi-a 「ぐり」か「おぐりもん」かクイズ hatenablog://entry/6801883189067333563 2023-12-21T11:00:00+09:00 2024-02-27T11:26:30+09:00 こんにちは!株式会社DIGITALIOでエンジニアをしているぐりです。 この記事はCARTAエンジニアアドベントカレンダー12/21の記事です。 突然ですが、みなさんにクイズです! これから再生される音声は「ぐり」でしょうか? それともぐりから生成された合成音声「おぐりもん」でしょうか? クイズは全5問です。目指せ全問正解! ※音声が再生されます。ご注意ください。 第1問 第2問 第3問 第4問 第5問 いかがでしたでしょうか?正解は以下の通りです。 正解はこちら(展開されます) 第1問 ぐり 第2問 おぐりもん 第3問 おぐりもん 第4問 ぐり 第5問 おぐりもん 簡単でしたか?思ったより難… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/guri3/20231216/20231216224904.png" width="1196" height="627" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!株式会社DIGITALIOでエンジニアをしているぐりです。<br/> この記事は<a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTAエンジニアアドベントカレンダー</a>12/21の記事です。</p> <p>突然ですが、みなさんにクイズです!<br/> これから再生される音声は「ぐり」でしょうか? それともぐりから生成された合成音声「おぐりもん」でしょうか?<br/> クイズは全5問です。目指せ全問正解!<br/> ※音声が再生されます。ご注意ください。</p> <p>第1問</p> <p><audio controls="controls" src="https://drive.google.com/uc?id=1Szvi9uS3RVh58F0Ku828pnpfUifLrdHx"></audio></p> <p>第2問</p> <p><audio controls="controls" src="https://drive.google.com/uc?id=1zb0Lgmp8DZ5dmh3NDpgxiOt9Aq_MY4-d"></audio></p> <p>第3問</p> <p><audio controls="controls" src="https://drive.google.com/uc?id=1rGCqkxAGaZTFWSBdza3p3_T5uCRf39nk"></audio></p> <p>第4問</p> <p><audio controls="controls" src="https://drive.google.com/uc?id=1T5T292Lf9GYajclI2jnkc6efhbMaUM3r"></audio></p> <p>第5問</p> <p><audio controls="controls" src="https://drive.google.com/uc?id=1y_WSO8jfPnpi70P74jtbYRZX79Fy9UCd"></audio></p> <p>いかがでしたでしょうか?正解は以下の通りです。</p> <div onclick="obj=document.getElementById('oritatami_part').style; obj.display=(obj.display=='none')?'block':'none';"> <a style="cursor:pointer;">正解はこちら(展開されます)</a> </div> <div id="oritatami_part" style="display:none;clear:both;"> 第1問 ぐり</br> 第2問 おぐりもん</br> 第3問 おぐりもん</br> 第4問 ぐり</br> 第5問 おぐりもん </div> <p>簡単でしたか?思ったより難しかったでしょうか?<br/> 実は、今回作成した「おぐりもん」は結構簡単に作ることができます。</p> <h2 id="作成方法">作成方法</h2> <p>自分の声を利用した合成音声の作成には、<a href="https://coeiroink.com/mycoeiroink">MYCOEIROINC</a>というものが利用できます。<br/> MYCOEIROINCは無料AIトークソフトCOEIROINK向けに自作の合成音声を作成できるものです。<br/> <a href="https://coeiroink.com/mycoeiroink/making">作る</a>というページが用意されているので、基本的にはこちらのページに記載されている手順で作成を行っていくことになります。</p> <h3 id="大まかな作成手順">大まかな作成手順</h3> <ol> <li>学習用の音声収録を行う</li> <li>Google colabで公開されている手順の通りに合成音声の学習を行う</li> <li>COEIROINCのインストールを行い、作成したMYCOEIROINCのインポートを行う</li> </ol> <p>それぞれの手順について解説をします。</p> <h3 id="学習用の音声収録を行う">学習用の音声収録を行う</h3> <p>合成音声を作成するには、学習用の自身の声を収録する必要があります。<br/> 収録の台本には、パブリックドメインで公開されている文章コーパスである<a href="https://github.com/mmorise/ita-corpus">ITAコーパス</a>や<a href="https://github.com/shirowanisan/coeiroink-corpus-manager/tree/main/mana-corpus">MANAコーパス</a>を用います。<br/> ITAコーパスやMANAコーパスには日本語のいろいろな音がバランスよく含まれているため、合成音声の作成に適した音声を収録することが出来ます。<br/> それぞれのコーパスに特徴があり、用途に合わせて自身が必要な量の文章を収録して利用することができます。<br/> 今回は、ITAコーパスの<a href="https://github.com/mmorise/ita-corpus/blob/main/emotion_%E6%9C%97%E8%AA%AD%E8%80%85%E7%94%A8.pdf">Emotion(100文)</a>を収録して学習に利用しました。<br/> 合成音声のクオリティを高めたい場合には、ITAコーパスのRecitation(324文)やMANAコーパスも合わせて収録を行うと良さそうです。</p> <p>収録方法に関しては、<a href="https://hasewoalice.wixsite.com/griffins-nest/mycoe">MYCOEIROINKの作り方 | 有響シロ 公式サイト</a>を参考にさせて頂きました。<br/> 収録用ソフトOREMOのインストール・設定方法や収録の注意点まで記載されているためとても参考になりました。<br/> 私が作成したときには、サンプリング周波数を44.1kHzに変更(他に音声の利用予定がある場合は48kHzで取っておくとよい)くらいの変更のみで、あとは記載の流れでスムーズに収録を行うことが出来ました。</p> <h3 id="Google-colabで公開されている手順の通りに合成音声の学習を行う">Google colabで公開されている手順の通りに合成音声の学習を行う</h3> <p>収録した音声を使って合成音声モデルの学習を行います。<br/> とはいっても、MYCOEIROINKの公式ページに貼られているGoogle colabの手順に従って作業を行うだけです。 きちんと利用規約を読み込んだら、あとはGoogle colabを上から実行していけばOKです。</p> <p>注意する必要があるポイントとしては、Google colabの無料枠で学習を行おうとする場合は制限により手間や時間が掛かってしまう場合があります。<br/> 私は1ヵ月分の課金を行い利用しました。課金を行うことで100コンピューティングリソースが与えられ、強いGPUで学習を進めることが出来ます。<br/> 今回はV100 GPUを用いて、良い品質になるといわれている50epoch(1から100epochまでの範囲で学習を進めることが出来ます)まで学習を行いました。<br/> 50epochまでの学習にざっくり8時間ほど掛かり、学習を終えた時点で23.9コンピューティングリソースが残っていたので、76.1消費したことになります。</p> <h3 id="COEIROINCのインストールを行い作成したMYCOEIROINCのインポートを行う">COEIROINCのインストールを行い、作成したMYCOEIROINCのインポートを行う</h3> <p>ここも基本的にはGoogle colabに記載の手順に従ってCOEIROINCにインポートを行うのみです。 後は好きに文章を入力してしゃべらせることが出来ます。</p> <h2 id="まとめ">まとめ</h2> <p>というわけで、自分の声から合成音声を作ってみた!という記事でした。 最近流行りの生成AIなどと組み合わせれば、もはや自分の代わりにミーティングに出席してもらおう、みたいな使い方も妄想できますね。</p> <h2 id="クレジット">クレジット</h2> <p>無料AIトークソフトCOEIROINK: <a href="https://coeiroink.com">https://coeiroink.com</a></p> guri3 【23新卒エンジニア】みんなで半期を振り返るajiting hatenablog://entry/6801883189068384206 2023-12-20T18:37:03+09:00 2023-12-20T18:37:03+09:00 技術広報しゅーぞーです。 今日は23卒の新卒エンジニアを集めて、「半期を振り返ってみよう」ajitingをやっています。 みんなこの半年で悔しかったことはある? 仕事を忙しくしていると、なかなか気が向かない振り返り。 CARTAでは、新卒エンジニアを半期ごとに集め「それぞれ振り返りをやってみよう」と時間を取っています。 みんなこの半年で悔しかったことはある? CTOからの一言でみんな胸に手を当てて「自分はどうだっかな?」と確かめていました。 流れ まずは個々人で振り返りワーク それぞれが半期でやった仕事を思い出し、それをメモに書いていきます。 黙々と作業。 次に同じテーブルでシェア 個々人で振… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231220/20231220183023.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>技術広報しゅーぞーです。 今日は23卒の新卒エンジニアを集めて、「半期を振り返ってみよう」ajitingをやっています。</p> <h2 id="みんなこの半年で悔しかったことはある">みんなこの半年で悔しかったことはある?</h2> <p>仕事を忙しくしていると、なかなか気が向かない振り返り。 CARTAでは、新卒エンジニアを半期ごとに集め「それぞれ振り返りをやってみよう」と時間を取っています。</p> <blockquote><p>みんなこの半年で悔しかったことはある?</p></blockquote> <p>CTOからの一言でみんな胸に手を当てて「自分はどうだっかな?」と確かめていました。</p> <h2 id="流れ">流れ</h2> <h4 id="まずは個々人で振り返りワーク">まずは個々人で振り返りワーク</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231220/20231220181014.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>それぞれが半期でやった仕事を思い出し、それをメモに書いていきます。 黙々と作業。</p> <h4 id="次に同じテーブルでシェア">次に同じテーブルでシェア</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231220/20231220181021.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>個々人で振り返った後は、同じテーブルでシェアします。 横で聞いていると盛り上がっていて採用や広報に関わっていた人は</p> <blockquote><p>今年は露出が多かった...!!</p></blockquote> <p>と文脈がないと勘違いしてしまうような言い方でシェアしていました...危険だ...笑</p> <h4 id="最後は全員シェア">最後は全員シェア</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231220/20231220181018.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>最後は全員にシェアします。20近くの事業がありエンジニア組織を持つ事業は10程度。 多様なCARTAらしく、様々な話が飛び交います。</p> <blockquote><p>大人も失敗するんだな、と気づいた。失敗してからリカバリをちゃんとすれば良いと思った。</p></blockquote> <p>と気づきをシェアする人も。</p> <p>CARTAは失敗と改善を大切にする会社なので、その文化が徐々に浸透してきていることを感じますね。</p> <blockquote><p>みんなこの半年で悔しかったことはある?</p></blockquote> <p>CTO @suzukenからの一言でみんな胸に手を当てて「自分はどうだっかな?」と確かめていました。</p> <p>改めて、未来の半期でやりたいこと、なっていたい姿を明確にして 乾杯しました!! 🍺</p> voyagegroup_tech GitLabからGitHubに移行したときに設定したら捗ったこと hatenablog://entry/6801883189068131487 2023-12-19T18:16:41+09:00 2024-02-27T11:23:49+09:00 こんにちは、CCIのkosaです。 本記事はCARTA TECH BLOGアドベントカレンダー 12/19の記事になります。 先日、担当しているプロダクトのリポジトリをセルフホスティング版GitLabからGitHubに引っ越しをしました。 引っ越し後にGitHubに設定して捗った(死語)Tipsを書いていきます。 やったこと PRを一覧化できるGitHub Projectを作成した 所属チームでは夕会のタイミングでGitLabのMerge Request一覧画面でレビュアーのアサイン状況を確認する時間があります。 GitHubではGitLabと違ってPullRequests一覧画面にレビュアー… <p>こんにちは、CCIのkosaです。<br/> 本記事は<a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTA TECH BLOGアドベントカレンダー</a> 12/19の記事になります。</p> <p>先日、担当しているプロダクトのリポジトリをセルフホスティング版GitLabからGitHubに引っ越しをしました。<br/> 引っ越し後にGitHubに設定して捗った(死語)Tipsを書いていきます。</p> <h2 id="やったこと">やったこと</h2> <h3 id="PRを一覧化できるGitHub-Projectを作成した">PRを一覧化できるGitHub Projectを作成した</h3> <p>所属チームでは夕会のタイミングでGitLabのMerge Request一覧画面でレビュアーのアサイン状況を確認する時間があります。<br/> GitHubではGitLabと違ってPullRequests一覧画面にレビュアーのアイコンが表示されないため、一つ一つPullRequestを開いて確認する手間が増えました。<br/> このストレスを解決すべく、GitHub Projectを作成してアサイン状況とステータスを一覧化しました。</p> <p><figure class="figure-image figure-image-fotolife" title="PR"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koko_cci/20231219/20231219174107.png" width="1200" height="517" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>PR一覧のProject</figcaption></figure></p> <p>このProjectを作成したことにより「この人にレビュアーのアサインが偏っている」「レビューされずに放置されている」「この人のPRは一通りマージ済なので追加のタスクをアサインしよう」といったことが一目でわかるようになりました。</p> <h3 id="Autolink-referencesを設定した">Autolink referencesを設定した</h3> <p>所属チームではバックログの管理にJiraを使用しており、GitHubのPRの中にJiraの課題IDを記入することがあります。<br/> 毎回リンクを探してきて書き込むのは手間なのでGitHubの<a href="https://docs.github.com/ja/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/configuring-autolinks-to-reference-external-resources">Autolink references</a>機能を設定しました。<br/> これにより、GitHubのリポジトリ内で登場したJiraの課題IDがすべてJiraの課題リンクに変更されるようになりました。</p> <p>PRのタイトルにJiraの課題番号を入れるようにすれば、リリースノートを生成した際にJiraの課題リンクが生成されるのがいい感じです。 (GitHubのリリースノートの自動生成機能便利ですね) <figure class="figure-image figure-image-fotolife" title="Autolink referencesを設定した状態のリリースノート"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/koko_cci/20231219/20231219174612.png" width="1200" height="859" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Autolink referencesを設定した状態のリリースノート</figcaption></figure></p> <h3 id="git-pr-releaseを導入した">git-pr-releaseを導入した</h3> <p>所属チームではfeat->develop->mainブランチの順番にマージしていき、develop->mainのマージでデプロイされるブランチ戦略を採用しています。<br/> develop -> mainへのPullRequest作成を自動化するために<a href="https://github.com/x-motemen/git-pr-release">git-pr-release</a>を導入しました。<br/> PRのdiscriptionにdevelopとmainの差分PRが一覧化されるので、デプロイされるPRがわかりやすくてとても便利です。</p> <h2 id="感想">感想</h2> <p>まだまだGitHubを使い始めたばかりなのですが、設定できる項目はGitLabと比較するとシンプルな印象を受けました。 設定項目が少ない分、GitHub ActionsでPRやissueの操作する自由度が高そうなのでこれからも継続的に改善していきたいと思います。</p> koko_cci ゲーム感覚で仕事がしたい(物理) hatenablog://entry/6801883189068020484 2023-12-19T10:41:40+09:00 2024-02-27T11:27:18+09:00 この記事は CARTA HOLDINGS アドベントカレンダー 15日目の記事です。 こんにちは、Lighthouse Studio(LS)ののざです。 LSでは神ゲー攻略というゲーム攻略サイトの開発・運用をしています。 そんな自分もゲームが大好きなわけで、ゲームをしているように仕事ができれば捗るのではないかと常日頃考えています。そこでゲームパッドのように入力を行えないか日々試行錯誤しています。 過去の事例 昨年のアドベントカレンダーにもあるとおり同様のチャレンジは行われており、以下のような開拓をしてきました。 JoyToKeyを使ってポインティングデバイスとしてゲームパッドを使う(去年の私)… <p>この記事は <a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTA HOLDINGS アドベントカレンダー 15日目</a>の記事です。</p> <p>こんにちは、Lighthouse Studio(LS)ののざです。 LSでは神ゲー攻略というゲーム攻略サイトの開発・運用をしています。 そんな自分もゲームが大好きなわけで、ゲームをしているように仕事ができれば捗るのではないかと常日頃考えています。そこでゲームパッドのように入力を行えないか日々試行錯誤しています。</p> <h2 id="過去の事例">過去の事例</h2> <p><a href="https://techblog.cartaholdings.co.jp/entry/co3k-susida-2022">昨年のアドベントカレンダー</a>にもあるとおり同様のチャレンジは行われており、以下のような開拓をしてきました。</p> <ul> <li>JoyToKeyを使ってポインティングデバイスとしてゲームパッドを使う(去年の私)</li> <li>0から<a href="https://github.com/co3k/gamepadkey">マッピングアプリを自作</a>してゲームパッドをキーボード代わりにする(co3k: チームメンバー)</li> </ul> <p>しかしながら上記の試みでは以下のような理由から挫折してしまいました。</p> <ul> <li>ポインティングデバイスのみゲームパッドのスタイルはそれ以外の入力にはキーボードを使っておりゲームをしていると言ってよいのか悩ましい。</li> <li>ゲームパッドの動きに全ての入力をマッピングする際に自分に適切なマッピングを探すのは難しく、またマッピングに慣れるまでの時間がかかることや慣れたとしてもキーボードの入力と比較すると速度が落ちてしまう。</li> </ul> <p>ということで半ばあきらめてしまっていたのですが今年に入ってまたチャレンジの機運がやってきました。</p> <h2 id="両手で持って入力できるキーボード">両手で持って入力できるキーボード</h2> <p>両手で持つことを想定したキーボード、<a href="https://grabshell.io/ja">Grab Shell</a>という画期的な入力デバイスが登場しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.itmedia.co.jp%2Fnews%2Farticles%2F2302%2F24%2Fnews163.html" title="つかんで打鍵する変態キーボード「GrabShell」登場 「寝ながら開発打ち合わせができる」" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.itmedia.co.jp/news/articles/2302/24/news163.html">www.itmedia.co.jp</a></cite></p> <ul> <li>両手で持つような形状で前後両面にキーがある <ul> <li>親指側にCtrlやAltなど、残りの4本の指でアルファベットのキーを入力する形になっている</li> </ul> </li> <li>親指側にはジョイスティックやトラックボールなどのポインティングデバイスもついている</li> <li>持つだけでなく、背面側を開くことによって机において利用することもできる</li> </ul> <p>ということでまさにうってつけなものが見つかってしまったわけです。 これを使えばゲーム感覚で仕事ができるのではないかという期待が膨らみます。</p> <p>実際にGrabShellを使って仕事(MTG)をしている様子がこちらになります。</p> <p><figure class="figure-image figure-image-fotolife" title="GrubShell使ってみた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuunoza/20231219/20231219093649.png" width="1200" height="678" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>GrubShell使ってみた</figcaption></figure></p> <p>これはぱっと見ゲーム(ドローンの操作)をしているようにしか見えないですね! 素晴らしい!!</p> <h2 id="カスタムじゃ">カスタムじゃ</h2> <p>さて、夢を叶えるデバイスを手に入れたわけですが手の小さい自分には少し不便な点もあり、</p> <ul> <li>遠くのキーを押す際に他のキーをまとめて押してしまいミスタイプが発生する</li> <li>数字キーに指が届かない</li> </ul> <p>といった問題が見えてきました。 そこでスイッチや設定をいじることによってより使いやすいようにカスタムしていきます。</p> <h3 id="キースイッチを変える">キースイッチを変える</h3> <p>握り込むようにタイピングするため、遠い位置のキーを押す際に他のキーを押してしまうことが多々ありました。 そこでキースイッチをロープロファイル(高さの低いもの)に変更することによってこの問題を軽減しています。 幸いにもGrab Shellで利用するスイッチはCherry MX互換のものかつはめこむだけで交換可能なため、手軽に交換可能です。</p> <p>←: 元々のスイッチ →: ロープロファイルのスイッチ</p> <p><figure class="figure-image figure-image-fotolife" title="スイッチ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuunoza/20231219/20231219093855.jpg" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>スイッチ</figcaption></figure></p> <p>すごくわずかな差に見えますが、体感の変化は大きくミスタイプをだいぶ減らすことができました。しかしながら、数字キーについてはキーキャップが干渉してしまい押しにくい、もしくは押しても反応しないという問題が発生しました。 なので数字キーのみ元のスイッチのままにしています。 これについては後述しますが、両手で持つ場合には数字を使わないキー入力設定にしているので大きな問題にはなっていません。</p> <h3 id="キー配置を変える">キー配置を変える</h3> <p> VIAというソフトを用いて自由にキー配置を変更できるので、自分にとって使いやすいようにキー配置を変更しています。</p> <ul> <li>数字キーに指が届かないので数字キーを使わないような配置にする</li> <li>親指側に一部の記号のキーが割り当てられているが、届きにくい位置にあるので背面側で入力できるようにする <ul> <li>親指側はCtrlやAltなどの修飾キーを配置する</li> </ul> </li> <li>Layer1+ジョイスティック、Layer1+トラックボールで矢印入力やマウスホイール動作になるので利用できるようにする</li> </ul> <p>上記を実現するために44キーのキーボードのレイヤー構造を使った指をなるべく動かさないキー配置を参考に設定しています。</p> <h4 id="レイヤー1">レイヤー1</h4> <p>通常のキータイプをするときに使うレイヤーです。</p> <p><figure class="figure-image figure-image-fotolife" title="layer1"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuunoza/20231219/20231219094020.png" width="881" height="463" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>layer1</figcaption></figure></p> <p>特別なことはなく、通常のキーボードに近い形になっています。 特徴的なのは、左手の外側にキーが2つしか存在せずShiftに対応するキーが存在しないので、内側に大きめのキーにShiftを割り当てています。</p> <h4 id="レイヤー2">レイヤー2</h4> <p>レイヤー1にある MO(1) というキーを押すことによってレイヤー2に切り替わります。</p> <p><figure class="figure-image figure-image-fotolife" title="layer2"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuunoza/20231219/20231219094049.png" width="876" height="463" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>layer2</figcaption></figure></p> <p>ここには矢印キーと数字キーを割り当てています。 数字はテンキーと同じような形に配置することによって直感的に入力できるようにしています。 MO(1) は基本左の親指で押していますが、Macの場合便利なコマンドが数字キーに割り当てられていることもあるので、CMDなどと同時に押せるよう右の親指でも押せるようにしています。</p> <h3 id="レイヤー3">レイヤー3</h3> <p>レイヤー1にある MO(2) というキーを押すことによってレイヤー3に切り替わります。</p> <p><figure class="figure-image figure-image-fotolife" title="layer3"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuunoza/20231219/20231219094106.png" width="877" height="460" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>layer3</figcaption></figure></p> <p>Shift + 数字に対応する記号はそのまま一列下げた部分に配置しています。 他のものについては利用頻度や入力しやすさ、()などは横に並べて対応する形で配置しています。 このレイヤーについてはまだ慣れておらず試行錯誤中です。</p> <h2 id="実際どうなのよ">実際どうなのよ</h2> <p>ということで実際にみんな大好き寿司打に挑戦した様子が以下になります。 持って入力する形と机の上に置いて入力する形の2パターンの結果を載せています。</p> <p><iframe width="560" height="315" src="https://www.youtube.com/embed/dnW0q-cyJ7A?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Grub Shellで寿司打をしてみた"></iframe><cite class="hatena-citation"><a href="https://www.youtube.com/watch?v=dnW0q-cyJ7A">www.youtube.com</a></cite></p> <p>動画の結果のキャプション</p> <p><figure class="figure-image figure-image-fotolife" title="Grab Shell寿司打結果"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuunoza/20231219/20231219094711.png" width="504" height="429" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Grab Shell寿司打結果</figcaption></figure></p> <p>Grab Shellを机に置いて寿司打をした結果。</p> <p><figure class="figure-image figure-image-fotolife" title="Grab Shell机上の寿司打"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuunoza/20231219/20231219094818.png" width="501" height="420" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Grab Shell机上の寿司打</figcaption></figure></p> <p>見てもらうとわかる通り、やや差はあるものの通常と同じようなスピードでタイピングすることができていると思います。</p> <h2 id="まとめ">まとめ</h2> <p>ゲーム感覚で仕事をするという目標にはかなり近づくことができたのではないかと思います。 実際には本体の重さがあるため机において使うことが基本になりますが、それでも十分にゲーム感覚(物理)で仕事ができていると思います。 ゲームというよりドローンを操作しているように見えるというツッコミは甘んじて受け入れて来年も精進していきます。</p> yuunoza 「Oracle Java Platform Extension for Visual Studio Code」を使ってみた hatenablog://entry/6801883189067067351 2023-12-16T00:00:00+09:00 2024-02-27T11:27:39+09:00 こんにちは、CCIのakimotoです。 本記事はCARTA TECH BLOGアドベントカレンダー 12/16の記事になります。 ネタがないことに延々と悩んでいたのですが、ふと2か月ほど前に公開されたVSCodeの拡張機能について気になっていたことを思い出したので、実際に試してみることにしました。 本記事では使ってみた所感をざっと書いていこうと思います。 www.publickey1.jp 今回筆者が使用した環境 Windows10 WSL2を使用(Ubuntu、バージョンは20.04) Java17 VSCodeはWindows側にインストール済み 本当はWSLは使わずWindows上で全… <p>こんにちは、CCIのakimotoです。<br> 本記事はCARTA TECH BLOGアドベントカレンダー 12/16の記事になります。<br> ネタがないことに延々と悩んでいたのですが、ふと2か月ほど前に公開されたVSCodeの拡張機能について気になっていたことを思い出したので、実際に試してみることにしました。<br> 本記事では使ってみた所感をざっと書いていこうと思います。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.publickey1.jp%2Fblog%2F23%2Fjavavscodeoracle_java_platform_extension_for_visual_studio_code.html" title="オラクル、Java開発を効率化するVSCode用拡張機能「Oracle Java Platform Extension for Visual Studio Code」を公開" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.publickey1.jp/blog/23/javavscodeoracle_java_platform_extension_for_visual_studio_code.html">www.publickey1.jp</a></cite></p> <h2 id="今回筆者が使用した環境">今回筆者が使用した環境</h2> <ul> <li>Windows10</li> <li>WSL2を使用(Ubuntu、バージョンは20.04)</li> <li>Java17</li> <li>VSCodeはWindows側にインストール済み</li> </ul> <p>本当はWSLは使わずWindows上で全て完結させたかったのですが、諸般の事情(PCのユーザー名が全角漢字、しかも変更不可)により、Cドライブにインストール済みのJavaのパスが通らなかったので、泣く泣くWSLにインストールしたJavaを使うことにしました。。</p> <h2 id="事前準備">事前準備</h2> <ul> <li>VSCodeをインストールする <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fazure.microsoft.com%2Fja-jp%2Fproducts%2Fvisual-studio-code" title="Visual Studio Code – コード エディター | Microsoft Azure" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://azure.microsoft.com/ja-jp/products/visual-studio-code">azure.microsoft.com</a></cite></li> <li>Javaをインストールしておく(今回はJava17を使用)</li> <li>JAVA_HOMEを設定しておく</li> <li>※筆者の真似をしたい方はWSLもインストールしておく <ul> <li>以前WSLをインストールする必要がでてきた時に下記サイトにお世話になったので参考まで <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fchigusa-web.com%2Fblog%2Fwsl2-win11%2F" title="WSL2のインストールを分かりやすく解説【Windows10/11】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://chigusa-web.com/blog/wsl2-win11/">chigusa-web.com</a></cite></li> </ul> </li> </ul> <h2 id="導入方法">導入方法</h2> <h3 id="Extensionsを開き下図の要領で対象の拡張子をインストールする">Extensionsを開き、下図の要領で対象の拡張子をインストールする</h3> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231215/20231215162352.png" width="1200" height="596" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> ソースコードを見たい方はGitHubにて公開されているらしいので、<a href="https://github.com/oracle/javavscode">こちら</a>を参照ください。</p> <h3 id="Getting-StartedにしたがってSettingsjsonに設定をする">Getting StartedにしたがってSettings.jsonに設定をする</h3> <p>Command Palleteを呼び出し、<code>Preferences:Open User Settings (JSON)</code>を入力してSettings.jsonを開きます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231215/20231215165030.png" width="1200" height="608" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Settings.jsonを開いたら、WSLにインストール済みのJAVA_HOMEの値を下図のように追記していきます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231215/20231215192543.png" width="1200" height="789" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> ここまで設定すれば使えるっぽいので、実際に使っていきます。</p> <h2 id="Javaプロジェクトを作成してみる">Javaプロジェクトを作成してみる</h2> <p>command palleteを開き、<code>java</code>と入力すると下図の赤枠の「Java:New Project...」がでてくるので、こちらをクリック<br> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231215/20231215192900.png" width="1200" height="414" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>すると下図のような選択肢がでてきますので、好きな方を選んでもらえればと思います。<br> 筆者はMavenの方が慣れているのでMavenを選択しました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231215/20231215193137.png" width="1200" height="325" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>次の選択肢にて、<code>Java Application</code>を選択します。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231216/20231216141217.png" width="1200" height="185" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>Javaプロジェクトの作成場所を適当に入力します。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231216/20231216142441.png" width="1200" height="246" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>最後にパッケージ名を入力します。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231216/20231216142543.png" width="1200" height="304" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>無事に作成されたようなので、開いて中身を確認してみます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231216/20231216143104.png" width="1200" height="847" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>シンプルなJavaクラスが作られてますね。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231216/20231216143732.png" width="1200" height="816" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="デバッグ">デバッグ</h2> <p>デバッグは下図の画面で実行するか、もしくはF5で実行できる感じですね。<br> mainメソッドの上に<code>Run main | Debug main</code>とあって、これをクリックすることでも動かすことができるようです。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231216/20231216153819.png" width="1200" height="491" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="コード補完">コード補完</h2> <p>コード補完はある程度してくれますね、サジェストはないと不便なのでひとまず安心。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231217/20231217151424.png" width="1200" height="916" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231217/20231217152133.png" width="1200" height="678" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="エラー検知">エラー検知</h2> <p>エラーもきちんと検知してくれます。まあこれもないと困るのであって良かったです。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231217/20231217152600.png" width="1161" height="636" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/ma-akimoto/20231217/20231217153059.png" width="1200" height="416" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この他にも、command palleteにて<code>java New fromTemplate</code>と入力すると、通常のJavaクラスやEnumクラス、Testクラスなどのテンプレートを選択できる機能などもありました。</p> <h2 id="まとめ">まとめ</h2> <p>ここまでお読みいただきありがとうございました。<br> 使ってみた所感としては、一通りの機能は揃っていそうでした。使い始めるのにそれほど手間もかからなかったので、これからJavaで開発してみたい人がVSCodeで手軽に始めるのに良いのではないかと思います。<br> 今回は他のJava用拡張機能との比較はできなかったので、機会があれば比較記事も書いてみたいと思います。</p> ma-akimoto 就活生に自分の失敗を話したら、フルサイクルやCARTAのエンジニア文化をよく理解してもらえた話 hatenablog://entry/6801883189066686538 2023-12-14T15:41:50+09:00 2023-12-14T19:07:43+09:00 こんにちは!23卒入社で現在DIGITAILIOでエンジニアをしているわっかーです! 普段の業務はDIGITALIOが運営しているECナビやPeXといったサイトの開発業務をしています! 先日、就活生向け1on1面談イベントに先輩エンジニアと一緒に新卒エンジニアの立場として参加させてもらいました! 就活生向けのイベントで、CARTAの特徴であるフルサイクル開発(以下、フルサイクル)について端的に話すのは難しいな、と思っていたのですが。 日々の業務の中で発生した個別具体的な失敗とそこからのリカバリーを経験談として話すと、フルサイクルや働く環境を解像度高くイメージしてもらえて、CARTAに対する興味… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231214/20231214153704.png" width="1200" height="674" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!23卒入社で現在DIGITAILIOでエンジニアをしているわっかーです!</p> <p>普段の業務はDIGITALIOが運営しているECナビやPeXといったサイトの開発業務をしています!</p> <p>先日、就活生向け1on1面談イベントに先輩エンジニアと一緒に新卒エンジニアの立場として参加させてもらいました!</p> <p>就活生向けのイベントで、<strong>CARTAの特徴であるフルサイクル開発(以下、フルサイクル)について端的に話すのは難しいな</strong>、と思っていたのですが。</p> <p>日々の業務の中で発生した<strong>個別具体的な失敗とそこからのリカバリーを経験談として話すと、フルサイクルや働く環境を解像度高くイメージしてもらえて、CARTAに対する興味を持ってもらえるかも…と思った</strong>ので書いてみます。</p> <p>僕の失敗エピソードは</p> <ul> <li><p>それがフルサイクルで高いオーナーシップを与えられているが故の<strong>副作用的な結果</strong>であること</p></li> <li><p>そしてその<strong>失敗を元に身をもって深く学べたり、周りが本質的なサポートをしてくれる</strong></p></li> </ul> <p>という流れです。では詳しく見ていきましょう。</p> <h2 id="フルサイクルで権限が与えられている故にやらかす">フルサイクルで権限が与えられている故にやらかす</h2> <p>やっぱりCARTAの文化に根付いているフルサイクルの特徴を短い時間でメリット・デメリット合わせて伝えるのって難しいなって思っています。</p> <p>質問タイムでも</p> <blockquote><p>「フルサイクルってどういうところが良くて、どういうところが難しいですか?」</p></blockquote> <p>みたいな質問が毎回来ます。というか正直にいうと僕も選考がしっかり進むまでちゃんと理解していたか怪しいですw</p> <p>そこで、今回の面談では、学生に向けてこんな感じで話してました。</p> <blockquote><p>就活生: 「フルサイクルってどういうところがやりがいがあって、どういうところが大変ですか?」</p> <p>わっかー: 「やっぱり全ての権限を配属初日にもらえて、スコープは小さいながらも要件整理・設計・実装・テスト・デプロイ・監視のデリバリーサイクルを自分が責任を持つというのは広範な領域に触れることが出来て面白いですよね」</p> <p>就活生: 「なるほど!でもそういう広範に知識が求められるのって大変ですよね」</p> <p>わっかー: 「それはやっぱりそうですねー。初日にインフラやデプロイ周り含めて全部権限与えられると、やらかしとかも当然ありまして...」</p> <p>わっかー: 「僕はまだ仕事始めて1年未満ですけど、<strong>すでにDB設計ミスって本番環境に一時的な影響を与えちゃったんですよね...。</strong>暫定対応は先に気がついた先輩エンジニアに対応していただいて。その後に自分の実装した部分は責任を持って調査し、根本的な対応は僕が行いました。一部のユーザさんに不便をかけてしまった<strong>”やらかし”を魂に刻んだ</strong>ので、もう同じ間違いはしないです。</p> <p>わっかー: <strong>チームの先輩たちも頭ごなしに叱るのではなく、『なんでそういう事故が起こったか、どうやったら同じミスをしないかを考えていこう』って本質的なところをサポート</strong>しながら助けてくれるので、そうやって今バチバチに鍛えられてますw」</p> <p>就活生: 「へー、それはとてもいいですね!!(←ここで明らかに表情が変わる)」</p></blockquote> <p>とこんな感じで、</p> <p>僕自身が本当にやった<strong>個別具合的</strong>な失敗のエピソードは</p> <ul> <li><p>それがフルサイクルで高いオーナーシップを与えられているが故の<strong>副作用的な結果</strong>であること</p></li> <li><p>そしてその<strong>失敗を元に身をもって深く学べたり、周りが本質的なサポートをしてくれる</strong></p></li> </ul> <p>という流れ。</p> <p>CARTAは入社直後から権限も与えられるし設計・開発・リリース・改善までデリバリーサイクルの全てに触るから「裁量があるが故にコンフォートの外側で失敗できる」環境なんですよね…</p> <p>ちなみにちゃんと説明するとこんな感じの失敗でした</p> <p>ECナビ<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>のトップページで必ず呼ばれるクエリに対して適切にインデックスを貼っていなかったため、新規リリース後1週間でレコードが積み上がったタイミングで問い合わせが重くなり、朝のラッシュでRDS過負荷によりサービスが繋がりにくくなったという感じです。</p> <p>チーム内でパフォーマンス監視する仕組みが整備されていたため、異常発生後すぐに検知して対応を行いました。ユーザーさんへの影響は小さくは抑えられたのですが、めっちゃ焦りました…</p> <h2 id="就活生は裁量権という言葉が好きだし企業も裁量権アピールするけど">就活生は「裁量権」という言葉が好きだし、企業も「裁量権」アピールするけど・・・</h2> <p>僕が就活していた頃を思い出すと、意識の高い就活生はやっぱり逆質問の場面でも「若手でも裁量権はありますか?」という質問が多かった記憶があります。</p> <p>で、企業も「若手でも裁量権ありますよ!」って話をするわけですが、個人的に<strong>具体例のスケールがちょっと大きすぎる</strong>のでは、と思っていました。</p> <p>例えば</p> <blockquote><p>「1年目からいきなり新規子会社の責任者になって、でも最初は失敗して事業撤退したけど、2回目のチャレンジで事業が軌道に乗って云々…」</p></blockquote> <p>みたいな。</p> <p>当時の僕みたいな就活生は、「<strong>自分の中途半端な実力だと大変そう</strong>」とか「<strong>常にハイレベルのアウトプットをミスなく求められるのかな</strong>」ってちょっと尻込みしてしまっていました。</p> <p>チャレンジの規模も失敗の規模もちょっと全社レベルのインパクトで逆にイメージが持ちづらかったり、微妙に自信がない学生はそう思いがちかもしれないですね。</p> <p>少なくとも就活生当時の自分はそうでした。</p> <h2 id="フルサイクルは素敵な文化だよって伝えていきたい">フルサイクルは素敵な文化だよって伝えていきたい</h2> <p>でも全社レベルじゃないけど、まぁまぁなやらかしの話を新卒や若手の人が話してくれたらどうでしょうか?</p> <p><strong>フルサイクル故に権限が最初から強く、デリバリーサイクルの全てに触れる文化がCARTAには根づいています。</strong></p> <p><strong>もちろん放置されるわけではなく、分からなかったら周りの助けをもらえますし、その力を借りながら徐々に一人で出来るようになっていくんですよね。</strong></p> <p>時には実力不足や勢い余ってサービスインシデントを起こすこともあるけど、だからこそ深く学べるしそれを許容する文化がCARTAにはあります。</p> <p>個人開発って正直壊れても問題ないけど、社会人になると大きなお金が絡むから壊してはいけないプレッシャーになります。それを踏まえた上で、やらかして損失は発生したけどCARTAとしては成長の機会として許容してくれてるし、僕自身が心の底から「同じミスはもうしないな」って思ったんですよね。</p> <p>それは失敗できるぐらい任せてもらえる環境だから、身をもって体感できたことだと思います!</p> <p>という感じで、個人的な考察のもと言語化してみました!</p> <p>将来有望なエンジニア人材がより多くCARTAに入社してくれますように・・・</p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> 僕が配属されているDIGITALIOが運営しているポイントサイト<a href="#fnref:1" rev="footnote">&#8617;</a></li> </ol> </div> ikoan08 確かみてみろ!社内ゲームサークル活動報告とストリートファイター6練習会のお知らせ hatenablog://entry/6801883189065837324 2023-12-14T11:00:00+09:00 2024-02-27T11:28:09+09:00 ※ この記事はCARTAエンジニアアドベントカレンダーの記事です。他の記事も読みたい方は一覧へどうぞ! こんにちは!ストリートファイター6ではマノン使いの jewel です。 今日は社内ゲームサークルの昼練の様子をお伝えします! 社内ゲームサークルってなに? CARTA HOLDINGSではサークル制度があり、フットサルサークルやテニスサークルなど20種類ほどのサークルがあります。共通の興味を介したコミュニケーションで部署を超えたXYZ軸の繋がりを作ることがねらいです。下記の記事では野球サークルやサウナサークルの様子がわかります。 evolution.cartaholdings.co.jp そ… <p>※ この記事はCARTAエンジニアアドベントカレンダーの記事です。他の記事も読みたい方は<a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">一覧</a>へどうぞ!</p> <p>こんにちは!ストリートファイター6ではマノン使いの <a href="https://www.streetfighter.com/6/buckler/ja-jp/profile/2724533524">jewel</a> です。 今日は社内ゲームサークルの昼練の様子をお伝えします!</p> <h3 id="社内ゲームサークルってなに">社内ゲームサークルってなに?</h3> <p>CARTA HOLDINGSではサークル制度があり、フットサルサークルやテニスサークルなど20種類ほどのサークルがあります。共通の興味を介したコミュニケーションで部署を超えたXYZ軸の繋がりを作ることがねらいです。下記の記事では野球サークルやサウナサークルの様子がわかります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fevolution.cartaholdings.co.jp%2Farticle-0027%2F" title="社会人になってこんなに本気で、仕事もサークルも楽しめると思わなかった | EVOLUTiON" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://evolution.cartaholdings.co.jp/article-0027/">evolution.cartaholdings.co.jp</a></cite></p> <p>そんなたくさんあるサークルの中でも僕はゲームサークルに参加しています。ゲームサークルはQ1でマリオパーティをやったり、格闘ゲームを日々練習したりするサークルです。リモートで働いている自分でもオンライン参加しやすいのでありがたいです。コロナ禍前は平日はほぼ毎日集まって、オフィスの部屋で練習してた時期もありました。</p> <p>ゲームサークルはいま、大人気格闘ゲーム「ストリートファイター6」を平日の昼に練習しています。</p> <h3 id="ストリートファイター6って">ストリートファイター6って?</h3> <p>ストリートファイター6はCAPCOMさんから今年(2023年)の6月に発売された格闘ゲームです。1on1で相手プレイヤーに技をしかけて、体力ゲージをゼロにしたら勝ちのやつです。長く遊ばれているシリーズなので、ゲームに興味がある人は名前だけでも聞いたことあるんじゃないでしょうか。</p> <p>しかし、名前は知ってるけど格闘ゲームってなんか難しそうでやったことないわという人も多いんじゃないでしょうか?僕もゲームサークルでストリートファイターⅣを遊ばせて<del>かわいがって</del>もらうまではそんなに興味のないジャンルでした。</p> <p>ストリートファイター6はそんな格闘ゲームを初心者でも遊びやすくしてくれました。個人的にはワンボタンで技が出るモダン操作と、駆け引きを分かりやすく楽しめるドライブインパクトの存在が大きいのかなと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnews.denfaminicogamer.jp%2Fkikakuthetower%2F230530e" title="『スト6』は通行人をブン殴りながら「第一歩」を踏み出せる、“クソ親切”な作品になっていた。初心者もすぐに格ゲーの“駆け引き”を楽しめるモダン操作がありがたすぎる…!" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://news.denfaminicogamer.jp/kikakuthetower/230530e">news.denfaminicogamer.jp</a></cite></p> <p>サークルでもストリートファイター6から格闘ゲームを始めたメンバーが増えました。むしろそういうメンバーの方が多いかもしれないです。コマンド入力(クラシック操作)に慣れていたメンバーでもモダン操作で十分以上に強いため、モダン操作で慣れても結局はクラシック操作にしないといけないということもなさそうです。</p> <h3 id="昼練習に参加したいですどうやって参加するのあわよくば企業対抗戦したいですね">昼練習に参加したいです!どうやって参加するの?あわよくば企業対抗戦したいですね!</h3> <p>ということでゲームサークルではストリートファイター6を平日昼に練習しています。だいたい12:30にはカスタムルームを作って、そこで13:00までやっています。</p> <p>昼練カスタムルームの様子(汚い机ですみません)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/j/jewel12/20231211/20231211105228.png" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h3 id="練習会の特徴">練習会の特徴</h3> <ul> <li>1試合ごとに勝ち残り <ul> <li>負けたらレーンで次の試合を待機</li> </ul> </li> <li>毎回3人以上は参加 <ul> <li>最近は8人ほど参加してることもありました</li> <li>社外のメンバーも参加します</li> <li>メンバーの状況によっては開催しないこともあります</li> </ul> </li> <li>ボイチャなしでもくもく <ul> <li>感想をSlackやDiscordに書き込むことがあります</li> </ul> </li> <li>ダイヤ〜マスターランクと強いメンバーが多め <ul> <li>格差が随分ある場合は相手に合わせてキャラを変えてきたりすると思います</li> <li>初心者も増えてきてるので、初心者レーンを作ってもいいかもしれない</li> </ul> </li> <li>使用キャラの種類多めなのでレアキャラ相手に練習できるかも</li> </ul> <h3 id="どうやって参加するの">どうやって参加するの?</h3> <p>Xで <a href="https://x.com/jewel_x12">@jewel_x12</a> に参加したい旨を伝えていただければ、DM等でコミュニケーションとります!</p> <h3 id="あわよくば企業対抗戦したいですね">あわよくば企業対抗戦したいですね!</h3> <p>ストリートファイター6は大人気格闘ゲームですし、弊社以外にもサークルのように集まってワイワイ遊んでいる企業さんがあるかと思います。もしよろしければ企業対抗戦とかどうでしょうか!?ぜひ連絡待ってます。</p> <hr /> <p>ではではレッツエンジョイスト6!</p> <p>※ ちなみにタイトルの「確かみてみろ!」はタイポじゃないので気になる方は検索してみてください。</p> jewel12 ユーザーを想って開発しよう【内定者バイトでの気付き】 hatenablog://entry/6801883189066125700 2023-12-13T16:12:47+09:00 2024-02-27T11:24:32+09:00 この記事は CARTA TECH BLOGアドベントカレンダーの12/13の記事になります。 こんにちは!fluctで24卒内定者アルバイト中のしゅんしゅんです! 前回ブログを書かせていただいたときはCCIに所属していたのですが、長期でバイトしているため部署異動があり現在はfluctで広告の管理画面を作っています。入社前にいろんな部署を経験できてとても充実してます! さてさて、fluctで働いて約2ヶ月、最近はただ開発をするだけでなくデザイナーさんと仕様について検討する機会が割と多くあり、そこでの気づきを書こうと思います。 ある日のできごと その日、私はデザイナーさんと次に作る新機能について話… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/shungenie/20231220/20231220112647.png" width="960" height="540" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>この記事は <a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTA TECH BLOGアドベントカレンダー</a>の12/13の記事になります。</p> <p>こんにちは!fluctで24卒内定者アルバイト中のしゅんしゅんです!</p> <p>前回ブログを書かせていただいたときはCCIに所属していたのですが、長期でバイトしているため部署異動があり現在はfluctで広告の管理画面を作っています。入社前にいろんな部署を経験できてとても充実してます!</p> <p>さてさて、fluctで働いて約2ヶ月、最近はただ開発をするだけでなくデザイナーさんと仕様について検討する機会が割と多くあり、そこでの気づきを書こうと思います。</p> <h3 id="ある日のできごと">ある日のできごと</h3> <p>その日、私はデザイナーさんと次に作る新機能について話していました。ただその機能は実装が見た目ほど簡単ではなく、作らなくても運用のできるものでした。</p> <p>そのとき私はこの機能を作ると<strong>実装は複雑化するし、作らなくて良いのではないか?</strong>となんの気なく言いました。私にとっての関心は、その<strong>コードのメンテナンスコスト</strong>に向いていたのです。</p> <p>それに対してデザイナーさんは「実装が複雑化するから作りたくない、というのはこっちの都合でユーザーには関係ないよね?もっとユーザーのことを考えて意見してみよう」と言われて思わずハッとしてしまいました。</p> <h3 id="機能追加とメンテナンスコスト">機能追加とメンテナンスコスト</h3> <p>全く恥ずかしいことに、余分な機能を作らずにコードが少なく、メンテしやすいのは良いことだというエンジニアの考えをそのままソフトウェアに適用しようとしていました。もしその機能がユーザー全員が欲しいと思っている機能であったら、実装が複雑になるから...というのは作らない言い訳にならないでしょう。</p> <p>ただ、ユーザーが欲しいからという理由でなんでも実装が複雑化してでも作るべきか?と言われるとそうではないと思います。複雑化したコードは、その後の<strong>開発の生産性を下げる</strong>というのはよく知られた話です。それは機能が欲しい!と思ってから実装されるまでの<strong>デリバリーが遅くなるという形でユーザーに不利益</strong>をもたらします。</p> <p>つまり、その後の開発生産性を著しく下げるのでその形での機能追加はユーザーのためにならないですよということが言えれば、メンテナンスコストは作らない理由になる可能性があります。</p> <h3 id="ユーザーを想って開発する">ユーザーを想って開発する</h3> <p>結局のところ、ユーザーにとって何が最善か?を常に考えて仕事をする必要があるように思われます。</p> <p>今まではコードを読みやすく書いたり、リファクタリングをしたりするのはエンジニアのためであると考えていました。ただそこからさらに突き詰めると、ユーザーに<strong>安定してデリバリーをし続けるため</strong>という目的があるのだということに辿り着きます。これこそがきっと本質であり、そのために自分は動いているのだということをより自覚しようと思いました。そう思うと、この仕様はどうあるべきかやタスクの優先順位が見えてくるような気がします。</p> <h3 id="ユーザーを主語にして議論する">ユーザーを主語にして議論する</h3> <p>またこうしたユーザーを主語にした議論をができるようになると、デザイナーさんとするプロダクトについての議論はグッと楽になりました。というのもユーザーというのは<strong>お互いの共通の関心事</strong>になるからです。今までの自分の中のイメージではプロダクトそのものを主語にして議論をしていました。ただ、そのときデザイナーさんがイメージしているプロダクトとエンジニアとしての自分がイメージするプロダクトはズレやすかったように思います。</p> <p>というのも、デザイナーさんは<strong>ユーザーが見ている画面</strong>と仕事をする時間が長いのに対し、エンジニアはその<strong>プロダクトのコード</strong>と向き合う時間が長くなります。そうなるとプロダクトについて頭の中にイメージするものが違ってくるのは仕方のないことのように思います。そしてこのズレがプロダクトに対する<strong>課題感のズレ</strong>を引き起こすように感じます。だから冒頭の話で自分はコードのメンテナンスについて懸念を持ったのかなと思います。</p> <p>ただユーザーを軸にして考えれば、デザイナーもエンジニアも自分を分離して<strong>客観的に考える</strong>ことができます。それが<strong>お互いの目線を合わせる</strong>ことに繋がってエンジニア以外の人との議論が楽になったのかなと思いました。</p> <h3 id="おわりに">おわりに</h3> <p>ユーザーのために作るって、言葉として聞くと軽いですが心からわかるようになると全然重さが変わってきたので、早いうちに考えることができてよかったです。</p> <p>また、こうした経験ができたのもアルバイトでもある程度裁量を持って開発させてくれる環境があるからかなと思います!内定者バイト万歳!</p> <p>こうした学びを活かして、来年の本入社以降もユーザーにとっての嬉しさを常に頭に置きながら日々の仕事にあたっていきたいです!</p> <p>最後までお読みいただきありがとうございました!良いお年を〜</p> shungenie 🍴虎ノ門ヒルズ36Fで500円で弁当が食べれる🍴 ランチの弁当が来た! hatenablog://entry/6801883189065872299 2023-12-11T16:33:34+09:00 2024-02-27T11:25:01+09:00 技術広報のしゅーぞーです。 今日はCARTAのオフィスに弁当が来た!ってことでレポートします😋 とんかつ和風あんかけ弁当 オフィス内で500円でランチのお弁当が買える オフィス新しくなったし、みんなでご飯食べたい! どうせなら新しいオフィスで食べようよ! ってことでなんとオフィス内で「500円」でランチのお弁当が買えるように...ありがてぇ...🙏 お弁当を入れてくれたのはコーポレートブランド室長のぴろさん!! コーポレートブランド室長のぴろさん 初日は180食入れてくれたらしく、ちゃんとハケるか不安らしくソワソワしながら歩いていました笑 虎ノ門ヒルズ36Fで食べるお弁当、それだけで美味しい … <p>技術広報のしゅーぞーです。</p> <p>今日はCARTAのオフィスに弁当が来た!ってことでレポートします😋</p> <p><figure class="figure-image figure-image-fotolife" title="とんかつ和風あんかけ弁当"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231211/20231211134826.png" width="1200" height="889" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>とんかつ和風あんかけ弁当</figcaption></figure></p> <h2 id="オフィス内で500円でランチのお弁当が買える">オフィス内で500円でランチのお弁当が買える</h2> <ul> <li>オフィス新しくなったし、みんなでご飯食べたい!</li> <li>どうせなら新しいオフィスで食べようよ!</li> </ul> <p>ってことでなんとオフィス内で「500円」でランチのお弁当が買えるように...ありがてぇ...🙏</p> <p>お弁当を入れてくれたのはコーポレートブランド室長のぴろさん!!</p> <p><figure class="figure-image figure-image-fotolife" title="コーポレートブランド室長のぴろさん"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231211/20231211134537.png" width="1200" height="987" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>コーポレートブランド室長のぴろさん</figcaption></figure></p> <p>初日は180食入れてくれたらしく、ちゃんとハケるか不安らしくソワソワしながら歩いていました笑</p> <h2 id="虎ノ門ヒルズ36Fで食べるお弁当それだけで美味しい">虎ノ門ヒルズ36Fで食べるお弁当、それだけで美味しい</h2> <p>CARTAは36-38Fにオフィスを構えており、オープンスペースがある36Fからはハッキリと東京タワーが見えます。</p> <p><figure class="figure-image figure-image-fotolife" title="お弁当を食べるオープンスペースの真横には東京タワー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231211/20231211135200.png" width="1200" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>お弁当を食べるオープンスペースの真横には東京タワー</figcaption></figure></p> <blockquote><p>「高級レストラン行くよりもいい眺めなんじゃないか」</p></blockquote> <p>と思えるほどには気持ちいい空間です。</p> <p>お財布には優しく、気持ちとしてはとても贅沢な気分に...!!<br/> 社員としては素直に嬉しいですね 😊</p> <h2 id="虎ノ門ヒルズ-の近くにランチはないの">虎ノ門ヒルズ の近くにランチはないの?</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.toranomonhills.com%2Ft-market%2Findex.html" title="T-MARKET | 虎ノ門ヒルズ - Toranomon Hills" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.toranomonhills.com/t-market/index.html">www.toranomonhills.com</a></cite></p> <p>T-MARKETというレストラン街があります。もちろんとても美味しいのですが、元々オフィスがあった渋谷・東銀座に比べて「ほんの少しランチ相場が高め」という社員のペインがありました..</p> <p>前述のぴろさんがそのペインを拾い、なんとオフィス移転から1週間でお弁当施策を実施💨 おいおい....はやすぎるぜ...</p> <h2 id="どんな弁当があるの">どんな弁当があるの?</h2> <p><figure class="figure-image figure-image-fotolife" title="定番お弁当系"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231211/20231211135351.png" width="1200" height="900" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>定番お弁当系</figcaption></figure></p> <p>初日は約20種類ほど180個が配膳されました 🍱<br/> 同じようなものが並ぶのかな?と思いきや、ノーマルのお弁当からガパオライス、グリーンカレー、ルーロー飯など非常に多種あり、ありがたい限りです。</p> <p>個人的に、お昼ご飯の味は日々のテンションに大きく響くので、ご飯にこだわりが感じられることはたいへん嬉しいところ。</p> <p><figure class="figure-image figure-image-fotolife" title="めちゃくちゃしれっとガパオライスやグリーンカレーが!! おしゃれ度の高さ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231211/20231211135435.png" width="1008" height="706" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>めちゃくちゃしれっとガパオライスやグリーンカレーが!! おしゃれ度の高さ</figcaption></figure></p> <p>会計は現金はもちろんのこと、PayPayやクレジットカードなど電子決済も可能でした。<br/> レジ混むかな?🤔と思っていたのですが、そんなこともなくとてもスムーズ。</p> <p>お弁当を仕出してくれている会社の方いわく、</p> <blockquote><p>他の会社は現金精算が多いのですが、CARTAは電子決済がほぼ100%でとてもスムーズな印象</p></blockquote> <p>とのことでした!! さすがIT企業ですねぇ👏</p> <h2 id="CTOがお弁当食べてたから聞いてみた">CTOがお弁当食べてたから聞いてみた</h2> <p><a href="https://twitter.com/suzu_v">CTO @suzuken</a> がお弁当を食べてたので、感想を聞いてみました。</p> <p><figure class="figure-image figure-image-fotolife" title="食べ終わったすずけんさん"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231211/20231211135938.png" width="1200" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>食べ終わったすずけんさん</figcaption></figure></p> <pre class="code" data-lang="" data-unlink>お弁当1個あたり数百円、会社から補助が入ってて元々は普通の値段するお弁当なんだよね 美味しかったね!ランチ全然これで良いねぇ〜 by @suzuken</pre> <p>とのことでした...!! 自分も実際食べてみたのですが、かなり美味しく「え、毎日これでいい...!」と素直に思えました🙆‍♂️</p> <p>以上おべんとうレポートでした!</p> voyagegroup_tech CARTAの社内に完成した 虎ノ門スタジオってどんなところ? hatenablog://entry/6801883189065665312 2023-12-10T19:11:41+09:00 2024-02-27T11:28:30+09:00 この記事はCARTA TECH BLOGアドベントカレンダーの12/7の記事になります。 こんにちは。自称CSO(チーフサウンドオフィサー) の前田@brtriver です!今日はCARTA HOLDINGSに新しくできたインハウス スタジオを紹介したいと思います。 CARTA 虎ノ門STUDIO CARTA HOLDINGS は12/4から虎ノ門ステーションタワーにオフィスを移転しました。 cartaholdings.co.jp そのオフィスの中にインハウスのためのイベント収録・配信スタジオができました!通路から内部も見渡せる開放感あふれる造りです! 虎ノ門スタジオ 外観 これまでも渋谷のオ… <p>この記事は<a href="https://techblog.cartaholdings.co.jp/entry/2023/12/01/110348">CARTA TECH BLOGアドベントカレンダー</a>の12/7の記事になります。</p> <p> こんにちは。自称CSO(チーフサウンドオフィサー) の<a href="https://twitter.com/brtriver">前田@brtriver </a>です!今日はCARTA HOLDINGSに新しくできたインハウス スタジオを紹介したいと思います。</p> <h2 id="CARTA-虎ノ門STUDIO">CARTA 虎ノ門STUDIO</h2> <p> CARTA HOLDINGS は12/4から虎ノ門ステーションタワーにオフィスを移転しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcartaholdings.co.jp%2Fnews%2F20231204_1%2F" title="CARTA HOLDINGSおよびグループ各社、虎ノ門ヒルズ ステーションタワーにオフィスを移転 | 株式会社CARTA HOLDINGS" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cartaholdings.co.jp/news/20231204_1/">cartaholdings.co.jp</a></cite></p> <p> そのオフィスの中にインハウスのためのイベント収録・配信スタジオができました!通路から内部も見渡せる開放感あふれる造りです!</p> <p><figure class="figure-image figure-image-fotolife" title="虎ノ門スタジオ 外観"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210173744.png" width="1200" height="743" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>虎ノ門スタジオ 外観</figcaption></figure></p> <p> これまでも渋谷のオフィス内に、会議室をひとつ改修しスタジオ利用できるようにしていましたが、普通の会議室にただ機材が並んでいるだけでした。そのときの利用経験を活かし新しく設計されたのが今回のスタジオになります。</p> <p> 渋谷のころの会議室スタジオの一番の課題は "防音" でした。どうしても壁、天井から隣の会議室の会話や笑い声、通路からの通行人の声などが入ってきたり、空調の音・サウンドマスキングシステムの音が入ってくるなど、マイクが拾ってしまうさまざまな環境音との戦いでした。</p> <p> そこで、今回の虎ノ門スタジオは きちんと防音壁で天井まで覆われた部屋で、ガラスも二重サッシになっています。</p> <p><figure class="figure-image figure-image-fotolife" title="スタジオの扉"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210174601.png" width="1200" height="1125" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>スタジオの扉</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="内部の仕切りにカーテンで収録中の出入りの音の侵入も考慮"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210174931.png" width="1200" height="1161" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>内部の仕切りにカーテンで収録中の出入りの音の侵入も考慮</figcaption></figure></p> <p> 窓近くで会話しても収録にはほとんど影響ないレベルまで防音されるようになりました。また、収録中に人が出入りすることもありますが、そのときに音に影響がないように扉のところと、コントロールルームとの境にカーテンを設置しました。</p> <p> 集音はマイキングが命です。録音した音にノイズリダクションなどを掛けることも可能ですが、なによりもきれいな音で集音することが大事なのです。それが実現できるスタジオになりました!</p> <p> 内部は半円形になっていますが、6人ぐらいが並んでも大丈夫なぐらいの広さはあります。まだ机が用意されていなかったりしますが、オフィスには素敵な色んな形の椅子があるので、それをコンテンツにあわせてスタジオに持ってくれば様々な画作りができるようになります。また、カーテンが用意されているので外部に見せずに収録することもできます。<figure class="figure-image figure-image-fotolife" title="スタジオ内部"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210175725.png" width="1200" height="823" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>スタジオ内部</figcaption></figure><figure class="figure-image figure-image-fotolife" title="カーテンで外部から見えないように"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210180006.png" width="1200" height="827" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>カーテンで外部から見えないように</figcaption></figure></p> <h2 id="機材選定はあまり詳しくない人でも使えて凝った画作りもやればできるかどうか">機材選定はあまり詳しくない人でも使えて、凝った画作りもやればできるかどうか</h2> <p> 虎ノ門のスタジオはインハウスのスタジオです。つまりプロの方だけが利用するわけではありません。どちらかというと社員も気軽に利用できる環境にし使い倒してもらうことが大事だと考えます。</p> <p> そのため、最低限のことはできるように且つ、知識ある人であれば凝ったこともできるような機材選定を行っています。</p> <h3 id="カメラ">カメラ</h3> <p> メインのカメラは SONY ZV1とOBSBOT Tail airです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.sony.jp%2Fvlogcam%2Fproducts%2FZV-1%2F" title="VLOGCAM ZV-1 | デジタルカメラ VLOGCAM | ソニー" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.sony.jp/vlogcam/products/ZV-1/">www.sony.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.obsbot.com%2Fjp%2Fobsbot-tail-air-streaming-camera" title="OBSBOT Tail Air: AI搭載 4K PTZストリーミングカメラ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.obsbot.com/jp/obsbot-tail-air-streaming-camera">www.obsbot.com</a></cite></p> <p> ZV1はコンデジを使ったことがあれば大体使い方はわかりますし、なによりオートフォーカスが強いというのが選定理由です。</p> <p> OBSBOT Tail air はコンパクトなPTZカメラで、リモコンからパン・チルトズームの操作が行えます。スタジオでの収録は少人数のオペレーションで行うことがほとんどです。カメラの操作に付きっきりになることはなかなか難しいのでこのようなカメラはとても便利です。<figure class="figure-image figure-image-fotolife" title="リモコンで操作できるPTZカメラ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210181937.png" width="1200" height="839" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>リモコンで操作できるPTZカメラ (リモコンが上下逆だったw)</figcaption></figure></p> <p> また、HDMI出力だけではなくPCと繋げばWEBカメラとして動作するというのも機材に詳しくない人でも扱えるカメラになっています。</p> <h3 id="マイク">マイク</h3> <p> メインのマイクは ゼンハイザーのMKE600です。ショットガンマイクで演者から離れていても明瞭に音声を集音することができます。 <figure class="figure-image figure-image-fotolife" title="メインマイクのゼンハイザーMKE600"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210182232.png" width="1200" height="705" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>メインマイクのゼンハイザーMKE600</figcaption></figure></p> <p> マイクの存在を気にせずに会話してもらえば集音できますし、マイクにぶつかってノイズがのるといった心配もほとんどありません。</p> <p> ただし、用途によっては使いづらいこともあるので、オーディオテクニカのグースネックマイク、パソコンにもUSBで直接つなぐことができる SHURE MV7も置いてあります。ポッドキャストをとるときはMV7を使うなど用途にあわせて選択できます。   <figure class="figure-image figure-image-fotolife" title="グースネックマイク"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210182611.png" width="1200" height="771" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>グースネックマイク</figcaption></figure> <figure class="figure-image figure-image-fotolife" title="SHURE MV7"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210182626.png" width="1200" height="992" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>SHURE MV7</figcaption></figure></p> <h3 id="コントロールルーム">コントロールルーム</h3> <p><figure class="figure-image figure-image-fotolife" title="コントロールルームからスタジオを一望"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210183019.png" width="1200" height="904" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>コントロールルームからスタジオを一望</figcaption></figure></p> <p> コントロールルームとスタジオ内部は窓ガラス越しに見えるようになっています。大型モニタをおいてスイッチャーのプレビュー画面もとても見やすくなりました。</p> <p> コントロールルームの常設機材は以下のようになっています。</p> <ul> <li>ミキサー <ul> <li>YAMAHA MG12 アナログミキサー</li> </ul> </li> <li>スイッチャー <ul> <li>Roland V8HD + iPad</li> <li>IDK MSD-S52 (5⼊⼒2出⼒マトリクススイッチャ)</li> </ul> </li> <li>その他 <ul> <li>Birddog play (NDI to HDMI)</li> <li>Roland Pro A/V - UVC-01 (HDMI to UVC)</li> <li>ブルーレイ Player</li> <li>BOSE Soundlink mini2 (モニタ用)</li> <li>SONY MDR-7506 (ヘッドホン) <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210185713.png" width="1200" height="1039" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></li> </ul> </li> </ul> <p> 知識ある人であればスタジオ入るだけでこれだけ機材があり、カメラ・マイクとつながっているのは準備がほぼゼロでいけるのでとても便利です。今後はこの機材をさわれる人を増やしていくのが大事になってきます。</p> <h3 id="貸出機材">貸出機材</h3> <p><figure class="figure-image figure-image-fotolife" title="貸出機材"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/brtRiver/20231210/20231210190147.png" width="1200" height="786" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>貸出機材</figcaption></figure>  コントロールルームを使わなくても気軽に収録・配信ができるようにと機材もいくつか用意してありますし、約200人ほどが入れるセミナールームでのハイブリッドイベントなども想定されるための機材も用意しています。</p> <ul> <li>Roland VR-6HD (ミキサー兼スイッチャ)</li> <li>ATEM mini pro</li> <li>zoom Livetrak L8 / L12</li> <li>HDMI エキスパンダー</li> <li>SHURE MV5, MV7, SM58, beta57a</li> <li>各種ケーブル</li> </ul> <h3 id="CARTAでインハウススタジオの事例を作っていく">CARTAでインハウススタジオの事例を作っていく</h3> <p> ざっくりとCARTAの虎ノ門スタジオについて紹介してきましたが、スタジオは作って終わりではなく使い倒されて初めてスタジオとしての価値が上がります。</p> <p> 社内にスタジオがあることで認知につながっていったり、結果として売上を伸ばすことにつながることを目的にインハウスのスタジオ事例と、その機材をフル活用したオンラインイベント事例を多く出せればと思っています。</p> <p> もし、社内にスタジオや配信設備を持っていて意見交換したい!や、これから導入をかんがえていらっしゃる方がいたらお気軽にお声がけください。一緒に これからのインハウススタジオについてお話しましょう!</p> brtRiver CARTA のエンジニア読書会はt-wada さんと本を読めるらしいぞ...? hatenablog://entry/6801883189064263753 2023-12-08T17:08:57+09:00 2024-02-27T11:25:14+09:00 こんにちは、技術広報のしゅーぞーです。今日は CARTA の読書会について掘り下げます! CARTA 読書会とは? CARTA HOLDINGS には、エンジニアの中に「読書会」の文化が根づいています。 お互いに読み合うことに加え、ファシリテーターが入ることも 事業部を超えて、みんなで同じ本を読む 書籍購入費は会社が負担 ファシリテーターとして「t-wada1」さんが参加してくれることも 流れとしては以下のような形で行います。 まずは読み進める部分を決定 個々人が google docs に学びを得た本文を引用し、感じたことを書く それをファシリテーター(t-wada)が拾い、出版当時の文脈や… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20231208/20231208113910.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、技術広報のしゅーぞーです。今日は CARTA の読書会について掘り下げます!</p> <h2 id="CARTA-読書会とは">CARTA 読書会とは?</h2> <p>CARTA HOLDINGS には、エンジニアの中に「読書会」の文化が根づいています。</p> <p><figure class="figure-image figure-image-fotolife" title="お互いに読み合うことに加え、ファシリテーターが入ることも"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231205/20231205141128.png" width="1200" height="344" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>お互いに読み合うことに加え、ファシリテーターが入ることも</figcaption></figure></p> <ul> <li>事業部を超えて、みんなで同じ本を読む</li> <li><strong>書籍購入費は会社が負担</strong></li> <li>ファシリテーターとして「t-wada<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup>」さんが参加してくれることも</li> </ul> <p>流れとしては以下のような形で行います。</p> <p><img src="https://lh7-us.googleusercontent.com/4jsZZYldKBDdJSd4AFXT0KCbPCAdKqClwjrW-MhiJsNW46o4o5hQhwRr0dxV0Ynzqy6OT_Em-AaUp7dzDzOR2404MEKPxaVSIR8_KV0WBOy1beirK6v55xoUZKqA_w5SrxoWSJQVEoU4uYsGviUBh_M" alt="" /></p> <ol> <li>まずは読み進める部分を決定</li> <li>個々人が google docs に学びを得た本文を引用し、感じたことを書く</li> <li>それをファシリテーター(t-wada)が拾い、出版当時の文脈や歴史的経緯を補いながら解説</li> </ol> <p>このような形で、本を中心に若手〜シニアが本の知識を得ながら共に学んでいく時間をとっています。</p> <h3 id="本はどうやって決まるの">本はどうやって決まるの?</h3> <p>読書会で読む本は Slackの #読書会チャンネルで決まります。</p> <ol> <li>リストアップされた読みたい本リストを元にMTG</li> <li>みんなで3冊くらいに絞る</li> <li>3冊を最終投票し、読む本を決定</li> </ol> <p><figure class="figure-image figure-image-fotolife" title="エンジニアが読みたい本をリストアップ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240209/20240209172807.png" width="1120" height="992" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>エンジニアが読みたい本をリストアップ</figcaption></figure></p> <p>このパターンでは後で登場する ryuさんが本の選出を進めていますね!</p> <p><figure class="figure-image figure-image-fotolife" title="本の選出はSlackで投票を募る"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240209/20240209172559.png" width="1090" height="632" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>本の選出はSlackで投票を募る</figcaption></figure></p> <p>選ばれたものを t-wadaさんに共有し、次の読書会へ進んでいきます。</p> <p><figure class="figure-image figure-image-fotolife" title="Slack投票で本はエンジニアが決める"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/v/voyagegroup_tech/20240209/20240209172402.png" width="1200" height="567" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Slack投票で本はエンジニアが決める</figcaption></figure></p> <p>このように文化の中に根付いているのが特徴的ですね📚</p> <h2 id="今回は企画者と参加者にインタビュー">今回は企画者と参加者にインタビュー!</h2> <p>今回は『エクストリーム・プログラミング(以下、XP)』読書会を企画した ryu さん、参加者として kawaken さん、 yamachu さんにインタビューを行い、CARTA 読書会の在り方や良さについて聞いてみました。</p> <p>では、早速見ていきましょう!</p> <p><strong><span style="color: #F5A2A2">技術広報:</span></strong> ryu さん、読書会を企画した具体的な動機は何でしたか?</p> <p><span style="color: #00796b"><strong>ryu: </strong></span> シニアエンジニアの 1 人が XP のプラクティスを例にだして、私を含む若手にアドバイスをしていたのが印象的でした。 彼のアドバイスに触発されて調べてみたところ、 <strong>XP はチームやマインドについて語る部分も多いことを知り、自分だけではなく他の人を集めてやろうと思ったんです。</strong> そこで、一人で読むよりも多くの人と一緒に学ぶ機会を作ろうと考えました。</p> <p><strong><span style="color: #F5A2A2">技術広報:</span></strong> なるほど。それでは t-wada さんをどのように読書会のファシリテーターとしてお呼びになったのでしょうか。</p> <p><span style="color: #00796b"><strong>ryu: </strong></span> 最初は、事業部内の読書会として開催するつもりでした。ちょうど、t-wada さんがファシリテーションを務める読書会が終わったタイミングで、次の読書会の本が決まっていなさそうだったんです。</p> <p>t-wada さんはエンジニアリング分野におけるチームビルドや歴史においても専門性が高い方なので、 <strong>せっかくなら t-wada さんにファシリテーションしていただいたほうが学びがあると思い、突撃しました。</strong></p> <p><strong><span style="color: #F5A2A2">技術広報:</span></strong> t-wada さんに依頼することについて心理的ハードルはありましたか?</p> <p><span style="color: #00796b"><strong>ryu: </strong></span> <strong>これまでも周りの人たちが突撃しているから「自分もやって良いんだ」と思ったんです。</strong> times(分報チャンネル)で「t-wada さんに聞けば?」というのをよく見かけていたし、自分が提案しても周りの人が「いいじゃん」って言ってくれそうだから突撃してみたんです。結果、快く OK をもらえました。</p> <p><strong><span style="color: #F5A2A2">技術広報:</span></strong> 実際に読書会を開催してみていかがでしたか?</p> <p><span style="color: #00796b"><strong>ryu: </strong></span> 思ったより人が来てくれて嬉しかったです。初回は 40 人もいました。最終的に 20 人が残りました。 <strong>所属部署なども関係なく横断的に人が集まりました。</strong></p> <p>XP は単に開発手法だけでなく、働き方に対するマインドの部分が大きいと改めて感じました。自分だけでなく周りや文化としての馴染みが必要だと思います。</p> <p><strong>XP に書かれていることは CARTA にすでにある文化と近い部分も多く、色んな人と認識を合わせるために読めたのは良かった</strong> と思います。</p> <p><strong><span style="color: #F5A2A2">技術広報:</span></strong> ありがとうございます。それでは kawaken さん、yamachu さん、読書会に参加されて感じたことを教えてください。</p> <p><span style="color: #dd830c"><strong>kawaken:</strong></span> 自分の直感で大事だと思っていたけど、言語化されていなかったエンジニアワークにおけるコミュニケーションやフィードバック、勇気などの重要さが明瞭に言語化されていて、とても勉強になりました。著名なエンジニアが本の主張に加えて歴史的経緯や解釈の幅を持たせてくれるのもありがたいです。</p> <p><strong>特に、チーム内で小さい失敗を隠さずにできるマインドセットや環境の大切さを学びました。書籍内で登場したペアプロは少しずつチームに取り入れて実践しています。</strong> 読書会中に、みんなで docs に感想を書くことで、その考えを読書会参加者の言葉で消化できたのも良かったです。</p> <p><figure class="figure-image figure-image-fotolife" title="読書会中に使われるdocs(青/黒:感想、 緑:引用)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/n/namu_r21/20231205/20231205142005.png" width="568" height="910" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>読書会中に使われるdocs(青/黒:感想、 緑:引用)</figcaption></figure></p> <p>特に「価値」の章に書かれたエッセンスは、他の本で一冊使って話されていることがまとまっているな、と感じました。読んでよかったです。</p> <p><span style="color: #2196f3"><strong>yamachu:</strong></span> 自分たちが他のチームの動きなどを見て、 <strong>経験的に良さそうと思っていた行動や考え方が言語化されたことは非常に学びになりました。</strong></p> <p>この本の内容は、開発手法だけでなく、人間関係のことを伝える場面が多く、他の職種にも応用できるエッセンスが詰まっていると感じました。チームとして参加したため、同じエッセンスを即座に動きに反映できたのが良かったです。</p> <p>また、読んで感じたのは、<strong>自分が所属しているサポーターズですでに行われているプラクティスがいくつもあったことです。</strong></p> <ul> <li>カンバン方式</li> <li>エンジニアサイドと運用サイドが同じチームとして動く</li> <li>チームが同じ課題を見続ける</li> </ul> <p>このあたりは、 <strong>すでに事業部の中で実践しているもので、すでに持っていた感覚を改めて綺麗に言語化してもらった感覚</strong> がありました。</p> <p><strong><span style="color: #F5A2A2">技術広報:</span></strong> 最後に、みなさんに共通の質問ですが、XP のプラクティスは CARTA の開発文化に似ていると感じましたか?</p> <p><span style="color: #00796b"><strong>ryu: </strong></span> CARTA の仕草に XP が取り入れられている部分があるので、言語化されているのを読んで納得できた部分がありました。</p> <p><span style="color: #dd830c"><strong>kawaken:</strong></span> 情報をオープンにしていくことや、カンバンのように目に見える形で進捗を共有することは、CARTA の Slack や GitHub 利用と似ていますね。</p> <p>「いきいきとした仕事」の節に書かれていたことは、普段の個人チャンネルや ajito でお酒を飲みながら喋っている時に感じていたものと近かったです。</p> <p><span style="color: #2196f3"><strong>yamachu:</strong></span> サポーターズが実践していた内容が XP のプラクティスとして書かれている部分が多かった印象です。CARTA の開発文化もそこから影響を受けた部分があるのだと思います。</p> <h2 id="読書会は単発ではなく継続的に続いている">読書会は単発ではなく、継続的に続いている</h2> <p>現在は、社内で t-wada さんに<a href="https://speakerdeck.com/twada/understanding-the-spiral-of-technologies-2023-edition">「技術選定の審美眼」</a>の再演をやっていた際に、話題に上がった UNIX の哲学について書いてある『UNIX という考え方』の読書会をやっています。</p> <p>以上、CARTA 読書会の解説でした!</p> <p>3 人が所属する事業部はこちら 👇 <br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcorp.fluct.jp%2F" title="株式会社fluct" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://corp.fluct.jp/">corp.fluct.jp</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.cci.co.jp%2F" title="CARTA COMMUNICATIONS(CCI)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.cci.co.jp/">www.cci.co.jp</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcorp.supporterz.jp%2F" title="株式会社サポーターズ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://corp.supporterz.jp/">corp.supporterz.jp</a></cite></p> <div class="footnotes"> <hr/> <ol> <li id="fn:1"> t-wada: 20 年以上エンジニアリングを行っており、技術的な歴史や背景に詳しい。手がけた本として『SQL アンチパターン』『テスト駆動開発』など。普段は、技術コーチとして携わっていただいています。<a href="#fnref:1" rev="footnote">&#8617;</a></li> </ol> </div> namu_r21