TECH PLAY

ニフティ株式会社

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

485

はじめに 皆さん初めまして、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を導入できておらず、様々な角度から検討を進めています。 最後に、最近聞いた言葉で響いたものを紹介します。 「データは資産のはずが、今は負債に近い。”ある”だけでは価値にならない」 コールセンターに限らず、ナレッジを含む様々なデータは大切な企業資産になるので、それをただ溜めておくのではなく、会社全体で活用していければと思っています。 今後もお客様にとってもオペレーターにとっても価値のあるコールセンターの実現に向けて努力していきます。
アバター
この記事は、 ニフティグループ Advent Calendar 2025  5日目の記事です。 こんにちは!セキュリティSREチームでOJT中の坂野です! 今回は、7月にプレビュー版としてローンチして以来、長らくウェイトリスト制だったAgentic IDEである「Kiro」について紹介します。 2025年11月17日、ついにKiroの一般提供が開始され、今回のアップデートでは、AWS IAM Identity Center(旧 AWS SSO、以下 AWS IIC)経由でのログインに対応したほか、コンソール上から直接プラン加入が可能になりました。本記事では、これらの新機能についてまとめていきます! AWS IIC経由だと何が嬉しいのか 実際にAWS IIC経由になると何が嬉しいのか、という点ですが、主に以下のポイントが挙げられるのではないでしょうか。特にこれまでのプレビュー版では個別にクレジットカードを登録する必要があったことを踏まえると、組織利用において大きな進歩だと言えます。 組織としてAWS請求に一本化できる チーム単位でのライセンス管理がコンソール上で可能になる 既存のAWS管理アカウントにコストをまとめることができる AWS IIC経由での外部IdPとの連携が可能になる ポリシーの管理が容易になる 登録方法 1. 管理アカウントのセットアップ まず、コンソールからKiroにアクセスし、メールアドレスを使って管理アカウントをセットアップします。 2. プラン選択とユーザー割り当て 次にプランを選択し、ユーザーもしくはグループを検索し、プランを割り当てます。 なお、同一のKiroプロファイル内であれば、1ユーザーに複数のサブスクリプションが割り当てられても、最も高いTierの価格のみが請求される仕様のようです。 割り当て後、Kiroのページに戻りセッティングページへ移動します。そこで表示されている 「IAMアイデンティティセンターリージョン」 と サインインURL を控えておきます。 3. クライアント側の設定 最後に「Sign in with your organization identity」をクリックし、先ほど控えたリージョンとサインインURLを入力します。また、注意点なのですがここに記載されている「Region」はKiro自体のリージョンではなく、AWS IICを有効化しているリージョンです。ここを間違えるとログインできないため注意が必要です。 入力後、「Continue」をクリックすれば、AWS IIC経由でのログイン完了です。 ポリシー、利用状況の管理について 「Kiro profile」という機能により、グループ全体のポリシーを一括制御できるようです(現時点では未検証)。具体的には以下のような項目が管理対象となります。 MCPの使用 月間の制限超過時の動作 Kiroプロンプトやメタデータのログ記録 使用状況の追跡 ユーザーアクティビティに基づく日次レポートの作成 また、AWS IIC経由(Kiro for enterprise)であれば自動的にオプトアウトがされ、管理者が設定を制御するためユーザー個人の判断でデータ共有が有効化されることはありません。しかしながら、Telemetry送信などのIDEレベルでのオプトアウトの設定については、現状ポリシーでは一括制御できず、各個人が手動で設定する必要がある点には注意が必要です。 まとめ これまでは個別のカード登録など導入のハードルがありました。しかしながら、今回のアップデートによって請求の一本化やユーザー管理が非常にスムーズとなり、組織として安全かつ便利に活用できる土台が整ったと言えるのではないでしょうか。開発体験を向上させるツールとして、ぜひこの機会にチームでの導入を検討してみてはいかがでしょうか。 明日は、@hirost00 さんの記事です!お楽しみに!
アバター
この記事は ニフティグループ Advent Calendar 2025 および Rust Advent Calendar 2025 シリーズ 1 の 9 日目の記事です。 忙しい方向け Rust でファミコンの ROM を作ろうとしたら想定外に大変だったけど楽しかった、というお話です。 mrustc + cc65 という組み合わせで Rust コードをファミコン用にコンパイルしたかった mrustc が生成した C コードを cc65 でコンパイルするためにひたすら魔改造 本物の libcore も改造しつつコンパイルが通るところまでいって感動 しかし出来上がったバイナリは 32KB の枠に対して 347KB オーバーという衝撃の結果に… リンカ (ld65) を魔改造して不要なシンボルを削ろうとしたが、低レイヤーが分からず撤退 最終的には最小限の libcore を実装し、とりあえず動かすことに成功 紆余曲折ありましたが、とても楽しい冒険でした。 まずファミコンってどんなハード? ファミリーコンピュータ (以下ファミコンと呼びます) は 1983 年に任天堂から発売された家庭用ゲーム機です。海外版の Nintendo Entertainment System という名前から NES とも略されます。今から 40 年以上前のハードウェアですが、スーパーマリオブラザーズやゼルダの伝説など、今なお語り継がれる名作を数多く生み出した伝説的なプラットフォームです。 自分も全く詳しくないのですが、スペックはこんな感じ。 CPU: 8 bit で MOS 6502 ベースの改造版 (Ricoh 2A03) RAM: わずか 2KB ROM: カートリッジによるが、今回は 32KB 程度 画面解像度: 256×240 ピクセル 現代の感覚からすると信じられないほど小さいスペックですが、この制約の中で当時の開発者たちは驚くべき工夫を凝らしてゲームを作っていたのですね…。 なんでやろうと思ったのか ある日 YouTube を見ていたら、Rust でファミコンのエミュレータを作る動画に出会いました。 面白そうなので自分もやってみようと思ったのですが、いやちょっと待てよと。 作ってもエミュレータで動かせるソフトを持っていないんですよね。 だったらそれも Rust で作ってしまえばいいのではないでしょうか? 大昔のファミコンで現代の言語である Rust が動いたらうれしいと思いませんか。 私はうれしい気持ちになったのでやってみることにしました。 そもそも Rust でファミコンは可能なの? アプローチは二通りあると思います。 まずは LLVM ベースのバックエンドを作るなり探すなりして、通常の Rust ツールチェインの上で動かす方法。 もう一つは、何らかの方法で Rust をトランスパイルし、既存の 6502 用のコンパイラでコンパイルする方法です。 まず、既存のツールチェインを探す方法から。通常、新しいプラットフォームを用意するならこちらが正攻法になりそうです。 最初に確認するのは Rust の 公式な Platform Support (Tier 1 〜 3) です。 見ます。ファミコンは含まれていません。そりゃそうだ。 しかし、コミュニティによる素晴らしいプロジェクトがいくつか存在するようです。 まず、 llvm-mos という LLVM バックエンドがあります。 こちらは 6502 系の CPU をターゲットにしており、ファミコンで動作させた事例も紹介されています。 しかもこの リポジトリ 、2 週間前にもコミットがあってちょっとびっくりしました。 Rust は基本的に LLVM をバックエンドにしているので、それさえあれば Rust コードを 6502 系 CPU 向けにコンパイルすることが可能です。 実際、それをベースにした rust-mos というプロジェクトも存在して、Rust ツールチェインの構築までできていそうでした。今回は試していませんが、実用的なレベルの環境を求めるのであればこちらの方が良いような雰囲気があります。 もう一つは、Rust を何らかの言語にトランスパイルしてそこから 6502 用のコンパイラでコンパイルする方法です。 その場合、第一選択肢はコンピュータ界の標準語であるところの C 言語です。 そして、実はこれにちょうど良いプロジェクトが存在していました。 mrustc : Rust → C へのトランスパイラ cc65 : C → 6502 用のコンパイラ 特に cc65 はファミコン用 ROM を作る界隈では有名なようで、日本語での解説やサンプルコード、情報も多数あります。 どちらのアプローチをとるか考えたのですが、後者の方が私にとっては取り組みやすいのではないかと思いました。何かうまく動かないことがあったとしても、C 言語を中間言語にしていれば何が起きているか読めばよいからです。 ということで、今回は mrustc + cc65 の組み合わせで Rust コードをファミコン用にコンパイルすることを目指すことにしました。 Q: でも C 言語経由するのずるくないですか? Rust で書いたって言えるの? A: ずるいと思いました。でも最終 Rust だけ書いて機械的処理だけで動くって考えたら Rust で書いたって言えると思いませんか? 私はそう言い張ります。 mrustc とは mrustc は、rustc をブートストラップすることを目標に C++ で書かれた Rust コンパイラです。 若干話が逸れてしまいますが、公式の Rust コンパイラである rustc 自身は Rust で書かれています。そのため Rust をコンパイルするのには Rust コンパイラが必要です! これは困りました。もしこの世からすべての Rust コンパイラが消滅してしまったら、もう一度 Rust コンパイラを使えるようになるまでには相当な道のりが必要になってしまいます。 ちなみに、その作業をコンパイラのブートストラップと呼ぶらしいです。 そこで mrustc があれば、C++ コンパイラさえあれば Rust コンパイラを再構築できるようになって便利というわけです。 さらに脱線しますが、じゃあ、その gcc やら Linux やらまで一緒に消し飛んだら再構築はできないのでしょうか? これについては live-bootstrap というプロジェクトなどがあって、手で書いた小さい機械語からアセンブラ、 GCC、coreutils などを経て OS を再構築する手順が丁寧に解説されています。これは異世界転生に備えて覚えておくのも良いかもしれません。きっと現代的なコンピュータ環境を再構築できることでしょう。ソースコードが降ってくれば、ですが…。 話を戻しましょう。 そんな mrustc のツールとしての一番の特徴は、あくまでも valid な Rust コードがコンパイルできることを最重要視している、という点です。逆に invalid な Rust コードがうっかりコンパイルできてしまってもあまり気にしません。例えば… ライフタイム検査をしない コンパイルエラーは「本来コンパイルが通るはずのコードなので mrustc のバグである」と考え、診断は丁寧ではない この割り切りは目標がぶれていなくていいなと思いました。そのかわり、コンパイルエラーが発生したときの調査は若干難しいときがありました。 ただ、今回私にとっての何よりの重要ポイントは、mrustc がバイナリではなく C 言語のコードを出力するという点です。 これはおそらく mrustc 自身のコンパイルに C++ を使うので、同じコンパイラで C コードもコンパイルできてブートストラップ上便利という意図なのでしょう。 今回はその特長を生かして高級なトランスパイラとして使わせていただきます。 そして、この C 言語コードを cc65 でコンパイルすることを目指します。 cc65 とは cc65 は、ファミコンに搭載されている MOS 6502 系 CPU をターゲットとした C コンパイラツールチェインです。 Apple II や Commodore PET、そしてもちろんファミコンなど、6502 系 CPU を搭載したハードウェアで動くソフトウェアの開発に広く使われています。 C99 の一部機能に対応しており、コンパイラ (cl65) 、アセンブラ (ca65) 、リンカ (ld65) に加え、ディスアセンブラ (da65) やオブジェクトダンプ (od65) まで何でも一通りツールがそろっています。 また、ファミコン向けのメモリ領域設定が標準で付属しており、ちょっとした標準ライブラリも備えているので、文字を出力するだけの ROM なら本当にすぐに作れてしまいます。 自力で実装しようと思うと CHR ファイルにフォントのビットマップを用意して PPU レジスタを操作して… と大変らしいですが、cc65 のライブラリを使えばなんと cprintf() 関数一発でリッチに画面に文字を出すことができます。 ファミコン開発においては cc65 は定番ツールの一つとなっているようで、特に日本語の解説記事やサンプルコードも豊富に存在します。今回ファミコン初挑戦なのでこれはありがたいです。 C コンパイラの候補としては先ほどの llvm-mos の成果物である clang だけ使わせていただく手もあると思うのですが、今回はそのお手軽さが決め手となり、cc65 を採用することにしました。 頑張って mrustc が出力した C コードをコンパイルできるようになってもらおうと思います。 レギュレーション さて、実際に作業を始める前に、どこまでできたら「成功」とするかのレギュレーションを決めておきます。 Rust だけで書かれたコードに機械的な処理だけを行い、何かしらファミコンエミュレータで動けば OK とします。 cc65 に入っているライブラリ (例えば cprintf() ) は使って良いことにします。 動かしたい部分が動けば OK で、任意の Rust の言語機能が動く必要はありません。 Q: 2 番目と 3 番目、ずるくないですか? A: いやずるいとは思いました。でも低レイヤー分からなすぎて、PPU の初期化とか割り込みとか、アセンブリレベルで手書きするとすごく変なところで沼りそうな予感がしたんです。技術をつけたらいつか再チャレンジするかもしれません。 とはいえ、最終成果物のために自分で書くコードはすべて Rust で、あとは機械的な変換だけで動かすという方針だけは貫きたいと思います。 cc65 と mrustc の動作確認をする さて、まずは cc65 と mrustc がそれぞれちゃんと動くことを確認しておかないと始まりません。 cc65 でファミコン用 ROM を作ってみる cc65 は使うだけならなんと apt でインストールできます。 apt install cc65 まずは試しに “Hello, World!” を表示するプログラムを書いてみましょう。 #include <conio.h> int main(void) { cprintf("hello, world!"); while (1); // 無限ループで終了しないようにする } これを cl65 -t nes hello_world.c でコンパイルすると、あっという間に hello_world という ROM ファイルが生成されます。 もうこれが ROM です。 hello_world.nes という名前にしてエミュレータで実行してみると画面に “Hello, World!” と表示されました。あっけないけど、でもすでにちょっとうれしい。 なお今回、スクリーンショットには MERU という Web 上で動くエミュレータを使わせていただいております。なんとこちらのエミュレータも Rust で書かれているそうです。 mrustc をビルドする 続いて mrustc をダウンロードしてビルドしてみます。 git clone https://github.com/thepowersgang/mrustc cd mrustc make -f minicargo.mk これも驚くほどあっさりとビルドが完了しました。 生成されるのは以下のものです。 mrustc : rustc の代替となるコンパイラ本体 minicargo : cargo の代替となるビルドツール 標準ライブラリのオブジェクトファイル ちなみにこの状態でビルドされたオブジェクトファイルは、ホストマシンの同じ C コンパイラを使って生成されたバイナリです。なのでもうホストマシンで動く Rust プログラムはコンパイルできる状態になっているはずです。試してみましょう。 fn main() { println!("Hello, world!"); } 実行します。 $ mrustc hello.rs && ./hello Hello, world! 問題ないですね。 ちなみにこのとき生成された C コードは 324 行ありました。 軽く眺めてみると、ネームマングリングされた大量の関数、名前が消去された変数名、MIR を彷彿とさせる bb1: , bb2: といったラベル、そして大量の goto 文が見られます。 いかにも機械生成されたコードという感じですが、所々に元の型名などをコメントで残してくれているのが良心的で、意外と読みやすそうです。 ↓ Rust の main() 関数をコンパイルしたと思われる部分 // ::"bin#"::main void ZRG1cD3bin4main0g(void) // -> () { tUNIT rv; struct s_ZRG2cE9core0_0_03fmt9Arguments0g var1; // ::"core-0_0_0"::fmt::Arguments<'static,>/*S*/ struct e_ZRG2cE9core0_0_06option6Option1gBsSG4c_A3fmt2rt2v18Argument0g var2; // ::"core-0_0_0"::option::Option<&'static [::"core-0_0_0"::fmt::rt::v1::Argument/*S*/],>/*E*/ SLICE_PTR var3; // &'static [&'static str] SLICE_PTR var4; // &'static [::"core-0_0_0"::fmt::ArgumentV1<'static,>/*S*/] var3 = make_sliceptr(&ZRG2cD3binB_09FRAGMENTS0g.val, 0x1ull); // _3 = MakeDst(&::"bin#"::#0::FRAGMENTS, 0x1 usize) var4 = make_sliceptr(&ZRG1cD3binF6const00g.val, 0x0ull); // _4 = MakeDst(&::"bin#"::const#0, 0x0 usize) memset(&var2, 0, sizeof(struct e_ZRG2cE9core0_0_06option6Option1gBsSG4c_A3fmt2rt2v18Argument0g )); // _2 = Variant(::"core-0_0_0"::option::Option<&'static [::"core-0_0_0"::fmt::rt::v1::Argument/*S*/],> #0, {}) var1._0 = var3; var1._1 = var2; var1._2 = var4; // _1 = Struct(::"core-0_0_0"::fmt::Arguments<'static,>, {_3, _2, _4}) ZRG3cD8std0_0_02io5stdio7__print0g( var1 ); // ^ Call( _0 = ::"std-0_0_0"::io::stdio::_print<'static,>( _1, ), bb1, bb2) /* ZST assign */ return ; // ^ Return bb2: _Unwind_Resume(); // Diverge } さて、それぞれのツールが動くことは確認できました。 ここからが本番です。 mrustc の吐き出すコードを cc65 でコンパイルする いよいよ mrustc が出力した C コードを cl65 でコンパイルしてみます。 cl65 が対応していないコマンドラインオプションをごまかすラッパースクリプトだけ挟み、それ以外は素の状態でまず試してみます。 早速自分で書いたコードをコンパイルしたいところですが、実は何よりも先にコンパイルしなければならないものがあります。core という crate です。 急に出てきてなんだと思われると思いますが、これは Rust の標準ライブラリの一部です。 Rust の標準ライブラリは、実は以下のような複数の crate によって構成されています。 core : 最小限の言語のコア機能を集めた小さいライブラリ。プリミティブ型のメソッドはもちろん、四則演算 ( Add , Sub , Mul , Div ) や Iterator , Sized といったコンパイラが特別扱いするトレイトの実体もここにある。OS やヒープアロケーションに依存しない、純粋な言語機能だけを提供する。 alloc : メモリアロケーションが必要な機能を提供するライブラリ。 Vec , String , Box などがここに含まれる。OS ほどリッチな機能はないがアロケータくらいなら作れる、という環境で便利。 std : OS 機能を含む、フル機能の標準ライブラリ。ファイル I/O、ネットワーク、スレッドなど、通常のアプリケーション開発で必要な機能がすべて揃っている。 core と alloc の上に作られており、主要な要素を re-export してくれている。そのため、私たちは背後にある core と alloc を意識せず、 std::ops::Add や std::string::String として参照できている。 組み込みプログラミングではリッチな OS サポートがないこともあるので、 #![no_std] というフラグをつけて標準ライブラリを切り離すことが多いと聞きます。 しかし core を外すことは通常ありません。core は Rust が正気を保てる最後のラインであり、これを外すと本当に文字通り何もできなくなってしまいます。そのため、今回も cl65 で core をコンパイルできるところをまず目指す必要があります。 さて、core crate を mrustc でコンパイルして C ファイルを作るところまでは、ホストマシン向けの処理と一緒なので特に問題はありません。果たして libcore.rlib.c という単一の C ファイルが得られました。 問題はここから。このファイルを cl65 はコンパイルできるでしょうか。 ドキドキしながら最初のコンパイルを試してみた結果がこちらです。 --- BUILDING core v0.0.0 (0.0% 1r,0w,12b,0c/13t) > /workspace/mrustc-master/bin/mrustc /workspace/mrustc-master/rustc-1.29.0-src/src/libcore/lib.rs -o output/libcore.rlib -C emit-depfile=output/libcore.rlib.d --cfg debug_assertions -O -L output --crate-name core --crate-type rlib --crate-tag 0_0_0 > output/libcore.rlib_dbg.txt (0.0% 1r,0w,12b,0c/13t): core v0.0.0 /workspace/mrustc-master/rustc-1.29.0-src/src/libcore/slice/mod.rs:1947-1948 warn:0:Unexpected attribute rustc_on_unimplemented on impl /workspace/mrustc-master/rustc-1.29.0-src/src/libcore/slice/mod.rs:1960-1961 warn:0:Unexpected attribute rustc_on_unimplemented on impl output/libcore.rlib.c(9): Error: Include file 'stdatomic.h' not found output/libcore.rlib.c(12): Error: Include file 'math.h' not found output/libcore.rlib.c(22): Error: Identifier expected output/libcore.rlib.c(22): Warning: Implicit 'int' is an obsolete feature output/libcore.rlib.c(22): Error: ';' expected output/libcore.rlib.c(26): Warning: Implicit 'int' is an obsolete feature output/libcore.rlib.c(26): Error: ';' expected output/libcore.rlib.c(27): Warning: Implicit 'int' is an obsolete feature output/libcore.rlib.c(27): Error: ';' expected output/libcore.rlib.c(28): Error: Identifier expected output/libcore.rlib.c(28): Warning: Implicit 'int' is an obsolete feature output/libcore.rlib.c(28): Error: ';' expected output/libcore.rlib.c(28): Warning: Implicit 'int' is an obsolete feature output/libcore.rlib.c(28): Error: ';' expected output/libcore.rlib.c(28): Warning: Implicit 'int' is an obsolete feature output/libcore.rlib.c(28): Error: ')' expected output/libcore.rlib.c(28): Warning: Implicit 'int' return type is an obsolete feature output/libcore.rlib.c(28): Error: '{' expected output/libcore.rlib.c(28): Fatal: Too many errors C Compiler failed to execute - error code 256 … あの、28 行目で諦められたら困ります。mrustc が吐き出した C コードはまだ 28 万行もあるんですけど… まあ最初はそんなものでしょう… cl65 でコンパイルできるようにいじり回す 細かいものは除いて、主な原因は次のような感じです。 特定のコンパイラ (gcc または MSVC) に依存するコードが生成されている。 __builtin_* 系関数とか。 zero-sized array とか。 cl65 が対応している C 標準が若干古い。 C99 のいくらかに対応しているとのことですが、足りないものも多かったです。 簡単なところでは for の中での識別子定義 for (int i = 0; ...) ニッチなのだと designated initializer struct X x = { .member = value } とか。 プリミティブ型が限定されている。 cc65 (6502 CPU) には float, double サポートがありません。 整数型が (u)int32_t までしかありません。 Rust でいうと i64, u64, i128, u128 がサポートできません。 特に u64 は core::any::TypeId で必要になるので避けられないのですが、避けるしかありません。 アドレス空間が 16 bit しかない。 それと関連して、アセンブラ (ca65) が用意するラベル用の領域が合計 16 bit = 32768 個分しか用意されておらず、28 万行というコードベースに耐えられなくてオーバーフローしてしまいました。 ローカル変数領域のサイズが少ない。 メモリが小さいのでローカル変数をたくさん配置するのは無理です。 調査の段階で core::hash の中で計算用に 1024 バイトの配列をとっているコードを発見し驚くなどしました。現代の PC は大富豪ですね。 ほとんどは mrustc のコード生成の修正です。ただ、ラベル上限などはアセンブラ (ca65) やリンカ (ld65) の修正が必須になりますし、float 等のサポートがない問題については core ライブラリ側の修正が必須でした。 とりあえずまず一旦はコンパイルが通るところを目指し、そのために暴挙という暴挙をいとわず魔改造します。 typedef int float; なんて今後書くことはないでしょうね… 試行錯誤の末、なんとか魔改造 cc65 で魔改造 core をコンパイルできるまでには至りました。 ゴールを間近に感じます。 ここからは単純なユーザープログラムをコンパイルできれば良いはずです。最初の C とほぼ同様な、単純なプログラムをコンパイルしてみたところ… #![feature(no_core)] #![no_core] #![no_main] extern "C" { #[no_mangle] fn gotoxy(x: u8, y: u8); #[no_mangle] fn cprintf(fmt: *const u8, ...); } #[start] fn start(_argc: isize, _argv: *const *const u8) -> isize { cprintf(b"hello\0" as *const u8); loop {} } … なんと、以下のようにリンク時にエラーになってしまいました。 ... (中略) ... output/main.c(252): Warning: '__mrustc_op_and_not32' is defined but never used ld65: Warning: /workspace/cc65/cc65-2.19/cfg/nes.cfg(15): Segment 'CODE' overflows memory area 'ROM0' by 356168 bytes ld65: Error: Cannot generate most of the files due to memory area overflow C Compiler failed to execute - error code 256 Process exited with non-zero exit status 1 FAILING COMMAND: /workspace/mrustc-master/bin/mrustc /workspace/rust-hello-nes/main/src/main.rs -o output/main -C emit-depfile=output/main.d --cfg debug_assertions -O -L output -L output/host --crate-name main --crate-type bin --crate-tag 0_1_0 --target ./target.toml --extern core=output/libcore.rlib Env: OUT_DIR=/workspace/rust-hello-nes/output/build_main-0_1_0 CARGO_MANIFEST_DIR=/workspace/rust-hello-nes/main CARGO_PKG_NAME=main CARGO_PKG_VERSION=0.1.0 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=1 CARGO_PKG_VERSION_PATCH=0 (100.0% 0r,0w,0b,2c/2t): 曰く、プログラムサイズが ROM 上の領域を 347 KB ほど超過してしまったとのこと。 手元の nes.cfg によれば、0x7FFA = 32762 バイト、つまり約 32 KB しか存在しません。 ゴールが遠ざかっていく雰囲気を感じました。あまりにもあんまりで笑ってしまいました。 ld65 (リンカ) を改造して不要なシンボルを落とせるようにする (失敗) なぜこんなにサイズが大きいのか、原因は明らかでした。core crate はこの時点で一つのオブジェクトファイルである libcore.rlib.o になっているのですが、ここには core にあるすべての実装が入っているからです。 そして ld65 にはリンク時最適化のようなものがないので、使いもしない関数を含めてすべてバンドルしようとしているようです。そうなると ROM には到底収まらないということですね。 しかし、最適化がないのであれば、最適化を作れば良いのではないでしょうか。 od65 によれば、圧倒的に大きいのは確かに CODE セグメントです。 となれば、スタートアップルーチンから exports の依存関係ツリーを作り、参照されていないものを削除すれば解決できそうな気もします。リンカの魔改造の始まりです。 $ od65 --dump-segments ./output/libcore.rlib.o Segments: Count: 6 Index: 0 Name: "CODE" Flags: 0 Size: 416728 Alignment: 1 Address size: 0x02 (absolute) Fragment count: 280296 Index: 1 Name: "RODATA" Flags: 0 Size: 12346 Alignment: 1 Address size: 0x02 (absolute) Fragment count: 8758 Index: 2 Name: "BSS" Flags: 0 Size: 51 Alignment: 1 Address size: 0x02 (absolute) Fragment count: 51 Index: 3 Name: "DATA" Flags: 0 Size: 3308 Alignment: 1 Address size: 0x02 (absolute) Fragment count: 2104 Index: 4 Name: "ZEROPAGE" Flags: 0 Size: 0 Alignment: 1 Address size: 0x01 (zeropage) Fragment count: 0 Index: 5 Name: "NULL" Flags: 0 Size: 0 Alignment: 1 Address size: 0x02 (absolute) Fragment count: 0 今まで特にアセンブリを直接触って何かをしたことはありません。 最初は削除条件を厳しくしすぎてしまい何もしない ROM ができあがってしまいました。 しかし、だからといってもう少し保守的にやると、結局 ROM サイズが 150KB 程度にしか小さくできませんでした。これでは結局オーバーしてしまうことに変わりありません。 デバッグ出力を見る限りまだまだ不要なシンボルを詰め込んでいそうなので、多分最初のスタートアップルーチンとしてマークしている範囲が大きすぎるとか、何か間違ってるんだと思います。 と、いろいろ試行錯誤していたのですが、次第に export 境界の判別という問題がかなり難しいことに気づき始めました。 C や Rust 由来の普通の関数はまだよいのです。 というのも、コンパイラがきちんと .proc – .endproc というもので関数を囲んで生成してくれるからです。そうしておけばアセンブラが .o ファイルにそのまとまりのサイズを一緒に書き込んでくれます。 そのため、特定の関数が別の何を参照しているのかは、そのサイズの範囲内でどこに jmp しているのかを調べればよく、削除を決めたときもどこから何バイト削除すればいいのか比較的容易でした。 一方、直接アセンブリで書かれている関数 (ラベル) たちは自由奔放です。 処理のど真ん中にラベルがついていて外から横入りするようなこともあるみたいですし、極めつけは自己書き換えコードなる存在です。 最初に jmp $0000 という命令を見たときは首をかしげました。しかし、どうも本当に 0 番地に入っているアドレスに飛びたいわけではないらしいのです。その命令に到達する前に、ちょうどそのオペランドにあたるコード領域のメモリを上書きして使うらしいです。 つまり、 jmp 命令に到達する前に store 系の命令をつかい、 $0000 というオペランドそれ自体を書き換えてしまうということですね。すると CPU が jmp 命令に到達したときには、さも最初から jmp $1234 と書かれていたかのように書き換わっているとのことです。なにそれ怖い。 こうなるとどこからどこまでが一つの「関数」なのか判断するには、究極的に実行をシミュレーションする必要がありそうです。分岐などもあるでしょうからとんでもないことになるでしょうし、そもそもどういうステートでその処理に侵入するかによって莫大なパターンがあります。仮にそれを乗り越えてなんとか範囲が分かったとしても、それを適当な位置にちゃんとリロケーションできるのかも怪しくなってきました。 結局、時間切れもあって、このアプローチは断念することになりました。 既存のリンカの最適化はどうなってるんでしょうか。本当にすごいですね。 諦めて必要な部分だけ core を作る リンク時最適化が無理なのであれば、発想を転換するしかありません。 つまり私が最適化をします。必要な実装だけを含む最小限の libcore を書いてリンクすれば最低限動かすことはできるはずです。 言語機能に必要な marker trait ( Sized , Copy たち) などを含めると必要な実装は思ったより多いのですが、例えば四則演算は u8 だけに定義するなどの涙ぐましい努力によって、とにかく最小構成を目指しました。 そして再度コンパイルしてみると… $ make ... 中略 ... Completed welcome v0.1.0 [bin welcome] (100.0% 0r,0w,0b,3c/3t): やっと、コンパイルが通りました。長かったあ… ファイルが小さくなったので cc65 は無改造でいけるようになるという副次的効果もあります。 遠回りをしましたが、それにしても core crate そのものを書くなんていう経験はなかなかできないので、結構楽しかったです。 Rust 使いなら、プリミティブ型に impl [T] {} とかしてみたいと一度くらいは思ったことがあると思うのですが、それがまさに叶う瞬間でした。 できたもの 最後にもうちょっとライブラリを整理して見た目を整えて、こんな感じのソースコードが実行できるようになりました。 #![feature(no_core)] #![no_core] #![no_main] #[macro_use] extern crate cc65; use cc65::{cprintf, get_screen_size, goto_xy}; fn print_at_center<const N: usize>(line: u8, s: &[u8; N]) { let (width, _) = get_screen_size(); let padding = (width - N as u8) / 2; goto_xy(padding, line); cprintf!(b"%s\0", s); } #[start] fn start(_argc: isize, _argv: *const *const u8) -> isize { print_at_center(5, b"HELLO NES FROM RUST!\0"); print_at_center(15, b"ADVENT CALENDAR 2025\0"); print_at_center(20, b"STATIOLAKE\0"); loop {} } これだけ頑張った割に、C を不便にラップしただけで、Rust っぽいことはほとんどできないのは悲しいようにも思えますが、実際は、(自力では) Rust だけを書けばファミコンを動かせるようにできた、という喜びが大きいです。 ちなみに fizzbuzz も動きます。ちょっとは Rust っぽさあるかな…。 #![feature(no_core)] #![no_core] #![no_main] #[macro_use] extern crate cc65; use cc65::{cprintf, get_screen_size, goto_xy}; fn fizz_buzz(n: u8) { let mut i = 1; while i <= n { goto_xy(0, i - 1); match (i % 3, i % 5) { (0, 0) => cprintf!(b"FizzBuzz\0"), (0, _) => cprintf!(b"Fizz\0"), (_, 0) => cprintf!(b"Buzz\0"), _ => cprintf!(b"%d\0", i), } i += 1; } } #[start] fn start(_argc: isize, _argv: *const *const u8) -> isize { let (_, height) = get_screen_size(); fizz_buzz(height); loop {} } なんで for i in 1..=n じゃないのか、ですか?core に Iterator を実装しなかったからです…。 cprintf! マクロもフォーマット指定が自前なのは片手落ちですが、 format_args! をサポートするのはコード長的に相当大変そうだったので見送りました。 とはいえその辺も、頑張れば必要に応じて実装することも可能だと思います。 長かったですが、ここまでなんとかたどり着けました。 お付き合いいただきありがとうございました。 おわりに 相当難しい遊びでしたが、自分が持つ技術セットで考えれば一番楽しいルートを選べたとも思います。最初はずるいな〜と自分でも思っていたのですが、十分に学びのあるルートでした。 目論見通り、C 言語を中間言語として挟んだことで、何が起きているかがわかりやすく、自分であがける範囲が大きかったのも良かったです。 この方針で大きなものを作るのは難しいでしょうが、もうちょっとくらいは作り込めそうではあります。明らかにオーバーヘッドは大きいのでヌルヌル動くボリューミーなゲームは作れないと思いますが、リアルタイム性が低めな、それこそ迷路みたいなものならできても良さそうだなと。いずれ挑戦するかもしれません。 また今回、core crate のサブセットを再実装することで Rust で当たり前にできていることがどのような仕組みで解決されているのかの一端を体感しました。たとえば &str -> *const u8 のようなデリファレンスすらできません。 Unsize とか CoerceUnsize とかいう、普段なら目にもしないようなトレイトが必要なんだそうです。両手両足をもがれたようなプログラミングでしたが、それはそれで新鮮で面白かったです。 40 年以上前のハードウェアと現代の言語を無理やり繋げるという、誰得なチャレンジでしたが、ここまでお読みいただきありがとうございました! 明日の記事は、Rust Advent Calendar はまだ未定のようですが、ニフティグループ Advent Calendar は @su6y さんの「AWS IAM Identity Center経由でKiroを使ってみる」です!お楽しみに。 ※「ファミコン」「ファミリーコンピュータ」は任天堂株式会社の登録商標です。 ※本記事の内容は個人の技術検証に基づくものであり、任天堂株式会社とは一切関係ありません。
アバター
この記事は、 ニフティグループ Advent Calendar 2025  3日目の記事です。 はじめに こんにちは!新卒1年目のパクパクです OJT中のタスクの中でGraphQLで処理していたIssue作成のプロセスを、GitHub CLI(ghコマンド)に処理する作業を行いました。 この過程で学んだghコマンドについてご紹介します。それでは Let’s go!   目標 ghコマンドを使い、メインIssueとサブIssueを作成してプロジェクトに追加する   1. プロジェクトデータの取得 (Get Project and Field IDs) Issueを作成してプロジェクトボードで管理するには、まず必要となる各種IDを収集する必要があります。 ghコマンド で情報を取得し、 jq を使ってJSONデータを使います。   gh project view gh project view [<number>] [flags] プロジェクトの詳細情報を取得します。 オレンジ色の情報をコマンドで取得します オプション [flags] --owner : 個人またはOrganizationのアカウント --format : 出力フォーマット   例えば、Organizationが my-org である 1 番プロジェクトに関する詳細情報を取得したい場合は、以下のように入力します。 入力 $ gh project view 1 --owner my-org --format json 出力 { "closed": false, "fields": { "totalCount": 10 }, "id": "ABC_dkBO...", "items": { "totalCount": 50 }, "number": 1, "owner": { "login": "my-org", "type": "Organization" }, "public": false, "readme": "プロジェクト Readme...", "shortDescription": "プロジェクト 説明...", "title": "GitHub CLIでIssueを作成しよう", "url": "https://github.com/orgs/my-org/projects/1" } 末尾の -q .id オプションで、プロジェクトIDの値だけ抽出しました。 PROJECT_ID=$(gh project view 1 --owner my-org --format json -q .id)   gh project field-list gh project field-list [<number>] [flags] プロジェクトのフィールド情報を一覧表示します。 オプション [flags] --owner : 個人またはOrganizationのアカウント --format : 出力フォーマット   入力 gh project field-list 1 --owner my-org --format json 出力 { "fields": [ (...) { "id": "PVT_kw...", "name": "Status" , "options": [ { "id": "98234798...", "name": "In progress" } (...) ], "type": "ProjectType..." }, { "id": "abc123", "name": "Sprint" , "type": "ProjectV2Field" }, { "id": "def456", "name": "Point" , "type": "ProjectV2Field" }, { "id": "ghi789", "name": "Priority" , "type": "ProjectV2Field" }, (...) ], "totalCount": 5 } Status フィールドの ID を取得するために、下記のコマンドを使いました。 gh コマンドの結果を jq に直接渡して ID を抽出します。 STATUS_FIELD_ID=$(gh project field-list 1 --owner my-org --format json | jq -r '.fields[] | select(.name=="Status") | .id')     gh project item-list gh project item-list [<number>] [flags] プロジェクトに追加されているアイテム(IssueやPRなど)を一覧表示します。 オプション [flags] --owner : 個人またはOrganizationのアカウント --format : 出力フォーマット 入力 gh project item-list 1 --owner my-org --format json 出力 { "items": [ { "assignees": [ "pakupaku" ], "content": { "body": "# 概要nサービスローンチに向けて...", "number": 1, "repository": "my-org/backend-api", "title": "ユーザー認証機能の実装", "type": "Issue", "url": "https://github.com/my-org/backend-api/issues/1" }, "due": "2025-12-15", "id": "PVID_abc123", "labels": [ "bug", "high-priority" ], "linked pull requests": [ "https://github.com/my-org/backend-api/pull/2", "https://github.com/my-org/backend-api/pull/3" ], "point": 1, "repository": "my-org/backend-api", "sprint": { "duration": 14, "iterationId": "abc12345", "startDate": "2025-11-18" , "title": "Sprint" }, "status": "In Progress", "title": "ユーザー認証機能の実装" }, (...) ], "totalCount": 3 } プロジェクトアイテムの中から現在のスプリント情報を特定するために、各sprintのstartDateを取得します。   2. Issueの作成 (メインIssueとサブIssue) gh issue create gh issue create [flags] 新しいIssueを作成します。   オプション [flags] R , -repo : Issueを作成するリポジトリを選択します。 t , -title : Issueのタイトルを指定します。 b , -body : Issueのボディ(内容)を指定します。 l , -label : Issueのラベルを指定します。   このコマンドで、GitHub上でIssueが作成されます。 入力 gh issue create --repo "my-org/my-repository" --title "ログイン画面のUI実装" --body "Figmaのデザイン案に従って..." --label "feature"     gh issue view gh issue view {<number> | <url>} [flags] Issueの詳細情報を表示します 入力 gh issue view "https://github.com/my-org/my-repository/issues/2" 出力 title: ログイン画面のUI実装 state: OPEN author: pakupaku labels: feature comments: 2 assignees: developer123, reviewer456 projects: Development Board (In Progress) milestone: v1.0.0 number: 2 url: https://github.com/my-org/my-repository/issues/2 -- Figmaのデザイン案に従って、ログインフォームを実装します。 メールアドレス、パスワードの入力フィールドおよびバリデーション(Validation)を含めてください。   ここでも -q .id を使って、作成されたIssueのIDだけ取得できます。 入力 gh issue view "https://github.com/my-org/my-repository/issues/2" --json id -q .id   3. Issueをプロジェクトに追加する gh project item-add gh project item-add [<number>] [flags] プロジェクトにアイテム(IssueやPR)を追加します。 オプション ([flags] -owner : 個人またはOrganizationのアカウント -url : プロジェクトに追加したいIssueやPRのURL 入力 gh project item-add 1 --owner my-org --url "https://github.com/my-org/my-repository/issues/2"     gh project item-edit gh project item-edit [flags] プロジェクト内のアイテム(IssueやPRなど)のフィールド値を変更します。   オプション [flags] -id : 編集したいアイテムのID -project-id : 対象のプロジェクトID -field-id : 値を設定したいフィールドのID -single-select-option-id : 単一選択フィールド(Single Select)で設定したいオプションのID -iteration-id : イテレーション(Iteration)フィールドで設定したい値のID -date : 日付フィールドに設定したい値(YYYY-MM-DD形式) -number : 数値フィールドに設定したい値   例えば、プロジェクトの日付を指定したりする場合に下記のコマンドを使います。 入力 gh project item-edit --id "PVTI_lA..." --project-id "PVT_kw..." --field-id "PVTF_lAD..." --date "2025-12-8"       4. メインIssueとサブIssueを紐付ける gh api gh api <endpoint> [flags] GitHub APIに対してHTTPリクエストを送信します。   Issueをメイン サブ関係で紐付ける機能は、 gh コマンドではまだサポートされていません。そのため、 gh api コマンドを使ってGitHub APIを直接呼び出すことで対応します。 Request Bodyとして送信するデータはJSON形で渡します。 入力 # サブIssueのIDを指定 SUB_ISSUE_ID="I_kwDO..." # Request BodyをJSON形式で作成 JSON_BODY=$(printf '{"sub_issue_id": "%s"}' "$SUB_ISSUE_ID") echo "$JSON_BODY" | gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "/repos/${OWNER}/${REPO}/issues/${MAIN_ISSUE_NUMBER}/sub_issues" --input -   このようにして、GitHub REST APIのエンドポイント( .../sub_issues )に対して直接POSTリクエストを送信し、Issueの親子関係を設定しました。 まとめ これまではブラウザ上で操作するのが当たり前だと思っていましたが、今回、GitHub CLI( gh コマンド)を使ってみて、Issue作成から親子関係の紐付けまでできるということを学びました。 UIだけでなく、CLI環境でも色んな作業ができることを知り、これからもCLIツールを活用して、開発効率を上げていきたいと思います! 明日は、中井さんの「ファミコンのソフトを作る。Rust で」です。 お楽しみです   参照 https://cli.github.com/
アバター
この記事は、 ニフティグループ Advent Calendar 2025 2日目の記事です。 こんにちは。ニフティポイントクラブ開発・運用担当の西根です。 この度、AWS認定資格の一つである SysOps Administrator – Associate (SOA-C02) に合格しました。 AWS Certified SysOps Administrator – Associate (SOA-C02)は2025年9月30日よりAWS Certified CloudOps Engineer – Associate (SOA-C03)に更新されています。 実は、上期の目標に設定していたにもかかわらず、9月に入るまで全く手をつけていないという状況でした…。 そこから 約3週間(実働学習20時間) という短期間で、なんとか合格までたどり着いた経緯と勉強法をご紹介します。 SAAは持っているけれどSOAはどうしようか迷っている方や、とにかく時間がない!という方の参考になれば幸いです。 筆者の前提スキルと状況 まずは、学習開始前の私の状況です。 保有資格: 1年前にSAA(ソリューションアーキテクト アソシエイト)に合格済み。 AWS実務経験: 業務でAWSは使用していますが、AWS Organizationsを使ったアカウント管理などは別部署が担当しており、知識・経験ともにほぼゼロの状態でした。 状況: 上期の目標達成のため、「とにかく最速で」 合格する必要がありました。 学習戦略 学習戦略は「とにかく問題演習から入る」にしました。 というのも、学習期間は 3週間(合計1200分 / 20時間) しかなく、動画教材を最初から全部見るような余裕はなく、以下の戦略を取る必要がありました。 まず問題演習(Udemy)を解く。 間違えた問題、理解が曖昧な問題の解説をしっかり読む。 どうしても概念すらイメージできないサービスだけ、ピンポイントで動画教材を視聴する。 AI(ChatGPTなど)を壁打ち相手として使い、知識の穴を埋める。 時間の作り方 確保した時間は以下の通りです。 平日: お昼休み: 40分(ご飯を食べながらUdemy動画を流し見 + 20〜30分の問題演習) 通退勤: 0〜30分(気が向いたらやる程度) 休日: 予定がない日に 2〜4時間 集中して確保 使用した教材 使用したのは以下の3つです。 [動画教材] AWS認定CloudOps Engineer Associate(SOA-C03)試験 対策トレーニング 主に知識補完用。全編は見ず、Systems Manager, Organizations, Service Catalog, Control Towerなど、全く知らないサービスの章だけを1.25倍速で視聴しました。 [問題演習:メイン] AWSトップ講師によるAWS認定SysOpsアドミニストレーター アソシエイト模擬試験問題集(5回分325問) 今回の学習の核です。 これをひたすら周回しました。 正答率を回数ごとに記録してもらえるので、成長が可視化されてモチベーションになりました。 [問題演習:直前] AWS認定資格 WEB問題集&徹底解説 Udemyの問題に慣れすぎたため、受験前日〜当日にかけて「無料問題」のセクションを解き、知識の最終確認をしました。 AI活用が合格の鍵 今回の短期合格の要因として、 AIの活用 が非常に大きかったと感じています。 SAAと違い、SOAはSystems Manager (SSM) やOrganizationsなど、「実際に組織で使っていないとイメージが湧きにくい」サービスが頻出します。 これらをドキュメントだけで理解しようとすると時間がかかりますが、AIに壁打ち相手になってもらうことで、効率的に理解を深めることができました。 活用例1:サービスの比較と違いの明確化 例えば、SSMの「Run Command」と「Runbook (Automation)」の違いが曖昧でした。 そこで、AIに以下のように質問しました。 (質問) AWS SSMのRun CommandとRunbook(Automation)の違いを、初心者にもわかりやすく教えてください。 (AIの回答要約) Run Command: 目的: 複数のEC2やオンプレミスサーバーに対して「単発のコマンド(シェルスクリプトやPowerShell)」を実行する機能。 例: 「全サーバーにパッチを当てる」「ログファイルを収集する」 Runbook (Automation): 目的: もっと複雑な「一連の運用タスク」を自動化する機能。Run Commandの実行、Lambdaの実行、EC2の作成などを組み合わせたワークフローを作れる。 例: 「AMI(マシンイメージ)の作成とパッチ適用を自動化する」 このように、AIに「〇〇と××の違い」を平易な言葉で説明してもらうことで、公式ドキュメントを読むより早くイメージを掴むことができました。 もちろん、AIが常に真実を教えてくれるとは限らない(ハルシネーション)ため、 最終的なファクトチェックは必要 です。しかし、そもそも何から調べていいかわからない時の「とっかかり」として、非常に優秀でした。 活用例2:直前対策チートシートの作成 試験直前には、AIに「SOA-C03の試験に出る重要なサービスとキーポイントをまとめたチートシートを作って」と依頼しました。 作成されたリスト(SSM, CloudWatch, Organizations, Service Catalogなど)を眺めることで、頭の中を整理して試験に臨むことができました。 実務でSOAはどう活きているか? 正直なところ、1年前に取得したSAAではAWSのサービスをある程度網羅的に学習することとベストプラクティスをインプットすることが主目的であり、日々の業務で「あ、これSAAでやったやつだ!」と直接的に感じる場面はそれほど多くありませんでした。 しかし、SOAに関しては実務で活かせているなという手応えがあります。 ログ監視の提案ができるようになった 具体的な変化として、チーム内で「システムのログ監視方法を見直したい」という話題が上がった際のエピソードがあります。 これまでの自分なら方法を調べることから始めていたと思いますが、「CloudWatch Logsのメトリクスフィルターを使用すれば、特定のエラー文字列を検知して簡単にCloudWatch Alarmを発火させられますよ」 と、具体的な実装方法を含めた提案を調べずに行うことができました。 自社サービスの開発・運用・保守を一貫して行うチームにおいて、「どう運用していくか」「どう守るか」というSOAの知識は、開発者にとって非常に強力な武器になると感じました。 まとめ 今回の合格の勝因は、以下2点に尽きると思います。 良質な問題集(Udemy)を周回したこと 使用したUdemyの模擬試験は、実際の試験より少し難易度が高めに設定されている印象でした。これを解き切ることで、本番で慌てず対応できる実力がつきました。 AIを「壁打ち相手」として活用したこと 実務で触れないサービスの理解を、AIとの対話でスピーディに補完できました。 SAAに合格したものの、次のステップを迷っている方にはSOAの受験をおすすめです。 SAAが「設計」にフォーカスしているのに対し、SOAは「運用・監視・トラブルシューティング」に重点が置かれており、AWSをより深く理解するのに役立ちます。 時間がない方も、問題演習とAI活用を組み合わせれば、効率的に合格を目指せるはずです。 次回はもう少し時間に余裕を持って効率よく勉強したいと思います!  
アバター
はじめに ニフティ アドベントカレンダー トップバッターの西野です。エンジニアブログではほぼ毎回スクラムの記事を書いています。アドカレの枠をとりにいった11月は「12/1、間に合いそう!」と思っているのに当日になって書いています。あわてんぼうのサンタクロースの前倒し精神を見習いたいです。 この記事では、AIを活用し「質の高いレトロスペクティブ」の準備を30分以内に終わらせるコツ、レトロスペクティブ中のアイデア発散と収束にどうAIを役立てるかという内容を、実際に自分たちのチームで活用している具体的なプロンプト付きで紹介しています。ぜひ拡散してください。 スクラムでどうAI活用するか 開発者にもプロダクトオーナーにもAIのビッグウェーブが来て早数年。 スクラムマスターとしてAIが使える領域がないかいくつか試してみたのですが、 ファシリテーション:特にアイデアの収束 チーム分析:特に会話ログに基づく感情・行動分析 この2点についてAI活用が効果的だと思うので、AI活用が向いている領域と具体的な使い方を紹介します。 スクラムでAI活用ができる領域 AI活用により実行時間が短縮できること アクションの作成 ブレスト内容からPBIの受け入れ基準を作成する ミーティングの最後に実行可能なアクションアイテムを生成する 課題の発見 ミーティングの会話内容、ベロシティといったデータに基づくチーム課題の発見 自分が知らないやり方の選択 レトロの手法、ミーティングの手法など AIじゃないと難しいこと 会話における客観的な感情分析 分析結果が正しいかどうかは別なので判断は人間がする 膨大な分析 1スプリント〜半年間の、トランスクリプト(Google Meetなどの文字起こしによる会話ログ)を分析 トランスクリプトの会話内容とベロシティの関係性 一番効果的だった使い方:レトロスペクティブ チームでやってみて一番効果的だったスクラムにおけるAI活用は、レトロスペクティブでした。 自分はスクラムマスターとしてレトロスペクティブの手法を学んで実施していますが、そのぶんレトロの準備やファシリテートが属人化しやすく、他のメンバーでファシリを回す場合でもサポートとして入るケースが多い状態でした。 しかし、AIを活用することでレトロスペクティブの質を維持してファシリをローテさせることが可能となり、レトロスペクティブの内容にもなりますが準備時間も おおむね30分程度 と大きく短縮できています。 データの仕込み方 Google CalendarのMeetを想定した方法となります。Zoomなどほかミーティングツールでも大きく方針は変わらないと思いますが、オンラインを介さずリアルの場でミーティングをする場合は文字起こしを忘れないようにする必要があります。 ここではGoogle MeetやGeminiを軸とした使い方を紹介しますが、ほかのツールでもおおまかな流れは変わりません。 可能な限りスクラムイベント(ミーティング)をGoogle Meetで開催する Google CalendarのMeet設定で、「会議を文字起こし」をデフォルトONにしておく。繰り返し予定に対して反映するとGOOD。 レトロスペクティブのテーマ Google Driveの「Meet Recordings」の「Geminiに相談」にプロンプトを投げる レトロスペクティブのテーマを決めるためのプロンプト例 シンプルなテーマ提案 過去1週間(※スプリントの長さに合わせる)の会話を分析して、レトロスペクティブのテーマとして最適なものを5つ提案してください 長所と課題分析 過去1週間の会話を分析して、チームの長所と課題を指摘してください 出来事を振り返る 過去1週間の会話を分析して、主要な出来事を挙げてください。具体的には、成功した点(Keep)、課題となった点(Stop/Improve)、そして議論の中で繰り返し現れた重要なキーワードやトピックを含めてください 頻発する課題の特定 過去1週間の会話を分析して、同じ問題が繰り返し議論されていた箇所を特定してください。 議論傾向の特定 過去1週間の会話を分析して、「プロセス・ツール」と「人間関係・コミュニケーション」の2つに分類し、どちらの課題の議論に最も時間が割かれていたか、その比率を示してください。そこから、より深い議論が必要な課題を5つ提案してください 感情分析 過去1週間の会話を分析して、チームの感情分析をしてください。そこから見つかったレトロスペクティブのテーマとして最適なものを5つ提案してください トランスクリプト(会話ログ)だけだと的外れな分析を返してくることも多いため、しっくりくるテーマになるかはファシリテーターが事前にトライアンドエラーしておきます。 AIを使ったレトロスペクティブの準備ではここが一番大変です。 レトロスペクティブの手法を決める チームの課題が絞り込めたら、その課題を解決するための方法を尋ねます。 レトロスペクティブに関する情報はそこまで多くないのか、目新しい振り返り手法の提案は基本的に出てこないように思います。 一方で、チームメンバーが「トラブルの振り返りで、変わったレトロスペクティブのやり方を提案してほしい」とAIに頼んで「過去の自分に宛てて手紙を書くなら、いつ、どんな内容にするか」という今までのレトロで採用したことがないアイデアが出てきて、新鮮なレトロスペクティブを開催することもできました。マンネリ化したテーマの場合は振り返り手法をゼロからAIに考えさせてみるのもいいかもしれません。 レトロスペクティブの手法を決めるための プロンプト例 シンプルな振り返り手法の提案 「複雑なタスクの見通しと計画」についてレトロスペクティブを行いたいと思います。どんなレトロスペクティブの手法を提案し、その理由を説明してください 感情にフォーカスした振り返り手法の提案 チームの心理的安全性を高めるのに役立つレトロスペクティブの手法(例:Mad Sad Glad、Sailboatなど)を提案し、その理由を説明してください 真因をさぐるための振り返り手法の提案 「障害がなぜ再発するか」については、根本原因の特定が必要であることを示唆します。根本原因の深掘りに適したレトロスペクティブの手法(例:5つのWhy、フィッシュボーン図など)を提案し、その理由を説明してください 新鮮な振り返り手法の提案 従来のレトロスペクティブの手法にとらわれない、ユニークで感情的な要素を含むレトロスペクティブの手法を3つ提案してください レトロスペクティブを組み立てる ここまでで、チームの課題とその課題の検討方法が決まりました。あとは時間配分を決めます。 AIの活用はレトロの準備だけでなく、アクションアイテムの絞り込みにも使うので、以下のようなプロンプトがおすすめです。 レトロスペクティブの組み立てをする プロンプト例 AIを活用したレトロスペクティブの時間配分とコツ 「障害がなぜ再発するか」というテーマで、5つのWhyを用いてレトロスペクティブを行います。時間配分をふくめて、1時間のスクラムイベントの組み立てをしてください。最初にアイスブレイクを入れてください。アクションプランの絞り込みは、出た意見に基づいてGeminiで行います。このレトロスペクティブのファシリテーションをするときのコツも添えてください。 レトロスペクティブを実施する あとは、決めた時間に沿って進行し、現状把握やアイデア出しを行います。 アイデアを出すフェーズは、おおまかに発散フェーズ(たくさんアイデアを出すフェーズ)と収束フェーズ(アイデアからアクションを絞り込むフェーズ)に分かれます。 AIを活用したアイデアの発散と収束 AIはあくまでミーティング中の会話しか知らないため、発散フェーズは「ミーティング以外の場で起きたことを知っている人」=スクラムチーム本人に任せることをお勧めします。 収束フェーズはAIが得意とするところです。出たアイデアから意見をまとめたり、絞り込ませてスクラムチームがピンとくるアクションを出せるかを高速に試すことができます。 レトロスペクティブ中に出たアイデアの収束をする プロンプト例 (ホワイトボードのキャプチャや、アイデアリストなどのテキストデータをAIに伝えたうえで)出た意見をもとに、1週間(※スプリントの長さに合わせる)実行可能なアクションアイテムを5つ挙げてください 改善アクションの決定 このプロンプトで出たいくつかのアクションアイテムを選ぶ方法は、話し合いでもいいですし、良いアイデアが多くて意見が割れるようであればドット投票などの投票でもかまいません。 次のスプリントで実施するアクションアイテムを選んでAIを使ったレトロスペクティブの完了です。 最後に レトロスペクティブで大切なことは「チームが主体となって」スプリントを振り返ることです。より大量かつ詳細にチームのデータが取れるようになったときには、AIを活用するなかで意見を出すフェーズはAIに行わせて、収束するフェーズをあえて人間がやるという方向性もあると思います。 肝心なのは「レトロスペクティブの全てをAIに任せきりにしない」ことです。 今の時点のAIでは、チームに関する情報の全てを拾いきることはできません。たとえばAIは朝のチーム内の雑談も、隣の席同士で会話のなかで相談した設計の話も、机の散らかり具合も、チームの中に漂う空気感というのも正確にはわかりません。 スクラムチームについて一番詳しいのがスクラムチーム自身なのは、当面は変わらないと思います。 ここまでAIを活用する話を書いていますが、この記事を書くことそのものにはほとんどAIは使っていません。(プロンプトのアイデアや校正にはAIを活用しています) これは、ブログ記事でアウトプットすることで自分の考えを整理することを目的としているからです。スクラムマスターは、要所要所でAIを使うことがチームが目指す目的に沿っているのかも確認する目線があるといいと思います。 次回(自分の掲載が1日遅れなので今日ですが……)のアドベントカレンダーは西根さんによる「SAA持ちエンジニアがAI活用と問題演習でAWS SysOps (SOA-C03) に3週間で合格した勉強法」です。お楽しみに!
アバター
こんにちは!NIFTY engineeringブログ運用チームです! 今年もあっという間に残りわずかとなりました。クリスマスが迫り、アドベントカレンダーの季節が到来しましたね! ニフティグループでは毎年、アドベントカレンダーに積極的に参加しており、今年でなんと10回目の開催となります! 日頃の知見をアウトプットする機会として、ニフティグループのエンジニアが楽しみながら成長していくことができています。 今年も皆様に新たな技術や知識をお届けできることを楽しみにしています! アドベントカレンダーとは? 元々はクリスマスまでの日数をカウントダウンするために使われていたカレンダーで、12月1日からはじまり、25個ある「窓」を毎日1つずつ開けて中に入っている小さなお菓子やプレゼントを楽しむものです。 このカレンダーにならい、定められたテーマに従い参加者が持ち回りで自身のブログやサイトに記事を投稿する、リレー形式のブログ執筆イベントです! ニフティグループ アドベントカレンダー ニフティグループではフリーテーマとなっておりますので、好きに執筆してもらっています。 また、記事に関してはQiita様のアドベントカレンダーページにて登録させていただき、 公開自体は本ブログ(NIFTY engineering)や ニフティライフスタイル Tech Blog 、Qiitaにしていく予定です!(ニフティ、ニフティライフスタイル、セシールでそれぞれ投稿場所が異なります) ニフティグループ AdventCalendar 2025 アドベントカレンダーでは、ニフティグループのエンジニアが日頃の業務で培ったノウハウを共有していきますのでぜひチェックしてみてください!
アバター