TECH PLAY

ニフティ株式会社

ニフティ株式会社 の技術ブログ

491

はじめに こんにちは。ニフティ株式会社の高垣と申します。 私が所属しているチームでは、会員様向けのお問い合わせに対応するコールセンターの業務改善にAIを活用しています。その中で、Amazon BedrockのLLMを呼び出すLambdaを実装する際に 「LLMの出力を安定してJSON形式で受け取りたい」 という課題にぶつかりました。 本記事では、この課題を Converse APIのTool Use で解決した方法をご紹介します。BedrockでLLMの出力を構造化データとして扱いたい方の参考になれば幸いです。 簡単な業務背景 弊社のコールセンターでは、お電話をいただいた際にお客様がニフティのご契約者かどうかを確認する「本人確認」のステップがあります。これにAIを活用することで、本人確認にかかる時間を短縮し、お客様がよりスムーズにお問い合わせできるのではないかというアイデアが生まれました。私はこの取り組みの中で、Amazon Bedrockを呼び出すLambdaの作成を担当しました。Bedrockから受け取った結果を後続の処理にJSON形式で渡す必要があったのですが、ここで課題が発生しました。 invoke_model の課題:LLMの出力が文字列になる 通常、Python(boto3)のLambdaからBedrockを呼び出す際は invoke_model がよく使われます。しかし、この方法ではLLMからの回答が「文字列(String)」として返ってきます。 LLMの特性上、出力は確率に基づいて生成されるため、たとえプロンプトでJSONを返すように指示しても、常に期待通りのデータ構造になるとは限りません。その結果、後続の処理でパースに失敗し、エラーを招くリスクがありました。 Converse APIのTool Useによる解決 そこで今回は、この不確実性を排除するために Converse API を採用しました。Converse APIを活用することで、LLMからの出力を安定して「JSON形式」で受け取れるようになり、後続処理への連携が非常にスムーズになりました。 Converse APIのドキュメント 具体的には、 toolConfig の tools に期待するJSONの形式を定義し、Converse APIでBedrockを呼び出す際にtoolを指定します。 以下にサンプルコードを載せます。 import boto3 bedrock_client = boto3.client("bedrock-runtime") #tool_listの定義 tool_list = [ { "toolSpec": { "name": "parse_json", "description": "JSONにパースする", "inputSchema": { "json": { "type": "object", "properties": { "A": {"type": "number", "description": "Aの説明"}, "B": {"type": "string", "description": "Bの説明"}, }, "required": ["A", "B"], # レスポンス時に必須なもの } }, } } ] # Bedrockを呼び出し response = bedrock_client.converse( modelId=MODEL_ID, #モデルIDを指定(anthropic.claude-3-sonnet-20240229-v1:0など) toolConfig={"tools": tool_list, "toolChoice": {"tool": {"name": "parse_json"}}}, messages=[ { "role": "user", # 送信者のロール "content": [{"text": user_prompt}], # ユーザーからの入力プロンプト } ], system=[{"text": system_prompt}], # システムプロンプトの設定 ) このサンプルコードでは {A: 1, B: "test"} のような形式のJSONレスポンスが返されます。 tool_list の inputSchema で定義したスキーマに沿って、LLMの出力が構造化されます。 レスポンスからJSONデータを取り出すには、以下のようにします。 # レスポンスのcontentリストからtoolUseブロックを探して取得する json_result = None for block in response["output"]["message"]["content"]: if "toolUse" in block: # toolUseのinputには、パース済みの辞書(dict)データが格納されている json_result = block["toolUse"]["input"] break # 見つけたらループを抜ける if json_result is not None: print(json_result) # {'A': 1, 'B': 'test'} else: print("レスポンスにtoolUseブロックが含まれていませんでした。") 終わりに invoke_model ではLLMの出力が文字列になるため、JSON構造を保証するのが難しいという課題がありましたが、Converse APIの Tool Use 機能を活用することで、この問題をシンプルに解決できました。 LLMの出力を構造化データとして扱いたい場面では、ぜひConverse APIを利用してみてください。 今後も、お客様の満足度向上のために、AIやクラウド技術を活用したコールセンター業務の改善に取り組んでいきます。 参考 https://catalog.workshops.aws/building-with-amazon-bedrock/en-US https://docs.aws.amazon.com/boto3/latest/reference/services/bedrock-runtime/client/converse.html
アバター
ニフティには所属部署での業務のほかに、有志による社内活動が存在します。もちろん強制ではなく、それぞれが興味のある分野について、自主的に活動しています。なかには会社公認のもと予算がつき、社内業務に貢献しているケースも。業務とは別のやりがいや、自分の専門外の知見を得られることが、一つのモチベーションになっています。 その一つが、オンラインサポートチーム。オンライン会議や社内・社外イベントなど、配信関連のクオリティを向上させる目的で発足し、現在は10名ほどで活動しています。具体的な活動内容や、活動にかける思いについて、メンバーに聞きました。 自己紹介 Aさん 2019年4月に新卒入社。普段の業務内容はカスタマーサポート業務に関するアプリケーション及びシステムの開発・運用。オンラインサポートチームでの役割は、チームの運営実務・業務調整、社内における配信イベントの運営・機材サポート。趣味はゲーミングガジェット集め、eスポーツ観戦、写真撮影。 Sさん 2019年4月に新卒入社。普段の業務内容はデータセンターとオフィスのネットワーク設計・構築・運用。オンラインサポートチームでの役割は、社内・協賛イベントにおける配信イベントの運営・機材サポート・動画/写真撮影。趣味は廃道探索・環境測定。 Sさん 2021年4月 に新卒入社。普段の業務内容はコラボレーションツールおよび会計システムの運用・保守。オンラインサポートチームでの役割は、会議運営・音声編集・音声機材導入など。趣味は楽器演奏(ギター/ベース)と DTMでの楽曲制作。 オンライン会議やイベント配信の精度向上を目的に、専門チームが発足 みなさんは「オンラインサポートチーム(以下、オンサポ)」のメンバーということですが、所属部署や普段の業務はバラバラとお聞きしました。 Sさん そうですね。オンサポは会社公認の有志による活動で、さまざまな部署からメンバーが集まっています。ちなみに私は普段、社内プラットフォームチームに所属し、GWSやSlackといった外部ツールや、社内会計システムの運用などを担当しています。趣味はDTMを使った楽曲制作や、楽器の演奏です。 Aさん 私はカスタマーサポートグループ所属で、コールセンター業務にあたるスタッフさんが使うソフトウェアの開発や運用を行っています。趣味はゲームで、ゲーミングガジェット集めやeスポーツ観戦です。 Sさん 所属は Aさん と同じカスタマーサポートグループで、オフィスやコールセンターなどで使うネットワークのメンテナンスをしているエンジニアです。今は主に、オフィス周りのネットワークを設計しています。趣味は廃道の探索と環境測定です。 そもそもオンサポとはいつ頃から、どんな目的で発足したのでしょうか? Sさん 正式なキックオフは2022年の4月ですが、チームが立ち上がる前から活動自体は始まっていたと記憶しています。当時はコロナ禍がまだ収束しきっていないタイミングで、在宅でのリモート会議や、全国の拠点をオンラインでつないで全社的な報告会を行う時、あるいは社内イベントの配信の際などに、画質や音質といった面でストレスを感じることが多かったんです。 そこで、当時のプラットフォームチームのリーダーの呼びかけによって、部署を超えて課題感を持つ有志が集まり、オンラインでの環境をサポートするチームが立ち上がったというのが大まかな経緯です。現時点でのメンバーは10人ほど。入れ替わりもありますが、ここにいる3人はわりと長いほうですね。 ちなみに、みなさんはどんな思いからオンサポへの参加を決めたのでしょうか? Sさん 周囲からも「オンライン会議の品質を上げたい」という声は聞いていましたし、個人的にマイクなどの音響関係に興味を抱いていたこともあって、そうした部分で携われたらなと思い、参加することにしました。 Aさん そもそもチーム発足前から改善に向けて試行錯誤している人たちがいて、私はその様子を横から眺めていたのですが、正式にオンサポチームができるにあたって当時のリーダーがメンバーを募集したんです。みんなが頑張っている姿も見てきたし、個人的にも配信系に興味があったので、じゃあやってみようと。 Sさん 私はオンサポが立ち上がって少し軌道に乗ってきた頃に社内に向けた募集があって、参加を決めました。オンサポの活動自体は知っていましたし、趣味で少しカメラをやっていることもあって、業務として撮影機材を扱えるのは面白そうだなと。 趣味の範疇では触れない、本格的な機材を扱えるのが楽しい オンサポのミッションは「オンライン配信環境の品質改善」ということですが、具体的な活動内容や、これまでの成果を教えてください。 Sさん 今は隔週で1回1時間程度の定例会議を行なっています。社員からオンサポに寄せられた、配信環境にまつわる相談や問い合わせに対してみんなで議論をして、対応策を考えたり、プロジェクト化してメンバーをアサインしたり、進行中の案件の進捗を共有したりといった感じですね。 Sさん プロジェクトの具体的な内容としては、まず、「(品質を上げるための)機材の選定」です。映像関連はカメラ、音響関連はマイクやミキサーなど。実際に導入してみて、運用しながらどれくらい性能の向上や工数の削減につながったかを検証していきます。検証を踏まえて使い方を工夫したり、設定を変えたり、必要であれば別の機材を導入したりして、クオリティを上げていくのがメインの役割です。 Aさん 社内向けとなると、どこまでクオリティにこだわるかの線引きが難しいのですが、「音声が明瞭に聞こえる」というのは大前提として、映像もできるだけ鮮明にストレスなく届けられるようにしたいと考えています。 たとえば、社内オンラインイベントの配信中に映像や音響関連で気づいたことがあればSlackに書き込んで、イベント終了後にみんなで振り返りを行なっています。それで、次回はまた違う設定を試してみたり。社内だからこそ、ある程度の失敗は許容してもらえる前提で試行錯誤できるのは大きいですね。やっていくうちにノウハウを積み上げて、クオリティが上がってきた実感があります。 ちなみに、 Sさん は音響、 Sさん は撮影機材に関心があったということですが、オンサポチームでもご自身が興味のある分野を担当されているのでしょうか? Sさん オペレーションに関しては全員が分かっている状態にすることが前提ですが、それに加えて各自が得意な領域、関心のある分野で役割を担っているイメージですね。僕の場合は音声機材だったりしますし、 Sさん はカメラをはじめとする映像周りの機材、 Aさん は調整周りを担当することが多いです。 Sさん 得意といっても僕の場合は趣味で一眼レフカメラを触っていた程度なのですが、オンサポでの活動を通じて学べたことも多く、いい経験をさせてもらっていると思います。先ほども言いましたが、趣味の範疇ではなかなか触れない機材を扱えるのも、すごく楽しくて。最近ではPTZカメラという、遠隔で方向や角度を調整したり、ズームしたりできる機材を導入しました。セミナーなどを配信する際、最後列から撮影しても観客の方を映さずに登壇者にズームできるので、とても便利ですね。 従業員の方からは、オンサポチーム発足後の配信のクオリティに対して、どんな声が寄せられていますか? Aさん 評判はいいと思います。特に、日頃から外部のエンジニアが集まるオンラインイベントに参加している人からは「うちのオンサポはレベル高いね」と言ってもらえますね。 業務外活動の範疇で、できることを模索 もともとは社内の配信の品質を上げる目的で発足したオンサポですが、今は活動も幅も広がっているのでしょうか? Sさん そうですね。たとえば、最近は動画制作の相談を受けるケースも出てきました。社内のとあるチームのイベント配信を我々が請け負った時に、後でアーカイブとして残す、あるいは外部に発信できるような形に編集することもあります。あとは、NIFTY Tech Dayというニフティが社外向けに行なっている技術カンファレンスがあるのですが、セッションの間に流す社員インタビューを僕らが撮影したこともありました。 ただ、配信とセットで撮影したり、動画を編集することはあっても、動画制作単体の依頼は受けていません。社内にはメディア関連の制作を請け負うチームも別にありますし、基本的にオンサポは配信のサポートがメインなので。どこまで枠を飛び越えるべきかの判断は、なかなか難しいですね。 メンバーとしては、活動の幅を広げていきたいのか、それとも役割を絞って、あくまで配信のクオリティを突き詰めていきたいのか、どちらですか? Aさん そこは正直、悩みどころですね。配信のクオリティでいうと、ある意味でやり尽くした部分もあります。これ以上となると、さらに高い機材を導入するかみたいな話になりますが、果たして社内向けでそこまでやる必要があるのかと。また、コストカットや効率化についても突き詰めてきましたので、たとえば当初は何かのイベントを配信する時に準備と片付けを合わせて3時間かかっていたところを、今では合計1時間で終わらせられるようになっています。 Sさん かといって、活動の幅を広げようにもなかなか難しいですよね。たとえば、オンサポがクリエイティブチームみたいな形で、幅広く動画制作を請け負う方向性もあるとは思います。ただ、そうなるともう“仕事”の範疇になってしまう。オンサポは本業外の社内活動という位置付けなので、あまり手を広げすぎるのもどうなんだろうと。 結局のところ、我々の軸足はあくまでエンジニアなので、今は本業に支障のない範囲で次に何ができるのかを模索しているところです。 ただ、オンサポの活動自体はこれからも続けていきたいと考えていらっしゃいますか? Sさん そうですね。活動自体は楽しいので、僕自身は可能な限り続けたいと思っています。もちろん、年次を重ねてさらに多忙になればそうも言っていられなくなるかもしれませんが、仮に僕らが抜けたとしても“ユーザー”が困らないよう、配信にまつわる困りごとをカバーするような体制や仕組みは作っておきたいですね。 後編に続きます! 今回はニフティのオンラインサポートチームのインタビューの様子をお届けしました。続きは近日公開の後編の記事をご覧ください。 このインタビューに関する求人情報 /ブログ記事 ニフティ株式会社 求人情報
アバター
はじめに こんにちは。ニフティの山田です。 AWSを利用するうえで、S3を使うことは多いと思います。静的ファイル配信のみならず、アプリケーションから参照されることも多いでしょう。このようなアプリケーションの動作確認やテストの上では、ローカル環境で動くS3エミュレータがあると便利です。 しかし、従来有力だった以下の2つはいずれも無償提供が実質終了してしまいました。 MinIO: Web UI機能が削除され、バイナリ提供が終了したため、実質的にOSS版は終了 Web UI機能削除: https://github.com/minio/object-browser/pull/3509 バイナリ提供終了: https://github.com/minio/minio/pull/21625 localstack for AWS: 利用にはlocalstackアカウントの発行と認証が必要となり、CIでの実行には有償契約が必須 告知: https://blog.localstack.cloud/the-road-ahead-for-localstack/ そこで今回はこの代替となるものを探してみました。 移行先 Dockerイメージでサーバを立てることができ、複数コンテナの連携や複雑な初期化が必要ないものを選択します。 S3Mock SeaweedFS RustFS イメージ名 abode/s3mock chrislusf/seaweedfs rustfs/rustfs 言語 Java Go Rust GitHub Stars 1,031 29,900 20,900 Web GUI × ◯ ◯ path-style access ◯ ◯ ◯ virtual-hosted-style access × × × バケット基本操作 ◯ ◯ ◯ オブジェクト基本操作 ◯ ◯ ◯ ListObjectsV2 ◯ ◯ ◯ multipart upload ◯ ◯ ◯ タグ ◯ ◯ ◯ 署名付きURL △(署名を無視) ◯ ◯ sigv4auth × ◯ ◯ バケットポリシー × ◯ ◯ バージョニング ◯ ◯ ◯ ライフサイクルポリシー × △(TTL設定のみ) ? CORS × ◯ ? 表は公式ドキュメントを参照したものであり、すべての機能を検証したわけではない点にご留意ください。 S3Mock 文字通りテストなどで利用するS3のモック用途で作られたものです。Testcontainersでの利用も公式に想定されています。 GitHub でAPIごとのサポート状況がリストアップされている点も親切です。 docker composeでの起動例は以下の通りです。 services: s3mock: image: adobe/s3mock:4.11.0 environment: - COM_ADOBE_TESTING_S3MOCK_STORE_RETAIN_FILES_ON_EXIT=true - COM_ADOBE_TESTING_S3MOCK_STORE_ROOT=data - COM_ADOBE_TESTING_S3MOCK_STORE_INITIAL_BUCKETS=my-bucket ports: - 9090:9090 volumes: - s3mock-data:/data volumes: s3mock-data テスト用だけあってデフォルトだと終了時にデータ消去が走ってしまいますが、環境変数で永続化が可能です。また自動作成するバケット名を環境変数で設定できる点が便利です。 Pros エラーレスポンスなど、S3の挙動再現には最も期待できる Cons 署名など高度機能には対応していない Web GUIはない SeaweedFS 分散ストレージOSSの一つです。FUSE、Hadoopなど多くのプロトコルに対応しており、S3互換APIも備えています。 分散システムだけあってMaster・Volume・Filerなど複数コンポーネントから構成されていますが、S3をシングルノードで起動するだけなら以下でできます。 services: seaweedfs: image: chrislusf/seaweedfs:4.09 ports: - 8333:8333 # S3 API - 8888:8888 # Filer UI - 9333:9333 # Master UI command: ["server", "-s3", "-dir=/data", "-ip=0.0.0.0"] volumes: - seaweedfs-data:/data volumes: seaweedfs-data: 上記設定だと無認証でアクセス可能です。簡易的ですがUI(上記のFiler UI)も備わっています。 Pros S3の高度機能にもある程度対応している Web GUIがあるので確認はしやすい Cons 分散システムであるため、構成要素は多い 不具合があった際のデバッグは難しい あくまでもS3互換であるため、エラーレスポンスなど細かいとことで違いが出る可能性がある RustFS 同じく分散ストレージOSSです。…とはいえまだアルファ版でありシングルノードでしか動作しません。MinIOの代替を目指している模様です。 docker composeでの起動方法は以下の通りです。 services: seaweedfs: image: rustfs:rustfs:1.0.0-alpha.82 environment: - RUSTFS_ACCESS_KEY=rustfsadmin - RUSTFS_SECRET_KEY=rustfsadmin - RUSTFS_CONSOLE_ENABLE=true ports: - 9000:9000 # S3 API - 9001:9001 # Admin UI volumes: - rustfs-data:/data volumes: rustfs-data:   アクセスキー・シークレットが必須となります。(デフォルトはrustfsadmin/rustfsadmin) Web GUIへのアクセスにも必要です。 OpenTelemetryにも対応しているらしく、docker composeの サンプル が用意されています。 Pros Web GUIを持ち、SeaweedFSよりモダンなデザイン Cons まだalpha版 ドキュメントがあまり詳細でない 認証トークンをハードコード して CVSS 9.8 の脆弱性を出したことがある 結論 個人的には テスト(Testcontainer)用 S3Mock docker compose用 GUI不要なら: S3Mock GUI必要なら: SeaweedFS で良いのではないかと思っています。localstackほどの機能網羅性はありませんが、単純なCRUDL用途であれば実用に耐えるでしょう。私の担当プロダクトでは規模の小さいものからS3Mock/SeaweedFSへの置き換えを行っています。 Tips: 初期化方法 ローカル実行時やテスト開始時など、バケット作成・初期データ投入を自動的に行いたいケースがあります。 locakstackでは特定ディレクトリにhookスクリプトを置いておくと自動実行される機能がありましたが、上記のいずれもその機能はありません。(S3Mockのみ、バケット作成は自動実行可能) 初期化したければ、別途初期化用のコンテナを用意することになります。 ex) SeaweedFSに対してバケット作成、初期データ投入 seaweedfs: image: chrislusf/seaweedfs:4.09 ports: - 8333:8333 # S3 API - 8888:8888 # Filer UI - 9333:9333 # Master UI command: ["server", "-s3", "-dir=/data", "-ip=0.0.0.0"] volumes: - seaweedfs-data:/data healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:8333"] interval: 5s timeout: 5s retries: 10 seaweedfs-init: image: amazon/aws-cli:2.33.15 depends_on: seaweedfs: condition: service_healthy environment: AWS_ACCESS_KEY_ID: test AWS_SECRET_ACCESS_KEY: test AWS_DEFAULT_REGION: ap-northeast-1 volumes: - ./mock-contents/data:/data:ro entrypoint: /bin/sh command: - -c - | aws --endpoint-url http://seaweedfs:8333 s3 mb s3://seaweedfs || true aws --endpoint-url http://seaweedfs:8333 s3 sync /data/ s3://seaweedfs/ echo "Initialization complete" 初期化処理を行うコンテナ(上記のaws-cliコンテナ)を用意し、そこで初期化処理を実行する 上記ではバケット作成とs3 syncで初期化している depends_onで起動順の制御が必要 プロセスの起動完了後に実行される必要があるため、S3コンテナ側にヘルスチェックの設定が必須 その他の選択肢 分散ストレージ Ceph Garage Zenko など、S3互換APIを持つストレージOSSは他にもありますが、分散化を前提としておりセットアップが複雑です。ローカル・テスト用で使うには不向きかもしれません。 プロキシ S3Proxy APIだけを提供し、他のストレージバックエンド(Google Cloud Storageなど)に対するプロキシとして機能するものです。 ファイルシステムバックエンドがあるので単独でも使えそうではありますが未検証です。 AWS SDKモック moto (python) aws-sdk-client-mock (JavaScript) ユニットテスト用途であれば昔ながらの方法に立ち戻り、S3互換サーバを立てるのではなく、AWS SDKをモック化することで対応可能です。SDKレスポンスの再現度という点では最も力が入っています。 ただしテスト時にしか使えないので、ローカルPC上で環境を再現する用途には不向きです。また、複数アプリケーションを起動してのテストにも対応できません。 インメモリモック gofakes3 (Go) インメモリでS3互換サーバを立てるものです。SDKモックとは異なりますが、テスト用途であり、やはり環境再現などには使えません。 おわりに 今回は、ローカルS3エミュレータの代替となるサービスについて解説しました。 参考になれば幸いです。  
アバター
はじめに こんにちは。 寺島です。 普段はマイニフティチームでスマホアプリ(マイ ニフティ)の開発に携わっています。 最近はAIでの開発が活発になってきましたね! 私は主にClaude Codeでの開発を行っています。 基本的には、快適に開発をお任せしている状況なのですが、 Claude CodeでiOSアプリの開発を行う中で、とてもネックになる部分が出てきました。 ビルドがうまくできない 新規グループやファイルの追加が、Xcodeで認識されない 上記の2点で本当によく詰まってしまい、開発体験が著しく下がってしまう状況でした。 ビルドは、対象のシミュレーターを起動してみたいなことが苦手のようで、延々と転け続けていましたし、ファイル追加は、Xcodeprojの操作が苦手なようで既存の構成を壊してしまっていました。 最近は公式のMCPなどで解決ができるようなのですが、もっと手軽にスクリプトで解決する方法がありますので、ご紹介させていただければと思います。 この記事では、プロジェクトルートの .claude/ 配下に scripts/ ディレクトリを配置し、CLAUDE.mdやSKILLS.mdから呼び出すパターンを紹介します。 scriptsの解説 .claude/scripts/ ディレクトリにプロジェクト固有のスクリプトを配置します。 今回は、CLAUDE.mdなどから相対パスで呼び出す形で実現します。 嬉しいこと ビルドで失敗することがなくなります .xcodeproj を編集を代わりにやってくれます ディレクトリ構成 your-project/ └── .claude/ ├── CLAUDE.md # 「./.claude/scripts/on_build.shや./.claude/scripts/on_new_file を実行」と記載 └── scripts/ ├── on_build.sh # ビルド実行スクリプト └── on_new_file.sh # ファイル作成スクリプト 1. ビルドスクリプト(.claude/scripts/on_build.sh) 実装例(iOS/Xcode) : ※ 指定するシミュレーターは必ず利用できるシミュレーターを指定してください #!/bin/bash # iOS: Xcodeビルド # 環境に応じて以下を変更してください: # - ワークスペース/プロジェクト名 # - スキーム名 # - ターゲットデバイス xcodebuild -workspace YourApp.xcworkspace \ -scheme YourScheme \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ -derivedDataPath ./.build \ build 実行権限を付与: chmod +x .claude/scripts/on_build.sh 2. ファイル作成スクリプト(.claude/scripts/on_new_file.sh) 環境固有の部分があります。 : Dir.glob('*.xcodeproj').first – プロジェクトファイルの検索パターン(環境に応じて変更が必要) project.targets.first – ターゲットの選択(複数ターゲットがある場合は適宜変更が必要) 実装例(Xcodeプロジェクトへの自動登録) : #!/bin/bash # iOS: Xcodeプロジェクトにファイルを追加するフック # 前提: xcodeproj gem がインストール済み(gem install xcodeproj) # 使用例: ./.claude/scripts/on_new_file.sh app/Module/Feature/NewFile.swift FILE_PATH="$1" if [ -z "$FILE_PATH" ]; then echo "使い方: $0 <ファイルパス>" exit 1 fi if [ ! -f "$FILE_PATH" ]; then echo "エラー: ファイルが存在しません: $FILE_PATH" exit 1 fi # Rubyスクリプトを実行してXcodeプロジェクトに追加 ruby -r xcodeproj <<RUBY def find_or_create_group(project, path_components) current_group = project.main_group path_components.each do |component| next_group = current_group.children.find { |child| child.is_a?(Xcodeproj::Project::Object::PBXGroup) && child.display_name == component } if next_group.nil? next_group = current_group.new_group(component, component) end current_group = next_group end current_group end begin project_path = Dir.glob('*.xcodeproj').first if project_path.nil? puts "エラー: Xcodeプロジェクトが見つかりません" exit 1 end project = Xcodeproj::Project.open(project_path) file_path = '${FILE_PATH}' path_parts = file_path.split('/') file_name = path_parts.pop group_path = path_parts group = find_or_create_group(project, group_path) # ファイルの重複チェック existing_file = group.children.find { |child| child.is_a?(Xcodeproj::Project::Object::PBXFileReference) && child.display_name == file_name } if existing_file puts "エラー: ファイルは既にプロジェクトに存在します: #{file_path}" exit 1 end # 新規ファイルの追加 file_ref = group.new_file(file_name) project.targets.each do |target| target.add_file_references([file_ref]) end project.save puts " ファイルを全ターゲットに追加しました: #{file_path}" rescue => e puts "エラー: #{e.message}" exit 1 end RUBY 実行権限を付与: chmod +x .claude/scripts/on_new_file.sh CLAUDE.md/SKILL.mdでの活用方法 プロジェクトルートの .claude/CLAUDE.md 等に以下を記載することで、Claudeにスクリプトの存在を伝えられます: ビルド手順 コード変更後は必ず以下を実行してビルドを確認してください: ./.claude/scripts/on_build.sh ビルドエラーが発生した場合は、エラーメッセージを確認して修正してください。 新規ファイル作成時 新規ファイルを作成した場合、以下を実行してプロジェクトに登録してください: ./.claude/scripts/on_new_file.sh <作成したファイルのパス> まとめ scriptsパターンは、Claude Codeにおける「プロジェクト固有の処理」を一箇所に集約し、プロンプトをシンプルに保つための設計パターンです。 設計の利点 : Claudeは標準化されたインターフェース(スクリプト)を呼び出すだけ チームでリポジトリ共有するだけで環境構築が完了 この仕組みにより、Claude CodeでのiOS開発がより快適になります。 おわりに 色々な実行方法がある中で、今回は選択肢の一つとして.shで管理する方法をご紹介しました。 もっと快適にするために、サブエージェントから呼び出したりなど様々あると思います。 色々試すものの一つになれると幸いです。
アバター
ニフティで利用しているAWS、Notion、GitHubなどのクラウドやSaaSの管理・運用を担当している石川です。 最近社内で利用しているIDaaS(Identity as a Service)をOneLoginからMicrosoft Entra ID(旧Azure AD)へ移行しました。 同じ「IDaaSの切り替え作業」でも、アプリケーションごとに事情はまったく異なり、想定外のハマりどころが数多くありました。本記事では、実際の移行作業を通じて得られたノウハウを共有します。今回は私が移行したアプリの中でも AWS、GitHub Enterprise 、 Notion 、Redash の4つを例に取りつつ説明していきます。 この記事の読み方 各セクションの内容を 一般論 (IDaaS移行全般で押さえるべき知識)と 体験談 (筆者が実際に経験した具体事例)に分けています。 IDaaS移行は「認証設定の差し替え」だけではない 1. ユーザープロパティの確認と表示名の定義 移行を機にデータが「整理」されることがある 表示名の設計は悩みどころ 2. Entra IDのグループ管理の注意点 3. IDaaSごとに異なるマッピングの仕様 4. アプリごとに異なるプロビジョニングの挙動 5. IDaaS移行時のセッション・トークンへの影響 6. グループプロビジョニングの罠 ネストされたグループは同期できない 旧IDaaSで作成されたグループは引き継げない グループ削除時の権限孤立リスク Entra IDのプロビジョニング間隔は40分固定 OneLoginからの移行で行ったグループ切り替え手順 Notionでの権限孤立チェックの限界 グループプロビジョニングは多用しないのが無難 7. アプリのバージョンが古い場合のリスク 対応策の検討 8. 申請フロー・運用ワークフローの再構築 AWS IAM Identity Centerグループ管理のTerraform化 まとめ IDaaS移行は「認証設定の差し替え」だけではない IDaaSを変えるというと、SAML設定のURLや証明書を貼り替えるだけに思えるかもしれません。しかし実際には、以下のような周辺領域にまで影響が波及します。 ユーザープロパティのマッピング (表示名・属性情報などの紐付け) IDaaS/SaaSごとのプロビジョニング(SCIM)挙動差異の把握 グループ管理の方式変更 既存セッションや認証トークンへの影響 申請フロー・運用ワークフローの再構築 認証設定そのものよりも、これらの周辺対応のほうがボリュームが大きいです。 1. ユーザープロパティの確認と表示名の定義 一般論 新旧のIDaaSで連携するADやLDAPが同じでも、 IDaaS側でのプロパティの持ち方や参照先が異なる ことがあります。 移行を機にデータが「整理」されることがある IDaaS移行のタイミングで、AD側の属性を正しいフィールドに寄せ直す(=綺麗にする)動きが起きることがあります。その上で「名」と「体」が合っていないフィールドがあると、 一時的なものか恒常的なものかを事前に確認 しておかないと、マッピングで使っている値が突然変わるリスクがあります。 表示名の設計は悩みどころ SaaS側のユーザー表示名をどう構成するかは意外と悩むポイントです。 SaaSの挙動で特に確認すべき点は以下の2つです。 ユーザーサジェストはどのフィールドに対して行われているか → 表示名でしか行われないとローマ字でユーザーを検索できない、逆も然り 同姓同名の区別が付くか → ユニークなIDがないと区別が付かない → サジェスト時は補助情報としてユニークなIDが出て区別が付くが、設定後は区別が付かない場合もあり 体験談 検索サジェストやユーザー名オンマウスで、固有ID(メールアドレス or ユーザーID)を併記してくれているアプリに関しては表示名を 姓 名 。そうでないものに関しては 姓 名 メールアドレスのユーザー部 という併記構成にしています。冗長に見えますが、以下のメリットがあります。 常に同姓同名の区別 がつく 漢字の読み方がわからない場合もメールアドレスにある ローマ字で補完 できる TIPS : 通常であれば表示名には英字を推奨します。日本語話者が圧倒的多数の環境では 漢字 + ローマ字 の併記が現実的な妥協点になります。サジェストが前方一致でしか機能しないサービス(Amazon Qなど)もあるので、 ローマ字 + 漢字 のパターンも有効です。 2. Entra IDのグループ管理の注意点 一般論 アプリによっては、全社員にデフォルトで権限を与えるものがあります。その場合、以下のような流れでライセンスが消費されます。 Entra IDにユーザーが追加される Entra IDの特定グループ(動的グループ)に自動で追加される そのグループにアプリの利用権限が付与されている アプリにユーザーがプロビジョニングされる アプリの利用シートが増える つまり、Entra IDにユーザーを追加すると同時に、アプリのユーザー数が増えてライセンスが消費または追加されます。 事前に確認すべきポイントは2つです。 動的グループがどのような基準で構成されているか その基準に合致するプロパティが、どのような運用で付与されるか 例えば、Entra ID上でテストアカウントを作成したところ、意図せず動的グループに含まれてしまい、アプリ側のユーザーが自動生成されてライセンスが消費される、といったことが起こり得ます。 体験談 移行前に確認したところ、人間だけのはずのグループにテストアカウントが含まれていたり、入社の数ヶ月前からアカウントが存在していてライセンスが無駄に消費されていたりするケースがありました。 また過去には、LDAPで更新ミスが発生し大量のユーザーが削除されたことがありました。その際、LDAP連携していたIDaaSおよびプロビジョニング先のアプリ側でも、大量のユーザーが無効化される障害が発生しました。 ユーザーの無効化・削除によって保管データに不可逆な変化が生じるタイプのアプリでは、プロビジョニングの誤削除防止機能を有効化し、閾値を設定することを強く推奨します。 Microsoft Entra プロビジョニング サービス で誤削除防止機能を有効にする – Microsoft Entra ID | Microsoft Learn 3. IDaaSごとに異なるマッピングの仕様 一般論 Entra IDでは、プロビジョニング時の属性マッピングに 独自の式 を使います。 Join() 、 IIF() 、 Replace() などの関数を組み合わせてフィルターや値変換を行うことができます。 Microsoft Entra アプリケーションのプロビジョニングで属性マッピングの式を記述するためのリファレンス – Microsoft Entra ID | Microsoft Learn 体験談 # 表示名を 姓 名 メールアドレスのユーザー部にする式 Join(" ", [surname], [givenName], Item(Split([mail], "@"), 1)) # 特定のグループは同期先では別名に置換、決まったprefixが付いたグループはprefixを置換して同期 IIF( [displayName] = "ほげほげ", "group-hogehoge", IIF( [displayName] = "ふがふが", "group-fugafuga", IIF( InStr([displayName], "YourApp-team-", , ) > 0, Replace([displayName], "YourApp-team-", , , "team-", ), [displayName] ) ) ) 上の例はNotionの表示名マッピングですが、こうした式を 本番で一発勝負で試すのは危険 です。 SCIMに対応した検証用アプリを用意して、本番適用前にマッピング式の動作確認をしましょう。Entra IDには式ビルダーのテスト機能もありますが、実際のプロビジョニング結果で確認するのが確実です。 4. アプリごとに異なるプロビジョニングの挙動 一般論 SCIMプロトコルを使っていても、 利用するIDaaSとSaaSによって挙動が異なります 。 体験談 過去、実際に遭遇した問題は以下のとおりです。 SaaSのSCIMエンドポイントへの アクセス過多でエラー (初回同期時) IDaaS or SaaSのSCIMが RFCどおりに実装されておらず 、一部の同期処理が失敗 IDaaS or SaaSが グループ名変更やグループ削除に対応していない グループプロビジョニングで メンバーの同期状態と実態に齟齬 がよく出る サポートに依頼して改修してもらったり、自前でSCIM APIを叩いて同期用Lambdaを動かしたりと、正常な状態にするまでにはそれなりに時間がかかります。 そのため、できる限り検証環境を用意して事前に確認することを推奨します。ただし、SaaSでは 検証環境の用意が難しい ため、本番環境で一発勝負になることもあります。 今回は、自前での用意が難しいものだけ検証なしで本番一発勝負で切り替えました。 Notion:本番環境で一発勝負 SAML SSOはOrganizationレベルの設定のため、Workspaceごとの事前検証が不可能 GitHub Enterprise Cloud:日常的に使っていないOrganizationで検証 SAML SSOをEnterpriseレベルで行うとSCIMが利用できないため、Organizationレベルで設定 AWS:検証用の別Organizationで検証 Redash:検証用の別環境を作成 TIPS: GitHubのSCIM設定で、Azure AD OAuthアプリがひとつでもOrganizationに対して承認状態になっていると、SCIMセットアップ時のGitHub OrganizationにGrantする画面が表示されないという問題に遭遇しました。個人設定のOAuthアプリからRevokeすることで解消しましたが、ドキュメントに記載がないのでハマりどころです。 5. IDaaS移行時のセッション・トークンへの影響 一般論 SAMLのIdPを切り替える際は、既存セッション・認証トークンへの影響をアプリケーションのサポートに事前確認することが重要です。 体験談 GitHubでの移行時に、影響範囲を事前にGitHubサポートへ確認しました。 Enterprise管理者のアカウントから英語で質問するとレスポンスが早い気がします。 項目 影響 対応 ブラウザアクセス 再認証が必要 SAMLで保護されたリソースにアクセスする際に、新しいIdP(Entra ID)での認証が必要 既存セッション 有効期限まで維持 強制的なログアウトはないが、期限切れ後の再ログイン時に再認証 OAuth/PAT 機能継続 特別な対応不要 Copilot 初回のみ再認証 変更後の初回使用時に再サインインとSAML SSO完了が必要 GitHub OrganizationのIDaaS切り替え時の認証情報への影響 TIPS: IDaaS変更についてドキュメントに具体的な記載がないことがままあります。影響がないかサポートに問い合わせましょう。また影響があるにしろないにしろ、切り替え前にユーザーへの周知を忘れずに。あとで来る問い合わせの量が減ります。 6. グループプロビジョニングの罠 IDaaSとの連携で一番厄介だと思っているのがグループプロビジョニングです。 一見便利なのですが運用まで考えると考慮しないといけない事項が多くて、有効活用できるシーンは限られます。 一般論 ネストされたグループは同期できない Entra IDのSCIMプロビジョニングは、 ネストされたグループ(Group in Group)のメンバーを読み取れません 。直接メンバーとして割り当てられたユーザー・グループのみが同期対象です。 Nested groups.  The Microsoft Entra user provisioning service can’t read or provision users in nested groups. The service can only read and provision users that are immediate members of an explicitly assigned group. Understand how Application Provisioning in Microsoft Entra ID – Microsoft Entra ID | Microsoft Learn グループ設計時にネストを前提としている場合、フラットな構造に見直す必要があります。 旧IDaaSで作成されたグループは引き継げない 旧IDaaSのグループプロビジョニングで作成されたグループを、Entra IDで そのまま同期状態で引き継ぐことはできません 。グループにはメールアドレスのようなユニークIDにしやすい項目が存在しないため、IDaaS側が独自にIDを割り振ります。そのため、移行時にIDを引き継げず、グループの再作成が必要になります。 グループ削除時の権限孤立リスク 削除予定のグループだけに権限が付いたリソースがないか、事前に確認が必要です。そのグループが唯一の権限付与先になっている場合、削除後にリソースへアクセスできなくなります。 Entra IDのプロビジョニング間隔は40分固定 Entra IDのグループプロビジョニングは約40分間隔で実行されます。即時性がないため、急ぎのオペレーションにはグループプロビジョニングは不向きです。ユーザー単位のプロビジョニングならばオンデマンドで実行できるため、少数であれば手動での即時同期が可能です。 体験談 OneLoginからの移行で行ったグループ切り替え手順 OneLoginで作成されたグループをEntra IDに引き継げなかったため、以下の手順で対処しました。 旧グループを事前にリネーム (古いことを記すプレフィックスを付与)して名前の衝突を回避 Entra ID側で 新しいシンプルな命名規則 (YourApp- team-xxx )でグループを再作成 グループプロビジョニング グループへの権限付与などを利用者に依頼(APIで再付与できない場合) 旧グループは棚卸しタイミングで削除 TIPS: グループのリネームはAPIがないと手作業になります。システム的にカバーできない手順がある場合は、数日前から計画的に進めるのがおすすめです。 Notionでの権限孤立チェックの限界 Notionではコンテンツ検索で対象グループが権限を持つページを洗い出せますが、 そのグループ「だけ」が権限を持っているかどうかは目視確認が必要 で、網羅的なチェックは困難でした。 グループプロビジョニングは多用しないのが無難 実際に運用を考えてみると、追加・削除・統合が発生する組織変更のたびにグループの付け替えや廃止管理に悩まされます。SaaS側にグループ管理の手段がSCIMしかない場合を除き、グループプロビジョニングは最小限にとどめるのがよいと感じています。 7. アプリのバージョンが古い場合のリスク 一般論 SaaSではなくオンプレでOSSを動かしている場合、アプリケーションのバージョンが古いとIDaaSのSAML構成をそのまま組めないことがあります。 体験談 実際にこの問題に直面しました。利用しているRedash v10.1.0ではマニュアル通りでは動きません。 SAML & Azure/Entra · getredash redash · Discussion #7026 Authentication Options (SSO, Google OAuth, SAML) 対応策の検討 案 結果 Redashをアップグレード アップグレード後の動作確認に手間がかかるため最終手段 別のIdPを使う(AWS IIC等) 動作はしたので選択肢としてあり 設定だけで回避する 動作したので採用 Redash v10.1.0だとEntra IDのSAML認証が通らないバグ、どこかで見た話だなと思ったら過去にOneLoginでも同じような不具合を喰らっていました。 RedashをECS Fargate構成にしてv7からv10へアップグレードする – NIFTY engineering マニュアルだとEntity IDにRedashインスタンスのURLを入れるようにと書いてますが、Microsoft Entra Identifierを入れれば利用できるようになります。これならコード修正・イメージ再作成も不要です。 TIPS: OSSのドキュメントやIssueの情報を鵜呑みにせず、実際に試してみることが大事です。なお、FirstName, LastNameのクレーム名に名前空間は不要でした。 8. 申請フロー・運用ワークフローの再構築 一般論 IDaaS移行に伴い、以下のような 運用フローの再構築 が必要になる場合があります。 アプリ利用者の追加・削除フロー グループ作成・メンバー変更の申請フロー 例外的なメンバー追加の手順 退職者管理の仕組み 一連の自動化処理 この部分は日常的な運用工数や利用者の使い勝手に直結するため、しっかりとフローを設計して手作業の運用は可能な限りゼロにすることを目指しましょう。 体験談 AWS IAM Identity Centerグループ管理のTerraform化 AIで加速するAWS運用効率化 〜IAM Identity Center IssueOpsとセキュリティ基準自動作成〜 | ドクセル AWS IAM Identity Center(以下AWS IIC)への権限割り当てには、TerraformによるGitOpsを採用しています。今回の移行に伴い、グループ作成とメンバー割り当てもできるように改修しました。 グループへの割り当てでは、実際に誰が何の権限を得るのか分かりにくくなるため、レビューをサポートするGitHub Actionsを追加しました。 まとめ IDaaSの移行は、表面的には「認証設定の差し替え」ですが、実態は アプリケーションごとの仕様差異・制約・運用フロー再構築との格闘 です。 IDaaS移行で押さえておきたいポイント ユーザープロパティの差異 を事前に洗い出す Entra IDのグループ管理 の基準や動的グループの挙動を確認する マッピング式 はIDaaSごとに仕様が異なるため、検証環境でテストする プロビジョニング挙動はアプリごとに異なる 前提で、できること・できないことを整理する セッション・トークンへの影響範囲 をサポートに確認し、ユーザーへ周知する グループのネスト非対応 やグループ引き継ぎ不可など、SCIMの仕様制約を把握する アプリのバージョン が古いとそもそもSAML構成ができないことがある 運用フローの再構築 (申請・自動化)まで含めて移行計画を立てる 同様の移行を検討されている方の参考になれば幸いです。
アバター
イベント概要 NIFTY Tech Talkは、ニフティ株式会社の社員が主催するトークイベントです。 本イベントでは、ニフティグループの社員が業務を通じて学んだことを発信しています! テーマ ハッカソン優勝チームが語る!実践的AI活用アイデアの創出と実装 NIFTY Tech Talk #26 2025年、ニフティ社内でエンジニアハッカソン合宿が開催されました。 AIをテーマにした今回のハッカソンにて、優勝チームがアイディア創出に挑戦と学びの物語などについて語ります。 チームに分かれ激しい競争の中で勝ち抜いたその成果を是非ともご覧ください。 開催概要 日程:2月19日(木)12:00〜12:30 YouTube Liveで配信 登録はこちらから https://nifty.connpass.com/event/383673/ こんな方におすすめ AI開発に興味がある方 開発にAIを利用している方 社内でのAI活用やハッカソン企画を検討されている方 タイムテーブル 時間 コンテンツ 12:00 – 12:05 オープニング 12:05 – 12:10 エンジニアハッカソン概要 12:10 – 12:25 【仮】AI開発の活用と学び 12:25 – 12:30 クロージング 登壇者プロフィール 添野 翔太(ファシリテーター) ニフティ株式会社 サービスシステムグループ 第1開発チーム 新卒入社9年目。 主に@niftyトップページの開発・運用を担当し、日々スクラムマスターとして奮闘しています。 深田 健太(登壇者) ニフティ株式会社 サービスシステムグループ 第三開発チーム @niftyメールや@nifty安心メールパックの開発をしています。 社内のブカツ制度では、ビリヤード部の部長をしてます。 清水 利音(登壇者) ニフティ株式会社 基幹システムグループ サービスインフラチーム ログインシステムなどを開発しているサブチームのリーダーを担当しています。 ニフティグループでは一緒に働く仲間を募集中です 新卒採用、キャリア採用を実施しています。ぜひ リクルートサイト をご覧ください。 ニフティエンジニアが業務で学んだことやイベント情報を エンジニアブログ にて発信しています! ニフティエンジニアのX(旧Twitter)アカウント NIFTY Tech Talkのことや、ニフティのエンジニアの活動を発信していきます。 @NIFTYDevelopers アンチハラスメントポリシー 私たちは下記のような事柄に関わらずすべての参加者にとって安全で歓迎されるような場を作ることに努めます。 社会的あるいは法的な性、性自認、性表現(外見の性)、性指向 年齢、障がい、容姿、体格 人種、民族、宗教(無宗教を含む) 技術の選択 そして下記のようなハラスメント行為をいかなる形であっても決して許容しません。 不適切な画像、動画、録音の再生(性的な画像など) 発表や他のイベントに対する妨害行為 これらに限らない性的嫌がらせ 登壇者、主催スタッフもこのポリシーの対象となります。 ハラスメント行為をやめるように指示された場合、直ちに従うことが求められます。ルールを守らない参加者は、主催者の判断により、退場処分や今後のイベントに聴講者、登壇者、スタッフとして関わることを禁止します。 もしハラスメントを受けていると感じたり、他の誰かがハラスメントされていることに気がついた場合、または他に何かお困りのことがあれば、すぐにご連絡ください。 ※本文章はKotlinFest Code of Conductとして公開された文章( https://github.com/KotlinFest/KotlinFest2018/blob/master/CODE-OF-CONDUCT.md )を元に派生しています。 ※本文章はCreative Commons Zero ライセンス( https://creativecommons.org/publicdomain/zero/1.0/ ) で公開されています。
アバター
はじめに 皆さん初めまして、2025年10月に入社しました田中と申します。 所属はサービスシステム第一開発チームです。 担当業務は主に@niftyトップページの改修・運用などを担当しています。 趣味は料理とドラマ鑑賞です。 たまにジムに行きます。 特に昔のドラマを見ていると、その時代の街の風景や時代の価値観を感じられて面白いです。 最近だと東京ラブストーリー、ロングバケーション、やまとなでしこを見ました。 本記事では私の転職のきっかけや、実際に感じたことを伝えれればと思います。 少しでも参考になれば嬉しいです。 経歴と転職のきっかけ 前職では主に官公庁のシステム開発に従事していました。 転職をしたいと思ったきっかけは下記です。 工程や分野問わず幅広い仕事がしたい モダンな技術にも触れられる仕事をしたい プライベートの時間で個人開発をちょこちょこやっていたこともあり、上記について強く思うようになっていきました。 重視していたことは下記です。 一緒に働く人が穏やかな人が良い どちらかといえば対面がベースになった働き方がいい AIを活用していること ITエンジニアはリモートワークを希望することが多い印象です。 しかし、自分は一緒に働く人が周りにいたほうが仕事が捗りやすいタイプでちょっとしたことでも聞ける環境が良いなと思っていました。 ニフティに決めた理由は上記で挙げたものに一致していたことと、特に面接を通じて人の良さを感じたことでした。 その他にも転職期間中に技術書典があり、ニフティの配布物をもらったのも実は決め手の一つでもありました。 入社して感じ た こと 文化について 人の良さをまず感じました。 入社後は週1で1 on 1の時間を設けていただいたり、振られるタスクもドメイン知識が身に付くように配慮してくださいました。 休みも取りやすく、フレックス制度も周りを見ていると上手に活用していると感じます。 業務について 使用する技術は業務で触れてこなかったものもあるので日々挑戦、といった感じです。 AIについてはGitHub CopilotやClaude, Gemini等 を活用していてコードを自分自身でEditする機会が前職と比べてかなり減りました。 またチームは主にアジャイルでタスクを進めており、レトロスペクティブやバックログリファインメントなどのスクラムイベントをやっています。最初はこの文化に少し戸惑うこともありましたが、すぐに慣れました。 最初小さな範囲での設計→実装→テスト→リリースを完了した時は単純に嬉しかったです。 企画の方やSREチームとのやりとりもあり他のチームとコミュニケーションをとる機会も多いです。 今後に向けて まだまだチームで使用している技術やドメイン知識が十分でないのでそちらを深めていきたいと思っています。 Udemyも会社が発行したアカウントで視聴ができるのでそちらも活用しながら、成長していき会社やお客様に貢献してきたいと思っています。 最後に ここまで読んでいただきありがとうございます。 「新しい技術に挑戦したい」 「対面でのコミュニケーションも大事にしたい」 「人が良い会社で働きたい」 など考えている人であればニフティは良い環境だと思います。 是非皆さんと一緒に働ける日を楽しみにしています!
アバター
はじめに こんにちは。H173です。 ヤマハルーター RTX1300使っていますか? IPv4 over IPv6にネイティブ対応したルーターは海外ベンダー製品ではまだ少なく、エンタープライズ向けのRTX1300は貴重な存在です。 自宅で使用している方も少なくないのではないでしょうか。 今回は、RTX1300をMAP-E環境で使用する際のNAT設定について解説します。 MAP-E と NAT IPv4 over IPv6の主要な方式としてDS-LiteやMAP-Eがあります。 いずれもIPv4アドレスを複数のユーザーで共有する方式ですが、利用可能ポート数に厳しい制約があるのが特徴です。 DS-Lite: VNE側の設備(AFTR)でNATを行う MAP-E: エンドユーザー側のルーターでNATを行う DS-Liteではエンドユーザー側でできることが限られますが、MAP-Eの場合はユーザー側で外側ポートをいかに効率よく使用できるかが重要になります。 ポートセービングIPマスカレード ヤマハルーターにはポートセービングIPマスカレードという機能が搭載されています。 これは、従来のNAPTでは1つのセッションで1つの外側ポートを消費するところを、宛先IPアドレスおよび宛先ポート番号が異なれば、同じ外側ポートを再利用できるという機能です。 この変換方式はRFC 4787で定義されるAddress and Port-Dependent Mapping(APDM)を基本として、外側ポートの再利用を積極的に行う処理が加えられたものと考えられますが、本記事では詳しい解説は割愛します。 NATテーブルの状態を調べる MAP-E 環境では外側ポートの枯渇問題が常に隣り合わせです。 ポート枯渇が発生する原因はさまざまあり、実利用環境でどのような通信が原因となって発生しているか調べる必要があります。 まず、NATテーブルの消費状況をクライアントごとに確認します。 show nat descriptor address このコマンドで、特定のクライアントが異常にセッションを消費していないかを確認します。 さらに、以下のコマンドで現在の全セッションを出力して詳しく調査します。 show nat descriptor address [NATディスクリプタ番号] detail 出力されたリストの宛先IPとポートを見ることで、特定のアプリケーションが大量に通信している、DNSリクエストが滞留しているといった傾向に目星をつけることができます。 また、以下のconfigを投入することでアドレス変換の結果をSyslogに出力させることができます。 nat descriptor log on ※NATディスクリプタのログは大量に出力されるため通常はoffにしておくことを推奨します。 もし外側ポートの枯渇が発生している場合、以下のようなログが出力されます。これが出力されていたら対策必須です。 [NAT] Session was limited. Port is exhausted for [宛先IPアドレス] ポート枯渇の例 ポート枯渇の原因は環境により様々ですが、代表的なパターンを紹介します。 1. UDP/IP通信の大量発生 もっとも典型的な原因です。 TCPと異なりUDPはコネクションレスであるため、通信終了の明確な合図がありません。そのため、ルーターはタイマーの時間切れまでそのNATエントリーを保持し続けます。 特に、最近のQUIC/HTTP3を使用するアプリケーションはUDPを多用します。これらが終了した後もエントリーが残り続け、新しい通信のためのポートが確保できなくなるケースです。 2. Short-livedコネクション TCPであっても、短時間に接続・切断を繰り返す通信(Short-livedコネクション)は要注意です。RTX1300はデフォルト設定でTCPFINを60秒間保持します。秒間数回のリクエストが発生するような環境では、この終了待ちエントリーだけで数千セッションに達し、ポートを食いつぶすことがあります。 3. VPNやSASEエージェントの利用 盲点になりやすいポイントです。クライアントPCでVPNエージェントやSASEエージェントを使用している場合、ルーターから見ると全ての通信の宛先がVPNゲートウェイのIPひとつだけに見えてしまいます。 ヤマハルーターのポートセービングIPマスカレードは、宛先IPおよび宛先ポートが別ならポートを再利用できる仕組みでした。しかし、通信の宛先が全て同じゲートウェイを向いてしまうと、ポートの再利用ができなくなります。 結果として、MAP-Eの割り当てポート上限に達した時点で通信ができなくなります。 ポート枯渇対策 1.タイマーの調整 ポート枯渇を防ぐにはNATの消去タイマーの値を変更することが第一に挙げられます。RTX1300のデフォルト設定ではTCP/UDPともに900秒という非常に長い値が設定されています。特にUDPの900秒保持はRFC 4787の推奨値である300秒と比較しても過剰なため、まずはUDPの消去タイマーから調整するとよいです。 以下はUDPのNAT消去タイマーを300秒に設定する例です。 nat descriptor timer [NATディスクリプタ番号] protocol=udp 300 実際は60秒未満あるいはもっと短い値に設定しても問題がないケースも多いですが、極端に短くすると通信品質に影響が出る可能性があるため、外側ポートの使用率を見ながら調整することを推奨します。 2. 動作タイプの変更 ポートセービングIPマスカレードには動作タイプが存在します。デフォルトは2で、TCPパケットに対してのみ有効です。タイプを3にすると、UDPに対してもポートセービングIPマスカレードが有効になります。UDPパケットが原因でポート枯渇が発生する場合に有効です。 nat descriptor backward-compatibility 3 3. DoT/DoHの利用 NATテーブルを確認すると、UDP:53のエントリーが大量に残っているケースがよく見受けられます。 UDP:53のタイマー短縮も効果的ですが、クライアント側のアプローチとして DNS over TLS (DoT) やDNS over HTTPS (DoH) を有効にする方法があります。 DNS名前解決がUDP:53からTCP:443、TCP:853に移行するため、UDPタイマー待ちによるテーブルの無駄遣いを減らすことができます。 このように、ルーターの設定だけでなく、クライアント側から発生する通信内容を変更することでポートの節約に繋がる例もあります。 まとめ コンシューマー向けルーターではブラックボックスになりがちなNATテーブルの状態確認やタイマーの設定変更ができる点は、RTX1300をはじめとするヤマハルーターの強みです。 タイマーのデフォルト値は過剰な値が設定されていますが、実利用環境にあわせて調整していくことで効率的で安定した運用が可能になります。
アバター
この記事は、 ニフティグループ Advent Calendar 2025  21日目の記事です。(遅刻) はじめに こんにちは。ムサシです。 みなさんは昔ゲームの攻略本やらPC系の雑誌やらについていた、デスクトップマスコットというものを知っているでしょうか? デスクトップに常駐して時計やカレンダーを表示してくれたり、キャラクターがおしゃべりをしてくれたり、メールが来ているのをお知らせしてくれたりといろいろなものがありました。 某借金を返すゲームの攻略本についていた時計が好きだったんですよね。 というわけで、昔を懐かしみながら自分もかわいい時計を作りたいと思います。 デスクトップマスコットもいろいろ現在に生き残っているものがあるのですが、今回はその中でもカスタマイズ性が高く、時計も作れそうな「伺か」を触ってみようと思います。 「伺か」とは、簡単な言語でカスタマイズ可能な、おしゃべりするキャラクターを常駐させられるアプリのようです。 準備 まず伺かをインストールします。 起動すると、デフォルトで設定されたキャラクター(伺かでは、キャラクターのことを「ゴースト」と呼ぶそうです)と設定画面が立ち上がります。 立ち上がった設定画面を見るといろいろいじれそうでした。メールの確認や、動画配信で利用する際の表示設定など、ここにある機能だけでも普通に便利そうです。 この「ゴースト」が大量にユーザの手で作成されており、好きなゴーストを見つけて常駐させるのが一般的な使い方だと思うのですが、今回は作っていこうと思います。 時計を作るには 見た目を変えたり、会話内容を変えるのであればすごく簡単にできるようなのですが、時計を作るにはもうひと手間かかります。 まず時間を取得する必要があります。こちらはYAYAというライブラリを連携させることで簡単に行えるようです。 伺かは「栞」と呼ばれる簡単な言語によって、少し複雑な実装ができるようになるようなのですが、YAYAはその「栞」の一種となります。 (独自の用語が多く、説明すると長くなってしまうので簡易的な説明にとどめさせていただきます。) YAYAを用いたサンプルゴーストとして「ややめちゃん」というゴーストが配布されており、これを基にして新しくゴーストを作って良い旨が書いてあったので、今回はこの子をいじって行こうと思います。とてもかわいいです。 はろーYAYAわーるど(紺野ややめ): https://ms.shillest.net/yayame.xhtml 一応ですが、何か更新があるかもしれないのでGithub上から最新のYAYAのdllファイルをゴーストの中に入れておきましょう。 また、伺かはanimationという機能で画像切り替えができるようです。 まばたきや表情変化に使うのが主目的のようですが、これとYAYAを組み合わせて時計にしていきましょう。 まとめると、YAYAで時刻を取得→animationでゴーストの数字画像を対応するものに切り替えるという流れで作成していきます。 素材を作る 時計の画像はデフォルトのキャラクターが250*380くらいで作られていたので、400pxの正方形で作成して行こうと思います。 イメージはこんな感じ。 手癖でキャラをでっちあげます。できました。ときちゃんと呼びますね。(安直) ゴリゴリ素材を書いていきましょう。 必要なのは、土台になる部分、数字(1~9)です。 土台はこんな感じで、数字部分はお気に入りのフリーフォント「 ビルの谷間と高架下 (リンク先はBooth)」をお借りしようと思います。 土台部分の名称を surface0.png 、数字部分を digit_0.png 〜 digit_9.png としました。 画像が作成出来たら中身の部分を作っていきます。 時間部分の処理 栞は複数の役割を持ったイベントを持っており、そのイベント内に処理を記述すると反映されます。詳しくは こちら で。 時間については OnMinuteChange{} という1分毎に起きるイベントがあるので、そこに「時間と分を取得して各桁の数字を返す処理」を書き加えます。 サンプルのややめちゃんは yaya_aitalk.dic 内で既に OnMinuteChange{} を使っていたので、この中に書き加えるか上書きしてしまいます。 YAYAは複数の.dicファイルに記述された処理を見て動くのですが、重複して書かれているとエラーが起きてしまうので、サンプルファイルをいじるときは気を付けましょう。(1敗) OnMinuteChange { //GETTIMEで現在時刻を取得 _now = GETTIME() _h = TOINT(_now[4]) //時間(0〜23) _m = TOINT(_now[5]) //分(0〜59) //テストメッセージ。1番目のキャラクターに喋らせる。 "\C\0\b[2]現在の時刻は %(_h)時 %(_m)分 です。\e" //各桁に分ける _h10 = _h / 10 _h1 = _h % 10 _m10 = _m / 10 _m1 = _m % 10 //テストメッセージ2。2番目のキャラクターに喋らせる。 "\C\1\b[10]現在の時刻は %(_h10)%(_h1)時 %(_m10)%(_m1)分 だよ。\e" //アニメーション用の返り値 "\C\0\s[0]\i[100,%(_h10)]\i[101,%(_h1)]\i[102,%(_m10)]\i[103,%(_m1)]\e" } テスト用に時刻が取得できているか喋らせてみました。 1分毎に現在の時刻を話してくれたので、問題なさそうです。次に行きましょう。 アニメーションの処理 先ほどの返り値に対応した画像を表示するように surfaces.txt に画像の対応や処理について書いていきます。 animationXXX.interval,neverで「イベントを受け取るまで次の処理を自動実行しない」という状態になり、時間が来たら次の画像が表示となるはずです。 charset,UTF-8 surface0 { // 土台 element0,base,surface0.png,0,0 // animation番号, 描画タイミング, 描画法, ID, 待機時間, X, Y // 時の10の位 (ID 100) animation100.interval,never animation100.pattern0,overlay,100,-1,50,175 animation100.interval,never animation100.pattern1,overlay,101,-1,50,175 animation100.interval,never animation100.pattern2,overlay,102,-1,50,175 // 時の1の位 (ID 101) animation101.interval,never animation101.pattern0,overlay,100,-1,120,190 animation101.interval,never animation101.pattern1,overlay,101,-1,120,190 animation101.interval,never animation101.pattern2,overlay,102,-1,120,190 animation101.interval,never animation101.pattern3,overlay,103,-1,120,190 animation101.interval,never animation101.pattern4,overlay,104,-1,120,190 animation101.interval,never animation101.pattern5,overlay,105,-1,120,190 animation101.interval,never animation101.pattern6,overlay,106,-1,120,190 animation101.interval,never animation101.pattern7,overlay,107,-1,120,190 animation101.interval,never animation101.pattern8,overlay,108,-1,120,190 animation101.interval,never animation101.pattern9,overlay,109,-1,120,190 // 分の10の位 (ID 102) animation102.interval,never animation102.pattern0,overlay,100,-1,213,230 animation102.interval,never animation102.pattern1,overlay,101,-1,213,230 animation102.interval,never animation102.pattern2,overlay,102,-1,213,230 animation102.interval,never animation102.pattern3,overlay,103,-1,213,230 animation102.interval,never animation102.pattern4,overlay,104,-1,213,230 animation102.interval,never animation102.pattern5,overlay,105,-1,213,230 animation102.interval,never animation102.pattern6,overlay,106,-1,213,230 // 分の1の位 (ID 103) animation103.interval,never animation103.pattern0,overlay,100,-1,280,255 animation103.interval,never animation103.pattern1,overlay,101,-1,280,255 animation103.interval,never animation103.pattern2,overlay,102,-1,280,255 animation103.interval,never animation103.pattern3,overlay,103,-1,280,255 animation103.interval,never animation103.pattern4,overlay,104,-1,280,255 animation103.interval,never animation103.pattern5,overlay,105,-1,280,255 animation103.interval,never animation103.pattern6,overlay,106,-1,280,255 animation103.interval,never animation103.pattern7,overlay,107,-1,280,255 animation103.interval,never animation103.pattern8,overlay,108,-1,280,255 animation103.interval,never animation103.pattern9,overlay,109,-1,280,255 } surface100 { element0,base,digit_0.png,0,0 } surface101 { element0,base,digit_1.png,0,0 } surface102 { element0,base,digit_2.png,0,0 } surface103 { element0,base,digit_3.png,0,0 } surface104 { element0,base,digit_4.png,0,0 } surface105 { element0,base,digit_5.png,0,0 } surface106 { element0,base,digit_6.png,0,0 } surface107 { element0,base,digit_7.png,0,0 } surface108 { element0,base,digit_8.png,0,0 } surface109 { element0,base,digit_9.png,0,0 } ざっくりとした説明になりますが、前半は画像の表示や位置の調整、後半はIDと画像の指定を行っています。 動かしてみよう! さて、再起動して動かしてみます! ??????? というわけで失敗しました。 時刻が正しく表示されない 時を刻まない 数字が消える ときちゃんもたまに消える テストで入れたおしゃべりの時報だけは正しいので、アニメーションや画像連携周りがおかしいのだと思います。 年を越してしまうのでアドベンドカレンダーとしてはこんな感じで終わろうと思います。 いかがでしたか?(やけくそ) 修正できたらまたブログ書きます。orz 余談 そういえば、VOICEVOXなどで声をあてる機能がデフォルトで付いています。 VOICEVOXインストール後、設定>音声認識/合成>音声合成の設定>ボイスID横の参照ボタンから声を選んでください。 キャラを右クリックして出るメニューから機能>音声合成を押して有効にしたら声がつきます。 最後に 時計作りは失敗しましたが、楽しかったので是非触ってみてください…! 関連記事など ばぐとら研究所 …伺かを構成しているソフトウェアが配布されています。初めはフルセット版のSSPをDLしましょう。 ukadoc …伺かのWikiです。 AYAYA/03 …YAYAのWikiです。 紺野ややめ …YAYAのテンプレートゴースト、紺野ややめちゃんの紹介サイトです。 【無料頒布】日本語フォント「ビルの谷間と高架下」を作りました …お気に入りのフォント制作者様のnoteです。 VOICEVOX …おしゃべりに声を付けたい人は是非。春日部つむぎちゃんが好きです。 著作権周り SSPの基本情報 この記事で紹介した「SSP」は、C.Ponapalt様およびDOIchan!様に著作権が帰属するフリーウェアです。 ヘルプドキュメント(Chameleon Ponapalt様、す~ちゃん様著作)に基づき紹介しています。 「YAYA」テンプレートゴースト 紺野ややめ シェル作者サトウ M様、ゴースト作者はリンク先のGithubを参照ください。 Public Domain (Unlicense)です。
アバター
この記事は、 ニフティグループ Advent Calendar 2025 20日目の記事です。 はじめに こんにちは。ニフティの並木です。 今回は、Pythonのpatch.objectについてご紹介します。 ・本記事の動作確認環境:Python 3.13 ・公式ドキュメント: https://docs.python.org/ja/3.13/library/unittest.mock.html patch.objectとは patch.object は、Pythonのオブジェクト(インスタンスやクラス)の一部を、一時的にモックに置き換える機能で、テストの時に活用することができます。 具体的なテストコードを見ていきます。 import unittest from unittest.mock import patch # テスト対象のクラス(実際の開発では別ファイルにある想定) class FileManager: def _get_latest_record(self): """DBにアクセスして最新のファイル名を取得する複雑なメソッド""" # 中身は省略 pass def generate_file_name(self): """新しいファイル名を生成するメソッド""" latest = self._get_latest_record() if latest is None: return "file001.csv" return f"file_{latest}.csv" # テストコード class TestFileManager(unittest.TestCase): def test_generate_file_name_when_db_is_empty(self): """DBにレコードがない(Noneが返る)場合の挙動を確認するテスト""" file_mgr = FileManager() # _get_latest_record が None を返すように一時的に置き換える with patch.object(file_mgr, '_get_latest_record', return_value=None): # 内部で _get_latest_record が呼ばれる result = file_mgr.generate_file_name() # 検証 self.assertEqual(result, "file001.csv") if __name__ == "__main__": unittest.main() 本来、 _get_latest_record というメソッドは、実際にDB見に行って「最後に保存されたファイル名」を探してくる複雑な処理を行っています。 しかし、今回のテストでやりたいことは「DBに該当レコードが無い時の挙動」を確かめることです。 実際にDBを空にするのは大変ですし、他のテストに影響が出てくるかもしれません。 そこで、 patch.object を使い 「このテストの間だけ、 _get_latest_record は何も考えずに None を返すようにして」 と命令しているのです。 with ブロックを使っている理由は、 「テストが終わったら、 _get_latest_record をモックオブジェクトから、本物に戻すため」 です。 with ブロックを抜けると、 モックオブジェクト に置き換わっていたメソッドが、自動的に 元の本物のメソッド へと戻されます。 patch.object の書き方 with patch.object(対象のオブジェクト, "メソッド名などの文字列", return_value=返り値): 第1引数 : ターゲットとなるインスタンスそのものを渡します。今回はインスタンスを渡していますが、クラスを渡すこともできます。 第2引数 : 置き換えたいメソッドの名前を「文字列」で指定します。メソッド名を打ち間違えても、実行するまでエラーに気づきにくいため注意が必要です。 return_value : そのメソッドが呼ばれたときに、何を返してほしいかを指定します。上の例ではNoneを設定していました。 おわりに Pythonのpatch.objectについてご紹介しました。 テストコード作成の際にぜひ利用してみてください。 また、デコレータを使ってモックオブジェクトを作る方法もあるそうです。機会があればこちらも使ってみたいと思います。 ※公式ドキュメント: https://docs.python.org/ja/3.13/library/unittest.mock-examples.html#patch-decorators 明日は、nemu_nemu_musashi さんの「デスクトップマスコットを作りたい」です。お楽しみに!
アバター
この記事は、 ニフティグループ Advent Calendar 2025 22日目の記事です。 はじめに こんにちは。添野隼矢です。 本記事では、NotionDBに新規で追加されたページを、Notion Automationを使ってSlackに通知する方法をご紹介します。 Notion Automationについては、こちらをご参照ください。 https://www.notion.com/ja/help/guides/category/automations 背景 ニフティでは、技術やイベントレポートなど、共有したい資料やレポート、メモを書くためのNotionDBがあります。 多くの人にこのNotionDBに新しく追加されたページを見てもらいたいという要望があり、NotionDBに新しくページが追加された際に「新しくページが追加されました!」と通知されるチャンネルを作りたいという意見が出ました。 どうすれば簡単に通知できるか悩んでいたところ、Notion Automationが使えるかもという助言を頂き、実装できましたので、本記事にてご紹介したいと思います。 要件 Notion Automaitonを使用 ページ作成通知で通知してほしいもの ページタイトル ただし、ページタイトルが作成後きまっていないもの/未設定のものはuntitled pageというタイトルにする 作成者の名前 メンション通知は不要 例えば、AさんがNotionDBにページを追加した際、SlackにAさんのメンション(@Aさん)ではなく、Aさんの名前を通知させたいです。 作成されたページへのリンク Notion Automationで通知されるとNotionへのリンクボタンが自動で通知されます。 作成手順 通知したいNotionDBのNotion Automaiton作成画面を開きます。 新規トリガーで追加されたページを選びます。 ここで、要件の通り、通知の際にページ作成者へのメンションを防ぐための変数とタイトルがない場合にuntilted pageとする変数を追加します。 ■ ページ作者変数(今回はcreater_nameという変数名にしました。) ■ ページタイトル変数(今回はtitleという変数名にしました。) 次にSlack通知を送信するアクションを追加します。 通知するチャンネルを選んで、カスタムメッセージを以下のように編集します。 有効化してテストします テスト ページを追加してみました。 Slackに通知されました。 おわりに 今回は、Notion Automationを使ってNotionDBに新規で追加されたページをSlackに通知する方法をご紹介しました。 みなさまの何かのお役に立てれば幸いです。
アバター
 はじめに こんにちは、新卒1年目のmoriです。 私が現在 OJTで所属している部署では、ネットワーク機器やサーバーの監視にZabbixを用いています。 しかし、登録機器が増えすぎた結果、情報の記録に用いているMySQLサーバーへの書き込みが増え、ディスクの書き込みも増加。 ディスクIO過多によってCPU使用率も100%に張り付いてしまいました。 そこで、Zabbixで使用しているDBを、TimescaleDBへの変更し、解決を図りました。 TimescaleDBとはPostgreSQLの拡張機能で、大量にある時系列データをhyper tableに分割することによって効率的に管理することが可能になります。 実行前にここだけは読んでほしい 私が苦労した原因のほとんどはリソース不足によるものです。 こちらは、「もっと早く言ってよ!」となりかねない情報なので最初に書いておきます。 DBマイグレーションをするだけではCPU使用率の削減には繋がらない場合も 今回の場合は純粋にZabbixの処理不可によるものだったので解決しなかった マイグレーション実行環境のマシンスペックは強めに取った方がいい メモリを多めに積んでください 16GBで足りず、32GBにしました。 ディスクの容量は移行するデータサイズの4倍程度は欲しい 各移行段階のデータを保持したいならそのくらい必要です マイグレーションの流れ 今回は以下のような手順に沿って既存データをMySQLからPostgreSQLにマイグレーションしました。 既存DBからmydumperを用いてデータをダンプ myloaderを用いて、マイグレーション用環境のMySQLにデータを取り込み pgloaderを用いて、MySQL→PostgreSQLにデータ移行 pg_dumpを用いて通常PostgreSQL状態のダンプを実行 通常のPostgreSQLからTimescaleDB環境に変換するためのスクリプトを実行 1. mydumper はじめにmydumperを用いて、既存のZabbixのログデータをダンプします。 本番環境を長時間停止して、同環境で直接作業できるならスキップ可能です。 ここでの詰まりポイントは、 --events --triggers オプションとrootユーザーでの実行になります。これらのオプションを付けないと event~ や tiggers~ といったテーブルのダンプが取れません。 また、 triggers~ 系のテーブルはrootユーザーでないとダンプすることができませんでした。 ❯ mydumper -u root --ask-password --compress --threads=16 --rows 100000 --events --triggers --routines --database {DB名} 2. myloader 続いてmydumperでダンプしたデータをマイグレーション作業環境のMySQLに取り込みなおす工程です。ダンプしたデータはsftpなりで移してください。 普通に実行すると、 events よりも event-XXXX テーブルが先に取り込まれてしまい、外部キー制約関係のエラーが出るので、適当にディレクトリを作ってそこにeventsテーブルのダンプデータを移動して先に取り込む必要がありました。 その後events以外要素を取り込むことでうまく取り込めます。 また、取り込み時でも trigger-XXXX テーブルが通常ユーザーでは取り込めなかったためrootユーザーで取り込みを実行する必要が有りました。 ❯ mkdir events # eventsテーブルの退避先を作成 ❯ mv events* ./events # eventsテーブルのダンプデータを退避 ❯ cp metadata ./events # ディレクトリ指定時にmydumperのダンプデータとして認識させるためにメタデータをコピー # 退避したeventsテーブルを先に取り込み ❯ myloader --host {ホスト名} --user root --ask-password --database {DB名} --verbose 3 --directory=./events --queries-per-transaction=25000 --threads=8 --compress-protocol --ssl --verbose=3 -e # その後他のテーブルを取り込み ❯ myloader --host {ホスト名} --user root --ask-password --database {DB名} --verbose 3 --directory=. --queries-per-transaction=25000 --threads=8 --compress-protocol --ssl --verbose=3 -e 3. pgloader 作業環境MySQLにデータを取り込めたのでいよいよ、MySQL→ PostgreSQLマイグレーションを実行します。 pgloaderは接続情報やオプションをファイルに記述できるので書いておきます。 こちらでlocalhostでDBを指定するとsocketにつなぎにいくため、dockerでDBを動かす場合はループバックアドレスを使用する必要があります。 以下をmy.loadとして保存 LOAD DATABASE FROM mysql://root:{rootのパスワード}@{ホスト名}/{DB名} INTO postgresql://{ユーザ名}:{パスワード}@{ホスト名}/{DB名} alter schema '{MyS}' rename to 'public' SET maintenance_work_mem TO '4096MB', work_mem to '1024MB' ; pgloaderを用いたマイグレーション作業は、かなり色々なエラーが発生したので、ログを交えつつ説明していきます。 エラーの数が多すぎて正確な順番を覚えていないため、順番が前後しているかもしれません。ご了承くださいください。 3.1 文字コード変換エラー ubuntuのaptからインストールできるpgloader 3.6.7~develは文字コード変換エラーが発生して動作しないので最新版の3.6.9のソースコードをダウンロード&ビルドしてそれを使用することで解決しました。 ❯ pgloader my.load 2025-11-05T01:44:32.006000Z LOG pgloader version "3.6.7~devel" 2025-11-05T01:44:32.110999Z LOG Migrating from #<MYSQL-CONNECTION {MySQLの接続情報} {1006A6ADF3}> 2025-11-05T01:44:32.111999Z LOG Migrating into #<PGSQL-CONNECTION {PostgreSQLの接続情報} {1006A6AF93}> 2025-11-05T01:44:32.149999Z ERROR mysql: 76 fell through ECASE expression. Wanted one of (2 3 4 5 6 8 9 10 11 14 15 17 20 21 23 27 28 30 31 32 33 35 41 42 45 46 47 48 49 50 51 52 54 55 56 60 61 62 63 64 65 69 72 77 78 79 82 83 87 90 92 93 94 95 96 97 98 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 254). 2025-11-05T01:44:32.149999Z LOG report summary reset table name errors rows bytes total time ----------------- --------- --------- --------- -------------- fetch meta data 0 0 0.000s ----------------- --------- --------- --------- -------------- ----------------- --------- --------- --------- -------------- 自分の環境ではビルドするのにopenssl-develとsbclが足りなかったので追加でインストールしました。 ❯ wget https://github.com/dimitri/pgloader/releases/download/v3.6.9/pgloader-bundle-3.6.9.tgz ❯ tar xavf ./pgloader-bundle-3.6.9.tgz ❯ cd pgloader-bundle-3.6.9 ❯ make # 完了するとプロジェクト直下のbinディレクトリにバイナリが生成されるので任意の場所に移動(パスを通してもいい) 3.2 MySQLの認証方法変更 pgloaderがMySQLの新しい認証方式に対応してない関係でMySQLに接続できないので、設定を変更する必要があります。 (デフォルトの認証プラグインが caching_sha2_password に変わっているため) ❯ mysql -u root -h {ホスト名} -p (パスワードを入力) # ユーザー情報を確認 SELECT user, host, plugin FROM mysql.user; # 変換 ALTER USER 'root'@'{ホスト}' IDENTIFIED WITH mysql_native_password BY '{新しいパスワード}'; FLUSH PRIVILEGES; 3.3 GCのメモリ不足 オプションでの指定なしで実行すると、データが大きすぎるため作業メモリが足りず、GCのエラーを吐いて停止してしまうのでメモリの割当を増やして対応します。 また、オプションでメモリ割当を増やしても、マシンに搭載されているメモリが不足すると、「MySQLのタイムアウトが発生した」旨のエラーが発生して停止してしまいます。 一応設定でタイムアウトまでの時間を延長し、それでも同じエラーが出るようでしたら、メモリが多い環境で実行するようにしてください。 GCエラー ❯ ./pgloader my.load 2025-11-05T05:03:53.006000Z LOG pgloader version "3.6.9" 2025-11-05T05:03:53.109999Z LOG Migrating from #<MYSQL-CONNECTION {MySQLの接続情報} {100686C0C3}> 2025-11-05T05:03:53.109999Z LOG Migrating into #<PGSQL-CONNECTION {PostgreSQLの接続情報} {100686C263}> Heap exhausted during garbage collection: 544 bytes available, 832 requested. Immobile Object Counts Gen layout fdefn symbol code Boxed Cons Raw Code SmMix Mixed LgRaw LgCode LgMix Waste% Alloc Trig Dirty GCs Mem-age 1 0 0 0 0 105 2 4081 0 0 35 0 0 0 2.3 135218064 116199738 4223 1 0.7799 2 0 0 0 0 376 3 13856 0 0 26 0 0 0 1.5 460182320 191227498 14037 1 1.1357 3 0 0 0 0 235 9 4052 0 2 60 0 0 0 3.0 138513456 20252714 4169 1 0.2367 4 0 0 0 0 60 1 267 0 0 18 0 0 0 4.7 10809520 21546938 268 1 0.0000 5 54 0 0 80 300 45 1149 1 9 94 0 0 5175 3.0 215344944 219748826 1169 5 0.9706 fatal error encountered in SBCL pid 70636 tid 70641: GC invariant lost, file "gencgc.c", line 523 Welcome to LDB, a low-level debugger for the Lisp runtime environment. (GC in progress, oldspace=1, newspace=2) ldb> MySQLのタイムアウト What I am doing here? MySQL ERROR: Partial Read of 25 bytes, expected 57 Detail: check MySQL logs for (Got timeout writing communication packets) Hint: adjust net_read_timeout and net_write_timeout # pgloaderの実行、ビルドしたバイナリを使用してるのでパスを直に指定している # my.loadは上記の設定ファイル # --dynamic-space-size が割当メモリ設定オプション MB単位 ❯ ./pgloader --dynamic-space-size 2048 my.load   3.4 ようやくの成功 成功すると以下のような結果が出力される mori in zabbix-db-migration-test in ~/disk ❯ ./pgloader --dynamic-space-size 2048 ./my.load 2025-11-06T00:57:46.006000Z LOG pgloader version "3.6.9" 2025-11-06T00:57:46.119000Z LOG Migrating from #<MYSQL-CONNECTION {MySQLの接続情報} {1006858643}> 2025-11-06T00:57:46.120000Z LOG Migrating into #<PGSQL-CONNECTION {PostgreSQLの接続情報} {10068587E3}> 2025-11-06T01:32:49.388234Z LOG report summary reset table name errors rows bytes total time --------------------------------- --------- --------- --------- -------------- fetch meta data 0 1005 0.113s Create Schemas 0 0 0.001s Create SQL Types 0 0 0.004s Create tables 0 414 1.205s Set Table OIDs 0 207 0.010s --------------------------------- --------- --------- --------- -------------- public.history_uint 0 247670390 7.1 GB 24m51.621s public.history 0 179818097 6.0 GB 18m19.154s public.trends 0 31790612 1.5 GB 11m52.358s public.trends_uint 0 42925943 1.5 GB 13m13.914s public.history_text 0 2908279 732.0 MB 1m27.851s public.event_tag 0 7249860 286.1 MB 1m8.851s public.event_recovery 0 549542 12.6 MB 6.828s public.history_str 0 92327 5.3 MB 1.679s public.items 0 60082 20.9 MB 6.865s public.item_discovery 0 35174 2.0 MB 1.866s public.item_rtdata 0 34842 1.0 MB 3.040s public.trigger_tag 0 29393 822.2 kB 5.422s public.triggers 0 24943 9.6 MB 7.189s public.graphs 0 10462 986.2 kB 4.056s public.trigger_discovery 0 8851 269.3 kB 3.134s public.widget_field 0 5864 346.1 kB 2.607s public.graph_discovery 0 3808 99.8 kB 2.796s public.lld_macro_path 0 2359 78.5 kB 3.023s public.changelog 0 1553 44.4 kB 3.338s public.problem_tag 0 1429 47.7 kB 3.602s public.host_tag 0 1370 40.1 kB 3.912s public.valuemap 0 1184 77.3 kB 4.169s public.lld_override 0 968 34.7 kB 3.970s public.hosts 0 791 253.9 kB 4.174s public.host_hgset 0 748 6.3 kB 4.399s public.hosts_templates 0 465 8.6 kB 4.643s public.host_rtdata 0 426 3.3 kB 4.834s public.dashboard_page 0 411 8.8 kB 4.960s public.dashboard 0 308 21.7 kB 5.741s public.media_type_message 0 213 58.0 kB 6.428s public.settings 0 118 5.3 kB 6.958s public.host_discovery 0 48 1.3 kB 7.277s public.hgset_group 0 36 0.2 kB 7.552s public.module 0 32 1.1 kB 7.805s public.hgset 0 27 1.8 kB 8.521s public.operations 0 22 0.4 kB 8.889s public.lld_override_optemplate 0 14 0.2 kB 9.032s public.interface_snmp 0 11 0.4 kB 9.866s public.expressions 0 10 0.5 kB 10.597s public.opmessage 0 8 0.1 kB 11.581s public.usrgrp 0 6 0.2 kB 11.790s public.interface_discovery 0 5 0.0 kB 12.276s public.users_groups 0 5 0.0 kB 12.606s public.opmessage_grp 0 4 0.0 kB 14.756s public.role 0 4 0.1 kB 15.239s public.scripts 0 3 0.3 kB 15.853s public.acknowledges 0 2 0.1 kB 16.007s public.ha_node 0 1 0.1 kB 16.248s public.sysmaps_elements 0 1 0.1 kB 16.642s public.config_autoreg_tls 0 1 0.0 kB 16.840s public.connector_tag 0 0 17.039s public.corr_condition_group 0 0 17.164s public.corr_condition_tagpair 0 0 17.328s public.corr_operation 0 0 17.458s public.dashboard_user 0 0 17.620s public.dbversion 0 1 0.0 kB 17.760s public.dhosts 0 0 17.893s public.dservices 0 0 17.992s public.event_suppress 0 0 18.193s public.globalmacro 0 1 0.0 kB 18.373s public.group_discovery 0 0 18.639s public.host_proxy 0 0 18.851s public.httpstep 0 0 18.988s public.httpstepitem 0 0 19.068s public.httptest_field 0 0 19.214s public.httptestitem 0 0 19.221s public.icon_mapping 0 0 19.241s public.lld_override_ophistory 0 0 19.444s public.lld_override_opperiod 0 0 19.631s public.lld_override_optrends 0 0 19.713s public.maintenances 0 0 19.900s public.maintenances_hosts 0 0 20.058s public.media 0 0 20.202s public.mfa 0 0 20.378s public.opcommand 0 0 20.444s public.opcommand_hst 0 0 20.532s public.opinventory 0 0 20.767s public.optag 0 1 0.0 kB 20.944s public.proxy 0 0 21.051s public.proxy_dhistory 0 0 21.158s public.proxy_group_rtdata 0 0 21.400s public.proxy_rtdata 0 0 21.471s public.report_param 0 0 21.455s public.report_usrgrp 0 0 21.576s public.scim_group 0 0 21.663s public.service_alarms 0 0 21.839s public.service_problem_tag 0 0 22.072s public.service_tag 0 0 22.190s public.services_links 0 0 22.277s public.sla_excluded_downtime 0 0 22.340s public.sla_service_tag 0 1 0.0 kB 22.599s public.sysmap_element_url 0 0 22.895s public.sysmap_shape 0 1 0.1 kB 23.598s public.sysmap_user 0 0 23.701s public.sysmaps_element_tag 0 0 23.780s public.sysmaps_links 0 0 23.937s public.task 0 0 24.096s public.task_check_now 0 0 24.348s public.task_data 0 0 24.666s public.task_remote_command_result 0 0 24.824s public.timeperiods 0 0 25.002s public.trigger_queue 0 0 25.067s public.user_scim_group 0 0 25.124s public.userdirectory 0 0 25.266s public.userdirectory_ldap 0 0 25.399s public.userdirectory_saml 0 0 25.495s public.events 0 1101492 183.6 MB 42.597s public.item_tag 0 117492 3.8 MB 1.333s public.item_preproc 0 61151 2.6 MB 1.380s public.functions 0 47934 1.3 MB 2.031s public.item_rtname 0 33155 2.8 MB 0.927s public.valuemap_mapping 0 32638 921.4 kB 0.403s public.graphs_items 0 28356 1011.7 kB 1.154s public.trigger_depends 0 12763 226.6 kB 1.462s public.item_condition 0 10214 554.6 kB 1.282s public.hostmacro 0 5725 551.3 kB 0.483s public.auditlog 0 5873 4.2 MB 0.878s public.housekeeper 0 4057 125.1 kB 0.138s public.widget 0 1521 58.0 kB 0.122s public.lld_override_operation 0 1385 41.4 kB 0.274s public.lld_override_opdiscover 0 1371 9.4 kB 0.513s public.lld_override_opstatus 0 1228 8.4 kB 0.396s public.hosts_groups 0 1172 15.2 kB 0.563s public.lld_override_condition 0 968 45.9 kB 0.784s public.item_parameter 0 745 29.6 kB 0.837s public.media_type_param 0 674 26.3 kB 1.250s public.interface 0 451 20.4 kB 1.231s public.autoreg_host 0 415 23.6 kB 1.190s public.profiles 0 321 16.0 kB 1.359s public.problem 0 256 32.8 kB 1.373s public.images 0 187 1.9 MB 1.666s public.group_prototype 0 65 1.7 kB 1.520s public.ids 0 46 1.3 kB 1.565s public.media_type 0 42 314.1 kB 1.923s public.hstgrp 0 29 1.6 kB 1.787s public.role_rule 0 27 0.8 kB 1.825s public.sessions 0 20 1.6 kB 1.931s public.conditions 0 12 0.2 kB 2.065s public.actions 0 10 0.5 kB 2.006s public.host_inventory 0 9 1.0 kB 2.199s public.opgroup 0 6 0.0 kB 2.297s public.history_log 0 5 3.2 kB 2.336s public.regexps 0 5 0.2 kB 2.247s public.graph_theme 0 4 0.9 kB 2.184s public.optemplate 0 4 0.0 kB 2.387s public.lld_override_optag 0 3 0.1 kB 2.474s public.ugset_group 0 3 0.0 kB 2.455s public.users 0 2 0.3 kB 2.661s public.sysmaps 0 1 0.1 kB 2.682s public.alerts 0 0 2.785s public.connector 0 0 2.645s public.corr_condition 0 0 2.605s public.corr_condition_tag 0 0 2.564s public.corr_condition_tagvalue 0 0 2.515s public.correlation 0 0 2.653s public.dashboard_usrgrp 0 1 0.0 kB 2.698s public.dchecks 0 1 0.0 kB 2.875s public.drules 0 1 0.0 kB 2.919s public.escalations 0 0 2.977s public.event_symptom 0 0 2.995s public.globalvars 0 1 0.0 kB 3.146s public.history_bin 0 0 3.026s public.hostmacro_config 0 0 3.061s public.httpstep_field 0 0 3.095s public.httptest 0 0 3.174s public.httptest_tag 0 0 3.315s public.icon_map 0 0 3.391s public.lld_macro_export 0 0 3.324s public.lld_override_opinventory 0 0 3.614s public.lld_override_opseverity 0 0 3.602s public.maintenance_tag 0 0 3.564s public.maintenances_groups 0 0 3.565s public.maintenances_windows 0 0 3.774s public.media_type_oauth 0 0 3.801s public.mfa_totp_secret 0 0 3.795s public.opcommand_grp 0 0 3.771s public.opconditions 0 0 3.933s public.opmessage_usr 0 0 4.030s public.permission 0 0 4.128s public.proxy_autoreg_host 0 0 4.033s public.proxy_group 0 0 4.178s public.proxy_history 0 0 4.183s public.report 0 0 4.212s public.report_user 0 0 4.213s public.rights 0 0 4.307s public.script_param 0 0 4.447s public.service_problem 0 0 4.542s public.service_status_rule 0 0 4.499s public.services 0 0 4.427s public.sla 0 1 0.1 kB 4.889s public.sla_schedule 0 0 4.536s public.sysmap_element_trigger 0 0 4.544s public.sysmap_link_threshold 0 0 4.539s public.sysmap_url 0 0 4.556s public.sysmap_usrgrp 0 0 4.556s public.sysmaps_link_triggers 0 0 4.511s public.tag_filter 0 0 4.524s public.task_acknowledge 0 0 4.556s public.task_close_problem 0 0 4.550s public.task_remote_command 0 0 4.545s public.task_result 0 0 4.605s public.token 0 0 4.585s public.ugset 0 1 0.1 kB 4.813s public.user_ugset 0 1 0.0 kB 4.652s public.userdirectory_idpgroup 0 0 4.616s public.userdirectory_media 0 0 4.646s public.userdirectory_usrgrp 0 0 4.669s --------------------------------- --------- --------- --------- -------------- COPY Threads Completion 0 4 33m34.875s Create Indexes 0 521 22m28.427s Index Build Completion 0 521 1m20.883s Reset Sequences 0 1 0.127s Primary Keys 0 207 0.291s Create Foreign Keys 0 277 5.553s Create Triggers 0 0 0.000s Install Comments 0 0 0.000s --------------------------------- --------- --------- --------- -------------- Total import time ✓ 514702901 17.4 GB 57m30.156s   3.5. pgdump pgloaderでMySQLからデータを移行しただけでは、通常のPostgreSQLのテーブル構造になっているため、TimescaleDB用の構造に変換する必要があります。 万が一変換に失敗した時のために、この段階でバックアップを作成することをオススメします。 pg_dumpを用いたダンプの出力はいくつか種類がありますが、今回はdocker環境での取り回しが良く、比較的サイズが小さい、plainのgzip圧縮を使用しました。 ❯ pg_dump -d {接続情報} | gzip > backup.sql.gz   4. TimescaleDB 先程述べた通り、PostgreSQLに移行したテーブルをTimescaleDBのhypertableに変換する必要があります。 変換スクリプトが用意されているのでそちらを実行します。私の場合は、コンテナ内から取り出して使用しました。 PostgreSQLの状態でデータ保存用のボリュームを作っていた場合、TimescaleDBの拡張の認識設定がされていないので設定を書き換える必要があります。 (最初からTimescaleDBのimageでやってたら不要) スクリプト実行時のエラーにかかれている通り設定に shared_preload_libraries = 'timescaledb' を追記する必要があります TimecaleDB拡張が認識されていない状態で有効化しようとして失敗したログ ❯ echo "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" | psql {接続情報} Password for user user: FATAL: extension "timescaledb" must be preloaded HINT: Please preload the timescaledb library via shared_preload_libraries. This can be done by editing the config file at: /var/lib/postgresql/data/postgresql.conf and adding 'timescaledb' to the list in the shared_preload_libraries config. # Modify postgresql.conf: shared_preload_libraries = 'timescaledb' Another way to do this, if not preloading other libraries, is with the command: echo "shared_preload_libraries = 'timescaledb'" >> /var/lib/postgresql/data/postgresql.conf (Will require a database restart.) server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. connection to server was lost この工程での詰まりポイントは、ディスクの空き容量不足です。 変換スクリプトの実行中通常のテーブルとhypertable両方の情報が同居する関係なのか、データサイズがかなり肥大化します。 私が実行した環境では1.5~2倍程度にまで肥大化していたと思います。 Zabbixサーバーのコンテナがいきなり登場してますが、今回は説明を割愛します。 記事の最後に私が検証に用いたDocker ComposeでのZabbixの最小限の構成をおまけで付けているので、そちらを参照してください。 # zabbix-serverが稼働した状態で変換スクリプトをコンテナから取り出す # zabbix-serverです、timescaledbではなく ❯ docker cp {zabbix-serverのコンテナID}:/usr/share/doc/zabbix-server-postgresql/. ./scripts # 取り出したスクリプトを確認 ❯ ls scripts  create.sql.gz  option-patches  timescaledb.sql # timescaledb拡張を有効化 ❯ echo "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" | psql -U {ユーザ名} -h {ホスト名} Password for user {ユーザ名}: CREATE EXTENSION # 変換スクリプトを実行 ❯ cat ./scripts/timescaledb.sql | psql -U {ユーザ名} -h {ホスト名} 実行ログ mori in zabbix-db-migration-test in ~/disk ❯ echo "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;" | psql -U {ユーザ名} -h {ホスト名} Password for user {ユーザ名}: NOTICE: extension "timescaledb" already exists, skipping CREATE EXTENSION mori in zabbix-db-migration-test in ~/disk ❯ cat ./script/timescaledb.sql | psql -U {ユーザ名} -h {ホスト名} Password for user {ユーザ名}: CREATE FUNCTION NOTICE: function base36_decode(pg_catalog.varchar) does not exist, skipping DROP FUNCTION NOTICE: PostgreSQL version 17.6 is valid NOTICE: TimescaleDB extension is detected NOTICE: TimescaleDB version 2.21.4 is valid NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. WARNING: column type "character varying" used for "source" does not follow best practices HINT: Use datatype TEXT instead. NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. WARNING: column type "character varying" used for "value" does not follow best practices HINT: Use datatype TEXT instead. NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. WARNING: column type "character varying" used for "auditid" does not follow best practices HINT: Use datatype TEXT instead. WARNING: column type "character varying" used for "username" does not follow best practices HINT: Use datatype TEXT instead. WARNING: column type "character varying" used for "ip" does not follow best practices HINT: Use datatype TEXT instead. WARNING: column type "character varying" used for "resource_cuid" does not follow best practices HINT: Use datatype TEXT instead. WARNING: column type "character varying" used for "resourcename" does not follow best practices HINT: Use datatype TEXT instead. WARNING: column type "character varying" used for "recordsetid" does not follow best practices HINT: Use datatype TEXT instead. NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. NOTICE: migrating data to chunks DETAIL: Migration might take a while depending on the amount of data. NOTICE: TimescaleDB is configured successfully DO   まとめ 残念ながら、当初の目的であったCPU使用率の削減にはつながりませんでした。 しかし、ディスクへの書き込み頻度と書き込みデータ量が大幅に減りました。 また、MySQLには大量のbinlogが溜まっていたというのもありますが、TimescaleDBの圧縮機能によってディスク消費量も大幅に減らすことができました。   おまけ MySQLの最小構成のDocker Composeファイル DBが初期化されるまで2,3分かかる(その前にアクセスすると異常な設定、みたいなエラーがでるのでしばらく待ち) services: mysql-server: image: mysql:8.0-bookworm restart: always command: - --log-bin-trust-function-creators=1 - --character-set-server=utf8mb4 - --collation-server=utf8mb4_bin environment: MYSQL_DATABASE: {DB名} MYSQL_USER: {ユーザ名} MYSQL_PASSWORD: {パスワード} MYSQL_ROOT_PASSWORD: {rootパスワード} healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 volumes: - ./data:/var/lib/mysql - ./mysql_conf/:/etc/mysql/ ports: - 3306:3306 cap_add: - SYS_NICE zabbix-server: image: zabbix/zabbix-server-mysql:ubuntu-latest restart: always ports: - 10051:10051 environment: DB_SERVER_HOST: mysql-server # 上で定義したMySQLサービス名 MYSQL_DATABASE: {上で定義したDB名} MYSQL_USER: {同上} MYSQL_PASSWORD: {同上} volumes: - ./zabbix_conf/:/etc/zabbix depends_on: mysql-server: condition: service_healthy zabbix-web: image: zabbix/zabbix-web-nginx-mysql:ubuntu-latest restart: always ports: - 80:8080 environment: DB_SERVER_HOST: mysql-server # 上で定義したMySQLサービス名 MYSQL_DATABASE: {上で定義したDB名} MYSQL_USER: {同上} MYSQL_PASSWORD: {同上} ZBX_SERVER_HOST: zabbix-server # 上で定義したZbbixSeverサービス名 PHP_TZ: Asia/Tokyo depends_on: - zabbix-server zabbix-agent: image: zabbix/zabbix-agent:ubuntu-latest restart: always environment: ZBX_SERVER_HOST: zabbix-server # 上で定義したZbbixSeverサービス名 ZBX_SERVER: zabbix-server # Azureの仮想マシン上でdocker composeを使うと謎にsshが死ぬのでつけてる # 普通はいらない networks: default: ipam: driver: default config: - subnet: 192.168.1.0/24 # 競合しないプライベートIP範囲を指定 gateway: 192.168.1.1 TimescaleDB(PostgreSQL)の最小構成Docker Compose ファイル 今回の使用法におていは、テーブル変換をしない場合は普通のPostgreSQLと同じ動きをするので、通常版は割愛 services: postgres-server: image: timescale/timescaledb:2.22.1-pg17 restart: always environment: POSTGRES_USER: {ユーザ名} POSTGRES_PASSWORD: {パスワード} POSTGRES_DB: {DB名} ports: - 5432:5432 volumes: - ./postgres-data:/var/lib/postgresql/data zabbix-server: image: zabbix/zabbix-server-pgsql:ubuntu-latest restart: always environment: DB_SERVER_HOST: postgres-server # 上で定義したDBのホスト名 POSTGRES_USER: {上で定義したDBユーザ名} POSTGRES_PASSWORD: {上で定義したDBパスワード} POSTGRES_DB: {上で定義したDB名} ports: - 10051:10051 volumes: - ./zabbix_conf/:/etc/zabbix depends_on: - postgres-server healthcheck: test: ["CMD-SHELL", "pgrep zabbix_server"] interval: 10s timeout: 5s retries: 5 start_period: 20s zabbix-web: image: zabbix/zabbix-web-nginx-pgsql:ubuntu-latest restart: always environment: DB_SERVER_HOST: postgres-server # 上で定義したDBのホスト名 POSTGRES_USER: {上で定義したDBユーザ名} POSTGRES_PASSWORD: {上で定義したDBパスワード} POSTGRES_DB: {上で定義したDB名} ZBX_SERVER_HOST: zabbix-server PHP_TZ: Asia/Tokyo ports: - 80:8080 depends_on: - postgres-server - zabbix-server zabbix-agent: image: zabbix/zabbix-agent:ubuntu-latest restart: always environment: ZBX_SERVER_HOST: zabbix-server # 上で定義したZbbixSeverサービス名 ZBX_SERVER: zabbix-server # 上で定義したZbbixSeverサービス名 depends_on: zabbix-server: condition: service_healthy # azureの仮想マシン上でdocker composeを使うと謎にsshが死ぬのでつけてる # 普通はいらない networks: default: ipam: driver: default config: - subnet: 192.168.1.0/24 # 競合しないプライベートIP範囲を指定 gateway: 192.168.1.1 参考資料 https://www.tigerdata.com/docs/use-timescale/latest/hypertables https://www.zabbix.com/documentation/current/jp/manual/appendix/install/timescaledb https://pgloader.readthedocs.io/en/latest/ref/mysql.html https://github.com/dimitri/pgloader/issues/1211 https://qiita.com/11ohina017/items/4a808e4fc03e1ac890ba https://assets.zabbix.com/files/events/meetup_20200702/meetup_20200702_MySQL2PgSQL-ENG.pdf
