コネヒト開発者ブログ

コネヒト開発者ブログ

アジャイル開発で日々の積み重ねを楽しくする、レゴの儀の工夫

こんにちは!@TOC です。 最近はスキップとローファーという漫画の 1 巻を kindle unlimited で見たら止まらず全巻買ってしまいました。 あの青春な感じ、いいですよね...ヽ(○´3`)ノ

今日は普段行っているアジャイル開発の中で日々の積み重ねを可視化するためのイベントとして行っているレゴの儀についてご紹介したいと思います!


目次


レゴの儀とは?

アジャイル開発では「リリースをして終わり」ではなく「リリースを積み重ねていく」ことが大事なので、ある種マラソンみたいな感覚があります。 持続可能なペースで走っていく中で、自分がどのくらいの距離を走ってきたのかわかると、「こんなに頑張ったんだ」って気持ちになりそうですよね。 そんな日々の積み重ねを可視化する目的でリリースごとにレゴを作っていく「レゴの儀」というイベントを継続して行っています。

この「リリースレゴ」のイベントは SCRUM BOOT CAMP THE BOOK という本の中で紹介されているイベントなので、ぜひ興味ある方は読んで見てください 🙌

今回のブログではそんなレゴの儀のイベントをちょっと楽しくする工夫について紹介していきたいと思います。

工夫1:みんなでワイワイ、レゴを決める

レゴって結構種類あって、どれにしようかなって悩むんですよね。 ただ一人で好きなレゴを決めるよりも、チームのみんなで決めたほうが作っていくモチベーションも上がりそうですよね! なのでレゴを決めるのもイベント化しちゃいましょう🙌

下記のように候補を決めてチームでの投票形式にしてみました。

レゴの儀の投票

スタンプで投票し、スレッドでは思ったより僅差でワイワイしてました笑

レゴの投票スレッドの様子

今回は僅差でくまのプーさんのレゴに決定しました。 こんな感じでワイワイ決めたら、実際にレゴが届くのもちょっとワクワクしてきますね◯

実際に届いたレゴ(でかい...!)

工夫2:レゴ大臣を決めて、各々のスタイルで盛り上げる

リモートワークが中心の昨今ですと、レゴの儀もオンラインでやるようになってました。 そのため、レゴを実際に作る場合は誰か一人が作ることになるので、新しいレゴを決める際にレゴ大臣というレゴを実際に作る人を任命して、作ってもらうようにしています。

ただレゴってやってみると結構地味なんですよね笑 しかもピースが多いとピースを探す時間があったりで見ている側からすると見守る時間が増えちゃいます。 なので、レゴ大臣なりに盛り上げをだんだんするようになりました。

僕がレゴ大臣をやっていた時はレゴを作る時間をYouTuberっぽくするために、YouTubeでよく聴くBGMを集めて流しながらレゴの儀をやりました。

季節に応じて、クリスマスソングやバレンタインソングを流したり、たまには好きなゲームのBGM流したりして、ワイワイ雑談しながらレゴを作ったりしています。

他の方だと、レゴの儀を息子さんにやってもらったりして、ただ見ているだけで癒される時間になったりしました😊

工夫3:レゴ進捗を投稿する

レゴの完成する様子をレゴ大臣が投稿するようにしています。

レゴ投稿1(「部屋とワイシャツと私」をBGMにした時)
レゴ投稿2(完成投稿)
Nice LEGOスタンプ
Slack でNice LEGOスタンプを作ったりしています。

LEGOを作った後はみんなで讃えましょう!NICE LEGO!

まとめ

今日はアジャイル開発を楽しくするレゴの儀をより盛り上がるようにする工夫点を紹介しました。 レゴの儀はリモートワークになって能動的に集まらないと、ちょっとした雑談をする機会も減っているのでチームが集まる一つのきっかけにもできるのではないでしょうか。 もちろん出社した時にみんなでワイワイ作りながらやるのも楽しいのでおすすめです🙌

レゴの儀は僕が入社した時にはすでにイベントとして存在してましたが、こういった日々の開発の中で遊び心を持った自分達のイベントを作ってみたいですね!

最後に今まで作ったレゴたちを紹介して終わりたいと思います。

完成したレゴたち

NICE LEGO!

コネヒトは Go Conference 2023 にシルバースポンサーとして参加しました!当日の様子や感想をご紹介します!

こんにちは @ryoです!

コネヒトは今回 Go Conference 2023にシルバースポンサーとして協賛してブースを出展させていただきました!
本ブログでは当日の様子や感想などをご紹介していきたいと思います!

Go Conference とは

Go Conference 2023 は2023年6月2日(金)に開催されたイベントで今年は10周年を迎える節目となる回でした。

gocon.jp

シルバースポンサーとしてブースを出展するぞ!

コネヒトでは現在メインの開発言語としてPHPを使用していますが、弊社のTech Visionに掲げているように「技術的な選択肢を増やす」「PHPの一般的な弱点(非同期処理など)を補完するため」などを目的としてGoをコネヒトの新たな強みとするべくLet's Go ギルドという有志でチームを結成してGoを社内で活用・推進する活動をしています。その活動の一環としてGo Conference 2023にシルバースポンサーとして初めて参加する事になりました!

以下のブログでは当日のコンテンツの内容なども紹介しているので是非一読してみてください!!

tech.connehito.com

当日の様子

当日は reBako というオンラインでイベント会場を提供できるサービス上でカンファレンスが行われました!
朝の10時に開場し大きいスクリーンの前に徐々に可愛いアバター達が集まり始めてからはオフラインとはまた違うオンラインならではの期待の高まりを感じました...!!

Go Conferenceが始まってからは発表されている方のセッションを聴きつつ色んな方とお話をする事ができました!

自分はオンラインでブースを出展する側として参加するのは初めてだったのですが個人的には以下の2点が嬉しかったです。

  • スポンサーブースに待機しながらも発表中のセッションを聞ける。
  • オフラインよりオンラインの方が緊張もなく手軽&気軽にコミュニケーションができる感じがしたので積極的に参加者の方とお話しが出来た。(自分が直接対面でお話しするのが少し気恥ずかしくなってるだけかもしれませんが笑)

参加者の皆様とはブースを出展されていた他社様も含めお互いのGo事情や会社の事、全然関係無いことから更には合同で勉強会を実施しようなどの話しも出てきたりとてもありがたい有意義な時間となりました!

コネヒトのスポンサーブース

Go Conference を終えて

始めてのGo Conferenceを終えた後に振り返りを実施して以下のような改善点が発見できました。

  • ブース出展の準備の段階でどのようなタスクがあってをそれをどう分担していくか
  • ブースに来てもらう為の集客設計

参加メンバーから次は登壇したいなど更にGoへのモチベーションも上がり大変充実したカンファレンスになりました!
これからも積極的にGoコミュニティへの貢献をしていきたいと思います‼︎

Go Conference 2023で貰えるノベルティ

コネヒトはGo Conference 2023にシルバースポンサーとして協賛します!

こんにちは!@TOC です。 今回は弊社が協賛する Go Conference と企業ブースについて紹介します。

Go Conference 2023に協賛いたします

コネヒトではメインプロダクトである「ママリ」を始めとして開発のメイン言語として PHP を採用しています。 ただ、弊社Tech Visionでも掲げているように「Let's Go」という PHP 以外の技術的な武器を手に入れるための取り組みを行っております。 今期では Let's Go ギルド、という有志のチームを作り、社内に Go を浸透させる取り組みを行なっております! その活動の一環として、今回は Go Conference 2023 にシルバースポンサーとして協賛させていただくことになりました。

イベント概要

gocon.jp

ブースも出します!

今年はコネヒトはシルバースポンサーとして企業ブースも出します! 企画としては

1. あなたは何問正解できる!? ママリドリル

弊社のオンボーディングでも使われているママリドリルというコンテンツを紹介します! ママリのコミュニティ作りのちょっとした秘密を見れますので、気になる方は遊びに来てください!

確実に弊社のメンバーが待機してるのは下記の時間帯です!

  • 12:00~14:00
  • 15:00~17:00

2. 開発部長に気になることを聞いちゃおう!(13:00~14:00予定)

弊社開発部の部長と気軽に気になることを聞ける場を用意しようと思います! フリーテーマもありますので、気になることをガシガシ聞いてみてください〜!

トークテーマ一覧

また、会社情報やコネヒトとGoの関係を記載した企業ボードも準備しております! 可愛く仕上がっておりますので、ぜひ覗きに来てください🙌

企業ボード

最後に

ここまで読んでくださった皆様、ありがとうございました。 今回はビンGoという企画も行なっており、弊社の問題の解答は企業ボードのどこかにあります...! ぜひ探してみてください😀

それでは、当日楽しみましょう!

LangChainとOpenAI APIを組み合わせて、文脈を考慮して会話できるSlack Botを作った話

みなさんこんにちは。AI・検索チームのたかぱい(@takapy0210)です。

最近猫を飼い始めました。名前は「きぬ」ちゃんです。名前からして可愛いのが伝わると思うのですが、とっても可愛いです。

さて、昨今大規模言語モデル(Large Language Model: LLM)の発展により業界では日々新しい話題が飛び交っています。例に漏れず弊社内でもLLMを用いた施策のPoCなどを進めていっている段階です。

今回は社内向けの施策として、Open AIのAPIを用いたSlack Botを開発した話をしようと思います。

いわゆる「ChatGPT × Slack Bot」の開発記事などは多く出回っていると思いますが、今回はLangChain と組み合わせることで、Web版のChat GPTのように過去の会話を記憶させながらSlack上でAIとコミュニケーションさせる、という部分にフォーカスを当てながら書いていますので、興味のある方は立ち読みしていってください。

ちなみに、このような感じでslackの同一スレッド内であれば、過去の会話を考慮しながらコミュニケーションしていくことができます。
(ちなみに、この [default.build] は使えないことが後に分かりました・・・😇 )


目次


LangChainとは?

LangChainはGPT-3のようなLLMを利用したサービス開発を支援してくれるライブラリです。
複数のモジュールを組み合わせることで、複雑なLLMアプリケーションを作成するプロセスを簡易化してくれます。
2023年5月現在、PythonとTypeScript(JavaScript) 版が提供されており、本記事はPython版を利用し開発しています。

Python版の公式ドキュメント
python.langchain.com

提供モジュールの種類

上記ドキュメントを見るとわかるのですが、大別して以下6つのモジュールに整理できます。

  • Models
    • 共通のインターフェースを通じて各 LLM API を使うためのモジュール
  • Prompts
    • モデルへの入力を組み立てるモジュール
  • Indexes
    • 独自のテキストデータをLLMと組み合わせて使いたいときに使えるモジュール
  • Memory
    • LLMとのやり取りを保持するためなどに使用するモジュール
  • Chains
    • 各モジュールを連結し実行することのできるモジュール
  • Agents
    • LangChainが提供しているさまざまなツールを使用して、より高度な回答を生成するためのモジュール。ユーザの入力に応じて、与えられたツールからどのツールをどういう順序で実行するかをLLM自身が決定していく。

今回使用したのは、Models, Prompts, Memory, Chainsです。

Slack Botを開発する際の事前準備

今回紹介するBotを作成するのに、必要な事前準備があります。ざっくり以下2点です。

  • Open AI APIの利用登録、API Key取得
  • Slack Bot周りのToken取得

Open AI APIの利用登録、API Keyの取得

OpenAI の Web サイトにアクセスし、右上の"Sign In"ボタンをクリック後、アカウント登録をします。
その後、API keysページからAPI Keyの発行ができるようになります。(このKeyは後ほど使います)

Slack Bot周りのトークン発行

ここが一番面倒臭い大変かもしれません。

以下のページに従って、Tokenの発行を行います。

slack.dev

今回は上記ドキュメントに書かれている以下2つのTokenが必要になります。

Bot tokens :ボットユーザーに関連づけられ、1 つのワークスペースでは最初に誰かがそのアプリをインストールした際に一度だけ発行されます。どのユーザーがインストールを実行しても、アプリが使用するボットトークンは同じになります。ほとんどのアプリで使用されるのは、ボットトークンです。
App-level tokens :全ての組織(とその配下のワークスペースでの個々のユーザーによるインストール)を横断して、あなたのアプリを代理するものです。アプリレベルトークンは、アプリの WebSocket コネクションを確立するためによく使われます。

上記ドキュメントを参考に、もろもろの手順を踏んだあとであれば、slack appのページに飛んで、該当のボットを選択した後、以下スクショの箇所にTokenが生成されているはずです。(このTokenも後ほど使います)

Bot tokens

App-level tokens

]

Slack Botの開発とサンプルコード

今回はslackのBoltフレームワークを用いてBotの開発を行っています。

コードの全体像は以下のようなイメージです(本当はリポジトリを公開したかったのですが、弊社オリジンの処理が入る可能性もあるため、ここではあくまでサンプルとして、コメントやコードの一部を改変して共有します。が、基本的にこのままでも問題なく動作するはずです)

import concurrent.futures
import os

import openai
from dotenv import load_dotenv
from langchain import LLMChain, PromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

load_dotenv()
SLACK_BOT_ID = "BotユーザーのIDを設定"
app = App(token=os.environ["SLACK_BOT_TOKEN"])  # 発行したBot tokensを設定
openai.api_key = os.environ["OPENAI_API_KEY"]  # 発行したOpenAIのAPI Keyを設定


def send_chat_request(prompt: PromptTemplate, memory: ConversationBufferMemory, input_message: str) -> str:
    """OpenAIの言語モデルにチャットメッセージを送信しレスポンスを生成する
    """
    try:
        llm = ChatOpenAI(model_name="gpt-3.5-turbo")
        chatgpt_chain = LLMChain(
            llm=llm,
            prompt=prompt,
            verbose=False,
            memory=memory,
        )
        return chatgpt_chain.predict(human_input=input_message)

    except Exception as e:
        return ""


def call_function_with_timeout(func, timeout_seconds, *args) -> dict:
    """ターゲット関数を別のスレッドで実行し、タイムアウトを適用する
    """
    with concurrent.futures.ThreadPoolExecutor() as executor:
        future = executor.submit(func, *args)
        try:
            response = future.result(timeout=timeout_seconds)
            return {"result": response}
        except concurrent.futures.TimeoutError:
            return {"error": "Function execution timed out"}


def remove_slack_id_from_text(text: str, slack_id: str) -> str:
    """textからslack_idを削除する
    """
    return text.replace(f"<@{slack_id}>", "")


def create_prompt() -> PromptTemplate:
    """OpenAIの言語モデルに入力するプロンプトを作成する
    """

    template = """Assistant is a large language model trained by OpenAI.
        必要であればここにプロンプトを追加する

        {history}
        Human: {human_input}
        Assistant:"""
    prompt = PromptTemplate(input_variables=["history", "human_input"], template=template)

    return prompt


def create_memory(user_id: str, channel: str, thread_ts: str) -> ConversationBufferMemory:
    """会話のメモリーを作成する
    """

    memory = ConversationBufferMemory(human_prefix="Human", ai_prefix="Assistant", memory_key="history")
    if thread_ts is not None:
        thread_message = app.client.conversations_replies(channel=channel, ts=thread_ts)

        # 過去の会話のやり取りをメモリーに追加していく
        for i in thread_message["messages"]:
            if i["user"] == user_id and SLACK_BOT_ID in i["text"]:
                memory.chat_memory.add_user_message(remove_slack_id_from_text(i["text"], SLACK_BOT_ID))
            elif i["user"] == SLACK_BOT_ID and user_id in i["text"]:
                memory.chat_memory.add_ai_message(remove_slack_id_from_text(i["text"], user_id))

    return memory


@app.event("message")
def handle_message_events(body):
    """slackにメッセージが投稿されたときのイベントハンドラー
    """
    return None


@app.event("app_mention")
def chatgpt_reply(event, say):
    """slackでメンションされたときのイベントハンドラー
    """

    # slackからの情報を取得する
    input_message = event["text"]
    thread_ts = event.get("thread_ts") or None
    channel = event["channel"]
    user_id = event["user"]

    # プロンプトテンプレートの作成
    prompt = create_prompt()

    # メモリ(過去の会話履歴)の作成
    memory = create_memory(user_id, channel, thread_ts)

    # slack botのIDがメッセージに含まれるので、削除する
    input_message = remove_slack_id_from_text(input_message, SLACK_BOT_ID) 

    # ChatGPTにメッセージを投げる
    timeout_seconds = 2 * 60  # タイムアウト時間
    response = call_function_with_timeout(send_chat_request, timeout_seconds, prompt, memory, input_message)

    # 返信文の生成
    if "error" in response or response["result"] is None:
        # エラー時の処理
        reply_text = f"<@{user_id}>\nごめんなさい。現在サーバーの負荷が高いため処理できませんでした。時間をおいて再度質問してください。"
    else:
        # 正常時の処理
        reply_text = response["result"]
        reply_text = f"<@{user_id}>\n{reply_text}"

    # slackのスレッドに返信する
    if thread_ts is not None:
        parent_thread_ts = event["thread_ts"]
        say(text=reply_text, thread_ts=parent_thread_ts, channel=channel)
    else:
        response = app.client.conversations_replies(channel=channel, ts=event["ts"])
        thread_ts = response["messages"][0]["ts"]
        say(text=reply_text, thread_ts=thread_ts, channel=channel)


if __name__ == "__main__":
    handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])  # 発行したApp-level tokensを設定
    handler.start()

以降では、LangChainのModels, Prompts, Memory, Chainsがどの部分に該当するのか解説しつつ、補足していきます。

Prompts

Prompt Templates を用いて、以下のようにプロンプトのテンプレートを作成します。テンプレートの中に変数(historyhuman_input)を埋め込むことができるのが特徴としてあげられます。

これにより、後述する過去の会話もこのテンプレートに沿って記録させていきます。

def create_prompt() -> PromptTemplate:
    """OpenAIの言語モデルに入力するプロンプトを作成する
    """

    template = """Assistant is a large language model trained by OpenAI.
        必要であればここにプロンプトを追加する

        {history}
        Human: {human_input}
        Assistant:"""
    prompt = PromptTemplate(input_variables=["history", "human_input"], template=template)

    return prompt

Memory

ConversationBufferMemory を用いて、過去の会話を記憶させていきます。

今回は同一スレッドでかつ、同一ユーザとBotの発言のみを記憶させることで、他の人がスレッド内で発言しても、それは文脈から除外するようにしています。

これにより、社内メンバーとのコミュニケーションの隙間に、違和感なくAIを介入させることができます。

このMemoryモジュールは、過去どのくらいの会話までを記憶させるか指定できる ConversationBufferWindowMemory や、過去の会話の要約を作成し、それを文脈としてプロンプトに与えることのできる ConversationSummaryBufferMemory などもあります。

def create_memory(user_id: str, channel: str, thread_ts: str) -> ConversationBufferMemory:
    """会話のメモリーを作成する
    """

    memory = ConversationBufferMemory(human_prefix="Human", ai_prefix="Assistant", memory_key="history")
    if thread_ts is not None:
        thread_message = app.client.conversations_replies(channel=channel, ts=thread_ts)

        # 過去の会話のやり取りをメモリーに追加していく
        for i in thread_message["messages"]:
            if i["user"] == user_id and SLACK_BOT_ID in i["text"]:
                memory.chat_memory.add_user_message(remove_slack_id_from_text(i["text"], SLACK_BOT_ID))
            elif i["user"] == SLACK_BOT_ID and user_id in i["text"]:
                memory.chat_memory.add_ai_message(remove_slack_id_from_text(i["text"], user_id))

    return memory

Models & Chains

Chat ModelsLLM Chain を用いて、OpenAIの gpt-3.5-turbo モデルと、これまで作成したPromptsとMemoryを連結し実行するように設定しています。

ユーザがslack上で入力した最新のテキストをpredictさせることで一連のChainが実行され、AIの回答を得られる、といった仕組みです。

def send_chat_request(prompt: PromptTemplate, memory: ConversationBufferMemory, input_message: str) -> str:
    """OpenAIの言語モデルにチャットメッセージを送信しレスポンスを生成する
    """
    try:
        llm = ChatOpenAI(model_name="gpt-3.5-turbo")
        chatgpt_chain = LLMChain(
            llm=llm,
            prompt=prompt,
            verbose=False,
            memory=memory,
        )
        return chatgpt_chain.predict(human_input=input_message)

    except Exception as e:
        return ""

番外編:timeout

OpenAI APIは調子が悪いタイミングも時々あります。

その時にユーザを長い間待たせるのは体験として良くないので、特定の時間が経過したらタイムアウト処理として扱うような処理も入れています。

def call_function_with_timeout(func, timeout_seconds, *args) -> dict:
    """ターゲット関数を別のスレッドで実行し、タイムアウトを適用する
    """
    with concurrent.futures.ThreadPoolExecutor() as executor:
        future = executor.submit(func, *args)
        try:
            response = future.result(timeout=timeout_seconds)
            return {"result": response}
        except concurrent.futures.TimeoutError:
            return {"error": "Function execution timed out"}

まとめ・所感

今回はLangChainのキャッチアップがてら、社内で利用できるAI Chat Botを開発してみました。

フレームワークに乗っかれば、同じインターフェースでいろいろな機能(今回で言うとMemory部分など)を使うことができるので、まさに「LLMアプリケーションを作成するプロセスを簡易化」してくれるなぁと感じました。

また、弊社のように自社にそこそこ大きなテキストデータがあれば、Indexesモジュールなどを組み合わせることで、独自のAI Chat Botなどを作ることもできそうなので、そういった機能も積極的に検証していきたいと思いました。

We Are Hiring !!

コネヒトでは一緒に働く仲間を募集しています!

www.wantedly.com

機械学習に関しては、過去の取り組み事例や今後の展望などを以下にまとめていますので、是非見てみてください!

tech.connehito.com

そして興味持っていただけた方はカジュアルにお話しましょう! 事前準備は不要ですのでお気軽にご連絡ください。

カオナビさん、スタフェスさんと合同交流会を開催しました!

こんにちは。2017年11月にAndroidエンジニアとしてjoinした@katsutomuです。

2023/04/20に株式会社カオナビスターフェスティバル株式会社両社の皆様と、オフラインの合同交流会を開催しました!

2月に開催したイベントを踏襲し、スタフェスさんにもお声がけして、3社合同での開催となりました。今回はイベントの様子をおすそ分けできればと思います。

前回のイベントブログはこちら tech.connehito.com

前回からのアップデート

変えたこと

前回のイベントを好評で終えられたことを受け、今回は2つのTryを実施しました。

  • 参加者をPdMメンバーに広げ、プロダクト開発全般で交流をする
  • 参加企業を増やし、より多様な文化に触れて刺激を得る

スタフェスの吉田あひるさんにお声がけし、二つ返事で参加の意向をいただけたので、開催までスムーズに進められたこと、感謝しております!

変えなかったこと

所属してるエンジニアに刺激になるイベントをやりたいという趣旨のもと、交流会とLT発表を継続しています。 好評だったその場で登壇への反応やFBをもらえる、 改まった場では得られない社外のリアルな声を聞ける効果を狙うために、引き続きクローズドな形式で開催をしています。

イベントの内容

各社2~3名のLTと交流会が主な内容になっています。 事前に組んだタイムラインは以下の通りです。

  • 18:30 - オープニング
    • 自己紹介
    • 懇親会
  • 19:30 - LT発表タイム
    • LT1 ~ LT4
    • 休憩
    • LT5 ~ LT8
    • 飛び入りLT
  • 20:45 - クロージング
    • LTの感想会 & 片付け
    • 締め

前回の開催で飛び入りLTが生まれたこともあり、事前にタイムスケジュールに組み込みました。 また発表者がLTへの質問やフィードバックをより受け取れるように、終わりの時間に多少の余白を持たせています。

会場

今回も、カオナビさんのオフィスに遊びに行かせてもらいました。 広いイベントスペースがあり、合計35名程度の参加者でもゆっくりとイベントを楽しむことができました。

交流会

今回はPdM/PMメンバーも参加し交流を行いましたが、各社のステージやサービス特性により同じ職種でもギャップがあり、自分の業務に持ち帰れるような刺激をもらえたようです。

LT枠

当日の飛び入りも含め、合計8名の発表が行われ、大いに盛り上がりました。 職種を広げたことで、より話題の幅が広がり会社に持ち帰れる内容も多くく刺激になりました!

実施後のアンケート

実施後に、参加した弊社メンバーにアンケートを実施しました。 設問は以下の通りです。

  • 今回のイベントに期待してたことを教えてください
  • LTで期待していたことは、得られましたか
  • 交流会で期待していたことは、得られましたか
  • LTや交流の中で、持ち帰ってTryしたい他社の事例はありましたか
    • その理由を教えてください
  • また次回あったら参加したいですか

結果を見ると、職種を広げたことが、ネガ/ポジ両方にふれていることがわかりました。

アンケート結果

今回のイベントに期待してたこと(抜粋)

  • とにかく他社の人と話したい!雑に話せる機会減ってる!
  • 他社との技術的な交流を通して新しい知見、業務に活かせることをinputする
  • 社外PdMとの会話、課題感共有
  • 自身のアウトプット、他社PdMとの繋がり

定量評価

※次回参加に対するいいえの回答も、前回の濃度の高さを評価してくれているものです。

具体的なお声(抜粋)

  • PullRequestにコメント書くならソースにコメントした方がいいよという話はTryしようかなと思いました。
  • POの心得やスクラムに対しての姿勢など参考にしたい
  • 対象範囲が広いと散文的になって専門領域を深掘りして話せないという課題もあると思うので、両方のニーズを満たす手段は何があるだろうなと答えのない海に潜り始めています。
  • 職種広がると共通の話題見つけるの難しいというか交流ムズそうな印象持ちました!

まとめ

職種を広げたことに加え、スタフェスさんに参加いただいたことで、前回よりも話せるテーマが広がったことは、ポジティブだったかと思っています。 特に弊社のPdMメンバーは、コネヒトに入社後に、本格的にPdMの仕事を始めたメンバーが多く、今回のイベントで、社外の課題や取り組みを知れたことがおおいに刺激になったようです。

反面、専門職種ならではの交流という点では、職種を限定するほうが思い切った議論ができることがわかったので、今後のイベント運営に活かしていければと思っています。

今後もアップデートしながら継続開催していきたいと思いうので、興味のある方はぜひお声がけください!

本番環境のAmazon Aurora MySQL v1(MySQL 5.6 互換)EOL対応でAmazon RDS Blue/Green Deploymentsを使ってアップグレードしてみた手順と所感

こんにちは。コネヒトのプラットフォームグループでインフラエンジニアをしている@sasashuuuです。

本日は、今年2月に対応した弊社の主力サービスであるmamariをはじめとする各種アプリケーションで使用している本番環境Amazon Aurora MySQL v1(MySQL 5.6 互換)のEOL対応において、まだリリースされてから間もない新機能であるAmazon RDS Blue/Green Deploymentsを本番導入した際の流れや所感についてまとめたいと思います。

はじめに

本記事の内容は実際にEOL対応に向けての検証を行っていた際や、EOL当日対応時の状況などを含んでおり、取り扱っているAWSサービスの仕様やその他記載内容は当時のものとなっています。最新の情報などはAWSの公式ドキュメントを参照してください。(公式ドキュメント先リンクは次項「Amazon RDS Blue/Green Deploymentsとは」を参照してください。)

Amazon RDS Blue/Green Deploymentsとは

Amazon RDS Blue/Green Deploymentsは2022年の12月にGAでリリースされた新機能です。

新機能 – Amazon Aurora と Amazon RDS でのフルマネージド型 Blue/Green Deployments

数ステップで本番環境と同期されたステージング環境を作ることができ、また、本番環境への昇格なども行えます。補足として、以降「ステージング環境」と「Green環境」という2つの用語が出てきますが、基本的に同義だと思っていただいて大丈夫です。

今回の弊社でのユースケースのようにEOL対応に伴うアップグレードに利用する他、パラメータのチューニングやスキーマ変更のテストなどさまざまなシチュエーションで利用することができます。詳しい内容は下記の公式ドキュメントを参考にしてください。(RDSとAuroraではドキュメントのページが変わりますのでご注意ください。)

また、本記事ではAuroraを対象に解説を行うため、以降記事内で紹介する公式ドキュメントのリンクはAuroraを対象としたものとなっておりますのでご了承ください。

なぜ使おうと思ったのか

EOL対応を行う上での対応方針として、大枠としてはざっくりと2つの方針に分かれるかと思います。

  • インプレースアップグレード
  • 待機系の環境を準備した上でのアップグレードおよび切り替え

それぞれ下記のメリデメなどがあるかと思います。

メリット デメリット
インプレースアップグレード ・手順がシンプル
・アーキテクチャが複雑にならない
・計画停止時間およびダウンタイムが長くなる
・アップグレードに失敗した場合のデータ破損に備える必要がある
待機系の環境を準備した上でのアップグレードおよび切り替え ・計画停止時間およびダウンタイムが短くなる
・切り戻しが行いやすいような構成を作ることができる
・データの不整合のリスクが生じる
・アーキテクチャが複雑になる恐れがある

今回選定したAmazon RDS Blue/Green Deploymentsは後者の方針に近いものですが、それらをマネージドな形で提供してくれる新機能となっています。

上記のような観点なども踏まえ、結果的に下記の理由からAmazon RDS Blue/Green Deploymentsを選定することに決めました。

  • 計画停止およびダウンタイムの時間をなるべく短縮したかった
  • EOLの期日が迫っていたこと、EOL対応に関するノウハウ・ナレッジが十分にはなかったことなどから、自前でアーキテクチャを構築するコストを削減すべく、それらを提供してくれるマネージドな機能を利用する方が効率的だと考えた
  • アップデートされた新機能を本番導入するという先進的な事例づくりおよび技術的なチャレンジを行いたかった

Amazon RDS Blue/Green Deploymentsを使ったEOL対応の流れ

下記は事前準備から本番対応完了までのざっくりとした流れです。前提として、Amazon RDS Blue/Green Deploymentsに限らず汎用的にEOL対応で必要になる手順などは、本記事のスコープ外として内容を割愛をしています。

  1. Amazon RDS Blue/Green Deploymentsの利用に必要な事前準備を行う(切り替え実施日以前に余裕を持って実施)
  2. ステージング環境の作成を行う(切り替え実施日の前日に実施)
  3. スイッチオーバーでステージング環境を本番環境と切り替える
  4. ステージング環境を削除する(切り替え実施日の当日に実施)

順を追って説明していきます。

1. Amazon RDS Blue/Green Deploymentsの利用に必要な事前準備を行う(切り替え実施日以前に余裕を持って実施)

事前準備についてざっと以下の対応を行いました。

  • Amazon RDS Blue/Green Deploymentsの利用条件を満たしているかの確認
  • 技術検証および開発環境でのテスト
  • binlog_format パラメータの変更
  • パラメータを変更してからのリブート(再起動)漏れがないかの確認
  • 対象Auroraリソースに紐づくサブネットグループを3az以上にする

Amazon RDS Blue/Green Deploymentsの利用条件を満たしているかの確認

そもそもの利用条件を満たしているかを確認します。これをクリアしないことには機能を利用することができないため、必ずはじめにやりましょう。公式ドキュメントの Limitations for blue/green deployments に制限事項の記載がありますそちらで確認してください。

技術検証および開発環境でのテスト

利用条件をクリアしたのちに技術検証および開発環境でのテストを行いました。大きく分けてざっくり以下のような段階を踏んでの検証作業やテストを行いました。

  • 開発環境や本番環境とは分離された検証用のAuroraリソースを別途作成した上で、実際にAmazon RDS Blue/Green Deploymentsを試し、仕様などをざっくりと把握する
  • 開発環境を用いて、Amazon RDS Blue/Green Deploymentsを含めたEOL対応の流れを可能な限り加味した上で、一連のオペレーションを実施する
  • Amazon RDS Blue/Green Deploymentsにより切り替わった開発環境のAuroraリソースをもとに稼働しているアプリケーションが問題なく動いているかをテストする

なお、具体的な検証内容やテストについては、本記事では割愛をさせていただきます。

その他の作業

  • binlog_format パラメータの変更
  • パラメータを変更してからのリブート(再起動)漏れがないかの確認
  • 対象Auroraリソースに紐づくサブネットグループを3az以上にする

これらに関しては、以前執筆した下記のブログで取り上げていますので、詳しくはそちらをご参照ください。

Amazon RDS Blue/Green Deployments利用時に躓いたエラーやハマりポイントなどをまとめてみた - コネヒト開発者ブログ

2. ステージング環境の作成を行う(切り替え実施日の前日に実施)

EOL対応の対象Auroraクラスターを選択し、ステージング環境の作成を行います。

基本的にスイッチオーバーさえ行わなければ、本番リソースのクローンが作成され、データが同期されるだけという認識であり、稼働中のサービスへの影響はないと判断しました。そのため当日作業の時間短縮を狙い、前日時点でステージング環境の作成を行なっておくことにしました。

AWSコンソールより RDS > データベース > 対象クラスターへ遷移し、「ブルー/グリーンデプロイの作成」を選択します。

ステージング環境の識別子、エンジンバージョン、割り当てるパラメータグループ(クラスター、インスタンスそれぞれ1つずつ)の選択を求められるので、設定を行います。

Amazon Aurora MySQL v2(MySQL 5.7 互換)へのアップグレードのため、エンジンバージョンは当時の最新の 2.11.1 を選択し、v2用に新たに作成していたパラメータグループを設定しました。

ステージング環境作成後は以下のように元のリソースにツリー状にぶら下がる形でGreenと表記されたリソースがAWSコンソール上へ現れます。

また、ステージング環境作成後の注意点ですが、新しく設定したステージング環境のパラメータグループを同期させるためにステージング環境のAuroraインスタンスをリブートする必要があるため、その対応も行いました。結果としてインスタンスごとのパラメータグループのステータスが以下のように「同期中」となっていればOKです。(この対応は先述した事前対応におけるパラメータグループ同期のためのリブート対応とは異なるものであるためご注意ください。)

3. スイッチオーバーでステージング環境を本番環境と切り替える

当日は計画停止をし、ユーザーからのアクセスが途絶えたことを確認したのち切り替えの作業を行いました。

切り替えの手順としては、Blue/Greenのリソースとは別に「ブルー/グリーンデプロイ」というロール名のついたリソースが存在するのでそれを選択し、アクション > 切り替え の順に切り替えを行っていきます。なお、切り替えの際にはタイムアウト値(min)を設定できます。今回は15minを設定しました。尚、タイムアウトの挙動としては、設定値の時間内に切り替えが行われなければ、ロールバックされ、Blue/Greenいずれの環境も変更は加えられないという挙動になります。公式ドキュメントには「ワークロードにもよりますが、通常は 1 分以内ですばやく切り替えます。」との記載があります。詳しくは下記の公式ドキュメントを参照してください。

実際に切り替え実施後は、切り替え完了ステータスになった「ブルー/グリーンデプロイロールのリソース」と 「-old1 というサフィックスがついたBlue環境のクラスターおよびインスタンス」が独立します。

弊社の場合、今回は計画停止を伴うため、DBへの疎通確認レベルでダウンタイムを計測するなどの確認は行なっておりませんでした。AWSコンソール上で確認したところ、Blue/Green環境のリソースが分離され、利用可能な状態を確認できるまでに15分弱ほどの時間がかかり、先述したタイムアウト値はギリギリの状態での切り替え実施となりました。

4. ステージング環境を削除する(切り替え実施日の当日に実施)

前項で触れた独立したBlue環境のリソースを計画停止の解除前(サービスイン前)に削除しました。不要になったリソースのため削除のタイミングはいつでも問題ないかと思いますが、万が一古いリソースが参照されてしまったという事故が発生した際にいち早く検知できると考えたため今回は念のためのスナップショットを取得したのち速やかに削除を行いました。

使ってみたメリット・デメリット

メリット

Amazon RDS Blue/Green Deploymentsを使うメリットに関しては公式ドキュメントの Benefits of using Amazon RDS Blue/Green Deployments にも記載されていますが、実際に使ってみて感じた弊社の実務におけるメリットに関しては以下のような点があると思いました。

  • 複雑なアーキテクチャや段取りを自前で組む必要がない
  • オペレーションが簡素
  • インプレースアップグレードに比べ計画停止の時間を短縮できる

順に説明をしていきます。

複雑なアーキテクチャや段取りを自前で組む必要がない

Green環境の作成とレプリケーションの開始、スイッチオーバー時の書き込みロックや切り替え失敗時のロールバック等々、マネージドな形で行ってくれるので、事前に複雑なアーキテクチャや段取りを準備するコストがカットできました。また、スイッチオーバーと共にBlue環境で使用していた識別子やエンドポイントなどはそのまま本番昇格後のGreen環境へと引き継がれるため、参照元の洗い出しおよび参照先の切り替え作業等が不要になり、アプリケーション側の変更などを意識しなくてもよくなる点も大きな魅力でした。

オペレーションが簡素

新機能ということもあり、事前準備や検証などには苦労しましたが、ステージング環境作成〜切り替えまでのオペレーションについては、わずか数ステップの手順を踏むだけで、一連のフローを実施できるため、オペレーションが簡素で扱いやすかったです。

インプレースアップグレードに比べ計画停止およびダウンタイムの時間を短縮できる

例えば、インプレースアップグレードなどを行う場合にはざっくり下記のような手順になるかと思います。

  1. 計画停止
  2. 本番環境リソースのインプレースアップグレード
  3. 計画停止解除

しかし、先述した手順にも記載のようにAmazon RDS Blue/Green Deploymentsを使って事前にステージング環境を作成し、当日は切り替える作業のみを行う段取りとすれば、「2. 本番環境リソースのインプレースアップグレード」のアップグレードの待ち時間がカットできるため計画停止およびダウンタイムの時間を短縮することができます。

デメリット

逆にデメリットについては下記のような点を感じました。

  • 本番導入することの懸念要素
  • 切り戻しを考慮する場合は別途自前のアーキテクチャや段取りの準備が必要
  • スイッチオーバーの所要時間が予想外にかかり見積もれない場合がある

本番導入することの懸念要素

検証時に気になった挙動や不可解なエラーなどはサポートへの問い合わせを行うなどし、対応しておりました。例えば下記の内容のものなどです。

  • Green環境作成前のBlue環境に対する何らかの書き込み操作が必要となる問題
  • 作成したステージング環境のインスタンスの aurora_server_id の値がスイッチオーバー後もグリーン環境の値が引き継がれ、残ってしまう問題

Green環境作成前のBlue環境に対する何らかの書き込み操作が必要となる問題

これに関しては、先述した Amazon RDS Blue/Green Deployments利用時に躓いたエラーやハマりポイントなどをまとめてみた - コネヒト開発者ブログ にも記載しており、担当部署にて修正すべき問題として認知済みとのことでした。

作成したステージング環境のインスタンスの aurora_server_id の値がスイッチオーバー後もグリーン環境の値が引き継がれ、残ってしまう問題

これに関しては、本記事や以前紹介したブログなどで詳しくは取り上げておりませんが、スイッチオーバー後もGreen環境の aurora_server_id の値が、リブート済みであるにもかかわらず、昇格した本番環境に適用されてしまうという現象が見られたため、サポートへ問い合わせておりました。結果、AWS様で抱える問題だったようで、開発担当部署にフィードバックを行うとの返答を受けました。とはいえ、aurora_server_id の値を使用するケースはそれほどないかと思いますので、あまりクリティカルな問題ではないかと思われます。

このように、機能特有の問題がいくらか見られました。そのため、まだ認知されていない問題などが本番作業時に発覚するリスクも0とは言えないため、本番導入するにあたり多少の懸念要素などは少し残りそうという印象でした。

切り戻しを考慮する場合は別途自前のアーキテクチャや段取りの準備が必要

こちらはメリットに挙げた内容の裏返しです。Amazon RDS Blue/Green Deploymentsは非常に便利なマネージドな機能ですが、切り戻しを考慮した仕様はありません。(切り替えに失敗した場合のロールバック機能は有)そのため、切り戻しを考慮する場合は双方向のレプリケーションの設計やバックアップからの復元および差し替え手順の準備などを自己責任で準備し、実施する必要があります。

スイッチオーバーの所要時間が予想外にかかり見積もれない場合がある

先述したように、ドキュメントには「ワークロードにもよりますが、通常は 1 分以内ですばやく切り替えます。」との記載がありますが、通常の目安以上の所要時間を超える場合があります。現に今回の対応では切り替えに十数分程度の所要時間がかかりました。このスイッチオーバー自体のオペレーションは強行で実施したり、実行中の状況を確認したりといったことはできかねるものかと思います。そのため、予期せぬ形でスイッチオーバーに多くの時間がかかってしまうことを許容できない場合は対策が必要となります。

おわりに

今回個人的には初めてのデータベースのEOL対応ということもあり、大変なことが多かったですが、ちょうどタイミング良くリリースされたAmazon RDS Blue/Green Deploymentsをうまく活用し、乗り切ることができました。皆さんも機会があれば是非ためしてみてください。

PHP8.1アップデートまでの道のり

こんにちは。コネヒトでバックエンドエンジニアをしております@homiTakahashiです。

今回は私たちの使用している言語PHPの8.1バージョンまでアップデートについて話していけたらと思います。

前提として私たちのアップデートしたいプロジェクトは以下のような環境です。

  • PHP7.4
  • フレームワークはCakePHP4.2
  • dockerを使用
  • BaseImageはalpine

そこでPHP8.1にする上でCakePHP4.2→4.3にバージョンを上げないといけないことが分かり段階を踏むように以下のように進めました。

作業手順

1. DockerImageを変更する

PHPのバージョンを指定し、dockerでコンテナが安定して起動するようにエラー文を読みながら対応していきます。alpineのバージョンは古いとPHP8.1に対応していないのでバージョンを上げる必要がありました。

FROM alpine:3.16
︙
RUN apk add --update-cache --no-cache \
    php81 \
    php81-dev~=8.1 \
︙

主に以下のようなミドルウェアのアップデートが必要になってくると思われます。

redis-3.1.1 \ → redis-5.3.6 \

2. テストコードからエラーや非推奨を対応していく

弊社はテストコードを導入していたため、CIなどで一旦全てのテストを回し一つずつエラーや非推奨を潰していきました。

例1)

PHPUnit\Framework\Exception: This test uses TestCase::prophesize(), but phpspec/prophecy is not installed. Please run "composer require --dev phpspec/prophecy".

prophesize はPHPUnit 10 でビルトインではなくなるため、ライブラリに phpspec/prophecy-phpunit を追加し、利用箇所で明示的にTraitを呼び出すように修正しました

composer require --dev phpspec/prophecy

※PHP8.1の非推奨はこちら

例2)

Deprecated Error: Return type of Frlnc\Slack\Http\SlackResponse::jsonSerialize() should either be compatible with JsonSerializable::jsonSerialize(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice

外部ライブラリのfrlnc/php-slack を利用してSlack連携を行っていたのですが、frlnc/php-slack ではPHP8.1対応が行われていなかったので、ForkされているレポジトリをGitHub上で検索し、対応しているレポジトリを利用するように composer.json の記述を変更しました。

"repositories": [
      {
        "name":"frlnc/php-slack",
        "type":"vcs",
        "url": "https://github.com/deqngeorgiev/php-slack"
      }
    ],
"require": {
        "frlnc/php-slack": "dev-master",
}

3. 外部サービス周りの動作確認をする

テストコードでは拾いきれないSentryやPapertrailなどの外部サービスは手動で動作確認をしました。

4. リリースをする

リリースをしていく上で、弊社では影響範囲を少なくするようにカナリアリリースを使用しました。カナリアリリースとは、全ユーザーにいきなりリリースするのではなく1%~30%くらいまで様子を見ながら徐々にリリースしていく方法です。

このような手順をPHP8.0、CakePHP4.3、PHP8.1と3度繰り返しアップデート作業を行いました。

感想

大変だったこととしてはバージョンのアップデートをしてテストを回した際、かなりの量の非推奨エラーが出ていたことです。 それらを一つ一つ調査→修正→レビューを繰り返し行うとかなりの作業量になります。私たちチームは以下のように工夫することによって結果としてスムーズに取り組めたのかなと思っております。

  • いきなりバージョンを8.1にするのではなくスコープを分けて小さく行う
  • 非推奨対応はチームで分担して取り掛かる

またテストコードがあることで対応箇所が可視化できるので、テストコードの重要性も改めて気付きました。

次のステップとしてはPHP8.1の新機能を取り入れ、PHP8.2にアップデートしていきたいと思っております!!

関連記事

tech.connehito.com

tech.connehito.com

コネヒトでは一緒に働く仲間を募集しています! そして興味持っていただけた方は気軽にご連絡ください! https://www.wantedly.com/companies/connehito/projects