アバター
この記事は、 ニフティグループ Advent Calendar 2025  19日目の記事です。 はじめに おはようございます。IWS です 2025年アドベントカレンダーも19日目の記事です。 19日目のこの記事では、 GitHub Actions を使った CI について書こうかなと思います。 GitHub Actions での CI 私のチームでは以下のような構成のコンテナ環境を使用しています。 apl コンテナを中心に複数のコンテナを使う構成で開発しています。テスト実行には各コンテナが必要なため、これまで GitHub Actions で CI を組んでもビルドを含めて10分以上かかるような状態でした。 何かあって CI を回すたびに10分待たなければいけないというのはかなりのストレスなため少しでも早くできるように試していきたいと思います。 イメージの準備 テスト実行にコンテナのDB等が必要な都合、GitHub Actions 上でもビルドが必要になります。CI にかかる時間の半分はビルドにかかった時間です。 今回は必要なコンテナが4つあり、すべてをビルドするのはかなりの時間がかかるため少しでも CI 実行時間を抑えられるようにしていきます。 GHCR にあらかじめイメージを保存しておく もしほとんど変更がなく都度ビルドする必要がないような場合は GHCR ( GitHub Container Registry ) にイメージをあらかじめ保存しておくことで CI 時のビルドを省くことができます。GHCR は GitHub が提供している コンテナレジストリです。 以下のコマンドでイメージを push することができます。 $ docker build --no-cache -f stub.Dockerfile -t stub . $ echo "<PAT>" | docker login ghcr.io -u <GitHubユーザ名> --password-stdin $ docker tag stub:latest ghcr.io/<org>/stub:latest $ docker push ghcr.io/<org>/stub:latest GHCR ではストレージや pull, push などのデータ転送の無料枠を超えた分の利用については課金が必要になるのですが、GitHub Actions からの利用に関しては Free としてカウントされるため課金を気にせず使用ができます。(データ転送のみ、詳しくは https://docs.github.com/ja/billing/concepts/product-billing/github-packages )  Push ができると GitHub の Packages からイメージの一覧を見ることができます。   WF からは↓のようにすればイメージを pull できます。ログインの action を呼びだし docker pull コマンドをするだけです。 jobs: ci: step: - name: ghcr login uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: stub image pull from ghcr run: | docker pull ghcr.io/${{ github.repository_owner }}/stub:latest docker tag ghcr.io/${{ github.repository_owner }}/stub:latest stub   複数ビルドするときは matrix で並列にビルド WF 上で複数イメージのビルドが必要な場合は matrix を使い並列にビルドすることで WF の実行時間を抑えることができます。   GitHub Actions では job ごとに別のランナーで処理されるため、通常 job A でビルドしたイメージを job B で使用することはできません。そのため GHCR などに一度イメージを保存して job B で pull するといった方法が必要になる…… と思っていたのですが「同じ key, 同じ path のキャッシュを使うことでファイルの共有をする」という方法があるそうでこちらの記事を参考にやってみました。 【裏技】別ファイルに切り出した Job 間で Docker イメージを共有し,高速に GitHub Actions をぶん回す jobs: # イメージの並列ビルド build: runs-on: ubuntu-latest timeout-minutes: 20 strategy: fail-fast: false matrix: include: - name: apl tag: apl context: . dockerfile: ./apl.Dockerfile.dev - name: db tag: db context: . dockerfile: db.Dockerfile steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Build Docker image uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: ${{ matrix.context }} file: ${{ matrix.dockerfile }} push: false load: true tags: ${{ matrix.tag }} cache-from: type=gha,scope=${{ matrix.name }} cache-to: type=gha,mode=max,scope=${{ matrix.name }} # ビルドしたイメージを tar としてキャッシュに保存する - name: Save the built image as a tar file run: docker save -o ${{ matrix.name }}.tar ${{ matrix.tag }} - name: Save the tar file to the cache uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ matrix.name }}.tar key: image-cache-${{ matrix.name }}-${{ github.sha }} # イメージの並列ビルド run: runs-on: ubuntu-latest needs: build steps: # キャッシュから tar を復元 - name: Restore apl image cache uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: apl.tar key: image-cache-apl-${{ github.sha }} # Save the tar file to the cache の key に合わせる fail-on-cache-miss: true # db でも同じことをする # tar から Docker イメージを復元 - name: Load images from tar files run: | docker load -i apl.tar docker load -i db.tar docker image ls のようにすることで 並列にイメージをビルド tar としてイメージをキャッシュに保存 後続の job で tar からイメージを復元 し、コンテナ起動時に作成されたイメージを使うことができました。 キャッシュも保存されるため2回目移行はさらに早くなるのも good ポイントですね。 テスト実行 おまけのようなものですが、WF 上でどうテストを実行しているかも書いておきます。 コンテナ起動〜テストコマンド実行には @devcontainers/cli  を使用しました。 devcontainer up で立ち上げ、 devcontainer exec でコマンドの実行が行えます。 - name: Install Dev Container CLI run: npm install -g @devcontainers/cli # Dev Container 起動 - name: Build and run Dev Container run: devcontainer up --workspace-folder . --config .devcontainer/ci/devcontainer.json # lint, test - name: Run lint id: lint continue-on-error: true run: devcontainer exec --container-id $(docker ps -aq --filter "name=<コンテナ名>") --workspace-folder . <lint コマンド> - name: Run test run: devcontainer exec --container-id $(docker ps -aq --filter "name=<コンテナ名>") --workspace-folder . <teset コマンド> # continue-on-error では job が成功扱いになってしまうため明示的に失敗させる - name: if lint failed if: ${{ steps.lint.outcome == 'failure' }} run: exit 1   lint に失敗した際も test の実行をしてもらいたいため lint の step に continue-on-error: true を追加しています。エラーを無視して後続 step を実行する設定です。ただし、このままだと lint 失敗時も job が成功となってしまうため if lint failed step で落とすようにしています。 まとめ ビルドしつつもかかる時間を抑えて CI を実装する話を書いてみました。 ちなみに現在は CI に約5分程度かかっています。最初の頃の10分↑と比べれば半分以下にはなったのですがやはりまだ長いなとは感じるので他にも短縮要素がないか試してみようと思います! 明日は namiki_ さんのアドベントカレンダー20日目です!おたのしみに!  
アバター
こんにちは、エンジニアリングマネージャーの芦川です。InnerSource Summit 2025にてニフティが写真撮影サポートしたこととセッション聴講・登壇からの学びを今回ご報告させていただきます。 InnerSourceとは? ニフティは3年前からインナーソースを導入しました。インナーソースって何?という方は、 インナーソースを導入してみた その① お試し導入編 をご参照ください!補足ですが、当時から進化したところでは、ソースコードだけではなくドキュメント・スライドなどコラボレーションできるところはインナーソースの考え方を取り入れていこう、という風潮が徐々に高まっていると思います。 InnerSource Summit 2025とは? さる2025-11-13に、横浜・ベルリン・ニューヨークに会場を8時間ずつ移しながら、まる24時間連続のとんでもないインナーソースのイベントがありました。他の技術イベントでも24時間ぶっ通しというのは見たことない。 InnerSource Summit 2025 InnerSource Summit 2025とは、企業内でオープンソース開発の手法を取り入れ、部門間の壁を越えたコラボレーションと共創を促進する「インナーソース」の最新動向や実践事例を共有する国際的なイベントです。 ニフティは、横浜会場の写真撮影サポートとして1人が張り付き、また私自身は人生初の英語セッションという恐ろしいことが待ち受けておりました。 このたびYoutubeにビデオがあがりましたので、遅くなってしまいましたがイベントのご報告させていただきます。プレイリストになってますので、右上のハンバーガーっぽいアイコンから各セッションを見ることができます。 冒頭、「 Yuki Hattori – Welcome to InnerSource Summit 2025 」の中で弊社ロゴがPHOTO SUPPORT枠にあり、載せていただいた運営の方に大感謝です。普段はあまり一緒に並ぶことがない企業様と並ぶことができ、めっちゃ嬉しいですね。撮影した写真やショートムービーの方は、イベント全体の振り返り動画のような形で公開されるとのことで非常に楽しみです! このイベントのすごいところを色々と言いたいことはありますが、強調して1点お伝えしたいものがあり、それは日本のインナーソースの広がりに間違いなく貢献した三菱電機様の取り組みについてです。パートナーや顧客、コミュニティとの共創により新たな価値を生み出すイノベーティブカンパニーへと変革すべく、おそらく日本ではじめて「インナーソース」という名前がつく部署を立ち上げています。オープンソース&インナーソース共創推進部です。で、横浜でのイベント会場は同社の「Serendie Street YIMP」で行われ当日も司会や機材周りなど同社社員様がたくさんホストされていました。本当にありがとうございます。当時、色々な新聞やメディアにも取り上げられ話題になっていましたね。 横浜、ベルリン、ニューヨークで国際会議 三菱電機が講演「シナジー加速」 セッションからの学び さて、横浜会場での数あるセッションの中から一押しセッションをご紹介します。私にとっての1番の学びは、人事院人事官 伊藤かつらさんによる「 Shaping What’s Next: The Transformative Power of Engineers and Organization 」でした。(こちらは日本語セッションです。) 正直めちゃめちゃ面白かったです。簡単ですが、以下にまとめます。 エンジニアからキャリアが始まり人事院人事官に至るまでの話。 人事院の使命やMVVの話。法律の実装は行政という話。 日本企業のエンゲージメントと世界からみた順位の話。 日本企業で働いている方の学び・キャリアと世界からみた順位の話。 人的資本経営の話。 日本の特異なIT人材・技術者を取り巻く環境の変化の話。 IQとEQ(感情知性、心の知能指数)、そのバランスの話。 キャリア上のあるある話。 リーダーシップ論(サーバントリーダーシップなど)とその教育不足の話。 グロースマインドセットの話。 マジでどこを切り取っても響く内容ばかりの神セッションでした。特にチームリーダーやマネージャー層に響く内容なのかな、と思います。エンジニアリングに関わっている方以外にもとても有用かと思います。是非、ご視聴あれ。 次に私の発表内容に触れたいと思います。 3年前よりこれまでニフティはインナーソース活動をしてきました。そのまとめのような内容になっております。今回はその活動の中で特にここは社内のインナーソース促進に効いたな、というポイントを3点に絞って発表しています。さらに、社内のインナーソース促進活動をどのように有志のメンバーとともに行っているかについても今回はじめて発表させていただきました。 と、、、いうようなことをちゃんと発表して伝えることができていればと思うのですが、なんせはじめての英語発表でしてガチガチに緊張しながら、発表用のメモ(スマホ)を握りしめながら喋る、ということになってまして、出来上がりのビデオを見ても、うわぁ、という仕上がりになっております。 動画のリンクは「 Ryo Ashikawa – Three points that promoted InnerSource activities 」ですが、マジで英語喋るのは自信がないため、以下に、話した内容を文字起こしして別スライドに盛り込んだバージョンをこの場に公開して難を去ろうかと思います。 ですが、今回とても貴重な機会をいただいたと思っています。英語セッションの学び・感想・気づきとしては、以下です。 英語の発音なんてとりあえず気にするな。結局のところ、文字や図などで伝わればよい。 発表できることよりもコミュニケーションできることが大事。英語はコミュニケーションツールだ。方法はなんでもいいから人とコミュニケーションできることが大事だ。 自分に自信を持たせるために、後で思い直したフシがありますが、実際、セッション後に声をかけてくれてきた方とのやり取りのほうが身になったことは事実です。 というわけで非常に有意義なイベントでした。 InnerSourceに関する情報は、 InnerSource CommonのLinkedIn に最新情報が流されますので、是非ご興味あればフォローください! それでは、最後に写真撮影サポートの中でとった写真をいくつか並べましておしまいにしたいと思います。 エントランスです。24時間のはじまりです。 インナーソースヒーロー の抜け殻。当日も誕生秘話や熱いメッセージを伝えてくれていました! セッション中。伊藤かつらさんです。 Thank you to our Summit Speakers!!!!! 以上です!
アバター
インターネットの世界では、サイバー攻撃をはじめとする新たな脅威が次々と生まれています。健全な企業活動を維持するためにも、社内のセキュリティ対策強化は欠かせません。 ニフティにも、社内全体のセキュリティを推進する専門チームがあります。 前編 では、具体的な業務内容やこの仕事の魅力、やりがいについてメンバーに語ってもらいました。後編では、ニフティという会社の良いところ、チームに迎えたいメンバー像、さらには各々が描く今後のキャリアについて伺います。 入社直後から「歓迎されている」と感じられた みなさんが思う「ニフティの良いところ」を教えてください。 Mさん 基本的に、新しく何かを導入したり、挑戦したりといったことに対して前向きな会社だと感じます。たとえば、最近の自チームでいうと新規チームの立ち上げの話やセキュリティシステム内製の話、他チームでも新規のソリューション導入や新規ビジネスなど、マネジメント層含めて前向きに取り組む方が多いですね。 あとは、私は中途入社なのですが、転職活動中に会社のことを調べていたら「ニフティには良い人が多い」という情報がけっこう出てきて。本当かな?と思っていましたが、入社してみたら実際に良い人ばかりで安心しました(笑)。みなさん人当たりがいいし、入社直後は特に積極的に声をかけてくださったり、中途入社組のコミュニティに招待してくださったりと、「歓迎されているな」と感じさせてもらえたのはありがたかったですね。 Tさん 僕も一番に思い浮かぶのは「人」ですね。僕の場合は新卒入社ということもあって、会社勤め自体が初めてという不安な状態のなかで、親身になって相談に乗ってくれる先輩がたくさんいました。おかげさまで社会人のスタートとしては、すごく良い入り方ができたと思います。 Mさんも言ってくれた挑戦という部分でいうと、個人のやりたいことも尊重してくれる会社だと感じます。定期的に上司との1on1の面談機会があって、将来的にやりたいこと、目指したいことを相談できるんです。その時すぐに実現するのは難しいことでも、先を見越して「じゃあ、今のうちにこういう経験を積むといいんじゃない?」と前向きな提案をしてくれるので未来も描けるし、日々の業務へのモチベーションも上がりますね。 Hさんは入社6年目と、2人よりも長くニフティに勤めていますが、どんなところに良さを感じますか? Hさん 二人に先に言われてしまいましたが、チャレンジしやすい環境はニフティの大きな特徴です。個人でいうと、たとえば何か学びたいこと、伸ばしたいスキルがあるとして、目的が明確であれば会社がそれをサポートする環境を用意してくれます。学んだことを生かして活躍できる場も多いと感じますし、成長の機会が至るところにある会社なのではないかと思います。 辛抱強く、丁寧に対応する姿勢が求められるセキュリティの仕事 現在、セキュリティチームは3名ということですが、これからさらに人手が必要になると思います。新しいメンバーを迎えるとしたら、どんな人がいいですか? 望ましいスキルや人物像を教えてください。 Hさん セキュリティチームの業務の一つに、社内各部署からのセキュリティに関する相談対応があります。そうした様々な困りごとに対して、真摯に取り組めるかどうかが、まずは大事な素養になると思います。たとえば、セキュリティ観点から見れば懸念がある相談ごとに対して「NO」と言うのは簡単ですが、できればそれを実現させる方法がないかを辛抱強く考えていく。そういった思考を持てるかどうかは重要です。 また、言われたことだけをやるのではなく、自身の興味に従って主体的に学べる人、動ける人も大歓迎です。TさんやMさんがまさにそうなのですが、二人とも新しい技術を学ぶことに対して貪欲で、各々が外で吸収してきたものを本業にフィードバックしてくれています。セキュリティの世界に限ったことではないかもしれませんが、その時々で押さえておくべき知識やスキルはどんどん変わっていきますので、自ら学ぶ姿勢がある人ほど活躍し続けられるのではないでしょうか。 最近でいえば、生成AIなどもその一つですね。 Hさん 社内でも今まさにAIを推進するチームが立ち上がっていますが、当然、セキュリティの観点で押さえておかなければいけないポイントもたくさんあります。ただ、そこでセキュリティが前面に出ていくと動きが鈍化してしまうかもしれませんので、良いタイミングでセキュリティについて評価する機会をつくるなど、うまく関わっていけたらと考えています。 分かりました。Tさんはいかがでしょうか?どんな人と一緒に働きたいですか? Tさん Hさんがおっしゃったように、何事に対しても辛抱強く、丁寧に対応できることは重要だと思います。たとえば、それまで社内で普通に使われていたツールであったとしても、セキュリティ上の問題が発生すれば利用停止にする判断を下さなければいけないケースもあります。当然、各部署からすれば不便になるとあって苦情が出ることもありますが、その一つひとつに真摯に向き合い、丁寧に説明して納得してもらうことが大事です。そこでコミュニケーションを怠らない人は、この仕事に適していると思います。 また、個人的には技術への関心が高い人に来てもらいたいです。僕自身も今後は技術を身につけていきたいと思っていますので、すでに技術に明るく引っ張ってくれる人、あるいは一緒に成長していける人がいてくれるとありがたいですね。 高まり続けるセキュリティリスクに対し、万全の体制を築いていく みなさんの今後のキャリアの展望や、挑戦してみたいことを教えていただきたいです。Mさんは社外のセキュリティに関わる女性たちと一緒に、キャリアについて考えるコミュニティを立ち上げ活動しているということですが、そこでロールモデルになるような出会いはありましたか? Mさん そうですね。そのコミュニティにはセキュリティの専門家としてキャリアを積んでおられる女性が複数いて、自分自身の今後を考えるうえでもありがたい交流をさせてもらっています。ロールモデルといっても、本当に多様な考え方、様々な道を歩んでいる方がいるので、今はまだ「こうあるべき」みたいに考える必要はないのかなと。あまり固定観念に捉われず、色んな先輩方のお話を伺いながら模索していきたいですね。ただ、いずれにせよセキュリティの仕事はずっと続けていきたいと思っています。 Tさん 僕は先ほども言った通り、当面は技術を身につけていきたいという目標を持っており、攻撃者目線で社内のセキュリティ向上を図れるようなポジションになれたらと考えています。その先についてはまだ深く考えられていませんが、今やりたいことを一つずつ実現しながら、次に目指す道を見つけていけたらいいですね。 Hさんはいかがでしょうか? 個人的な今後の展望を教えてください。 Hさん 私自身は入社以来ずっと、情報セキュリティの部署でやってきました。じつは、社内にセキュリティ1本でやってきた人のモデルはあまりなくて、これからどうキャリアを作っていくかは模索中です。 ただ、基本的にはマネジメント側に回る方向性で考えていて、この少人数のチームで400人の従業員を抱える会社のセキュリティをいかに強固なものにしていくか。今後もさらに高まり続けるセキュリティリスクに対して、いかに万全な体制を構築できるかといった点を突き詰めていきたいと思っています。 前編もご覧ください! 今回はニフティのセキュリティチームのインタビュー(後編)の様子をお届けしました。前編の記事はこちらをご覧ください。 【インタビュー】サイバー攻撃などの脅威から会社を守る。セキュリティチームの仕事とは?【ニフティ セキュリティチーム前編】 このインタビューに関する求人情報 /ブログ記事 ニフティ株式会社 求人情報
アバター
はじめに 例年新人向けに2泊3日のエンジニアハッカソンを行っている弊社ですが、今年は新たな試みとして中堅層向けの1泊2日の弾丸日程でのエンジニアハッカソンを実施いたしました。 その時の様子についてお伝えできればと思います。 エンジニアハッカソンのテーマ 未来のNIFTYを創る – 新サービス創出ハッカソン 概要 日程:9/11(木) – 9/12(金) 場所:ハートピア熱海( https://www.h-atami.com/ ) 内容:中堅層向けのAI駆動開発ハッカソン 人数:16人 スケジュール(当初の予定) ■ 1 日目 10:00 熱海駅集合 10:30 チェックイン 11:00 昼食 12:00 開発 18:00 夕食・入浴 20:00 開発 ■ 2 日目 7:30 朝食 9:00 開発 12:00 昼食 13:00 開発 16:30 片付け 17:00 現地解散 事前準備 エンジニアハッカソン当日を迎えるにあたり、以下を行っています。 顧客課題発掘ワークショップ AI活用講習会(全5回) 事前ワーク(全1回) アイデアソン(全2回) 顧客課題発掘ワークショップ 弊社の新規事業立ち上げをリードする樋沼さんにご協力いただき、事業創出のイロハをリーンキャンパスの作成を行いながらご教示いただきました! 普段とは違う、企画側の視点でプロダクトを考えるとても貴重な経験になりました。 通常業務でもここで学んだマインドを活かせる、貴重な講習会だったと思います。 AI活用講習会 社内一のAIマエストロである石川さんにご協力いただき、KiroやClaude Codeについて学びました。 MCPサーバの建て方や、スペックの利用方法という基礎からみっちり叩き込んでいただきました。 社内で各自キャッチアップを行なっていたAIを用いた開発ですが、集中して体系的に学ぶ機会ができてかなり好評をいただいた講習会になりました。 Kiro 参考: https://kiro.dev/ Claude Code 参考: https://docs.claude.com/en/home 事前ワーク 顧客課題発掘ワークショップでのリーンキャンパスを基に、よりエンジニアハッカソらしいサービスやプロダクトを考えるブラッシュアップの時間です。 ここで練られたリーンキャンバス/アイディアを基に、エンジニアハッカソ当日のチームを決定しました。 アイディアソン 全2回のアイディアソンでは、チームごとに別れての作業でした。 チームは1チーム3人で、5チームになりました。 それとは別に6チーム目として、AI活用講習会の講師を務めた石川さんが1人チームとして参戦となりました。 アイディアのブラッシュアップを行ったり、 使用技術の選定や、チーム単位のAI駆動開発を行う上での、進め方の検討など細かい調整を行いました。 全2回以外にも、チームの認識合わせなどは自由に行なって良いことになっていたので各チームとも積極的に集まって準備を行なっていました。 今回は、AIを用いた1から立ち上げるプロダクトの開発を2日で完了させなくてはならない関係上、かなり綿密に計画を練っていました。 チームによっては、エンジニアハッカソに向けてAIの特性を理解するために1つサービスを作り上げて、確認しているチームもありました。 本番 1日目 集合(10:00) 熱海駅に現地集合しました。 集合は10:00でしたが、7:30に到着して海を見に行っていた猛者も…(遅刻するわけにはいかないという責任感のある運営の一人です) 全員無事に集まることができました。 宿への到着とチェックイン 宿のバスに揺られること体感10分。 ものすごく景色の良い宿に到着して、ハッカソン参加者一同から感嘆の声が上がっていました。 昼食(11:00 ~ 12:00) 宿に着いたらまずお昼です! 開発への英気を養います。 開発(12:00) いよいよ開発スタートです! まずはモニターの設置から…。 初日は2部屋に分かれての開発でした。 各チーム、黙々と作業を行っていました。 KiroちゃんとClaude君が大活躍です! 一貫してKiroを利用する人や、Claudeに全掛けする人、タスクごとに分けて利用する人、様々いらっしゃいました。 各チームが何を作成していたのかは今後公開予定の個別のブログでご確認ください 夕食(18:00) 楽しい夕食の時間です! めちゃくちゃ豪華で賞賛の嵐でした。 どれも美味しかったです。 開発で疲れた身体に上質な栄養が行き渡りました。 開発再開(19:30) ちょっと早めに開発再開するチームが多かったです。 初日の開発はどのチームも上限の22:00までガッツリ行っていました。 お疲れ様です! 予定した進捗通りに進んだチームは少なく、みなさん悔しそうな感じでした。 夕食前まで、2部屋に分かれて作業になっていたですが、宿のご厚意により1部屋で作業を行うことになりました。(2部屋に分かれて作業させていただいたのもこちらの我儘を汲んでいただいた、宿のご厚意です) 新しいお部屋は、開発で利用して良い部屋なのか…?と気後れする様な荘厳な感じのお部屋でした。 各自お休み(22:00) お風呂に入ったり、部屋で休んだりと各自次の日に向けて英気を養いました。 2日目 朝食(7:30) ビュッフェ形式で、各自で朝食を取るスタイルでした。 朝食前に、散策したり、朝のお風呂に入っているメンバーもちらほらいました。 中には5:30くらいには入り始めているメンバーも… 開発開始(9:00) 昨日に引き続き、黙々と開発を行っていました。 時折「おー」やパチパチといった歓声や拍手が聞こえてきました。 その度、「あのチームはもうできたのか!?」みたいな焦りが各チームから見られてきました。 終いには、「他のチームにプレッシャーかけるためにとりあえず”おー”とか言っておく?」みたいな周りにプレッシャーを与える戦法もあるのではという疑心暗鬼が生まれるフェーズがありましたが、それ以外は粛々と進んでいたように思います。 昼食(12:00) そろそろ完成の目処が立ってきてもおかしくない頃。 昼食を食べる時間すら惜しむようなチームもありましたが、オセロをしたり卓球をしたりと余裕を見せる人もちらほら。 しらす丼はみなさん喜んで食べていました! 開発再開(13:00) 午後は、佳境を迎えた開発メンバーたちがより一層黙々と作業をしていました。 一部、余裕のあるメンバーはノスタルジー風味のエモい写真を撮ったり、館内の写真を集めたりしていたようですが、それ以外は他チームの歓声に怯える以外は鬼気迫る表情で開発を続ける面々でした。 片付け…?(16:30) 当初予定していた予定では片付けの時間でしたが、ここで延長戦に突入することが急遽決定。 これについては、運営側の落ち度であります。 外部モニターは集荷の関係で片付けてます。 会場の延長利用を快諾くださったハートピア熱海様には感謝しかありません。 開発延長戦(16:30) 最後の力を振り絞って、みなさん頑張っていました! 撤収(18:00) ホテル撤収(18:15) 2日間頑張った参加者で集合写真を撮って、各々帰路につきました。 おわりに 今回、場所の提供をいただいたハートピア熱海様( https://www.h-atami.com/ )には格別のご配慮を賜りましたことを改めてお礼申し上げます。 参加いただいたエンジニアのみなさまにも感謝を申し上げます。 この2日間で得た、知見などは社内に展開して、新たな会社の原動力になるよう業務に励んでいければと思います。
アバター
この記事は、 ニフティアドベントカレンダー の9日目の記事です。 はじめに こんにちは!新卒1年目のなべしまです。寒くなってきましたね。 街では華やかなクリスマスツリーが現れる季節になりました。そんな中、私の前に現れたのは複雑な DOMツリー でした。 筆者について 入社: 2025年4月(新卒1年目) 担当業務: カスタマーサポートグループ(ジョブローテ中) コールセンター支援ツールの運用開発業務 TypeScript/JavaScript 開発経験 学生時代の授業にて、少しだけ演習を行った程度 きっかけ 現在担当しているツールでは、開発言語としてTypeScriptを利用しています。業務中、DOMを操作する querySelector メソッドに遭遇しました。 getElementById メソッドは知っていたのですが、両者の違いについて気になり、まとめてみようと思いました。 この記事では、JavaScript/TypeScriptで使えるさまざまなDOM要素取得メソッドの特徴や違いを解説します。知識の整理や新たな学びのきっかけになれば嬉しいです。 事前準備 DOMツリーについて ブラウザは HTML を読み込むと、内部で DOM(Document Object Model) と呼ばれるツリー構造を生成します。DOM は文書をツリー構造で表現し、各ノードがオブジェクトを含みます。DOM のメソッドでツリーにアクセスし、文書構造やスタイル、コンテンツの変更ができます。 DOM は HTML をプログラムで操作するためのデータ構造 です。そして、JavaScript が操作するのはこのツリー内の「ノード(要素)」となります。 document └── <html> ├── <head> └── <body> ├── <div class="profile-card"> │ ├── <h2 id="card-title"> │ ├── <div class="user-info"> │ └── <div class="actions"> └── ... 要素について HTMLElement HTMLのタグ1つ分を表すオブジェクト(例: <div> , <p> , <button> など) HTMLCollection HTMLElementの汎用的な集合(配列風オブジェクト) 動的 であり、DOM の変更が自動的に反映 NodeList ノードのコレクション(配列風オブジェクト) querySelectorAll が返す NodeList は 静的 DOM の変更は反映されない DOM要素を取得する方法 例えば、以下のようなHTMLドキュメントを見てみましょう。ここから、要素を単一で取得したいことを考えます。 <body> <h1 class="main-title">タイトル</h1> <div class="profile-card"> <h2 id="card-title">ユーザプロフィール</h2> <div class="user-info"> <p class="text">名前: なべしま</p> <p class="text">職業: システムエンジニア</p> </div> <div class="actions"> <button class="btn">フォローする</button> </div> </div> </body> 要素取得メソッドの例 idから取得する場合 getElementById でidを指定することでHTMLElementオブジェクトを取得できます。要素が見つからない場合は null を返します。 const title = document.getElementById('card-title'); // 戻り値: HTMLElement | null classから取得する場合 getElementsByClassName でclassを指定することによって、HTMLCollectionオブジェクトを取得できます。このとき、単一要素で取得したい場合は、HTMLCollectionの要素番号を指定する必要があります。 複数のクラス名を指定する場合は、半角スペースで区切ります。 const texts = document.getElementsByClassName('text'); // 戻り値: HTMLCollection // 単一要素を取得する場合 const firstText = texts[0]; // 戻り値: HTMLElement | undefined タグ名から取得する場合 getElementsByTagName でタグ名を指定することによって、HTMLCollectionオブジェクトを取得できます。 getElementsByClassName と同様に、単一要素で取得したい場合は、HTMLCollectionの要素番号を指定する必要があります。 const buttons = document.getElementsByTagName('button'); // 戻り値: HTMLCollection // 単一要素を取得する場合 const firstButton = buttons[0]; // 戻り値: HTMLElement | undefined querySelectorでの要素取得 利用方法 CSSセレクタを利用した要素取得 querySelector では、id、class、タグ名など、CSS セレクタを利用して要素を取得することができます。要素が見つからない場合は null を返します。 // idでの指定 const title = document.querySelector('#card-title'); // classでの指定 const mainTitle = document.querySelector('.main-title'); // タグでの指定 const button = document.querySelector('button'); // 戻り値: HTMLElement | null 複数のCSSセレクタを利用 親要素と子要素の間に半角スペースを入れて指定することで、子孫要素を取得することができます。 const userName = document.querySelector('.user-info .text'); console.log(userName.textContent); // 出力: "名前: なべしま" 検索方式 querySelector メソッドは指定されたセレクタに一致する、ドキュメント内の 最初の HTMLElementを返します。 直前の例のような式では、「名前: なべしま」を含むDOM要素は取得できますが、「職業:システムエンジニア」が含まれるDOM要素を取得したい場合は、2番目の要素を取得する必要があります。 // 2番目の.text要素を取得する場合 const allTexts = document.querySelectorAll('.text'); const jobInfo = allTexts[1]; // 「職業: システムエンジニア」 複数の要素を取得する場合 querySelectorAll を使用すると、一致するすべての要素を NodeList として取得できます。 const allTexts = document.querySelectorAll('.text'); // 戻り値: NodeList // forEach で反復処理が可能 allTexts.forEach(text => { console.log(text.textContent); }); まとめ 今回登場した、DOM要素取得のメソッドを表にまとめます。 メソッド 戻り値の型 特徴 getElementById HTMLElement | null IDで単一要素を取得 getElementsByClassName HTMLCollection クラス名で複数要素を取得(ライブコレクション) getElementsByTagName HTMLCollection タグ名で複数要素を取得(ライブコレクション) querySelector HTMLElement | null CSSセレクタで最初の要素を取得 querySelectorAll NodeList CSSセレクタで全ての要素を取得(静的) 実務では便利な querySelector を利用しましたが、その他のDOM要素取得メソッドの特徴や戻り値の型の違いを知り、学びになりました。 今回の学びを、今後のDOM操作の実装や、思い通りに動かない時のエラー究明に役立てていきたいです。 参考文献 MDN Web Docs – Document Object Model (DOM) MDN Web Docs – Document.querySelector() MDN Web Docs – Document.getElementById()  
アバター
はじめまして。2024年にニフティに入社しました、城山と申します。 本エントリでは、全くの異業種からIT業界へ飛び込んだ私の経緯や、現在取り組んでいる技術的な業務内容、そして会社の雰囲気についてお話しさせていただきます。 これまでのキャリア:物流の世界から 入社前は、主に倉庫系・物流関連の作業に従事していました。日々体を動かし、モノの流れを支える現場で働いていましたが、IT業界への興味はずっと持ち続けていました。 転職のきっかけ:8年来のゲーム仲間との縁 転職の大きなきっかけとなったのは、友人からの紹介です。 この友人とは8年前にゲームを通じて知り合いました。地元が近かったこともあり、長く交流が続いていました。 「新しいことに挑戦したい」「元々興味のあったITの世界に飛び込んでみたい」という相談をする中で、彼が働いている当社を紹介してもらい、このチャンスを掴むことを決意しました。 現在の業務内容:監視業務と自動化への挑戦 現在は主にサーバー・サービスの監視業務を担当しています。それに加え、業務効率化のための自動化にも積極的に取り組んでいます。 具体的な取り組みとしましては、サーバー監視ツールなどを活用し、会社のサーバー不具合検知や監視を行っています。また、日々の定形業務を効率化するため、GAS (Google Apps Script) を活用した業務の自動化を行っています。 業務の自動化では、1時間ごとの定期タスクの自動化や、エラー発生時にSlackへ自動通知するボットの構築などをしました。 未経験からのスタートですが、実際にコードを書いて業務が楽になる瞬間は非常にやりがいを感じます。 入社後の印象:フラットで繋がりやすい組織 入社して一番驚いたのは、組織文化が予想以上にフラットだということです。 メンバー間の関係が非常に良好で、ギスギスした雰囲気は全くありません。わからないことも聞きやすく、心理的安全性が高いと感じています。 他チームとの連携もSlackを通じて、セキュリティチームやPF(プラットフォーム)チームなど、部署の垣根を超えた連携が日常的に行われています。オープンなコミュニケーションのおかげで、スムーズに業務が進められています。 今後の目標:技術の幅を広げたい 正直なところ、エンジニアとしての知識はまだ十分ではありません。しかし、だからこそ「もっと深く知りたい」という意欲が湧いています。 現在はGASをメインで使用していますが、今後はPythonなどを使えるよう他のプログラミング言語も習得し、対応できるスキルの幅を広げていきたいと考えています。 最後に:未経験でも安心できる環境 ニフティは、経験の有無に関わらず「やりたい」と声を上げれば任せてもらえる環境があります。 私のように全くの未経験であっても、基礎から丁寧に教えてくれる先輩たちがいます。 「ITに興味はあるけれど経験がない」という方でも、やる気さえあれば必ず成長できる場所です。興味がある方は、ぜひ応募してみてください。一緒に働ける日を楽しみにしています。
アバター
インターネットの世界では、サイバー攻撃をはじめとする新たな脅威が次々と生まれています。健全な企業活動を維持するためにも、社内のセキュリティ対策強化は欠かせません。 ニフティにも、社内全体のセキュリティを推進する専門チームがあります。会社にとって、ある意味「守りの要」であるメンバーはどんな意識で、また、どんなやりがいを持って仕事をしているのでしょうか?セキュリティチームのメンバーに話を聞きました。   自己紹介 Hさん(チームリーダー) 2019年4月入社。メイン業務はISMS(情報セキュリティマネジメントシステム)の推進、インシデント対応、社内セキュリティ対策の企画・運用。趣味はパズルや謎解き。   Tさん 2024年4月入社。メイン業務はISMSの推進、インシデント対応、社内セキュリティ対策の企画・運用。趣味はInstagramで見つけた美味しそうなお店巡り。   Mさん 2024年9月入社。メイン業務はISMSの推進、インシデント対応、社内セキュリティ対策の企画・運用。趣味は都内散策での美術館やカフェ巡り。   社内全体のセキュリティを推進し、インシデントに素早く対応 みなさんはニフティの「セキュリティチーム」のメンバーということですが、まずはチームリーダーであるHさんに、具体的な業務内容をお伺いしたいです。 Hさん メインの業務はISMS(情報セキュリティマネジメントシステム)と呼ばれる、情報セキュリティを管理するフレームワークに沿って規格・ルールの設定、改善などを行い、社内全体のセキュリティを推進していくことです。 また、ニフティとして新規サービスをリリースする際や各部署で新しいツールなどを導入する際に、リスクを判断するようなこともやっています。たとえば新規サービスの導入の際にセキュリティ対策が十分かどうか、リリース後に外部からの攻撃に対する脆弱性はどうかといった点を、ツールを使って評価する役割です。 社内のセキュリティに関して、重大な責任を担っていると。 Hさん そうですね。年に1度、ISMSに沿った運用をしているかの内部監査があるのですが、そこで各部署のセキュリティ対策に問題がないかをチェックして、もし不十分な点があれば我々が深く入り込んで改善を行います。 それ以外にも、各部署が日々の業務のなかでセキュリティに関する不備を発見した場合、セキュリティチームにアラートを上げてもらい、私たちが対応します。幸いなことに、これまでは企業活動の根幹に関わるような甚大なインシデントは起きていませんが、サイバー攻撃などのリスクが高まり続けていることをふまえ、何かが起きた時にすぐに対応できるような訓練や体制を整えておくことが大事ですね。 社内に向けて、リスクに対する発信や啓蒙なども行なっていますか? Hさん はい。技術はどんどん新しくなり、同時にリスクも増えていきます。先ほどのサイバー攻撃もそうですし、最近では生成AIですね。ニフティでは業務での生成AIの使用を厳しく制限していませんが、活用する際のリスクについても十分に知っておく必要があると思います。 ただ、それらに対して一人ひとりがキャッチアップを行うのはなかなか難しいので、やはり我々のような専門チームがリスクについて積極的に発信したり、ルールを作ったりしていく必要があると考えています。 社外のネットワークも駆使し、積極的に情報を共有 Tさん、Mさんはともに2024年入社ということですが、セキュリティチームでは現在どのような業務に携わっていますか? Tさん 入社は2024年4月ですが、セキュリティチームに来たのは2025年1月からで、現時点で10か月ほどになります。主な業務内容ですが、メインは社内の脆弱性の管理です。また、社内各部署からのセキュリティに関する相談に対応することもあります。たとえば、「このツールを使っても大丈夫ですか」「社外の人と連絡する時に、このやり方で大丈夫ですか」といった相談などがあって、その都度、リスクを検証しています。 Mさん 私も基本的な業務内容はHさん、Tさんと変わりませんが、社内セキュリティ対策の企画と検証・導入、外部連携活動がメイン業務になっています。 Tさんはセキュリティチームに配属されてまだ1年足らずということですが、これまでに携わってきたなかで特に印象的だった仕事を教えてください。 Tさん とあるセキュリティ対応で、自分主導で進めたプロジェクトがありました。社内で長く使われているファイルサーバーに問題が見つかったのですが、多くの人が活用しているものですので、影響範囲がとても広かったんです。各所への調整などもかなり大変でしたが、何とか旗を振って解消することができたのは自信にもなりましたし、印象深いですね。 Mさんは前職でもセキュリティの業務に従事されていたということですが、前職との違いを感じる部分はありますか? Mさん 違いとしては、OT(運用技術)からIT(情報技術)の範囲に変わったことと、オペレーション主体の業務からガバナンス主体の業務に変わったことです。また、前職はフルリモートに近い働き方でしたが、ニフティは週5出社なので、ちょっとした雑談をはじめ人と対面で直接会話する量が圧倒的に増え、飲み会の頻度も多く、働き方が大きく変わりました。前職ではOT、セキュリティを立ち上げ推進していくプロジェクトの業務に従事していたのですが、ニフティ入社後はOTにとどまらずIT領域にも携わるようになり、自分のなかで一気にやれることが増えた感覚があります。 また、対外的な活動にも積極的に参加する機会に恵まれました。たとえば、ニフティが加盟しているISAC(情報連携組織)ではセキュリティ分野の様々なWGやSiG、TFに参加しています。そのなかで社外のセキュリティに関わる女性たちと一緒に、セキュリティキャリアについて考えるコミュニティを立ち上げ活動したりもしています。社外との連携、外部から情報を得る機会は格段に増えましたね。 そこで得た情報が、本業にもフィードバックされると。 Mさん そうですね。サイバーセキュリティの脅威はニフティに限らず、全ての会社に共通しています。機微な情報は外部へ出ないので、実際にサイバー攻撃を受けた時の被害状況やその時の対応について範囲を限定して共有いただけることで知見を得たり、また攻撃手法や法制度など昨今の情勢についてキャッチアップすることで、それが結果的に自社の対策強化につながります。 リスクがあるからNGではなく、「実現できる方法」を一緒に考える セキュリティの仕事の魅力、やりがいはどんなところにありますか? Tさん セキュリティの業務をやっていると、色んなチームの人たちとコミュケーションをとる機会があります。ネットワークチームや社内ツールを扱うチーム、他にもたくさんありますが、そうした会話のなかから様々な知識を吸収できる点は魅力の一つですね。一つひとつ理解を深めていくうちにニフティがやっている事業の全貌が見えていく感じも楽しいというか、成長できている実感があります。 それに、仮に他部署へ異動になったとしてもセキュリティの知識は無駄になりませんよね。 Tさん そうですね。無駄にならないどころか、どこへ行っても活かせると思います。例えば、開発などの現場では意外とチーム内にセキュリティの専門知識を持った人がいなかったりもするので、そこでしっかりとした規制や基準に則った知見があれば重宝されるはずです。僕自身もそんな人材になりたいですね。 Mさんはいかがでしょうか? セキュリティの仕事のどんなところに魅力、やりがいを感じますか? Mさん 魅力は、社内で取り扱っている全てのドメインを見ることができる点です。もともと私がセキュリティの仕事を選んだ理由にも通じるのですが、好奇心が強く、新しいものを知りたい、色々な技術に触れていたいという人にとっては苦ではない業務ではないかと思います。 ありがとうございます。Hさんはお二人よりも長くセキュリティの仕事に携わっていますが、魅力を教えてください。 Hさん 今のMさんのお話に近いかもしれませんが、まずは新しい知識を得られることです。セキュリティのリスクにつながる新しい情報は常にキャッチアップする必要がありますし、そのためには社外に出て色んな人と会話をしなくてはいけません。そこで新たなつながりが生まれ、自分の世界が広がっていくような充実感があります。 あとはセキュリティというと、どうしても制限をかける役割というか、「これはリスクが高いからやってはいけない」と、ストップをかける仕事というイメージもあると思います。でも、じつはそうとも言い切れないんですよね。 というと? Hさん 例えば社員から新しいツールを導入したいという相談があった時に、リスクがあるからとすぐに否定するのではなく、できる限りそれを実現するための方法を考える。やりたいことに応えるために、一緒に課題をクリアしていく。私自身はそんな姿勢でいたいと考えていますし、社員一人ひとりの主体性を重んじるニフティという会社ならそれができると思っています。 後編に続きます! 今回はニフティのセキュリティチームのインタビュー(前編)の様子をお届けしました。続きは後日公開予定の後編の記事をご覧ください。 ※ ニフティでは安心安全への取り組みを行っています。   詳細はこちらをご覧ください このインタビューに関する求人情報 /ブログ記事 ニフティ株式会社 求人情報
アバター
この記事は、 ニフティグループ Advent Calendar 2025 6日目の記事です。 こんにちは。ニフティ株式会社の佐藤です。 カスタマーサポートグループのサポートシステムチームに所属しており、自社コールセンターにて、主にお客様からの電話入電に対応するオペレーターが使用するツールの開発・運用を行っています。 最近、コールセンター関連の社外イベントに参加する機会が多く、そこで頻繁に耳にする”KCS”というワードがあります。 今回はその”KCS”について説明をしていきます。 コールセンターの現場について ニフティでは、お客様からのお問い合わせ対応窓口の運用の内製化することで、お客様の生の声をより確実に収集し、本質的なサービス改善に直結させる取り組みを行っています。 コールセンターでは日々お客様から色んな媒体(電話、メール、チャットボットなど)からお問い合わせが寄せられます。例えば「インターネット機器の設定について聞きたい」や「契約内容を確認したい」など様々です。 オペレーターはこれらの問い合わせに対し、様々なツールを駆使して対応します。特にインターネット回線関連では、自社で内製開発されたツールに加えて、各キャリア様にご提供いただいたいているツールを使用することもあります。 しかし、 オペレーターは多々あるツールの種類や使い方、できることをどうやって学ぶのでしょうか? また、 様々なお問い合わせに対して、どのように対応すればよいのか、どうやって習得するのでしょうか? もちろん実際にお客様対応する前に研修を実施します。馴染みのないインターネット用語や契約関連の難しい言葉もここで知るかもしれません。 ですが、研修期間を長く設けたり、ベテランの講師を長期間研修に専念させるのはコールセンターを運営する側としては少し非効率です。現在多くの企業で人材不足が課題となる中、一刻も早く顧客対応可能な人材を育成することが重要です。 そこで役に立つのが ナレッジ になります。 誰かが付きっきりで見ていなくても良いですし、いろんな情報が漏れなく文字ベースで載っているので、業務の空き時間や電話対応中でも確認することができます。 そこでまた新たな問題が発生します。 ナレッジ内の情報は誰が最新化をしているのでしょうか? ナレッジに全て最新情報が書いてあると言ってもそれを担保しなければならない管理者が必要です。すべてのツールの使い方や用語、対応フローが全て頭に入っているスーパーマン的な人材が各社のコールセンターにいるはずがありません。複数人で管理してもよいですが、更新作業が頻発すると、それだけで1日が終わってしまうこともあるかもしれません。 そこで KCS というフレームワークを紹介します。 KCSとは? KCSは「Knowledge-Centered Service」の略称になります。アメリカの非営利団体「 サービスイノベーションコンソーシアム 」が作成したナレッジの方法論になります。 とても簡単に言うと「オペレーター自身が都度ナレッジの更新を行う仕組み」です。 KCSの根本的な考え方は「問題解決の過程でナレッジを作成し、そのナレッジを組織全体で共有・活用することで、継続的にサービス品質を向上させる」ことにあります。従来の「管理者が作成したマニュアルを参照する」というアプローチから「実務担当者が現場で得た知見を即座にナレッジ化する」というアプローチへの転換が図れます。 https://www.serviceinnovation.org/wp-content/uploads/2023/03/KCS_DoubleLoop922_notitle_TM.jpg これによって、ナレッジの網羅性が向上し、現場で本当に必要な情報が盛り込まれるため、より専門知識を持った上位者へのエスカレーション回数を減らすことができます。 ナレッジの品質を上げるために、オペレーターには下書きを書いてもらって、それを提出、専門知識を持った方が内容確認を行い、承認後に反映されるといったチェックフローも考えられます。 料金改定や契約期間の延長などのシステム的な変更はオペレーターが対応できないので、管理者もしくは対応部署の方が行う必要はあります。 AI時代のコールセンター AIの利活用が多くの企業で取り組まれています。コールセンターも例外ではありません。 例えば、電話をかけて問い合わせ内容を話すとAIが回答してくれたり、WEB上で入力すると、それに対する回答や、参考FAQを出してくれたりします。 これらのAIは最新の情報を学習して回答を生成するため、いかに情報を新しい状態かつ正確に保ち続けるかが、AI活用における重要な課題となります。KCSの導入により、この課題を解決できると考えています。 おわりに 私たちニフティのコールセンターではまだKCSを導入できておらず、様々な角度から検討を進めています。 最後に、最近聞いた言葉で響いたものを紹介します。 「データは資産のはずが、今は負債に近い。”ある”だけでは価値にならない」 コールセンターに限らず、ナレッジを含む様々なデータは大切な企業資産になるので、それをただ溜めておくのではなく、会社全体で活用していければと思っています。 今後もお客様にとってもオペレーターにとっても価値のあるコールセンターの実現に向けて努力していきます。
アバター