TECH PLAY

株式会社ラクス

株式会社ラクス の技術ブログ

919

インフラ開発部でテッ クリード を務めております上畑です。 みなさんはAnsibleコードを修正した後に そのAnsibleコードを本番環境へ適用する際、 ドキドキ していませんでしょうか? 前回、 Ansibleをバージョンアップする記事 を執筆し、大量のコード修正が必要になりました。 この記事では、 ラク スがどのようにしてAnsibleコードを ドキドキ せずに本番に適用しているか、その仕組みを紹介します。 目次 目次 1. はじめに 2. DockerによるAnsible自動実行CIシステム 3. その他、CI環境の工夫 3-1. 本番環境Dockerイメージの最新化 3-2. CI実行時のエラー調査 3-3. CI/CDの並列実行を実現するコード化 3-4. 定期Dockerイメージの構築 4. 最後に 1. はじめに 一般的に、Ansibleコードを修正してマージする際には、以下のような事前チェックを行うことが多いのではないでしょうか? Lint(構文)チェック 複数人によるコードレビュー 本番環境へのAnsible Dry-Run実行 ステージングや開発環境でのAnsible実行 特にAnsibleコードの修正で厄介なのは、 AnsibleのDry-Runでは確認できず、実際に実行しなければ挙動が確認できないコードがある という点ではないでしょうか。(例えばcronモジュールなど) そのため、多くの方が以下のような対策を取っているかもしれません。 本番環境の複製やステージング環境でAnsibleを実行し、問題を検証する しかし、これには次のような課題があります。 修正を繰り返すたびに実行が必要 環境準備が大変 作業後の環境復元でミスが発生する可能性 2. DockerによるAnsible自動実行CIシステム ラク スでは上記の対策に加え、次の方法を採用しています。 本番環境に近いDockerイメージを用意し、そのDockerイメージ対してAnsibleを実行する CI環境を構築しました。 これにより、Dry-Runではなく実行時の変更差異をAnsibleコードの修正を行いながら何度でも確認可能です。 Docker環境のため、一部実行できないコードや挙動の違いはありますが、実際にAnsibleを実行できるというのは大きなメリットです。 具体的なCI環境の処理フローは以下の通りです。 AnsibleコードをGitサーバにPush Gitサーバから親JenkinsのCI用ジョブへWebhook通知 CI用ジョブが、Lintチェックジョブを立ち上げてLintチェック(Ansible Lint、 YAML Lint)を実行 CI用ジョブが、Ansibleチェックジョブを立ち上げて、指定した本番相当のDockerイメージを起動し、Ansibleを実行 本番環境に対してAnsibleのDry-Runを実行 3. その他、CI環境の工夫 3-1. 本番環境Dockerイメージの最新化 上記CIを完了後にmainブランチへマージして本番環境へAnsibleの適用を行いますが、 その際、本番相当のDockerイメージへもAnsibleを適用してDockerイメージの更新を行います。この作業も自動化されています。 3-2. CI実行時のエラー調査 Ansibleを実行した際にエラーが発生して調査を行いたいことがありますが、CI環境では実行後にDockerコンテナを停止する為、エラーになった状態を維持することができません。 その為、CI実行後にDockerコンテナを停止しないオプションを用意し、Dockerコンテナにアクセスして調査できるようにしています。 3-3. CI/CDの並列実行を実現するコード化 弊社では、配配メールのサービスだけでも10種類以上のサーバがあり、1つのAnsible リポジトリ でこれらの環境を維持しています。 共通利用のコードに変更を行う際、すべての環境に対して手動でジョブを実行するのは非効率なため、 以下の様な YAML ファイルを用意して並列実行を自動化しています。 CDも同じ仕組みで自動デプロイを行っています。 # Docker CIジョブ ANSIBLE_DOCKER_RUN: - IMAGE: 'serverA:latest' PLAYBOOK: 'serverA.yml' stage: 'production' DOCKER_KILL: false - IMAGE: 'serverB:latest' PLAYBOOK: 'serverB.yml' stage: 'production' DOCKER_KILL: false # 本番Dry-Runジョブ ANSIBLE_HONBAN_DRY_RUN - PLAYBOOK: 'serverA.yml' stage: 'production' - PLAYBOOK: 'serverB.yml' stage: 'production' 3-4. 定期Dockerイメージの構築 Ansibleコードは当然ながら新規から構築を行っても同一の環境である必要があります。 よくあるコード修正の失敗として既存環境に対して適用することはできるが、新規に構築した環境では実行できないコードを作成してしまうことがあります。 例えば、以下のように実行順が逆になっている場合など # 新規に追加したコード - name: Install Package ansible.builtin.dnf: name: "A" enablerepo: "epel" # 修正前からあるコード - name: Enable Epel Repo ansible.buitin.file: src: "epel.repo" dest: "/etc/yum.repos.d/epel.repo" owner: "root" group: "root" これは既存の環境に適用した際はepel.repoが存在する為、エラーになることはありませんが新規に構築した際にはエラーになります。 実際にこんな単純なミスはないでしょうが、例えばrole単位に分かれていてplaybook内でrole名の指定順が逆であったため発生するなど 以外と発生するケースはあります。 これは上記DockerCIでも発見できない為、定期的(毎日)素のOSのイメージからAnsibleを実行して新規から構築することが可能か確認を行っています。 4. 最後に ラク スではAnsibleによるサーバの構成管理の環境を維持する為、その安全性を高める継続的な改善を行っています。 これ以外にもbehaveやmoleculeによる振る舞いテストの追加など強化を行っており、継続的改善を行っていきます。 以上、ありがとうございました!
アバター
インフラ開発部でテッ クリード をしております上畑です。 ラク スで利用しているAnsibleコードについて、Ansibleのバージョンアップを行った内容を記事にしました。 この記事が同じような境遇のどなたかの助力になれば幸いです。 1. 背景 2. Ansibleバージョンアップ 2-1. AnsibleとPythonの関係調査 2-2. 各OSの標準Pythonバージョン一覧調査 2-3. Porting Guideによる仕様変更の確認 2-4. バージョンアップ戦略 2-5. Ansibleコード修正内容 [修正対応内容] ansible-2.9.27 to ansible-8.7.0 ansible-8.7.0 to ansible-9.12.0 3. コード修正にはAnsible-Lintの自動修正(autofix)機能を使う 3-1. 実行方法 オプションの使い方 ルール一覧 4. Ansible-Lintバージョンアップ 4-1. Ansible-Lintバージョン推移 4-2. Ansible-Lint各バージョンへの対応 v4.7.0 to v5.4.0 var_naming unnamed-task v5.4.0 to v6.14.0 missing document start "---" (document-start) missing starting space in comment (comments) too few spaces before comment (yaml[comments]) deprecated-module name[casing] ※autofix利用可能 fqcn[action-core]※autofix利用可能 fqcn[action] no-changed-when: Commands should not change things if nothing needs doing. yaml[truthy]: Truthy value should be one of v6.14.0 to v6.22.2 var-naming[no-role-prefix] risky-shell-pipe 5. まとめ 1. 背景 2019 - 2020年に私が作った共通Ansibleテンプレートはansible-2.9.x(旧型Ansible)のコード体系をベースとして構築しており、現在も各サービス環境の構築や設定変更等業務で利用しています。 (記事) ラクスサービスを管理するAnsibleコードの共通テンプレートを作った話 - RAKUS Developers Blog | ラクス エンジニアブログ また、CI/CD環境としてAnsibleの構文チェック(Lint)やAnsible実行環境をJenkinsと連携して整備しており、業務にがっつり組み込まれていることからAnsibleのバージョンアップに伴うサービス影響を鑑みて見送って(半ば放置)しておりました。 しかし、定期的なJenkins(Dockerコンテナ)のバージョンアップを進めていくにあたって、Jenkinsイメージ内のOS更新に伴う Python サポートバージョン変更が上記環境の維持に影響が出たことが今回Ansibleバージョンアップを決心した背景となります。 2. Ansibleバージョンアップ 2-1. Ansibleと Python の関係調査 Ansibleはご承知の通り2.9から2.10に伴いansibleとansible-core(or ansible-base)に分かれましたので現在(2024年11月18日)までの状況をまとめました。 下記の表にもある通り、Ansible実行環境及び実行先の Python のサポートバージョンの範囲がある為、これに伴う影響を確認していきます。 出典: ansible-coreサポートマトリックス Ansible Version base/core Version EOL Ansible実行環境 Ansible実行先 ansible-2.9.x - EOL(2022/05/23) 2.7, 3.5-3.8 2.6-2.7 , 3.5-3.8 ansible-2.10.x ansible-base 2.10.x EOL(2022/05/23) 2.7, 3.5-3.9 2.6-2.7 , 3.5-3.9 ansible-3.x ansible-base 2.10.x EOL(2022/05/23) 2.7, 3.5-3.9 2.6-2.7 , 3.5-3.9 ansible-4.x ansible-core 2.11.x EOL(2022/11/07) 2.7, 3.5-3.9 2.6-2.7 , 3.5-3.9 ansible-5.x ansible-core 2.12.x EOL(2023/05/22) 3.8-3.10 2.6-2.7 , 3.5-3.10 ansible-6.x ansible-core 2.13.x EOL(2023/11/06) 3.8-3.10 2.6-2.7 , 3.5-3.10 ansible-7.x ansible-core 2.14.x EOL(2024/05/20) 3.9-3.11 2.7 , 3.5-3.11 ansible-8.x ansible-core 2.15.x EOL(2024/11/01) 3.9-3.11 2.7 , 3.5-3.11 ansible-9.x ansible-core 2.16.x 2025/05/01 3.10-3.12 2.7 , 3.6 -3.12 ansible-10.x ansible-core 2.17.x 2025/11/01 3.10-3.12 3.7-3.12 ansible-11.x ansible-core 2.18.x 2026/05/01 3.11-3.13 3.8-3.13 2-2. 各OSの標準 Python バージョン一覧調査 いくつか抜粋しますが、各OS標準の python のバージョンを確認しました。 上記の表ではansible-9.xからansible-10.xへ移行する際に、Ansible実行先として python -2.xのサポートを終了しています。その為、 RHEL / CentOS の7系への影響があります。 また、 Python -3.6のサポートも終了している為、ここではAlmaLinux/RockyLinux/ RHEL の8系の影響にも考慮が必要となることが考えられます。 OS OS Version Python Version RHEL / CentOS 7 2.7 AlmaLinux/RockyLinux/ RHEL 8 3.6 AlmaLinux/RockyLinux/ RHEL 9 3.9 Ubuntu 20.04 3.8 Ubuntu 22.04 3.10 Ubuntu 24.04 3.12 ラク スでも社内にあるいくつかの検証用の環境の一部がCentOS7の環境があり、現在リプレイス作業中でした。 その為、影響解決策として以下のいずれかの対応を検討しました。 python -2.7から python -3.xへの変更 OSのリプレイス完了まで待つ 並行で作業を行う為、まずは CentOS7環境のリプレイスが完了するまではansible-10.xへの変更は行わずに、ansible-9.12.0まで対応していくこと としました。 2-3. Porting Guideによる仕様変更の確認 Ansible公式サイトにはPorting Guide(移植ガイド)がありますので、利用しているAnsibleのモジュールにおいて仕様変更等がないか各バージョン毎に仕様変更への影響確認を行います。 その際に各モジュール単位で確認を行いますが、 ラク スのAnsibleコードではAnsible標準の機能による実装が中心となっているため、community.general. , ansible.builtin. などに限定することができ、比較的確認箇所が少なく済みました。 公式 Ansible Porting Guide 2-4. バージョンアップ戦略 一番留意が必要なのは、 「サービスへ影響をしないこと」 が重要である為、ansibleのバージョンアップは段階的に実施していきます。 以下ansible アップデート順になります。 From Version To Version base/core Version target Python Verison 2.9.x 2.9.27(2.9最終バージョン) --- 2.6-2.7, 3.5-3.8 2.9.27 2.10.7 ansible-base-2.10.17 2.6-2.7, 3.5-3.9 2.10.17 3.4 ansible-base-2.10.17 2.6-2.7, 3.5-3.9 3.4 8.7.0 ansible-core 2.15.13 2.7, 3.5-3.11 8.7.0 9.12.0 ansible-core 2.16.13 2.7, 3.6-3.12 一旦ココまで ---- ---- ---- 9.12.0 10.6.0 ansible-core 2.17.x 3.7-3.12 2-5. Ansibleコード修正内容 以下、各Ansibleバージョン間において対応した内容を記載しておきます。 弊社環境では以下2種類の修正のみで、ansible-9.12.0までの修正において、実行時の動作は問題ありませんでした。 [修正対応内容] ansible-2.9.27 to ansible-8.7.0 未定義の変数の使用について、厳格化されました。実際には[]内は文字列の予定でしたが、変数として評価されていた為対応しました。 The task includes an option with an undefined variable. --- - name: "{{ package_name }}" | default([httpd]) + name: "{{ package_name }}" | default(['httpd']) ansible-8.7.0 to ansible-9.12.0 Ansible 2.12(ansible 5.X)からDeprecatedとなっていたinclude:, import:の表記は[Ansible 9系から廃止]]( https://docs.ansible.com/ansible/latest/porting_guides/porting_guide_9.html#id65 )されました。 Removed include which has been deprecated in Ansible 2.12. Use include_tasks or import_tasks instead. --- - - include: setup.yml + - include_tasks: setup.yml 3. コード修正にはAnsible-Lintの自動修正(autofix)機能を使う ansible-lintにはv6.15.0から autofix機能が実装 されました。 この自動修正機能を利用することでAnsibleコードの修正の9割に対応することができましたので紹介します。 3-1. 実行方法 自動修正機能付きで実行するにはansible-lint実行時に--fixを付与するだけです。 渡したplaybookに記載されているroleに対して、引っかかったルールに対して修正が行われます。 ※このオプションは、v6.20.0から追加されておりますので利用時はこれ以降のバージョンを使用してください。 ansible-lint [playbook].yml --fix 実際に修正が行われるとModified * files.と修正されたファイル数が表示されます。 diff機能はない為、git等で差分を確認しましょう。 # ansible-lint ansible-lint.yml --fix --- INFO Identified /etc/ansible as project root due .git directory. INFO Set ANSIBLE_LIBRARY=/etc/ansible/.cache/ansible-compat/0cb87f/modules:/usr/share/ansible/plugins/modules INFO Set ANSIBLE_ROLES_PATH=/etc/ansible/.cache/ansible-compat/0cb87f/roles:roles:/etc/ansible/roles INFO Executing syntax check on playbook ansible-lint.yml (0.77s) Modified 6 files. Passed: 0 failure(s), 0 warning(s) on 39 files. Last profile that met the validation criteria was 'production'. オプションの使い方 --fix ... --fix=allと同等、自動修正できる全てのルールを修正 --fix=,--fix name, fqcn , yaml ... 指定したルールのみ修正 ルール一覧 全てのルールに対して自動修正が行われるわけではなく、以下のルール一覧に記載されているものに対して修正が行われます。 対応しているautofixルール一覧 ざっと紹介すると以下の修正が可能です。 * command-instead-of-shell ... shellをcommandに変換 * deprecated-local-action ... local_actionの代わりに delegate _toを使う * fqcn ... モジュール名を fqcn 形式に変換(ansible.builtin.など一部のみ) * jinja ... jinja表記のformat修正 * key-order ... blockにwhenをつける場合の記載修正 * name ... 先頭が小文字から始まっている場合、大文字に変換 * no-free-form ... one-linerの記載を複数行記載形式に修正 * no-jinja-when ... when文のjinja記載を修正 * no-log-password ... no-logの記載を修正 * partial-become ... becomeやbecome_userの記載を修正 * yaml ... yaml 表記を修正 4. Ansible-Lintバージョンアップ 続いてAnsible-Lint環境についてもアップデートを検討しました。 4-1. Ansible-Lintバージョン推移 現行のAnsible-Lintはv4.3.7を利用しておりましたので、現時点での最新バージョンであるv24.10.0まで段階的にアップデートをおこない影響を確認しながら修正を実施しました。 以下バージョン推移 v4.3.7 > v5.4.0 > v6.14.0 > v6.22.2 > v24.10.0 4-2. Ansible-Lint各バージョンへの対応 ここからはAnsible-Lintのバージョンアップにおいて、各バージョン間における実際に修正が必要だった内容を記載しておきます。 v4.7.0 to v5.4.0 var_naming 変数名として使用できる文字の制限 影響が大きい為、一旦修正を行わず例外追加で対応(大文字を許可) .ansible-lint --- var_naming_pattern: "^[A-Za-z_][A-Za-z0-9_]*$" # 大文字を許可 unnamed-task name定義のないタスクの禁止 - import_tasks: install.yml - import_tasks: setup.yml # 以下に修正 - name: Install import_tasks: install.yml - name: Setup import_tasks: setup.yml v5.4.0 to v6.14.0 missing document start "---" (document-start) yaml ファイルの先頭に---を付与 --- missing starting space in comment (comments) 先頭コメントの後にコメント文の前にスペースが必要 #-name # 以下に修正 # - name: too few spaces before comment ( yaml [comments]) コメントの前のスペースは2つ以上。またはコメントの開始後のスペースがありません command: test #noqa 503 command: test # noqa 503 deprecated-module include:, import:の禁止 - include: # 以下に修正 - name: ... include_tasks: - import: # 以下に修正 - name: ... import_tasks: name[casing] ※autofix利用可能 nameの先頭文字は大文字であること - name: test # 以下に修正 - name: Test fqcn [action-core]※autofix利用可能 module名をansible.builtin.など新しい表記に対応が必要 - name: Test command: echo 'test' # 以下に修正 - name: Test ansible.builtin.command: echo 'est' fqcn [action] module名をansible. posix .やcommunity.general.など新しい表記に対応が必要 - name: Test sysctl: name: ... - name: Timezone timezone: name: ... # 以下に修正 - name: Test ansible.posix.sysctl: name: ... - name: Timezone community.general.timezone: name: ... ただし全ての修正が終わるまでは古いansible-lintでciを動かす為、一旦対象外にしておく .ansible-lint --- skip_list: - fqcn[action] no-changed-when: Commands should not change things if nothing needs doing. command module使用時にはchanged_whenの定義が必要 # コマンド実行結果がrc: 0の場合changedになる ... ansible.builtin.command: echo 'test' register: result changed_when: result.rc == 0 # コマンド実行結果に関わらずchangedにはしない ansible.builtin.command: echo 'test' register: result changed_when: false # コマンド実行結果に関わらずchangedにする ansible.builtin.command: echo 'test' register: result changed_when: true yaml [truthy]: Truthy value should be one of yaml 構文においてboolianの値はtrue or falseである必要がある - name: Test ansible.builtin.command: echo 'test' register: result changed_when: no failed_when: True # 以下に修正 - name: Test ansible.builtin.command: echo 'test' register: result changed_when: false failed_when: true また、上記対応においては修正量が多い為、role単位での修正を行う対応として、 yaml -lintバージョンを1.26.3から1.35.1に更新しignore機能を利用して修正対象を絞り込んで順次対応を実施しています。 .yamllint --- rules: truthy: ignore: - "roles/roleA/*/*.yml" # ロール修正時に削除して対応 - "roles/roleA/*/*.yaml" # ロール修正時に削除して対応 - "roles/roleB/*/*.yml" # ロール修正時に削除して対応 - "roles/roleB//*/*.yaml" # ロール修正時に削除して対応 v6.14.0 to v6.22.2 var-naming[no-role-prefix] ロール内の変数名は role_name_ プレフィックス として使用する必要があります。 プレフィックス の前では下線が使用できます。 Ansibleドキュメントを確認しても、このルールが確認できませんでした。変更に伴う修正影響が大きい為、一旦修正対象外としました。( https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#playbooks-variables ) roles/hoge/vars/main.yml --- version: '0.1' # 以下に修正 hoge_version: '0.1' 今回はこのルールを全体的に対象外にする .ansible-lint --- skip_list: - var-naming[no-role-prefix] risky-shell-pipe shell内でpipeを使用する場合は、pipefailオプションを設定する必要がある - name: Test Shell shell: echo 'test' | tail # 以下に修正 shell: set -o pipefail && echo 'test' | tail 5. まとめ Ansibleバージョンアップ時にはAnsibleのバージョンによって Python のサポートバージョンが変わるので注意。Ansible実行環境、Ansible実行先の環境の Python のバージョンを確認すること Ansibleコードの修正にはAnsible-Lintの自動修正機能が使える。ただしv6.20.0以上を使うこと Ansibleのバージョンアップは段階的に行う Ansible-Lintも段階的にバージョンアップを行い修正範囲を極小化して段階的に行う 実際には、これ以外に弊社にはAnsibleを本番環境に適用する前に実際に実行して、安全性を確認する環境が存在しますが詳細は別記事で紹介しようと思います。 以上長くなりましたが、Ansibleバージョンアップ作業のまとめになります。 ありがとうございました。
アバター
はじめに 配配メール開発チームの id:takaram です。 2024年12月22日に、東京・蒲田で PHP Conference Japan 2024 が開催されました。 ラクス はブロンズスポンサーとして協賛させていただいたのに加え、エンジニア3名の トーク を公募で採択いただき、登壇してきました。 今回は ラクス からの参加者によるレポートを紹介させていただきます! はじめに 参加レポート PHPの今とこれから2024 PHP RMは何をする?コア開発者と兼任するメリット/裏話 終了の危機にあった15年続くWebサービスを全力で存続させる〜Twilog・Togetter統合の舞台裏〜 見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理 20年続くレガシープロダクトに10年携わったエンジニアが思う、システム長期運用のカギ PHPerのための計算量入門 情報漏洩させないための設計 PHPConferenceへの参加を後押しするためにしていること どうして手を動かすよりもチーム内のコードレビューを優先するべきなのか ラクスからの登壇セッションのご紹介 PHP開発者が挑むDKIM導入:Googleガイドライン対応の実例と学び Rustで作るPHP拡張モジュール:PSR-7ライブラリ編 20年もののレガシープロダクトに0からPHPStanを入れるまで まとめ 参加レポート PHP の今とこれから2024 report by id:hirobex fortee.jp 廣川さんによる、定例の PHP の動向についての トーク です! PHP8.4は新機能も多く、非常に盛り沢山な内容になっていました! プロパティフックや非対称プロパティ可視性などは、使ってみたいと思っているPHPerの人も多いのではないでしょうか? PHP RMは何をする?コア開発者と兼任するメリット/裏話 report by id:hirobex fortee.jp PHP コア開発者であり、かつリリースマネージャーでもあるSaki Takamachiさんの トーク です。 リリースマネージャーが集まったSlackがあるという話や、リリースマネージャーが PHP リリースのためにどんな作業をするのかと行った、非常に貴重な話を聞くことができました! 世界的な OSS がどのように開発されているのか、その一部を見ることができます! 終了の危機にあった15年続く Webサービス を全力で存続させる〜 Twilog ・Togetter統合の舞台裏〜 report by id:hirobex speakerdeck.com イーロン・マスク に買収されたXの API 廃止に伴う、 Twilog とTogetter統合についての トーク です。 大規模なDBを、どのようにして AWS に移行したのか、そのお話を聞くことができました! SQLite と MySQL が構成図に出てきたときは、どうなることかと思いました。 見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` と SQL 結果のメモリ管理 report by id:takaram speakerdeck.com 武田憲太郎 ( @KentarouTakeda ) さんによるセッションです。武田さんは、 PHP 8.4で導入された pg_result_memory_size() の提案・実装を行った方で、その関数にも関連する「 SQL 実行時のメモリ使用量」についてお話しされました。 普段何気なく使っているPDOの prepare、execute、fetchAll の裏で何が起きているのか、 SQL の結果データがどこでどのように保持、管理されているのかを知ることができました。それと同時に、自分のコードの実装は大丈夫……?と不安になったので、改めて見直してみようと思いました! 20年続くレガシープロダクトに10年携わったエンジニアが思う、システム長期運用のカギ report by id:uemura_rks speakerdeck.com 20年続くプロダクトをモダンな環境に移行してきた取り組みについての トーク でした。 私が担当するサービスは同じような境遇でいまだにレガシーから抜け出せていない状況にあるため、 非常にためになる内容でした。 システムだけでなく開発体制の改善(開発体制に再現性を持たせる)について述べられていたことも印象的でした。 モダンへの移行はタイミングが遅くなれば遅くなるほどやることが増えるので、出来るだけ早めに実施したいものです。 PHPerのための計算量入門 report by id:hirobex speakerdeck.com アルゴリズム において非常に大事な要素になる計算量について、非常にわかりやすく解説していただいた トーク です! データ数が大きくなればなるほど計算量の考えは大事になってくるので、とても参考になりました。 また、最後にチューニングにも限界があるから、仕様変更も視野に入れようねという話も入っていたのが、非常に実務的だと思いました。 情報漏洩させないための設計 report by id:uemura_rks speakerdeck.com ビジネスロジック の実装ミスなどによる情報漏洩を防ぐための設計手法についての トーク でした。 コンテキストごとにEntityを用意することで必要最小限の情報を扱えるようにするといった工夫 分けたEntityを取り違えないための工夫 が参考になりました。 情報漏洩を題材にした トーク でしたが、紹介されていた手法は情報漏洩に限らず普段の開発に活かせる考え方でした。 deptrac という依存関係をチェックできるツールが面白そうなので使ってみたいです。 PHPConferenceへの参加を後押しするためにしていること report by id:takaram fortee.jp 下岡葉子 ( @yoko_94b ) さんによる、 PHPカンファレンス のような技術イベントに参加したことがない人の背中を押すにはどうするか、というお話でした。 行かない理由として「難しそう」「すごい人が行くところでは?」のような気後れや「休みの日の朝から……?」のような声に対し、「若い人もたくさん来てるよ」「知らない単語を知るいい機会だよ」「午後からでも大丈夫」といった声掛けを社内で行って後押しされているそうです。 トーク 中「今回初参加の方いますか?🙋」と質問されたところ、結構たくさんいらっしゃったのが印象的でした。 弊社開発部は東京と大阪に分かれていて、大阪からだと距離のハードルという問題もあったのですが、2024年は全国各地で PHP 系カンファレンスが開催されたおかげでハードルがぐっと下がりました。 2025年も続くようなので、私も参加を後押ししていきたいです! 月刊 PHPカンファレンス 2025! #phpcon #track1 pic.twitter.com/xJYvrkZzs5 — takaram (@takaram71) 2024年12月22日 どうして手を動かすよりもチーム内のコードレビューを優先するべきなのか report by id:takaram speakerdeck.com おかしょい ( @okashoi ) さんによるLTです。 コードレビューをついつい後回しにしてしまう人(私含め)には耳が痛い話ではありました。私は「レビューを優先すべき」という話は聞いていても、理由まで具体的に理解できていなかったのですが、今回そこを知ることができました。 自分のタスクを横に置いてでもレビューを優先していきましょう!💪 ラクス からの登壇セッションのご紹介 PHP 開発者が挑む DKIM 導入: Google ガイドライン 対応の実例と学び report by id:uemura_rks speakerdeck.com トラック4の一発目として、楽楽販売で Google ガイドライン に対応した実録をお話させていただきました。 カンファレンス初登壇でしたが落ち着いて話せたので良かったです。 自分が設計寄りの人間なこともあり技術の話があまり出来なかったため、次回はもう少し技術ネタを持って参加してみたいです。 Rustで作る PHP 拡張モジュール:PSR-7ライブラリ編 report by id:takaram www.docswell.com 通常 C言語 で作る PHP 拡張モジュールを、 ext-php-rs というライブラリを使ってRustで実装してみる、という話をさせていただきました。 Rust自体本格的に触ったことがない状態から始めたため、かなり苦労したのですが、初心者だからこそできる話もあったかなと思います。今回の登壇を通じて、Rustにも PHP にも詳しくなれたのではと思います! 20年もののレガシープロダクトに0からPHPStanを入れるまで report by id:hirobex speakerdeck.com 登壇しました! タイトル通り、レガシープロダクトにPHPStanを入れた話です! PHPStanのオプションや、bootstrapFilesを駆使して、PHPStanを導入した話をしました! ありがたいことに、多くの人が来てくださりました。 発表した会場が、大学の講義室みたいで、人気教授になった気分でした。 まとめ 2024年は全国各地で PHPカンファレンス が行われましたが、歴史の長い PHP Conference Japan(世界最古だそうです!)は今年の締めくくりにふさわしい盛り上がりだったように思います。 2025年は6月28日開催と、実はもう半年後に迫ってきています。来年も楽しみですね!
アバター
はじめに こんにちは、 @rs_tukki です。 この記事は、 ラクス Advent Calendar 2024 の25日目の記事です。 今回は、開発中に見つけた重いクエリを改善するための記録と、改善のために使用した見慣れない構文の紹介をしようと思います。 はじめに 開発中の出来事 パフォーマンスチューニング UNNESTとは? チューニング結果 まとめ 開発中の出来事 ある日、私はWebアプリに新しい API を追加するための実装をしていました。 既存のテーブルからデータを取得するための新しいクエリを作成して、そのクエリを呼び出した結果を返すだけの単純な API です。 実装はつつがなく完了し、その後のテストでも特に問題は見られませんでした。 ただ、新規にクエリを作ったため、最後に JMeter を使ったパフォーマンス検証を行うことに。 【図解】はじめてでもわかるJMeterの使い方 - RAKUS Developers Blog | ラクス エンジニアブログ DBにデータを大量に入れて、顧客の利用状況を元にアクセス数の 閾値 を検討し、 その数だけリク エス トを投げる操作を3回繰り返し、それぞれの平均値を取ります。 そして結果がこちら。 平均値 1回目 1,881ms 2回目 1,886ms 3回目 1,929ms 1,900ms = 1.9秒 。 ...流石にちょっと時間がかかりすぎですね。 パフォーマンスチューニング 負荷検証は通常の運用で想定した分をはるかに超えたデータ量と呼び出し回数で行ってはいましたが、 とはいえ一度の API の実行で2秒弱もかかることがあるのではとてもリリースできません。 そのため、 ボトルネック となっている SQL を特定した上でそのチューニングを行うことにします。 実装したコードから呼び出しているクエリを1つずつ コメントアウト してはパフォーマンス検証、という操作を繰り返し、極端に処理が軽くなったタイミングがあれば、その時に コメントアウト しているクエリが ボトルネック ということになります。 そして、それを何度も繰り返している内に、とうとう原因となる一つのクエリに辿り着きました。 それがこちら。 SELECT count ( 1 ) AS count FROM ( SELECT a.column1 FROM tableA a WHERE EXISTS ( SELECT 1 FROM tableB B WHERE a.column1 = b.column1 AND a.column2 = b.column3 - 1 AND b.column4 = 0 AND b.column5 = 0 AND b.column6 IN ( SELECT column7 FROM tableC WHERE column8 = ' AAA ' OR column9 = ' AAA ' OR column10 = ' AAA ' ) ) AND a.column11 = 0 AND a.column12 >= 100 LIMIT 100 ) AS " result " ; このクエリの実行が極端に重いせいで、リク エス ト全体が遅延していたというわけです。 このクエリ……何か変…… 見てもらえれば分かるとおり、このクエリでは欲しい情報を一度に取ろうとするあまり、 サブクエリにサブクエリを重ねて極端にパフォーマンスが悪いクエリになってしまっています。 そのため、サブクエリの中の最も深い部分、tableCから column7 を抽出している部分を先に実行しておき、 後から条件に加えるようにしました。 SELECT column7 FROM tableC WHERE column8 = ' AAA ' OR column9 = ' AAA ' OR column10 = ' AAA ' ; -- 「test1」と「test2」が抽出される --- SELECT count ( 1 ) AS count FROM ( SELECT a.column1 FROM tableA a WHERE EXISTS ( SELECT 1 FROM tableB B WHERE a.column1 = b.column1 AND a.column2 = b.column3 - 1 AND b.column4 = 0 AND b.column5 = 0 AND EXISTS ( SELECT column7 FROM UNNEST(ARRAY[ ' test1 ' , ' test2 ' ]) AS C(column7) WHERE b.column6 = C.column7) ) AND a.column11 = 0 AND a.column12 >= 100 LIMIT 100 ) AS " result " ; UNNESTとは? さて、改善後のクエリに UNNEST という見慣れない構文があります。 これは PostgreSQL で使用できる構文で、配列を引数として渡すと、その各要素を単一列のテーブルとして出力する、という構文になります。 www.postgresql.jp つまり、 SELECT column7 FROM UNNEST(ARRAY['test1','test2']) AS C(column7) を実行すると、 column7 test1 test2 という列を含む C テーブルが結果として得られます。 この結果はそのまま EXISTS に引き渡すことが出来るため、事前に抽出しておいた複数件の値を1つずつIN句に入れるよりも若干パフォーマンスが改善するというわけです。 チューニング結果 さて、改善後のクエリを、改善前と同条件のもとで負荷検証にかけてみたところ... 平均値_改善前 平均値_改善後 1回目 1,881ms 507ms 2回目 1,886ms 518ms 3回目 1,929ms 516ms API リク エス トの実行時間が1/3にまで改善されました! クエリを分割するだけで速度が3倍近くにもなるので、やはりチューニングは大事だと気付かされた一件でした... まとめ 今回は、開発時に発見した ボトルネック とそれを改修するまでの記録を解説しました。 テストのタイミングでは問題なく見えても、大量のデータを投入し大量のリク エス トを投げるとパフォーマンスが落ちることは往々にしてありますので、しっかり検証は行うようにしましょう。 ここまで読んでいただき、ありがとうございました!
アバター
目次 リファクタリングに着手するまでの経緯 苦労した点や学び 仕様を理解する 既存コードを読み解く ①目的や仮定を持たずに一気に全体を追ってしまう ②コメントに惑わされてしまう ③効果的な作業メモを取らない 適切な命名 ①コードリーディング時 ②実装時 まとめ リファクタリング に着手するまでの経緯 初めまして!楽楽販売新卒エンジニアのudonrmです。 本記事では、配属後初めての業務として経験した既存コードの リファクタリング について過程と学びを伝えていきます。 弊社では、3ヶ月間の新卒合同研修を経た後にそれぞれの部署に配属され、そこで配属後研修が行われます。私の所属された楽楽販売では、配属後に検証環境を使用した実践的な開発演習や商材理解を中心に2ヶ月半ほどの研修がありました。9月中旬ごろには配属後研修が終了し、そこから実業務を開始しました。 初めての業務として取り組んだタスクが、コード品質の改善を目的とした既存コードの リファクタリング です。 この リファクタリング は、既存のバリデーション アーキテクチャ の改修やクラスの責務の見直し、可読性の向上がメインテーマになります。 バリデーション アーキテクチャ 変更の経緯については、弊社過去ブログをご参照ください。 tech-blog.rakus.co.jp 苦労した点や学び ここからは、 リファクタリング に着手する際に特に苦労した点や学びのあった点を観点別に紹介していきます。 仕様を理解する まず苦労した点に、仕様理解を挙げたいと思います。 楽楽販売は自社の業務フローに合わせて表示項目や操作メニューをカスタマイズできることが特徴の一つで、長所でもあります。ただ、その分どうしても機能数が増えてしまう傾向にあり、仕様理解が難しいことが業務を進めるうえで自分の課題になっていました。 今回のタスクは、配属後研修で軽く触れた程度でほとんど知見のない機能を対象とした リファクタリング でした。そのため、当然ですが仕様の理解にも苦労がありました。 当初の考えとしては、仕様理解は リファクタリング の方針検討やレビュワーとの認識合わせを経て自然と理解していくものくらいの認識を持っていました。 実装の序盤ごろまでは仕様の理解不足による影響は特になかったのですが、時間がたつほどじわじわと仕様の理解不足による影響を感じ始めます。 詳細は省きますが、画面上で使われているとある単語の意味を若干勘違いしていたり、細かい挙動の把握漏れがあったりしました。誤った前提知識を持ったままコードリーディングを進めるので、細部を読み間違えてしまったり、最悪の場合は デグレ が起きていることに気が付かなかったりしてしまいます。 結果的に、今回のタスクでは仕様理解に対して以下の学びを得ることができました。 適切な仕様理解はコードリーディングや実装の精度向上に役立つ 「ながらの仕様理解」には限界がある 進捗がストップするのを恐れずに積極的に時間を作るべき 違和感を持った仕様は問題ないはずと思いこまずに確認する これについては、実装のことで頭がいっぱいで確認を怠ってしまった経緯があります。 正しい仕様を理解しないと実装する必要のない箇所を実装してしまったり、考慮漏れが生まれたりしてしまう可能性があるので特に強く意識していきたいと感じた部分でした。 既存コードを読み解く リファクタリング 着手に当たり、既存コードのロジック把握にもかなり苦労しました。 当然ですが、簡単にスラスラと読めるものではありません。 機能理解が不十分なことや、処理の流れが脳に収まらないことなど原因はいくつもありますが、今振り返るとコードを読む際の取り組み方に問題があったと思います。 ここからは問題点を3点に絞ってふりかえってみたいと思います。 ①目的や仮定を持たずに一気に全体を追ってしまう 業務のコードは単に量が多いだけでなく、複雑な仕様が影響してどうしても複雑になってしまいます。楽楽販売の ソースコード も同様でした。 当初は、右も左もわからず目についた処理をとりあえず追ってしまっていました。上から下に流れるように読んでいくだけなので、修正箇所の特定にも時間はかかるし、何より脳が疲弊して効率がどんどん下がっていきます。ある日、そのモヤモヤを日報に書いてみたら先輩からこんなフィードバックが来ました。 後日実際に ペアプロ をしていただき、以下の学びを得ました。 命名 から役割が自明な関数に対して仮説を立てること 例えば、以下のようなメソッドがあります。ここで 命名 と返り値だけに注目すると、「ユーザーIDを受け取ってHogeArrayを返すんだな」ということが自明だと思います。 次に、 getHogeArrayByUserid() を呼び出す際は以下のようになると思います。 命名 から役割が推測できて返ってくる値も把握できている場合には、わざわざ getHogeArrayByUserid() の中身の処理まで追いに行く必要はなさそうです。 $hogeArray の正体に対して仮説を立てる→ デバッグ を使って検証の繰り返しでコードリーディングが進みます。 当初、目についたメソッドをただひたすら読みに行ってしまっていたので、このアド バイス を受けてコードリーディングが少し楽になったのを覚えています。 こうして書き出してみると当たり前のことだなとは思いますが、当時はコードリーディングがまったく進まないことに焦り、ヒントになりそうなロジックがないかを探そうとして本来読む必要がない箇所まで読んでしまっていたので悪循環になってしまっていました。 ②コメントに惑わされてしまう 命名 と返り値以外にも、コードリーディングの理解を助ける要素としてコメントがあります。 先輩との ペアプロ から得た学びを頼りに、ロジックを深追いしない代わりにコメントを意識して積極的に読むようにしてみました。 結論としては、「仕様理解、機能理解がままならない場合はコメントを読むとかえって混乱する場合がある」という学びを得られました。 実際に経験した例では、実態に即したコメントでなかったり、表現があいまいでなにを指しているのかがわからなかったりするケースがありました。 詳細は省きますが以下のようなコメントがありました。 コメントではターゲットのリストを取得という補足がされていますが、返り値の型がない(リリース当時は型宣言がサポートされていませんでした)ことやターゲットがさす内容がロジックを追わないとわからないことがコードリーディング時の負担になっていました。 実際には中身のロジックを追っていけば取得できるリストは確認できるのですが、仕様が複雑なためターゲットの実態をうまくつかめず不安を抱えたまま読み進めていきました。仕様理解や機能理解があれば、このメソッドの出力結果が使われる場面や使われ方が具体的に想像しやすくなるはずです。 実際には、このターゲットの正体は仕様をしっかり理解していれば特に苦労することなく理解できるものでした。仕様理解や機能理解に不安がある場合は、コメントで感じた違和感を放置せずにひとつひとつ確認していくことが大切だなと感じた場面でした。 ③効果的な作業メモを取らない 既存コードを読み解くうえで、作業メモの取り方に関しても課題感を感じていました。 「①目的や仮定を持たずに一気に全体を追ってしまう」の課題と類似しますが、作業メモも自分の思考の流れを一気に書き出しているだけだったので、読み返すのが辛いだけでなく、作業の進捗状況がわかりづらいアウトプットになってしまっていました。 当時の作業メモを見返してみると問題点は以下の2つが考えられます。 事実や気づきが入り混じっている メモを書いているときは問題ないが、あとから見返す際に信頼すべき情報かどうかの判断にリソースが使われてしまう フォーマットや粒度がそろっていない メモを追記していく際にどこに追記すればいいか迷う→フォーマットや粒度がさらに崩れてどんどん読みづらくなる メモを読み返す際にどこを読めばいいか探すのに時間がかかる 当時のメモをそのまま紹介することは難しいので、動物園に行くためのメモというテーマに変えて当時のメモを再現してみました。(GPT4o生成) このメモは極端な例ですが、 事実や気づきが入り混じっている 駐車場はあるけど使うかわからない。料金高めだった気がする(曖昧)。 この書き方は、書いている最中は思考が整理されて問題はないはずですが、量が増えれば増えるほど自分のメモに信頼が持てなくなってしまいます。気づきとして書いたつもりのメモを事実として誤読してしまうと、作業が直感的に進まずもはやメモを読まないほうがスムーズに進みます。これは実際に体験したので少し痛い目を見ました。 フォーマットや粒度がそろっていない 「目的」と「見たい動物」の見出しの粒度が明らかに揃っていません。適切な階層構造になっていないとメモを読み返す際の障害になってしまいます。メモを追記していく際にどこの階層に記述していくかの判断を誤ってしまうかもしれません。実際に当時のメモを読み返してみると階層構造が全体的に統一されていないため、読むのに時間がかかってしまいました。 せっかくメモを取ったのに、メモを見ないほうが進捗がいい気がするという本末転倒な状況になってしまったのでメモの取り方を見直してみました。 改善後のメモはこんな感じです。 まずは、どんなタスクをするとしても共通のフォーマットを使用するように変更しました。タスク単位でissueを+1していけばいいだけなので追記も楽ですし、読み返す機会も圧倒的に増えました。 メモには気づきベースのメモを書き、補足事項には事実ベースのメモを書くことで情報が一気に整理されて読みやすくなったのを実感できました。 このフォーマットが最適解と言い切れるわけではありませんが、無理なくメモを取る習慣を続けられているので今のところうまくいっている感じがします。 適切な 命名 命名 に関しても、コードリーディング時と実装時双方の場面で何度も頭を悩ませました。 *1 リーダブルコードの購読や配属後の開発演習などで、可読性の低い 命名 がもたらす弊害については認識していたつもりでしたが、修正方法や考え方について ソースコード ベースで学べたのは本当にいい経験だったと思います。ここではコードリーディング時と実装時にわけて適切な 命名 について考えたいと思います。 ①コードリーディング時 今回の既存実装は、汎用的な 命名 や抽象的な 命名 をしている箇所が多くあったため、直感的にコードリーディングをするのが厳しいなと感じる場面が多くありました。 以下のコードは一例です。 どの変数も 命名 が抽象的です。例えばこのメソッドの再利用性が高く、汎用的なロジックを提供している場合は具体的な文脈に依存しない抽象的な 命名 でも問題はないと思います。 ただ、用途が限定されたメソッドで呼び出し箇所が1か所しかないケースなどにおいてはコードリーディングの障害になってしまいそうです。 実体験ですが、中身にどんな情報が入っているかの把握や推測が難しくなってしまいました。 デバッグ で何とか情報を得ようとしましたが、クラス単位で 命名 が不適切な箇所が多くあったので全体の構造理解にはかなり苦労したことを覚えています。 不適切な 命名 の変数を放置したまま実装を進めても、コードリーディング時に無駄なリソースが割かれてしまうので リファクタリング 着手の最初のステップとして全体的に変数名を見直しました。 変数名見直しは主に以下の2点を意識しました。 1.地道に デバッグ して変数に格納されているデータの中身を確認する 格納されているデータの把握ができないと方針が立てられないため、 デバッグ を通じてデータの中身を確認していきます。変更しなくてもよさそうな変数や、不足した情報がある変数をリストアップして修正可能な箇所を洗い出しました。 2. 命名規則 を決めて粒度をそろえる 一例ですが、以下のような 命名規則 を決めました。 リストを扱う配列にはListを付けて $hogeList のような形にする 返り値がリストであることが自明であるにもかかわらず、変数名からそれが読み取れないケースが多くありました。こういったケースは 機械的 に修正する方針に決めました。 情報量を増やせそうな場合は増やす デバッグ した結果、用途が限定的かつ情報量を足せそうな変数に対しては情報量を増やす方針を決めました。メソッドの呼び出し階層が深いケースでも、呼び出し元までたどる必要がなくなるので以降のコードリーディングに大いに役立ちました。 ②実装時 新規で追加したメソッドやクラスの 命名 も、コードレビューのやり取りを通して何度も修正していきました。 PRの指摘などでいただいたものの中で今後も意識しておきたいと思ったものをまとめたいと思います。 変数名を変に省略しようとしない 変数名もそうですが変に省略しようとしない方が大体上手くいきますね。今は IDE が優秀なので長くても別に困りませんし。 下記は簡単な例ですがクラスの 命名 ではできるだけ汎用的な 命名 は避けたほうが無難という学びを改めて認識しました。 変に省略するよりも役割を適切に表現する 命名 にすることで可読性は上がる場合が多そうです。 対称性を意識する *2 書籍プリンシプルオブプログラミングの中で対称原理について触れられている箇所があり、以下のようなメリットが紹介されていました。 コードの形に対称性があると、予測がつきやすく、コード理解のスピードが速まります。また、視覚的にも美しいコードになるので、これもコードの理解のスピードを速めてくれます。 対称性を全く意識しない 命名 にすると、例えば以下のようなコード例になります。 命名 に対称性がなく、可読性が低いです。 例えば、 insert と deleteLast は 命名 の粒度がそろっていません。 insertAnimal はリスト全体に関する操作が想定されますが、 deleteLastAnimal は「最後の動物だけ」という限定的な操作が想定されます。このままだと、コードリーディング時に 命名 から直感的に処理を把握していくことが難しくなってしまいそうです。 次に、ロジックはそのままで 命名 が対称になるように修正されたコードの例を示します。 命名 の粒度をそろえ、 add ⇔ remove と open ⇔ close のようにごく一般的な対称性を意識しました。 命名 から役割も推測でき、「登録削除」「動物園の開閉」と観点が限定的になるのでコード理解のスピードが速くなります。 対称性は 命名 だけでなく設計全般にも役立ちそうな基本的な考え方なので、今後も意識しておきたいです。 まとめ 苦労の多かった リファクタリング ですが、チーム内外の様々な方の手厚いサポートのおかげもあっておよそ1か月半ほどで実装~受け入れが無事完了しました。 今回紹介したもの以外にもたくさんの学びがあったので、今後の業務に積極的に役立てていきたいと思います。 最後までご覧いただきありがとうございました。 *1 : O'Reilly Japan - リーダブルコード *2 : プリンシプル オブ プログラミング 3年目までに身につけたい 一生役立つ101の原理原則
アバター
この記事は ラクス Advent Calendar 2024 の14日目の記事(予定)です。 はじめに シンボリックリンク切替によるデプロイについて 今回の改善における無停止デプロイのスコープ 検証したこと 検証におけるゴール 検証観点 ①realpathキャッシュの動作検証 ②アプリケーションコードの動作検証 ③本番想定のアクセス下の動作検証 アプリケーションの改修内容 おわりに はじめに こんにちは、kasuke18 です。 楽楽販売のDevOpsチームメンバーとして、リリース周りなどインフラチームと協働する部分を主に担当しています。 私達のプロダクトでは、 シンボリックリンク 切替による無停止デプロイを導入しました。 導入にあたって私達のコンテキスト(特にサーバ構成などの アーキテクチャ )に合うか検証のうえ、必要なアプリケーション改修を実施しました。 その検証内容と改修ポイントをご紹介します。参考になれば幸いです。 シンボリックリンク 切替によるデプロイについて このデプロイフローの導入前は、 rsync によるファイルの直接差し替えで対応していました。 PHP のファイル読込は遅延読込されるため、タイミングによっては処理中のプロセスがエラーになってしまうケースがあります。 (例えば新しいクラスを含むコードをデプロイする際、その新ファイルの配置より先に利用個所のロジックが処理されてしまうと未定義としてエラーになる) そのため顧客影響を避けることから、リリース作業を夜遅くに実施するなど運用コストが高い状態が続いていました。 これを改善するため、各所で導入事例のある「 シンボリックリンク 切替によるデプロイ」を検討しました。 上記のようなデプロイフロー・具体的なデプロイの手順( mv コマンドで シンボリックリンク を上書きする)などは、導入事例を参考に、割とそのまま利用できる部分が多かった記憶があります。 https://developers.prtimes.jp/2022/05/31/prtimes-deploy-improvement/ とはいえ単純にデプロイ手順を変えるだけでよいのか、具体的にはアプリケーションの作り込みを変える必要があるのか、といった観点では私達のコンテキストに近しい事例があまり見つからず、導入するにあたって本当に問題ないのかを検証しました。 今回の改善における無停止デプロイのスコープ シンボリックリンク 切替という手法なので、サーバ上のアプリケーションコードの差し替え部分にのみ影響します。改善効果が期待できる範囲もサーバ上の PHP プロセスが担う処理に限定されます。 逆にフロントエンド↔バックエンド間のインタフェースの変更(リク エス トパラメータの増減)や PHP プロセス↔データベース間のインタフェースの変更(DBテーブル定義の変更)には対応できません。これらの改善には別の手段が必要になりますのでご留意ください。 検証したこと まず私達のプロダクトへ導入できないか検討するにあたって、他社の事例などを参考にさせていただきました。 例えば下記の導入事例などを見ると、realpathキャッシュというもの原因で問題が発生するリスクが説明されています。 https://www.klab.com/jp/blog/tech/2016/1062120304.html これだけを見ても単に導入すると、痛い目に遭う可能性が高そうです。 またOPcacheの利用有無やサーバソフトウェアなど、実行環境による違いによる挙動の変化もあり得るため、私達のコンテキストに合った検証が必要でした。 ちなみに今回検証を行った構成は以下のとおりです。 PHP 8.2 OPcache利用なし Apache + mod_ php 検証におけるゴール 今回の検証では、上記のような変更があったとしても「1つの PHP プロセス内では、デプロイ前・後のどちらかのコードのみが実行される」ということを担保することを目的に検証を進めました。 一番リスクなのは新旧コードが入り混じって実行されることで、その場合はなにかエラーが発生したとしてもまともに調査することができません。 新バージョンの反映に多少の遅延が生まれることは許容し、それよりも1つの PHP プロセス内での一貫性を優先しました。 検証観点 以下の3段階で検証を行い、新旧コードが入り混じって実行されることがないかを確認しました。 ①realpathキャッシュの動作検証 ②アプリケーションコードの動作検証 ③本番想定のアクセス下の動作検証 ①realpathキャッシュの動作検証 まずは私達のコンテキストにおけるrealpathキャッシュの効き方を把握するため、ミニマムなサンプルコードを作成しました。 sleep中に シンボリックリンク を切り替えてrealpathキャッシュの内容やロードされたクラスのファイルパスがどうなるかをするかを確認します。 詳細は長くなるので割愛しますが、下記のような関数を利用しました。 realpath_cache_get() : この関数の実行時点のrealpathキャッシュ内容を取得 ReflectionClass::getFileName : クラスが定義されているファイルを取得 // require_once("/app/symlink/SampleClass1.php"); // var_dump(realpath_cache_get()); ["/app/symlink/SampleClass1.php"]= > // キャッシュのキーはrequire_onceに指定したパス array(4) { ["key"]= > int(3616146466484062189) ["is_dir"]= > bool(false) ["realpath"]= > string(28) "/app/before/SampleClass1.php" // シンボリックリンク解決済のパスがキャッシュされる ["expires"]= > int(1733634941) } // var_dump((new ReflectionClass ("SampleClass1"))- > getFileName()); string(28) "/app/before/SampleClass1.php" // シンボリックリンク解決済のパスが取得できる ②アプリケーションコードの動作検証 ここでは実際のアプリケーションが実行されるパターンを洗い出し、新旧コードが混在することがないかを確認しました。 例えば私達のプロダクトでは、Webアクセス・ CLI 実行(バッチ)・メール受信からの起動 などのパターンがありました。 それぞれパターンについて、処理の何処かでsleepを仕込み、実行中に シンボリックリンク の切替を行い検証します。 ③本番想定のアクセス下の動作検証 アプリケーションコードを利用するという点では②と同じですが、検証したいポイントが異なります。 ②ではsleepを仕込んだ単発処理で、理論的上問題がないかを検証しました。 それに対して③では、アプリケーションの通常利用中に シンボリックリンク を継続的に切り替えて問題がないかを検証します。 イメージとしては同じバージョンの ディレクト リを2つ用意し、そのうち片方にメソッド・クラスのインタフェース変更を行い、従来の rsync によるデプロイではエラーになる状態を作ります。 その状態で シンボリックリンク の向き先を新旧切り替え続け、アプリケーション操作でエラーが発生しないかを確認します。 またアプリケーション操作は、リリース時に利用している動作確認ツールがありましたので、それを流し続けることで実現しました。 アプリケーションの改修内容 詳細は長くなるため、検証結果はまた別の機会にします。 特に①のサンプルコードも合わせて記載できるように頑張ります。 検証結果を踏まえて、アプリケーションの改修が必要な点は、 require に指定されるファイルパスが、すべて「 シンボリックリンク 解決済みのパス」となるようにすることです。 例えばComposerで生成されたautoloaderを利用している場合、autoloader経由で読み込まれるファイルは特別な考慮を必要としません。 autoload*.php では __DIR__ を基にパスを組み立てているため、 シンボリックリンク 切替の影響を受けません。( PHP の __DIR__ で取得できるパスは シンボリックリンク 解決済みのパス) 一方で、autoloader自体を読み込む際のパス指定には注意が必要です。 // OK例 require_once(__DIR__ . "/vendor/autoload.php"); // NG例 require_once("/path/to/project/vendor/autoload.php"); require_once(ini_get("user_dir") . "/vendor/autoload.php"); 上記NG例のように シンボリックリンク を含むパスになっていると、その処理タイミングで シンボリックリンク 切替が重なると、新旧入り混じった状態でアプリケーションが動いてしまう可能性があります。 特にWebアクセスにおいては、realpathキャッシュがリク エス トをまたいで共有されるケースがある(処理された Apache のプロセスが同じ場合は共有される)ため、分かりづらい形でエラーになってしまうことがあります。 おわりに 私達のプロダクトで「 シンボリックリンク 切替によるデプロイ」を導入したときの検証内容・アプリケーション改修内容をご紹介しました。 realpathキャッシュの挙動調査や Apache のプロセスとの関連など、記事のボリュームの都合で書ききれなかった部分については、今後別記事で詳しくご紹介します。
アバター
こんにちは、あるいはこんばんは。楽楽販売の開発をやっている @taclose です☆ ISUCONに参加するのはこれで2回目ですが、 今回は7位でした! ISUCON14 TOP30 微妙!とか言わないで!頑張った方ですよ!運が良かった方ですよ!(と言いたい!) 今日はそんなISUCON14がどんな感じだったのかを振り返っていこうと思います! 記事の概要・想定読者 ISUCONの準備 前回の反省からはじまる 前回の反省点 練習はISUNARABE! ISUCON当日 ISUCONの初動:初回ベンチマークまでにやる事! 初回後の次の一手:DBのINDEX見直し 各自が怪しいポイントを重点的に攻める 4時以降:サーバ構成を真剣に考える 最後のチューニング 最終結果 今回のISUCONの振り返り 良かった点 反省点 最後に 記事の概要・想定読者 想定読者 今年ISUCON出たけど、悔しい思いをした方 来年ISUCON出てみようかな?と思う方 記事の概要 ISUCON14の細かいチューニングポイントはあまり取り上げません。 Git管理や Makefile の準備やノウハウ集ではありません。 ISUCONに挑戦する準備や練習、当日の困りポイントをまとめた体験談です。 では早速、当日までの準備から振り返ってみましょうっ ISUCONの準備 前回の反省からはじまる 11月ごろ行われた作戦会議では以下の話が出ました。 前回の敗因はずばり準備不足、練習不足!以下のような社内ハンズオンを開催するぐらいの知識量はあったものの、 tech-blog.rakus.co.jp 当日は8時間という限られた時間で実践するのは至難の業でした。 前回の反省点 サーバ構成変更時のコマンド準備が不十分だった( Makefile に ベンチマーク 前のコマンドを準備するなど)。 サーバ構成変更時にネットワーク設定でアタフタして0点になりかけた。 そこで今回は事前準備とサーバ構成変更の練習に重きを置くことにしました。 練習はISUNARABE! ISUNARABE を使い、本番さながらの練習をしました。 特に課題だった AWS での3台サーバの使い方や初動の役割分担を重点的に練習しました。前回に比べると少ない練習時間でしたが、効果はありました。 ISUCON当日 ISUCONの初動:初回 ベンチマーク までにやる事! 私たちのチームでは以下の作業を分担して進めました。 configやアプリをGit管理下に置く makeコマンドで設定ファイルやアプリをデプロイ MySQL やnginxの初期設定(slow queryのログ出力など) 作業が終わったら初回 ベンチマーク を実行。 10時半時点でスコア1000点でした。 初回後の 次の一手 :DBのINDEX見直し nginxログ解析で遅い API は分かりますが、まずはDBのINDEXを整備するのが効率的。 pt-query-digestで解析し、30分~1時間で対応しました。この時点でスコアは3800点。 各自が怪しいポイントを重点的に攻める 我がチームは 全員遊撃部隊 ! nginxログやプロファイラー結果を見て処理時間が長い箇所を探る 怪しい箇所を自由に攻める 13時半でスコア8000点、15時に15000点、16時に18000点まで到達しました。 4時以降:サーバ構成を真剣に考える 3台サーバの使い方を再検討。 1台をnginx+アプリ用、もう1台をDB用に割り当て 16時半でスコア26000点。 最後のチューニング サーバ構成変更に伴い、nginxや MySQL の設定値を見直し。 17時半でスコア37000点。 最 終結 果 39957点 今回のISUCONの振り返り 良かった点 チームメイトに恵まれたこと。感謝感謝です!お疲れ様でした! 反省点 N+1問題への対処が不十分だったこと。 抜本的な解決策を思いついても時間内では躊躇してしまい、キャッシュで逃げてしまった。 場数を踏めばこういった勘所も磨かれると思うので、次回再挑戦したいと思います! 最後に これからISUCON始める人、始めたばかりの人に参考になれば幸いです! もしこれからISUCONはじめるぞ!という方は 目指せISUCON!!社内WEBパフォーマンス改善ハンズオンのすすめ - RAKUS Developers Blog | ラクス エンジニアブログ でも紹介している書籍を読まれたり、ローカルでパフォーマンスチューニングを練習してからやる事をお勧めします! ランク外で終わっても非常に学びが多く楽しいイベントなので、是非みなさんトライしてみてください!!
アバター
こんにちは。 株式会社 ラク スで先行技術検証をしたり、ビジネス部門向けに技術情報を提供する取り組みを行っている「技術推進課」という部署に所属している鈴木( @moomooya )です。 ラク スの開発部ではこれまで社内で利用していなかった技術要素を自社の開発に適合するか検証し、ビジネス要求に対して迅速に応えられるようにそなえる 「技術推進プロジェクト」 というプロジェクトがあります。 このプロジェクトで過去に検証した「継続的アプリケーションセキュリティ」について共有しようかと思います。 課題の経緯、前提条件 課題の経緯 期待する導入成果 シフトレフトや、脆弱性診断ツールに関する概念 前提条件 実現手法 SASTとIASTの特徴 共通の特徴 SAST IAST SAST/IASTを導入したらDASTが不要になるか どこまでコストをかけられるか ツール所感 → 今後に向けて SAST/IASTツールの製品傾向 SCMサービスに付随するサービス GitHub Code Scanner 調査の中で分かった導入に向けたハードル 最後に 課題の経緯、前提条件 課題の経緯 脆弱性 を含めた不具合の発生タイミングについては、発生するタイミングが開発工程の後であればあるほど、開発全体への影響が大きいです。しかしながら特別な対策を行わなければ開発工程の後半ほど不具合が見つかることが多いというのが現実ではないでしょうか。 それはプログラムが動く状態になってからの方がテストしやすいということが最大の理由かと思いますが、テストのタイミングを開発工程のなるべく早い段階、すなわち不具合発生の原因が生まれるタイミングに移す(=シフトレフト、開発工程を左から右に流れるとイメージして前工程である左側に移すこと)方法が考えられ生み出されています。 (出典: IPA 『 セキュリティ・バイ・デザイン 導⼊指南書 』 p.36) ラク スでもそれほど多いわけではありませんが、リリース前に不具合が見つかることがあり課題感がありました。 本調査ではテストのシフトレフトについて、有効そうな手法の探索と導入可否の検討を行いました。 なお、上で図を引用した IPA から発行されている『セキュリティ・ バイ・デザイン 導⼊指南書』は、このあたりの考え方がまとまっている資料なのでぜひ参考にしてみてください。 www.ipa.go.jp 期待する導入成果 期待する導入効果としては、 開発プロセス 全体で不具合対応にかかっているコストを現状よりも小さくすることです。そのために従来開発工程の後半で見つかっていた不具合を少しでも早い段階で検知できる仕組みを期待しています。 ただし、導入することで今までよりも手間がかかるようでは本末転倒 1 なので、導入後にセキュリティのシフトレフトによるコスト増(ツール費用など)を含めて現状よりも小さくなることが大前提となります。 ※なお実際の不具合対応コストはこんなに割合多くないです🙂 シフトレフトや、 脆弱性 診断ツールに関する概念 CAS: Continuous Application Security 継続的アプリケーションセキュリティ CIに組み込むなどして日常的に意識することなくセキュリティ向上策を実施するプ ラク ティス SAST: Static Application Security Testing 実装中に適用 設計にも適用できるという話もあるが現実的には実装中での適用になると思われる コード診断を行い、Linterのように動作する 製品としては SonarQube があったり、 GitHub やGitLabにも組み込まれている IAST: Interactive Application Security Testing E2Eテスト中に適用 操作と並行して検査する 製品としては Seeker , Contrast Security など DAST: Dynamic Application Security Testing リリース前に適用 DASTはすでに ラク スでも導入していたため今回の調査からは除外 SCA: Software Composition Analysis 利用しているライブラリの既知の 脆弱性 を検出できる SASTツールの機能として組み込まれている場合がある RASP: Runtime Application Self-Protection アプリケーション実行中に異常を検知する仕組み 本稿では直接扱わないが参考として 前提条件 現在の ラク スでは実装工程として 単体テスト 、その後 結合テスト 工程に 脆弱性 診断ツールを用いてのテスト(DAST)を行なっています。 DASTツールの見直しについては別途定期的に取り組んでいるため、シフトレフトするとなると検討すべきはSAST, IASTツールになります。今回はSAST, IASTツールの調査と ラク スで導入しやすそうなツールの検討を行なっていきます。 また、調査当時は ソースコード 管理にGitLab CEを利用しており、 クラウド 版 GitHub Enterpriseへの移行を検討していたこともあり、GitLab, GitHub に依存したGitLab SAST, GitHub Code Scannerについては調査対象外にしていました。 実現手法 SASTとIASTの特徴 共通の特徴 ホワイトボックステスト 対応できるのはメジャー言語に限られる しかし長期的にメンテナンスする想定のサービスで採用されるような言語は対応しているので実用上は問題なさそう コード外由来の 脆弱性 には対応できない ソースコード を対象に捜査するため仕方がない SAST 誤検知が多くなりがち フルスキャン可能で カバレッジ を100%にしやすい IAST 誤検知は少ない 機能操作に伴って カバレッジ が高まるため、 カバレッジ を高めるのは大変 カバレッジ 100%を目指すためには、すべての処理分岐を網羅するようなアプリケーション操作が必要 SAST/IASTを導入したらDASTが不要になるか 結論から言えば、SAST/IASTを導入してもDASTは不要にならなず、現状DASTツールにかけているコストはなくなりません。 なぜならば、DASTで出来ずSAST/IASTで対応可能な観点のテストがある一方で、SAST/IASTで出来ずDASTでしか出来ない観点のテストもあるためです。 例えばSAST/IASTでは ソースコード をフルスキャンしてのテストが可能だが、DASTで行うような外部ライブラリや複数のモジュールを結合してのテストは行うことが出来ません。 既存のDASTで無理してカバーしているような部分がある場合(例えば細かな条件違いの正常系経路の網羅など)は該当箇所をSASTおよびIASTに適切に移行することで、トータルのメンテナンスコストは削減できる可能性はあると思います。 どこまでコストをかけられるか 今回の調査を始めるにあたって少し甘かった部分ですが、調査中盤で改めて 社内で開発工程後半で不具合が発覚することによる手戻りの 工数 がどの程度か確認したところ、想定以上に開発品質が良く手戻りが多いサービスでも「2, 3人日程度」ということがわかりました。この時点で高額なツールの導入は割に合いません。むしろ「まだ導入する必要がない」という選択が現実的だと判断できるボリュームです。 セキュリティ関連のツールは総じて高額な事が多く、今回調査したツール類も有償ツールはすべて導入のコストメリットは見いだせませんでした。一方でCommunity Editionなどの無償でのツール提供が行われているものだと機能面で弱く、これも導入メリットが小さいと判断されました。 ツール所感 → 今後に向けて さて、有償無償問わずツール導入のメリットが薄くなってしまいましたが、こういった状況だとSAST/IASTツールの導入はかなり難しいです。 SAST/IASTツールの製品傾向 無償ツールとして利用できるものは機能制限が多い 自前運用しようとするとCI環境のマシンリソースを確保する問題が発生する 常時稼働するわけではないが、少ないと待ち時間が開発速度の悪化に直結する 多くのツールは クラウド サービスとして展開されている テスト環境での操作情報を外部に送信しなければならない セキュリティが厳しい会社だとこの部分で導入は厳しそう 2 予算が潤沢であれば、機能面でContrast Securityが良さそうに見えました www.contrastsecurity.com SCMサービスに付随するサービス GitHub Code Scanner 調査当時もCode Scanningは存在していましたが、当時弊社の ソースコード 管理はGitLab CEを利用していたため候補から外していました。またGitLab SASTも GitHub 移行の検討が始まっていた頃だったため、除外していました。 現在はほぼすべての ソースコード を クラウド 版 GitHub Enterpriseに移しているのでCode Scanningも候補に入ってきます。 課題として挙げたCI環境のリソースもCode Scanningであれば、(多少のお金の力があれば)なんとかなります。 と思ったら……Code Scannerの利用はプライベー トリポジ トリ対象だと、 GitHub Advanced Securityライセンスが必要で、アクティブコミッター1人あたり月額$49が追加で必要になるので弊社のようにそれなりの規模のチームで開発している場合に導入するのはハードルが高かった…… 3 。 docs.github.com 調査の中で分かった導入に向けたハードル 既存 ソースコード で検出される項目の扱い 導入時が一番多く検出される 検出されたから修正、とすると「今」必要ではない修正が多発する 導入直後に検出された不具合の扱いは事前に定めておかないと混乱のもと 現状の品質に対して期待できるメリットのコスト対比がまだ低い 以下のいずれかの状況にならない限り導入メリットが薄い 今後 ウェブサービス 開発が複雑化して不具合対応コストが増加 今後 ウェブサービス 開発速度が向上して不具合対応コストが ボトルネック となる SAST/IASTツールが コモディティ化 して導入コストが減少 最後に セキュリティのシフトレフト自体は 開発プロセス における理想であることに違いはないと考えています。 ただし商用サービスを提供する立場としては実現するためにはもう少し普及して コモディティ化 (に伴う低価格化)してくれないと導入は正直厳しいというのが現時点での判断です。 一方でライブラリなどを オープンソース で公開する場合など、不具合の影響が広範囲に波及するようなプロダクトに関していえば、 GitHub のパブリック リポジトリ でのCode Scanningが無料で利用できるため積極的に使っていくべきだと感じました。 いわゆるオーバーエンジニアリングになってコスト増を招いてしまうことは、ビジネスとしてサービス開発を行っている以上は避けるべき。 ↩ そしてセキュリティのシフトレフトに興味を持つ会社は比較的セキュリティ意識が高いはず……。 ↩ パブリック リポジトリ であれば無料で導入できるようなので、公開しているライブラリなどにはぜひ導入したいですね。 ↩
アバター
こんにちは、あるいはこんばんは。 私は楽楽販売の案件開発チーム(新機能開発チーム)の開発リーダーをしている @taclose です☆ 今回のブログでは、楽楽販売の案件開発チームの構成や業務を紹介しようと思います! 累計導入社数4,400社突破 の楽楽販売がどのような開発体制で、普段どんな事に取り組んでいるのかを知ってもらえればと思います! 楽楽販売の開発チーム構成 チームの紹介 チームのミッションは何か? 普段具体的にはどんな事をやっているのか?どんな事を考えているのか? 最近行った業務改善とは具体的にどんなものがあるのか? 今後の展望 さいごに まずは各チームの業務分担から説明します。 楽楽販売の開発チーム構成 チーム名 業務 DevOpsチーム 運用・保守、リリース作業等を行うチーム SAチーム カスタマーサポートが対応出来ない技術的な問い合わせに対応するチーム VCチーム ベトナム 開発拠点とのオフショア開発をするチーム 案件開発チーム 新機能の開発を行うチーム 楽楽販売開発課のチームは業務毎に大きく分けてこのように分かれています。 私たち案件開発チームはメインとなる開発以外とも言える運用・保守・サポート・定例作業等から独立しており、 メインとなる業務に集中する事ができます! 他のチームの方々に感謝ですね! そのおかげで、リソース調整も非常にやりやすく 計画的に業務を進める事ができています 。 私は 開発に集中できて ワークライフバランス が保ちやすいこのチーム が一番気に入っていますっ! では、もっと具体的にチームの紹介をしていきましょう。 チームの紹介 楽楽販売は既に多くの機能が存在していますが、法改正の影響や顧客のニーズに合わせて常に新しい機能が要望されています。 そんな機能追加を概要設計〜実装/テストまで行うのが案件開発チームになります。 メンバーは8名。だいたい常に2案件の機能開発が同時進行で開発が進められており、新機能開発以外にもバグ改修、 リファクタリング なんかも行っています。 構成としてはこんな感じでしょうか 役割 業務内容 AM(1名) アシスタントマネージャー。メインはリソース調整やスケジュールの調整。課長と開発メンバーの間に入って活躍してくれます。概要設計とかもやってます。 SP(2名) 開発リーダー的存在。コードレビューや設計方針、開発フローの見直し等をメインに行う。直接コーディングする機会は少ないです。 開発メンバー(5名) 実装の要。詳細設計、実装、テストを行う主力部隊です。 一覧にすると上のような立ち回りですが、SPと開発メンバーの垣根はそこまで厳密ではありません。 私としては、「 スケジュールとかリソースとかの調整毎から解放されてメイン業務に集中できる 」これがまたうれしいポイントですね! AMお疲れ様です!! とは言いつつも表面的な事を話しても余りイメージがわかないかもしれないですね! FAQ的な形で魅力を紹介していこうかと思いますっ! チームのミッションは何か? 一つは言わずもがなですが、顧客が求める機能を迅速に実装する事です。 もう一つは 内部品質の向上というのも手が抜けないミッション となっています。 楽楽販売は15年以上開発が続けられている長い商材であるため、内部品質としては問題がある実装というのが存在しています。 紆余曲折とした経緯があるのはエンジニアであれば容易に想像がつくでしょう・・・ でも、楽楽販売がすごいなと思うのは、「 リファクタリング 」「 アーキテクチャ 見直し 」といったような、 内部品質の改善を行う事も機能開発の一環として実施している事 です。 これの何がすごいのか? エンジニアならわかるかもしれませんが、案外ここにコストを割く事を経営側は良しとしないからです。 でも 楽楽販売は違います!今後の更なる成長のためには必要な事なら リファクタリング だって実施します! デメリットとして バグを出すかもしれない(何か機能がグレードアップするわけでもないのに) コストがかかる(何か機能がグレードアップするわけでもないのに) こんな事があるのはわかった上でミッションコンプリートを目指します。 では、「具体的にはどんな事をやっているのか?」を話していこうと思います。 普段具体的にはどんな事をやっているのか?どんな事を考えているのか? ざっくり言うなら、新機能の開発に対して 概要設計を作成する 実装する テストする 当然これが主な業務になります。60%〜80%の時間はこれに使ってます。1週間単位で見るなら 1週間のスケジュールでどこまで開発が進められるか話す 主業務を1週間やる 目標通り進んだか? KPT (Keep, Probrem, Try)で振り返る ここがよかった!今後これはルール化しよう! これは改善しないとまずいよ! こんな感じですね!じゃ 残りの20%ぐらいは何をやっているのか? 「 KPT で出たTryや、積み残されていた業務改善をしています」 最近行った業務改善とは具体的にどんなものがあるのか? 例えばですが、最近は プルリク エス トの粒度を小さくしよう メトリクスを計測して複雑度を意識しよう 機密情報が リポジトリ にpushされるのを事前に防ごう XXX(色々)を自動化しよう リファクタリング を提案しよう とかでしょうかっ!あくまで読者に伝わりやすい例です。他にも色々やってますよ! 今後の展望 これからもまだまだ楽楽販売は進化を続けます 。そのため、 内部品質の改善 というのは重要なテーマとなっています。 案件開発チームは新機能を開発出来る土台作りも担っているわけです。 今後としては 新機能の迅速なリリースが行える体制作り 新機能を不具合なくリリースできる内部品質の更なる向上 これを推し進めていきたいと思います。 さいごに 楽楽販売の案件開発チームについて紹介させてもらいました。 チームが一丸となり ラク スが掲げる リーダーシッププリンシプル を念頭に置きながら、常に発生する課題(理想と現実のギャップ)とどう向き合っていくべきかを考え、「限られたリソースの中で今は何をどのようにするべきか?」を考え、それを行動にうつしているすぱらしいチームです。 時にユーモア、時にエレガント、興味があれば是非楽楽販売の開発チームに来てくださいね!
アバター
はじめに ltreeとは ltree型 ltreeの操作 活用法 1. 承認フローの構築 事前準備 テーブル作成 データ追加 2. テーブルに細かくアクセス制御をかける 事前準備 ltreeの有効化 テーブル作成 ポリシー作成 行セキュリティポリシーの有効化 ポリシーの設定 データを追加 ユーザー作成 試す まとめ はじめに こんにちは! エンジニア2年目のTKDSです! 今回はltreeについて調べ、その活用法を考えてみました。 ltreeについて、ltreeの活用法の2段構成です。 ltreeとは 階層ツリー構造を模した構造を格納する機能を提供する 拡張機能 です。 詳しくは ドキュメント をみてください。 ltree型 階層ツリー構造を表す型です。 例)`Company.Department.Team1 ドット区切りで大文字小文字は区別しないようです。 各データはラベルと呼びます(上記でのCompany、Department、Team)。 ltreeの操作 よく使いそうな操作だけ2つ挙げます。 ltree @> ltree : 左辺の引数が右辺の親要素(か同じ)かどうか 例) SELECT name FROM organization WHERE path @> 'Top.IT.Software'; テーブルのpathがTop.IT.Softwareの親に該当する要素をすべて探します。 ltree <@ ltree : 左辺の引数が右辺の子要素(か同じ)かどうか 例) SELECT name FROM organization WHERE path <@ 'Top.IT'; テーブルのpathがTop.ITを親に持つ要素を探します。 このSELECT文では、Top.ITを親に持つ要素をすべて探します。 ~ :一致するパスの検索 右辺に指定したパスに一致するltreeを探します。 SELECT name FROM organization WHERE path ~ ' Top.IT.* ' ; SELECT name FROM organization WHERE path ~ ' Top.I*.* ' ; 活用法 2種類ほど活用例を考えてみました。 1. 承認フローの構築 ltreeを使って、承認フローに使えるテーブルを構築してみます。 事前準備 compose. yaml ces : db : image : postgres:16.4-bullseye container_name : db environment : POSTGRES_USER : postgres POSTGRES_DB : postgres POSTGRES_PASSWORD : postgres ports : - "127.0.0.1:5432:5432" volumes : - db_data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck : test : [ "CMD-SHELL" , "pg_isready -U postgres -d postgres" ] interval : 30s timeout : 10s retries : 5 start_period : 10s volumes : db_data : init. sql -- ltree拡張の有効化 CREATE EXTENSION IF NOT EXISTS ltree; テーブル作成 CREATE TABLE approval_flow ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, flow_path ltree UNIQUE , -- 階層構造を表現するためのltreeカラム status text, -- 各承認ステップのステータスを格納 approver text -- 承認者(ユーザーIDや名前) ); データ追加 INSERT INTO approval_flow (flow_path, status, approver) VALUES ( ' 1.approver.userA ' , ' open ' , ' userA ' ), ( ' 2.approver.userB ' , ' pending ' , ' userB ' ), ( ' 2.approver.userC ' , ' pending ' , ' userC ' ), ( ' 3.approver.userD ' , ' closed ' , ' userD ' ); ここまででデータ投入までできました。 では、2の承認者でpendingのひとを検索してみましょう。 SELECT approver FROM approval_flow WHERE flow_path ~ ' 2.approver.* ' AND status = ' pending ' ; userCを approved にしてみましょう。 UPDATE approval_flow SET status = ' approved ' WHERE flow_path = ' 2.approver.userC ' ; 1ユーザー検索結果から消えてるのがわかります。 階層構造を表現できるので、1列で承認が必要な人を管理できて便利です。 2. テーブルに細かくアクセス制御をかける 次に活用例2です。 もともと考えてたのはこの使い方でした。 スキーマ よりも細かく、グループを区切ってアクセス制御できないかなと考えたことがありました。 そのときに、調べてみつけたのが階層構造を扱うltreeでした。 この活用方法では、ltreeでグループを作り、 Row Level Security (行セキュリティポリシー) の有効化で参照単位を制限してアクセス制御を実現します。 テーブルの関連のイメージは以下の図です。 基本的にすべてのテーブルをテナントIDで紐付けて、事故ってテナント外のデータを参照しないようにしました。 では、実際に環境を構築してきます。 事前準備 1のときと同じです。 docker compose down -v、docker compose up で環境作り直しておきます。  docker exec -it db psql -U postgres でログインします。 ltreeの有効化 -- ltree拡張の有効化 CREATE EXTENSION IF NOT EXISTS ltree; ついでにtest スキーマ を作って参照するようにしておきます。 ltreeの設定はpublic スキーマ にインストールされるようなので、publicも追加しておきます。 publicを含めないとltreeが参照できず、ltree型がないエラーになります。 CREATE SCHEMA test; set search_path to test, public ; テーブル作成 tenantテーブル CREATE TABLE test.tenants ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT NOT NULL ); usersテーブル CREATE TABLE test.users ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, username TEXT NOT NULL , email TEXT NOT NULL , tenant_id BIGINT NOT NULL , CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES test.tenants(id) ); rolesテーブル ロールの名前を設定するテーブル CREATE TABLE test.roles ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT NOT NULL , tenant_id BIGINT NOT NULL , CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES test.tenants(id), CONSTRAINT unique_role_name_per_tenant UNIQUE (name, tenant_id) ); role_permissionsテーブル:ロールに紐づく権限を設定するテーブル ltreeでアクセス制限を設定します。 CREATE TABLE test.role_permissions ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, role_id BIGINT NOT NULL , access_ltree LTREE NOT NULL , tenant_id BIGINT NOT NULL , CONSTRAINT fk_role FOREIGN KEY (role_id) REFERENCES test.roles(id), CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES test.tenants(id) ); user_rolesテーブル:ユーザーとロールの紐付けを管理するテーブル CREATE TABLE test.user_roles ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id BIGINT NOT NULL , role_name TEXT NOT NULL , tenant_id BIGINT NOT NULL , CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES test.users(id), CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES test.tenants(id) ); documentsテーブル:アクセス制限するデータを投入するテーブル CREATE TABLE test.documents ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, title TEXT NOT NULL , content TEXT NOT NULL , tenant_id BIGINT NOT NULL , access_ltree LTREE NOT NULL , CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES test.tenants(id) ); すべて作成したらテーブル一覧を確認してみます \dt test.* ポリシー作成 アクセス制御を実現するためのルールを作っていきます。 行 セキュリティポリシー の有効化 ALTER TABLE test.documents ENABLE ROW LEVEL SECURITY; ALTER TABLE test.documents FORCE ROW LEVEL SECURITY; 有効になっているか確認します。 SELECT relrowsecurity, relforcerowsecurity FROM pg_class WHERE relname = ' documents ' ; ポリシーの設定 詳しくは ドキュメント をみてください CREATE POLICY tenant_rbac_ltree_policy ON test.documents USING ( EXISTS ( SELECT 1 FROM test.user_roles ur JOIN test.roles r ON ur.role_name = r.name AND ur.tenant_id = r.tenant_id JOIN test.role_permissions rp ON r.id = rp.role_id WHERE ur.user_id = current_setting( ' session.authorization_user_id ' )::BIGINT AND rp.tenant_id = test.documents.tenant_id AND rp.access_ltree @> test.documents.access_ltree ) ); CREATE POLICY tenant_rbac_ltree_policy_insert_update ON test.documents WITH CHECK ( EXISTS ( SELECT 1 FROM test.user_roles ur JOIN test.roles r ON ur.role_name = r.name AND ur.tenant_id = r.tenant_id JOIN test.role_permissions rp ON r.id = rp.role_id WHERE ur.user_id = current_setting( ' session.authorization_user_id ' )::BIGINT AND rp.tenant_id = test.documents.tenant_id AND rp.access_ltree @> test.documents.access_ltree ) ); session.authorization_user_idを設定することで実際のアクセス制御を実現します。 データを追加 テナントデータの投入 INSERT INTO tenants (name) VALUES ( ' Tenant A ' ); INSERT INTO tenants (name) VALUES ( ' Tenant B ' ); ロールデータの投入 -- Tenant Aのロール INSERT INTO roles (name, tenant_id) VALUES ( ' Admin ' , 1 ); INSERT INTO roles (name, tenant_id) VALUES ( ' User ' , 1 ); -- Tenant Bのロール INSERT INTO roles (name, tenant_id) VALUES ( ' Admin ' , 2 ); INSERT INTO roles (name, tenant_id) VALUES ( ' User ' , 2 ); ロール権限の投入 -- Tenant AのAdminロールの権限 (Sales部門全体にアクセス可能) INSERT INTO role_permissions (role_id, access_ltree, tenant_id) VALUES ( 1 , ' TenantA.Sales ' , 1 ); -- Admin role for Tenant A (Sales部門全体) -- Tenant AのUserロールの権限 (Sales部門のTeam 2のみアクセス可能) INSERT INTO role_permissions (role_id, access_ltree, tenant_id) VALUES ( 2 , ' TenantA.Sales.Team2 ' , 1 ); -- User role for Tenant A (Team 2のみ) -- Tenant BのAdminロールの権限 (Marketing部門全体にアクセス可能) INSERT INTO role_permissions (role_id, access_ltree, tenant_id) VALUES ( 3 , ' TenantB.Marketing ' , 2 ); -- Admin role for Tenant B (Marketing部門全体) -- Tenant BのUserロールの権限 (Marketing部門のTeam 2のみアクセス可能) INSERT INTO role_permissions (role_id, access_ltree, tenant_id) VALUES ( 4 , ' TenantB.Marketing.Team2 ' , 2 ); -- User role for Tenant B (Team 2のみ) ユーザーデータの投入 -- Tenant Aのユーザー INSERT INTO users (username, email, tenant_id) VALUES ( ' user_a1 ' , ' user_a1@example.com ' , 1 ); INSERT INTO users (username, email, tenant_id) VALUES ( ' user_a2 ' , ' user_a2@example.com ' , 1 ); -- Tenant Bのユーザー INSERT INTO users (username, email, tenant_id) VALUES ( ' user_b1 ' , ' user_b1@example.com ' , 2 ); INSERT INTO users (username, email, tenant_id) VALUES ( ' user_b2 ' , ' user_b2@example.com ' , 2 ); ユーザーとロールの紐付け -- Tenant Aのユーザーにロールを割り当て INSERT INTO user_roles (user_id, role_name, tenant_id) VALUES ( 1 , ' Admin ' , 1 ); -- user_a1 is Admin in Tenant A INSERT INTO user_roles (user_id, role_name, tenant_id) VALUES ( 2 , ' User ' , 1 ); -- user_a2 is User in Tenant A -- Tenant Bのユーザーにロールを割り当て INSERT INTO user_roles (user_id, role_name, tenant_id) VALUES ( 3 , ' Admin ' , 2 ); -- user_b1 is Admin in Tenant B INSERT INTO user_roles (user_id, role_name, tenant_id) VALUES ( 4 , ' User ' , 2 ); -- user_b2 is User in Tenant B ドキュメントデータの投入 -- Tenant Aのドキュメント (会社=Tenant A, 部門=Sales, チーム=Team 1) INSERT INTO documents (title, content, tenant_id, access_ltree) VALUES ( ' Document A1 ' , ' Content of Document A1 ' , 1 , ' TenantA.Sales.Team1 ' ); INSERT INTO documents (title, content, tenant_id, access_ltree) VALUES ( ' Document A2 ' , ' Content of Document A2 ' , 1 , ' TenantA.Sales.Team2 ' ); -- Tenant Bのドキュメント (会社=Tenant B, 部門=Marketing, チーム=Team 1) INSERT INTO documents (title, content, tenant_id, access_ltree) VALUES ( ' Document B1 ' , ' Content of Document B1 ' , 2 , ' TenantB.Marketing.Team1 ' ); INSERT INTO documents (title, content, tenant_id, access_ltree) VALUES ( ' Document B2 ' , ' Content of Document B2 ' , 2 , ' TenantB.Marketing.Team2 ' ); ユーザー作成 postgresユーザーのままだとすべてのレコードがみえてしまうので、別のユーザーを作成します。 CREATE USER access_user WITH PASSWORD ' password ' ; -- スキーマへのアクセス権限(USAGE)を付与 GRANT USAGE ON SCHEMA test TO access_user; -- テーブルに対するCRUD操作権限を付与 GRANT INSERT, SELECT, UPDATE, DELETE ON ALL TABLES IN SCHEMA test TO access_user; 試す 最初に全データをpostgresユーザーでみておきましょう。 各記事に access _ltreeが設定されているのがわかります。 この権限通りにアクセス制限できているか確認していきます。 ログインします。 docker exec -it db psql -U access_user -d postgres どの権限でアクセスするかは、 session.authorization_user_id で決められます。 まずは設定なしでやってみます。 権限がないのでみれません。 次はuser_id=1でやってみます。 権限はTenantAのSales部門全体にアクセス可能です。 次はuser_id=2でやってみます。 権限はTenantAのSales部門のTeam 2のみアクセス可能です。 次はuser_id=3でやってみます。 権限はTenantBのMarketing部門全体にアクセス可能です。 次はuser_id=4でやってみます。 権限はTenantBのMarketing部門のTeam 2のみアクセス可能です。 次はuser_id=5でやってみます。 紐付けられるユーザーはいません。 しっかり操作権限が絞られているのが確認できました! まとめ ltreeについて紹介と活用方法を考えてみました。 階層構造を簡単に扱えて、パターンマッチで検索もできるので非常に便利です。 記事をみてくださったかたもぜひ活用方法を考えてみてください!(社内のかたはこっそり教えていただけるとありがたいです) ここまで読んでいただきありがとうございました!
アバター
こんにちは、あるいはこんばんは。 だいたいサーバサイドのエンジニアの( @taclose )です☆ GitHub Copilotが活躍している昨今、弊社では GitHub で更に開発効率を良くしていこうという流れで日々自動化が行われております。 今回はそんな時代だからこそ求められている GitHub Actionsについて、初心者向けにワークフロー作成の際に知っておきたいコツと教訓について紹介します。 GitHub Actionsのワークフローを読めるけど、まだ自信がないという方はぜひ参考にしてください! 「それもっと早く知っておきたかった!」「初心者がつまづきがちなポイント!」を解説します! 読者ターゲット ワークフロー作成のコツ 1. run セクションで式 ${{}} は極力使わない 危険その1:コードインジェクションのリスク 危険その2:データのサニタイズ不足 2. workflow_call を使う際の注意点 注意点その1:github コンテキストは引き継がれる 注意点その2:secrets の扱い 3. GITHUB_TOKEN で行った操作は再度トリガーされない 補足: workflow_run トリガーを活用する まとめ 参考記事・お勧めサイト 読者ターゲット GitHub アクションなんとなく書いてるけど、 最適な書き方が出来ているか自信がない 方とか! ワークフロー作成のコツ 1. run セクションで式 ${{}} は極力使わない 例: jobs : example-job : runs-on : ubuntu-latest steps : - run : echo "Hello, ${{ github.event.inputs.name }}!" このように、 run セクションで ${{ }} 式を使われるのはよく見かけますが、実は避けるべきです! 理由は主に2つあります。 危険その1:コードインジェクションのリスク ${{ github.event.inputs.name }} などを直接 run に渡すと、もしその値が悪意のあるものであった場合、 意図しないコマンドが実行される危険 があります。 例えば、もしシークレットの中に rm -rf / なんてあったら…ッ!! 危険その2:データの サニタイズ 不足 データを サニタイズ (無害化)せずに渡すことで、特定の記号や文字列が予期しない動作を引き起こすことがあります。 GitHub Actions内で式を使う場合は、 env 変数や他の手段を使ってデータの安全性を確保しましょう。 良い例: jobs : example-job : runs-on : ubuntu-latest steps : # "${NAME}" このダブルクォーテーションで囲んでいるのがミソ!何を代入されてもこけません! - run : echo "Hello, ${NAME}!" env : NAME : ${{ github.event.inputs.name }} このように、式を直接 run に渡さず、 環境変数 ( env ) に渡してから使うと、 "(ダブルクォーテーション) が混じっているかも?とかの考慮が要らず安全です。 2. workflow_call を使う際の注意点 workflow_call を使うと、他のワークフローを再利用できて便利 です。 時には リポジトリ を跨いで共通のワークフローなんて事もやりたいですよね。 しかし、いくつか気を付けるべき点があります。 注意点その1: github コンテキストは引き継がれる 呼び出し元のワークフローで使用していた github コンテキストは、そのまま呼び出し先にも引き継がれます。 例えば、 github.event や github.actor などは呼び出し元のものが反映されるため、何か 特定のイベントやユーザーで動作させたい場合には注意が必要 です。 例: on : workflow_call : inputs : name : required : true type : string jobs : example-job : runs-on : ubuntu-latest steps : # ここで出力されるevent_nameは呼び出し元になります! - run : echo "Running as ${{ github.actor }} for event ${{ github.event_name }}" 注意点その2: secrets の扱い workflow_call では、 secrets が自動で渡されるわけではなく、呼び出し側で明示的に渡す必要があります 。 忘れがちなポイントなので注意しましょう! 例: # workflow_callを呼び出してる側の実装 jobs : call-reusable-workflow : uses : ./.github/workflows/reusable-workflow.yml secrets : # secretsはこのように渡してあげないと参照出来ません! MY_SECRET : ${{ secrets.MY_SECRET }} 呼び出し元でこのシークレットを適切に設定しないと、ワークフローがうまく動かない可能性があるので、しっかりと設定してください。 3. GITHUB_TOKEN で行った操作は再度トリガーされない 何言ってるんだろ?って思うかもしれませんが、ワークフローで色々自動化していると案外詰まるポイントです! GitHub Actionsでは、デフォルトで GITHUB_TOKEN が提供されており、これを使って API 操作や リポジトリ への変更を行うことができます。 しかし、ここで一つ覚えておきたい仕様があります。 それは、 GITHUB_TOKEN で行った操作は再度ワークフローがトリガーされない という点です。 例: # GITHUB_TOKENを使ってissueを投稿しているサンプルです。 jobs : example-job : runs-on : ubuntu-latest steps : - name : Create a new issue run : | curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -d '{"title":"New Issue","body":"This issue is created by GitHub Actions!"}' \ https://api.github.com/repos/your-repo/issues このように GITHUB_TOKEN を使って何かissueを投稿した場合、その操作自体が新しいワークフローを起動することはありません。 つまり、「issueが作成されたら通知するワークフロー」とかを作っていたとしても通知が飛ばない結果になります。 これを知らないと「なんでワークフローが動かないんだろう?」とハマることがあるので覚えておきましょう! 補足: workflow_run トリガーを活用する もし、 GITHUB_TOKEN での操作後にワークフローを自動的に起動したい場合は、 workflow_run トリガーを活用することを検討してみるのも良いかもしれません。 これで他のワークフローの完了をトリガーにできます。 まとめ GitHub Actionsでワークフローを作成する際には、いくつかの落とし穴や仕様に注意する必要があります。 今回紹介した3つのポイントを押さえておけば、初心者でも安全かつスムーズにワークフローを作成できるはずです。 run セクションで式 ${{}} は使わず、代わりに env を活用しよう。 workflow_call では github コンテキストと secrets の扱いに注意! GITHUB_TOKEN の操作では再度ワークフローがトリガーされない仕様を覚えておこう。 このコツを頭に入れて、快適な GitHub Actionsライフを送ってくださいね! 参考記事・お勧めサイト 最後に参考にした記事やお勧めのサイトを紹介しておきます。 actionlint playground workflowの構文チェックが出来ます。pushする前にサッと確認すると無駄が減ります! GitHub CI/CD実践ガイド――持続可能なソフトウェア開発を支えるGitHub Actionsの設計と運用 (エンジニア選書) 初心者から上級者まで幅広い人にお勧めできる良書です! zenn.dev
アバター
こんにちは。 株式会社 ラク スで先行技術検証をしたり、ビジネス部門向けに技術情報を提供する取り組みを行っている「技術推進課」という部署に所属している鈴木( @moomooya )です。 ラク スの開発部ではこれまで社内で利用していなかった技術要素を自社の開発に適合するか検証し、ビジネス要求に対して迅速に応えられるようにそなえる 「技術推進プロジェクト」 というプロジェクトがあります。 このプロジェクトで「 PostgreSQL 環境における、DB定義変更を伴う無停止リリース」にまつわる検証を進めているので、その中間報告を共有しようかと思います。 ※本記事はタイトルに 「概要と計画」編 とあるように、通年で行う調査の前半時点の中間報告となります。 実際の検証結果については3月末に予定している後編をお待ち下さい。 課題の経緯、前提条件 課題の経緯 無停止リリース実現のモチベーション 前提条件 実現手法 候補に上がった手法 DDL最適化によるロック時間短縮 リトライ機構によるロック時間回避 pg-oscを利用してのロック時間短縮 pglogicalを利用してのロック時間短縮 Patroni + etcd pgpool-II 検証対象とならなかった手法 pglogicalを利用してのロック時間短縮 を除外した理由 Patroni + etcd / pgpool-II を除外した理由 検証の観点 最後に 課題の経緯、前提条件 課題の経緯 ラク スではこれまでも特定条件下のリリースにおいては無停止でのリリースを実現していましたが、一部のパターン、特にDB定義に変更が入る場合に無停止でのリリースを実現できずにいました。 これに対して、2020年に新規サービスを想定して(≒ ミドルウェア もゼロベースで選定して)の無停止について検証を行いました( 参考記事1 , 参考記事2 , 参考記事3 )が、それほど新規サービスが多くなかったことと、既存サービスへの転用コストが高すぎるためにノウハウを流用できないことから、さらなる検証を必要としていました。 今回の検証では既存サービスで主に採用されている ミドルウェア やシステム構成をベースとして、無停止リリースを実現するために検証を進めています。 無停止リリース実現のモチベーション そもそもなぜ無停止リリースを行いたいかというと、主としてはリリースの自由度を高めたい、逆に言えばリリース時の制約を減らしたいというのが最大のモチベーションです。 近年注目されるDORA, Four Keysといった開発生産性の観点でもリリース単位を小さくすることを良しとされていますが、1日に何度もリリースすることが必ずしも正しいとは言えない 1 ものの、無停止リリースが出来ないがためにリリース規模を大きくしなければならないという状態は良い状態ではありません。 無停止リリースを実現することで、意図に反するリリース規模肥大が抑制できることが期待する状態です。 前提条件 前述の通り、今回の検証では既存サービスで主に利用している PostgreSQL をベースとした無停止リリースの実現となります。 ここでいうリリースには、テーブル定義の変更を伴うものも含みます。 PostgreSQL にはオンライン DDL が用意されていませんが、サービス全体としてオンラインでの DDL 適用を実現することが目標となります。 なお、「サービス全体で」としているように、無停止の定義も 「リリース中のエンドユーザーの操作を欠落、およびエラーにしないこと」 として取り組みます。 実現手法 本記事はタイトルにもあるように通年で行う調査の前半中間報告記事となります。 課題を解決するための候補となった手法と、それらを用いてどういった検証を行う予定なのかを記述したいと思います。 候補に上がった手法 調査の結果、候補に上がった手法は以下の6つです。それぞれに対して概要をまとめます。 DDL 最適化によるロック時間短縮 リトライ機構によるロック時間回避 pg-oscを利用してのロック時間短縮 pglogicalを利用してのロック時間短縮 Patroni + etcd pgpool-II DDL 最適化によるロック時間短縮 PostgreSQL の DDL 実行において、例えばカラムの追加と制約の付与を1クエリで実行すると、すべての処理が終わるまで高いロックレベルでロックされるという現象があります。 これを「カラムの追加」と「カラムへの制約付与」の2クエリにすることで、高いロックレベルと低いロックレベルの2段階になり、ロックされる操作が緩和される状態を狙います。 ただ PostgreSQL もバージョンアップに伴い、上記のようなクエリを内部で最適化して実行してくれるケースも増えているため、改めて効果のあるもの無いものを整理して、必要なものだけに DDL の最適化を適用するような ガイドライン を導ければと考えています。 リトライ機構によるロック時間回避 DDL によるロックは一時的なものなのでアプリケーション側にリトライ機構を用意することで、 DDL によるロックをアプリケーション側からみて「なかったこと」に出来ないかという試みです。 今回の検証では Java (かつSpringを利用したアプリケーション)に限定されてしまいますが、Spring Retryを用いたリトライ機構を組み込むことで、 DDL を実行してもアプリケーション側からは意識することなく処理を継続できることを期待します。 github.com pg-oscを利用してのロック時間短縮 DDL 適用によるロック時間の短縮テクニックとして、テーブルのコピー(これをシャドウテーブルと呼ぶ)を作成し、シャドウテーブルに対して定義変更やデータ マイグレーション を行ってから、元のテーブルと差し替えることでロック時間を短くするというテクニックがあります。 作業中はトリガーを利用してデータの同期も行います。 ただしこれは、手作業で行うには手順が煩雑でミスのもとになりそうな危険性を感じます。 これを補助してくれるツールがpg-oscとなります。 github.com pglogicalを利用してのロック時間短縮 PostgreSQL には スキーマ やテーブルの単位で複製を行う「論理 レプリケーション 」という機能があります。 論理 レプリケーション ではWALではなく、WALをデコードした操作内容で同期を取るため、大雑把にいえば同じ SQL を適用できるのであれば余計なカラムが増えていたりテーブル定義が厳密に同一ではなくても同期を取ることができるようです。 pglogicalは論理 レプリケーション を補助するツールで、論理 レプリケーション を上述のシャドウテーブルのように使ってロック時間を短縮することができるのではないかと考えました。 github.com Patroni + etcd Patroniは PostgreSQL ノード内に設置し、外部のetcdなどと連携して PostgreSQL ノードの状態管理を行ってくれるツールです。 これによって PostgreSQL がロックしている間の状態管理と振り分けを行えないかと考えましたが……あくまでPatroniは高可用性ソリューションで PostgreSQL の死活監視を行ってくれるもので、テーブル単位のロックを監視するものではなさそうでした。 github.com pgpool-II こちらもpgpool-IIのコネクションプーリング機能で対応できないかと考えましたが、Patroni同様死活監視を行うものでロックによるフェイルオーバーを行うものではなさそうです。 www.pgpool.net 検証対象とならなかった手法 本検証はメインの開発業務の傍らで特別チームを組んで行っているため、すべてを検証することは出来ません。 そのためこの時点で優先度が低いと考えた以下の手法については検証対象外としました。 pglogicalを利用してのロック時間短縮 Patroni + etcd pgpool-II pglogicalを利用してのロック時間短縮 を除外した理由 発想としてはpg-oscと同様だが、pg-oscよりもより低レイヤを扱うツールであるため、pg-oscで必要な操作が賄えるのであればより人手を介さない手法のほうが今後の運用トラブルを避けることができると考え、pg-oscを優先しました。 Patroni + etcd / pgpool-II を除外した理由 これらは初期調査時は使えるかと思っていましたが、上述の通りそもそも用途が違っていた(ロックの監視ではなく、死活監視)ので対象外にしました。 また、Patroniの場合は新たにetcd クラスタ を構築しなければならず増加する運用コストもそれなりに高額になることも見込まれたため対象外になりました。 検証の観点 今後の予定として、10月以降の後半では、以下の3つの手法について検証を進めていく予定です。 DDL 最適化によるロック時間短縮 リトライ機構によるロック時間回避 pg-oscを利用してのロック時間短縮 検証内容としては、アプリケーションからの SQL クエリ(SELECTによる参照や、UPDATEによる更新など一通りのパターン)が連続してリク エス トされている状況をテスト環境に構築し、それぞれの手法を用いた状況で DDL クエリ(ロックレベル別に主要な DDL クエリを試します)を実行し、アプリケーション側からの SQL クエリに欠損や、エラーが出ないかを観測します。 実運用時よりも高密度な連続リク エス ト環境を用意して、アプリケーションでのエラーが発生しないことを目指します。 また手法についても結果と必要によっては組み合わせた環境での検証も行い、理想的な実現方法を見つけたいと思います。 最後に ラク スの技術推進プロジェクト(本取り組み)では、世間的に「切り替えは一瞬で完了する」「ドキュメント上は〇〇パラメータによって実現可能」と謳われている技術要素に関しても、自社の基準と照らし合わせて要求を満たしているかどうかを検証しています 2 。 これらは一見無駄にも見える検証ではありますが、ユーザーに満足してもらえるシステムを提供するエンジニアの責任として地道に取り組んでいきたいと考えています。 検証結果は3月末(もしかしたら4月になるかも)にまた記事にして公開したいと思いますので、しばしお待ちいただければと思います。 私としては妄信的に1日に何度もデプロイする運用にすべきではないという考えです。1日に何度もデプロイ可能な環境を整えること自体はそこまで否定しない(それによって運用コストが上がるなら正しく技術評価しないといけないけど)ですが。もちろん事業内容や業績上プラスになると評価できるサービスであれば目指すべきです。 ↩ 「一瞬」が数秒だったり、「◯◯パラメータ」が動作しなかったり、特定環境下で無効だったり、ということはよくある。 ↩
アバター
初めまして!新卒1年目のmochi_proteinと申します。 CSR / SSR / SSG / ISRがどのような概念か、 架空アプリを例として、それぞれの違いを初学者向けにやさしく解説していきます! 🔖目次は以下の通りです🔖 はじめに 架空アプリ「楽楽鮮魚」の仕様 前提知識 レンダリングとは? 動的にHTMLを生成するとは? CSR(クライアントサイドレンダリング)とは? 概要 「楽楽鮮魚」が CSR を採用したら? 初期画面(在庫一覧画面)表示までの流れ 詳細画面への遷移時の流れ CSRのメリット CSRのデメリット どのようなサービスに向いているか SSR(サーバーサイドレンダリング)とは? 概要 「楽楽鮮魚」が SSR を採用したら? 初期画面(在庫一覧画面)表示までの流れ 詳細画面への遷移時の流れ SSRのメリット SSRのデメリット どのようなサービスに向いているか SSG(スタティックサイトジェネレーション)とは? 概要 「楽楽鮮魚」が SSG を採用したら? 初期画面(在庫一覧画面)表示までの流れ 詳細画面への遷移時の流れ SSGのメリット SSGのデメリット どのようなサービスに向いているか ISR(インクリメンタルスタティックリジェネレーション)とは? 概要 「楽楽鮮魚」が ISR を採用したら? 初期画面(在庫一覧画面)表示までの流れ 初期リクエスト後、複数回/ユーザからの在庫一覧画面へのアクセス時(キャッシュ有効期限内)の流れ キャッシュの有効期限後のリクエスト時の流れ 詳細画面への遷移時の流れ ISRのメリット ISRのデメリット どのようなサービスに向いているか おわりに 参考 はじめに 今回、4つの レンダリング 手法を解説するにあたり、 「楽楽鮮魚」という架空の在庫管理アプリ を例として、それぞれの レンダリング 手法を採用した時、 どこで レンダリング が行われて画面表示されるのか、流れを追っていくことで理解していきましょう! 楽楽鮮魚(架空アプリ)の仕様 架空アプリ「楽楽鮮魚」の仕様 「楽楽鮮魚」は、自社の魚の在庫状況を確認することができるアプリ 初期画面=在庫一覧画面(一覧テーブルに「魚種名 / 在庫(匹数) / [詳細ボタン]」がある) [詳細ボタン]押下で、詳細画面に遷移する 詳細画面は、「魚種名 / 在庫 / 産地 / 入荷日」が表示される 実際にこのアプリをリリースするならどの手法が良いのか も考えながらご覧になってみてください! 前提知識 レンダリング とは? 「 レンダリング 」という単語自体は、
広くデータを視覚化することを指しますが、
 CSR や SSR といった概念が指す「 レンダリング 」として、ここでは
 JavaScript などで 動的にHTMLを生成すること を指して解説いたします。 動的にHTMLを生成するとは? 動的にHTMLを生成するとは、 JavaScript などによって新たなHTML要素を生成したり、既存のHTMLを更新することを指します。 
例えば、
ユーザーがボタンを押すなどのアクションを取ったとき、またはサーバーから新しいデータを取得した際に、その内容に基づいてページのHTMLを動的に書き換えることで、ページを再読み込みすることなく内容を更新することができます。 CSR (クライアントサイド レンダリング )とは? 概要 CSR は、 C :Client / クライアント = ブラウザ S :Side / サイド = 側 R :Rendering / レンダリング = 動的にHTMLを出力する の略で、 つまり、 「ブラウザ側で動的にHTMLを生成する」 手法を指します。 「楽楽鮮魚」が CSR を採用したら? 初期画面(在庫一覧画面)表示までの流れ 初期表示(在庫一覧画面表示)までの流れ / CSR ブラウザはWEBサーバーに、リク エス トをします WEBサーバーはブラウザに、在庫情報が 埋め込まれていないファイル をレスポンスします
 ブラウザは API サーバーに、在庫情報をリク エス トします
 API サーバーはブラウザに、在庫情報をレスポンスします ブラウザは、 レンダリング を行い(JSによってDOMが更新され)、在庫一覧画面が表示されます 詳細画面への遷移時の流れ 詳細画面への遷移時の流れ / CSR ブラウザは API サーバーに、詳細情報をリク エス トします
 API サーバーはブラウザに、詳細情報をレスポンスします ブラウザは、 レンダリング を行い、詳細画面が表示されます CSR のメリット ページ遷移が早い サーバーの負荷が軽い CSR のデメリット 初期表示が遅い(初期ロードで大量のJSを読み込むため)
 SEO 対策が難しい(ページの内容が JavaScript で後から表示されるため、 検索エンジン が内容を正しく認識できず、検索結果に反映されにくくなる) JSが⁨⁩無効だと正しく表示されない ページごとのOGP(Open Graph Protocol)設定ができない
 クライアントのスペックに依存する どのようなサービスに向いているか 部分的にコンテンツを、動的に更新するアプリ(map系など) チャットアプリや SNS など頻繁に投稿などが更新されるアプリ SSR (サーバーサイド レンダリング )とは? 概要 SSR は、 S :Server / サーバー = サーバー S :Side / サイド = 側 R :Rendering / レンダリング = 動的にHTMLを出力する の略で、 つまり、 「サーバー側で動的にHTMLを生成する」 手法を指します。 「楽楽鮮魚」が SSR を採用したら? 初期画面(在庫一覧画面)表示までの流れ 初期表示(在庫一覧画面表示)までの流れ / SSR ブラウザはWEBサーバーに、リク エス トします WEBサーバーは API サーバーに、在庫情報をリク エス トします API サーバーはWEBサーバーに、在庫情報をレスポンスします WEBサーバーは、 レンダリング を行います
 WEBサーバーはブラウザに、 レンダリング 済みのファイルをレスポンスします ブラウザは、 レンダリング しなくとも5の時点で在庫情報が埋め込まれているため、ブラウザでJSを動作させる前に在庫一覧画面が表示されます 詳細画面への遷移時の流れ 詳細画面への遷移時の流れ / SSR ブラウザはWEBサーバーに、リク エス トします WEBサーバーは API サーバーに、詳細情報をリク エス トします API サーバーはWEBサーバーに、詳細情報をレスポンスします WEBサーバーは、 レンダリング を行います
 WEBサーバーはブラウザに、 レンダリング 済みのファイルをレスポンスします ブラウザは、 レンダリング しなくとも5の時点で詳細情報が埋め込まれているため、ブラウザでJSを動作させる前に詳細画面が表示されます SSR のメリット SEO 対策に優れている(サーバーでページ内容が生成され、 検索エンジン が JavaScript を実行しなくてもコンテンツを容易に読み取ることができる) 初期表示が早い(既に レンダリング 済みなため)
 OGPを設定できる SSR のデメリット サーバー負荷が高い(リク エス トごとにサーバーで レンダリング 処理が必要なため) どのようなサービスに向いているか SEO 対策が重要なサービス(ECやニュースなど)
 初回表示速度が重要なサービス
 SSG (スタティックサイトジェネレーション)とは? 概要 SSGは、 S :Static / スタティック = 静的に S :Site / サイト = サイト G :Generation / ジェネレーション = (あらかじめ)HTMLを生成する の略で、 つまり、 「あらかじめ、完成された(静的な)HTMLを生成しておく」 手法を指します。 SSGには、 CSR や SSR のように「Rendering」ではなく、
「Generation」の文字が使われています。 SSGでは、ビルド時に レンダリング を行い、生成済みのHTMLをWebサーバー上に配置しておくことで、クライアントサイド・サーバーサイドどちらでも JavaScript による初期 レンダリング を行わなわない方式を指しています。 よって厳密には、 CSR や SSR のようにリク エス ト時にどちら側で レンダリング を行うかといった観点の レンダリング 方式を指している言葉ではありません。 「楽楽鮮魚」が SSG を採用したら? 初期画面(在庫一覧画面)表示までの流れ 初期表示(在庫一覧画面表示)までの流れ / SSG  0.【前提】 ビルド時に レンダリング を行います   *SSGでは、ビルド完了時にはすべてのページが静的なHTMLファイルとして生成され、   WEBサーバー上に配置されます。  1.ブラウザはWEBサーバーに、リク エス トします
  2.WEBサーバーはブラウザに、 レンダリング 済みのファイルをレスポンスします  3.ブラウザは レンダリング しなくとも2の時点で在庫情報が埋め込まれているため、    ブラウザでJSを動作させる前に在庫一覧画面が表示されます 詳細画面への遷移時の流れ 詳細画面への遷移時の流れ / SSG ブラウザはWEBサーバーに、リク エス トします WEBサーバーはブラウザに、 レンダリング 済みのファイルをレスポンスします ブラウザは、 レンダリング しなくとも2の時点で詳細情報が埋め込まれているため、ブラウザでJSを動作させる前に詳細画面が表示されます SSGのメリット サーバーの負荷が軽い(リク エス トごとにサーバーで レンダリング しないため) セキュリティが高い(動的な要素が無いため) SEO 対策に優れている(静的に生成されたページは、 検索エンジン が JavaScript を実行しなくてもコンテンツを容易に読み取ることができる) 初期表示が早い SSGのデメリット コンテンツの更新が難しい(デプロイ時にしかコンテンツを更新できないため) リアルタイムに更新できない
 ビルド& レンダリング に時間がかかる どのようなサービスに向いているか コンテンツ更新頻度の低いサービス(個人ブログ・ ポートフォリオ など) SEO 対策が重要なサービス ISR (インクリメンタルスタティックリジェネレーション)とは? 概要 ISRは、 I :Incremental / インクリメンタル = 増分的 S :Static / スタティック = 静的に R :Regeneration / リジェネレーション = 再生成 の略で、 つまり、 「静的に生成されたHTMLを、定期的に裏で再生成する」 手法を指します。 ISRは、 SSR とSSGの掛け合わせのような手法です。
 具体的には、初期リク エス トが来た時に レンダリング を行い、
その レンダリング 結果をサーバー側に有効期限付きのキャッシュとして一時的に保存をします。

 基本的にクライアントにはキャッシュを返すことで、サーバー側の負荷を下げます。
 有効期限後にリク エス トが来ると裏で再 レンダリング を行い、キャッシュを更新します。 「楽楽鮮魚」が ISR を採用したら? 初期画面(在庫一覧画面)表示までの流れ 初期表示(在庫一覧画面表示)までの流れ / ISR  1. ブラウザはWEBサーバーに、リク エス トします
  2. WEBサーバーは、 API サーバに在庫情報をリク エス トします  3. WEBサーバーは、 API サーバに在庫情報をレスポンスします  4. WEBサーバーは レンダリング を行います  5-1. WEBサーバーは、 レンダリング 結果を有効期限付きのキャッシュとして一時的に保存します
  5-2. WEBサーバーはブラウザに、 レンダリング 済みファイルをレスポンスします  6. ブラウザは レンダリング しなくとも5-2の時点で在庫情報が埋め込まれているため、   JSを動作させる前に在庫一覧画面が表示されます 初期リク エス ト後、複数回/ユーザからの在庫一覧画面へのアクセス時(キャッシュ有効期限内)の流れ 初期リク エス ト後、複数回/ユーザからの在庫一覧画面へのアクセス時(キャッシュ有効期限内)の流れ / ISR ブラウザはWEBサーバーに、リク エス トします WEBサーバーはブラウザに、キャッシュの レンダリング 済みファイルをレスポンスします ブラウザは レンダリング しなくとも2の時点で在庫情報が埋め込まれているため、JSを動作させる前に在庫一覧画面が表示されます キャッシュの有効期限後のリク エス ト時の流れ キャッシュの有効期限後のリク エス ト時の流れ / ISR  1.ブラウザ1はWEBサーバーに、リク エス トします(これまでのキャッシュの有効期限後、最初のリク エス ト)
  2.WEBサーバーはブラウザに、これまでのキャッシュの レンダリング 済みのファイルをレスポンスします  3.ブラウザ1は、 レンダリング しなくとも2の時点で在庫情報が埋め込まれているため、   JSを動作させる前に在庫一覧画面が表示されます

  2’.裏側でWEBサーバーは API サーバーに、新しい在庫情報をリク エス トします  3’. API サーバーはWEBサーバーに、新しい在庫情報をレスポンスします
  4.WEBサーバーは、 レンダリング を行います  5.WEBサーバーは、キャッシュを新しい レンダリング 結果に更新します
  6.ブラウザ2はWEBサーバーに、リク エス トします(これまでのキャッシュの有効期限後、2回目のリク エス ト)
  7.WEBサーバーは、更新したキャッシュの レンダリング 済みのファイルをレスポンスします  8.ブラウザは レンダリング しなくとも7の時点で(新しい)在庫情報が埋め込まれているため、   JSを動作させる前に(新しい)在庫一覧画面が表示されます 詳細画面への遷移時の流れ 詳細画面への遷移時の流れ / ISR  1.ブラウザはWEBサーバーに、リク エス トします
  2.WEBサーバーは、 API サーバーに詳細情報をリク エス トします  3.WEBサーバーは、 API サーバーに詳細情報をレスポンスします  4.WEBサーバーは レンダリング を行います  5-1.WEBサーバーは、 レンダリング 結果を有効期限付きのキャッシュとして一時的に保存します   *SSGとは違い、必要なページだけをアップデート(生成)することができることから   増分的(Incremental)という言葉が使われています。  5-2.WEBサーバーはブラウザに、 レンダリング 済みファイルをレスポンスします  6.ブラウザは レンダリング しなくとも5-2の時点で詳細情報が埋め込まれているため、   JSを動作させる前に詳細画面が表示されます ISRのメリット SSGの高速性を維持しつつ動的なコンテンツも扱える 指定された時間で自動的にページを再生成できる 必要なページだけを再生成するため、大規模サイトでも効率的に最新コンテンツを提供できる ISRのデメリット リアルタイム性の欠如 どのようなサービスに向いているか 大規模サイト(サイトに多数のページがある場合、SSGと比べビルド時間を大幅に短縮できる) リアルタイム性を必要としないサービス おわりに これらの レンダリング 手法は、一概に「この レンダリング 手法が優れている」と言えるものではありません。 サービスの特性や目的によって最適な手法は異なります。 例えば、リアルタイムでのデータ更新が必要なアプリケーションでは CSR が適していて、 SEO 対策や初期表示速度が重要なウェブサイトでは SSR やSSGが効果的です。 また、コンテンツの更新頻度が高くないが、定期的な最新情報を提供したい場合にはISRが有用であると言えると思います。 それぞれの手法の特徴やメリット・デメリットを理解し、プロジェクトの要件やユーザー体験を考慮して最適な選択をすることが重要です。 ISRの登場が2020年であるように、技術の進化に伴い新しい手法やツールも登場するので、継続的な学習と情報収集が欠かせないことに変わりはなさそうです。 この記事では、初学者の方へ向けて レンダリング 手法について解説してきました。 最後までご覧いただき、ありがとうございます。 参考 『フロントエンドの知識地図』出版のお知らせ - ICS MEDIA Rendering on the Web  |  Articles  |  web.dev Incremental Static Regeneration (ISR) Data Fetching: Incremental Static Regeneration (ISR) | Next.js CSR,SSR,SSG,ISRについて理解する SPA, SSR, SSGって結局なんなんだっけ? CSR、SSR、SSG、ISRの違いをわかりやすく解説 もう迷わないNext.jsのCSR/SSR/SSG/ISR Next.jsのCSR(SPA),SSR,SSG,ISRのまとめ&メリットデメリットについて - Wiz テックブログ レンダリング方式ごとの組み込み方法|microCMSドキュメント SSRってMPAやSPAと何が違うの!? - ecbeing labs(イーシービーイング・ラボ) EC構築のSEO対策におけるCSRとSSRの違いを徹底比較 SSR, CSR, SSG, ISG, ISRとは?それぞれの基本的な定義と特徴を解説 | 株式会社一創 CSR/SSR/SG/ISRについてまとめ│てりーのフロントエンドBLOG CSRとSSRとSSGの違い #Next.js - Qiita フロントエンド理解必須のCSR、SSR、SSG #初学者向け - Qiita 【初心者向け】クライアントサイドのメリット・デメリットや、サーバーサイドとの違いなどわかりやすく紹介! KURO DIGITAL LOG
アバター
はじめに こんにちは。SREの gumamon です! NewRelic、Datadog、モダンな監視ツール(オブザーバビリティ)って良いですよね。弊社も Kubernetes ( k8s )等を利用した環境が増えてきた折、そろそろ必要になってきたのですが、NewRelic、Datadog等の クラウド サービスは ランニングコスト が高くなりがちです。 では内製できないかやってみよう!・・・というようなことを昨年度から取り組んでいたのですが、やっとこさ形になりましたので改めてブログで紹介させて頂こうと思います。 今回ご紹介するのは、大まかなシステムの構成と設計時の観点です。各 コンポーネント の詳細や工夫できた点などについては、改めて別の記事でご紹介できればと思います。 また、「オブザーバビリティとは?」や「試行錯誤の過程」については、以前執筆した以下のブログをご参照ください。 tech-blog.rakus.co.jp 目次 はじめに 目次 全体構成 設計観点:ユーザーの認知負荷を低く保つ ユーザーインターフェイスはGrafanaに統一 プリセットのダッシュボードとアラート 設計観点:変更容易性を高く保つ テレメトリの種別ごとにパイプラインを水平分割する 目的別(テレメトリ収集・集約)にIngressを挟んで垂直分割する まとめ 全体構成 さっそくですが、今回構築した環境の全体像は以下のとおりです。 ※ k8s 上にデプロイしています Architecture 今回の設計では下記を重視しています。 ユーザーの認知負荷を低く保つこと 変更容易性が高いこと それぞれのポイントについて、設計観点を記載します。 設計観点:ユーザーの認知負荷を低く保つ ユーザーインターフェイス はGrafanaに統一 何かを確認したいときに、あちこち情報を探すのは面倒です。このため、テレメトリを見たい=Grafanaを見れば良い、という導線になるように インターフェイス を統一しました。今回の構成では複数のサービスが連携してオブザーバビリティを提供していますが、ユーザーが見るのはGrafana(とSlack)のみです。 User Interface この統一が実現できたのは、Grafanaの設計理念のおかげです(私はこの考え方がとても気に入り、Grafanaを中心に アーキテクチャ を構築しました)。 Grafanaの設計理念は Big Tent Philosophy と呼ばれています。 「データがどこにあってもアクセスでき、観測可能性戦略に最適なツールを選択できるべきだ」という考えが根底にあり、Grafanaは プラグイン ベースの アーキテクチャ を採用しています。 その結果、多くのデータソースに対応する プラグイン がコミュニティや企業で開発されており、Grafana上でほぼ何でも可視化できるという状況が生まれました。 Prometheus , Loki , OpenTelemetory , Cloudwatch , Datadog , PostgreSQL ... etc プリセットの ダッシュ ボードとアラート サービスの増減に伴うGrafanaのメンテナンスも面倒です。そのため、SREがプリセットの ダッシュ ボードやアラートを管理・提供し、ユーザーが自身で作りこむ必要がないようにしました。 ただし、これはGrafanaの優秀さだけではなく、オブザーバビリティを提供する対象が k8s 上のサービスだったことも幸いしています。 k8s はコア コンポーネント がPodやService、PersistentVolume等のメタ情報を持っているので、多くのメトリクスを容易に収集することができます(ログも決まったところに決まった形式で出力されてきます)。これらを先に述べた ダッシュ ボードやアラートに紐づけることで、SRE側も無理なく k8s 環境全体のオブザーバビリティを提供できています。 Dashboards Dashboards AlertRules AlertRules 設計観点:変更容易性を高く保つ 今回の構成では アーキテクチャ 選定に悩みました。PoCや ステークホルダー との議論で「とりあえずこれでよさそう」となりましたが、より良い選択肢が後から見つかる可能性も十分にあるため、将来的に「やっぱり変えます」が容易にできる構成にしたいと考えました。各 コンポーネント が 疎結合 になるよう、以下の軸で整理しています テレメトリの種別ごとにパイプラインを水平分割する Metrics, Logs, Traces は、それぞれ異なる性質を持つテレメトリです。 取得目的が違う 取得方法が違う 通信規格が違う 負荷のかかるタイミングが違う そのため、各テレメトリを独立させ、シンプルに扱える構成としました。初歩的な実装ならばオールインワンのエージェントを導入する方が楽ですが、運用の柔軟性を高めるのであれば、Prometheusなどの草分け的な OSS をテレメトリごとにそのまま使う方が最終的には楽だと判断しました。また、異なる種類のメトリクスを同一サービスに集約しないことにもこだわりました。これにより、どれか一つを別のサービスに置き換えたい場合の移行がスムーズに行えます。 Horizontal Split PrometheusはPull型の アーキテクチャ ですが、別のPrometheus Serverへメトリクスをリレーする RemoteWrite 機能を持っています。PrometheusとVictoriaMetrics間の通信は、この RemoteWrite を用いて実現しています。 目的別(テレメトリ収集・集約)に Ingress を挟んで垂直分割する 今回の主な監視対象は k8s 上のサービスです。 k8s はClusterやNodeが増減するため、データ収集を行うエージェント(Edge)と、データ集約を行う中央集権的なサービス(Central)は多対一の関係になります。このため、中央集権的なサービスの前に Ingress を配置し、エージェントは Ingress のエンドポイントにテレメトリデータをPushする構成にしました。 Vertical Split Edgesは必ずしも k8s である必要はありません。(ある程度手作業にはなりますが)通常の Linux 環境などからも、エージェントを配置することで情報を集約可能です。 まとめ 今回は Grafana Stack x OpenTelemetryを使ったオブザーバビリティ構成についてご紹介させていただきました! 同じようなチャレンジをされている方の一助となれていましたら幸いです。 なお、今後の展望としては・・・ フロントエンド監視やプロファイル監視を追加したい SLI・SLOをいい感じに設計して行きたい Traceについてはまだまだ改良をしていきたい(SLI・SLOとも深く関連してくる) 以上、最後までお読み頂きありがとうございました!
アバター
背景 経費精算システム「楽楽精算」は2009年にリリースされ、15年以上にわたり運用されてきました。 その間、基本的なシステム設計はリリース当初のまま維持されています。 しかし、年月が経つにつれ、技術トレンドやビジネス的な要求は大きく変化しましたが、現状のシステムではそれらの変化に柔軟に対応することが困難になってきています。 システムの柔軟性は低く、機能追加のたびに既存機能への影響を広範に調査する必要があり、既存の処理フローを変えることができないため、イレギュラーなテクニックが必要となることも多く、追加開発のたびに多くの手間とコストがかかるようになってきました。 すべての問題が現行システムに起因するわけではありませんが、特定の ミドルウェア に強く依存した構造を持っているため、将来的な技術革新や新しい ミドルウェア への移行が困難であるという課題も抱えていました。 このような背景から、 ミドルウェア に依存しない中立的な アーキテクチャ が必要であり、その実現のためにリ アーキテクチャ に近い大規模な リファクタリング が計画されました。 その際に最も懸念されたのは、 リファクタリング による動作不具合のリスクです。 現状では、すべての動作を保証する自動テストが存在しないため、このリスクを無視することはできませんでした。 加えて、仕様書は完全には整備されておらず、初期リリース時から積み重なった変更仕様が複雑に絡み合い、システム全体の動作を統一的に把握することが困難な状況にありました。 このような状況下で、 リファクタリング を成功させるためにはどのような対策が必要かが、プロジェクトの大きな課題となりました。 このプロジェクト「 リファクタリング に向けた自動インテグレーションの実装」は、こうした背景を踏まえ、動作を保証するためのインテグレーションプロセスを自動化し、 リファクタリング を安心して進められる環境を整備することを目指しました。 背景 使用した技術とツール 直面した課題 解決策と手順 結果 考察と今後の展望 最後に 使用した技術とツール プロジェクトで中心的な役割を果たしたのは、 AOP ( Aspect -Oriented Programming)、 JUnit 、そして DBUnit という3つの技術です。 リファクタリング を行う際に最も重要なのは、動作に変更がないことです。 そのためには、現行のシステムがどのように動作しているかを正確にトレースする必要がありました。 システムの動作は単純に言えば入力と出力です。 エンドユーザーの操作によってシステムに情報が入力され、その結果としてデータベースや画面に情報が出力されます。 操作時に呼び出されるエンドポイントへの入力と出力、そしてその前後でデータベースの状態がどのように変化しているかを記録する必要がありました。 しかし、これを手動で行うのは非常に手間のかかる作業です。 そこで、 AOP を用いてエンドポイント呼び出し前と呼び出し後の状態を自動的に取得する仕組みを構築しました。 これにより、通常の操作を行うだけでテストデータが自動的に蓄積されていく仕組みが出来上がりました。 次に、取得したデータを自動テストに活用するために、 JUnit と DBUnit を選定しました。 まず、取得したテストデータから事前のデータベースの状態を DBUnit でリストアします。 JUnit でテストデータの入力情報をエンドポイントに入力することで処理が行われ、データベースの変更と画面出力が行われます。 それらの結果を DBUnit と JUnit を用いて事後のテストデータと比較することで、動作を検証します。 JUnit は Java での 単体テスト の標準 フレームワーク であり、楽楽精算ではすでに ユニットテスト で使用していたため、その知見を活かせることが選定理由です。 DBUnit は、データベースの状態をテストケースごとに再現するためのツールであり、データベースを利用した システムテスト において非常に有効です。 この2つのツールを組み合わせることで、取得したデータを効果的にテストに利用し、 リファクタリング 後のシステムの安定性を保証することができました。 直面した課題 プロジェクトを進める中で、最も困難だったのは、現行のモジュール構成と リファクタリング 後のモジュール構成が互換性を持たない可能性が高かったことです。 このため、 単体テスト の範囲を限定することが難しく、ほぼすべてのケースをインテグレーションテストでカバーする必要がありました。 特に、仕様書が不完全であったことから、どの部分を優先的にテストすべきかを判断するのが非常に困難でした。 テストケースの作成とテストデータの取得にも多くの時間と労力を要しました。 テストケースの作成からテストデータの収集までを本プロジェクトのチームのみで行うのは現実的ではなく、大きな課題でした。 特に特殊なケースについては製品に対する深い知識が必要であったため、外部委託は難しい状況でした。 その際に頼りになったのがオフショアチームでした。 ラク スは ベトナム にも開発チームを有しており、10年以上にわたる開発経験があります。 楽楽精算の開発にも長く携わっているため、製品や仕様に精通しており、テストケースの作成からテストデータの収集までを一貫して行うことができました。 これにより、短期間で膨大な量のテストケースの作成とテストデータの収集を行い、プロジェクトを前進させることができました。 解決策と手順 このプロジェクトを成功させるために、いくつかの重要なステップを踏みました。 要件定義と技術選定 プロジェクトの初期段階で、すべての要件を詳細に分析し、それに基づいて最適な技術を選定しました。 AOP の導入により、システムの動作をトレースし、 JUnit と DBUnit を活用してそのデータを効率的にテストに利用することで、手作業の負担を大幅に軽減しました。 システム設計と開発 システムの設計段階では、 AOP を活用してエンドポイント呼び出し前後のデータを自動的にキャプチャする仕組みを構築しました。これにより、 リファクタリング 後のシステムが現行システムと同様に動作することを確実にしました。また、 JUnit と DBUnit を活用して自動テスト環境を整備し、開発中のモジュールが正しく動作するかを常に確認できる体制を構築しました。 テストとデプロイ テストの段階では、広範囲にわたるテストケースを作成し、手動で収集したデータを活用してすべてのケースをカバーすることを目指しました。特に、 リファクタリング 後の動作が現行と一致するかを検証するため、各テストケースでデータベースの状態が正確に再現されることを確認しました。 結果 プロジェクトの結果として、私たちは広範な自動テストを構築し、 リファクタリング による不具合のリスクを最小限に抑えることができました。 自動化されたテストが存在することで、システムの変更に伴うリスクを可視化し、迅速に対応することが可能となりました。 この結果、 リファクタリング の過程で発生する可能性のある多くの問題を事前に検出し、対応することができました。 実際の リファクタリング の際も、自動インテグレーションテストをTDDのように使用することで、着実に リファクタリング を進めることができました。 考察と今後の展望 このプロジェクトを通じて、自動テストの重要性と有効性をあらためて認識しました。 自動テストが存在することで、システムに対する信頼性が格段に向上し、 リファクタリング やシステム変更の際の不安を大幅に軽減することができます。 これは、システムの継続的な変化が求められる現代においては、不可欠な要素です。 一方で、自動テストのメンテナンスコストが予想以上に高くなることも判明しました。 今回の リファクタリング のために作成した自動テストは、今後も機能開発時の自動テスト(主に リグレッション テスト)として活用したいと考えていました。 しかし、現状の自動インテグレーションテストは基本的に入出力の完全一致を確認するものであるため、入力や出力に変更がある場合はテストデータを修正する必要があり、 その作業コストが当初の想定以上に大きくなっています。 今後は、自動インテグレーションテストの検証範囲を制限し、 ユニットテスト で担保できる部分はそちらで対応するなど、新たなテストプロセスの構築が求められます。 最後に このブログが、同様の課題に直面しているエンジニアにとって有益であることを願っています。
アバター
はじめに ラク スが開発する楽楽精算は、東京開発統括部の楽楽精算開発部が担っています。 楽楽精算の iPhone (Swift)& Android (Kotlin)対応のモバイル アプリ開発 を担当しているのが、モバイル開発課です。 本記事では、楽楽精算のモバイル アプリ開発 案件を担当しているモバイル開発課のマネージャーが厳選した 「モバイル開発を軸に、キャリアをステップアップするために役立つ書籍」をご紹介します。 それぞれの書籍に推薦コメントを記載していますので、是非ご参考になさってください。 はじめに モバイル開発でおすすめの書籍 Androidを支える技術<Ⅰ・Ⅱ> iOSアプリ設計パターン入門 Androidアプリ設計パターン入門 プロダクト開発・アジャイル開発でおすすめの書籍 プロダクトマネジメント ビルドトラップを避け顧客に価値を届ける プロダクト開発の罠:エンジニアの最大稼働率が生む遅延とその克服法 アジャイルな見積もりと計画づくり その他技術のおすすめ書籍 UNIXという考え方: その設計思想と哲学 SQLアンチパターン Webを支える技術 ― HTTP,URI,HTML,そしてREST おわりに モバイル開発でおすすめの書籍 Android を支える技術<Ⅰ・Ⅱ> LooperとHandlerによるイベント処理が Linux 上のイベント処理機構でどのように処理されるか、タッチイベントがどのように伝搬されて Android 上で処理されるのか、など Android システムの低レイヤーな部分に焦点を充てた良書です。 古い書籍だが Android の基本は変わっていないので押さえておくと普段の開発に役に立ちます。 iOS アプリ設計パターン入門 iOS アプリ開発 における設計パターンの基本から最新までを徹底解説した本です。 MVC 、MVVM、Redux、Clean Architectureなど、さまざまな設計パターンを包括的に扱い、設計の選定に迷っている開発者向けに、具体的な実例をもとにその歴史や選定基準を学べます。チーム内で共通認識を持ち、よりメンテナンス性の高い アプリ開発 を目指すための入門書となっています​。 Android アプリ設計パターン入門 Android アプリ開発 における設計手法を、初心者から上級者まで幅広く学べる書籍です。 MVC 、MVVM、MVPなど、主要な アーキテクチャパターン を基本から実際のプロジェクトに基づいた実例を用いて解説しています。DroidKaigiやメルカリなどの実際の アプリ開発 を参考にし、設計上の課題やその解決方法にフォーカスしています。また Android Architecture Componentsにも触れ、設計のベストプ ラク ティスを提供しています。 プロダクト開発・ アジャイル 開発でおすすめの書籍 プロダクトマネジメント  ビルドトラップを避け顧客に価値を届ける アウトカムを意識しないアウトプットを重視すると顧客のニーズではなくスケジュール優先やリリース回数などを重視する「ビルドトラップ」に陥ってしまいます。 ビルドトラップを避け、顧客の課題にフォーカスする プロダクトマネジメント の原則を解説した本です。 プロダクト開発の罠:エンジニアの最大 稼働率 が生む遅延とその克服法 プロダクト開発におけるリードタイム増大の原因と対策について、リソース効率とフロー効率という視点で書かれた本です。 リソース効率だけを追い求めるとタスクの受け渡しで待ち時間や切り替えコストが発生し、一生懸命稼働しているつもりでも結果的に全体のリードタイムは伸びてしまうというパラドクスが存在するが、これを解消するためにフロー効率を上げる事が大事、という事が書かれています。 アジャイル な見積もりと計画づくり ソフトウェア開発における見積もりの不確実性に焦点を当て、 アジャイル 手法を用いて効果的な計画を立てる方法を解説しています。不確実性コーンなどの概念を活用し、プロジェクトの進行に伴い見積もりの精度が向上する仕組みを紹介します。 アジャイル 開発における変化への柔軟な対応と、計画と見積もりのバランスを取るための実践的なアプローチを提供します。 その他技術のおすすめ書籍 UNIX という考え方: その設計思想と哲学 UNIX の歴史を紐ときながらなぜここまで UNIX 系のOSが普及したのか、その設計思想や哲学を学べます。 なぜ UNIX は初心者にやさしくないのかなど、 UNIX が何を重視し、何を犠牲にしてきたのかを解説しています。 UNIX の話とは言えコマンドの設計思想などは日々のプログラム開発の参考になります。 SQL アンチパターン RDB のテーブル設計でやりがちな アンチパターン を具体例を交えて解説しています。 DB設計はアプリケーションのパフォーマンスや設計に影響するのでとても大事です。 正規化できていないテーブルなどは被害が広がり易く負債解消のコストがとても高いのでしっかり理解して防ぐ必要があります。 Webを支える技術 ― HTTP, URI ,HTML,そしてREST HTTPメソッドや ステータスコード の役割と正しい使い方、ステートレス性などWeb本来の設計思想を学べます。 モバイル開発にとってもWebの技術は使われており、基礎をしっかり理解しているかどうかはとても重要となっております。 おわりに 今回は、モバイル開発を軸にキャリアをステップアップするために役立つ書籍とその理由を紹介させていただきました。 Android や iOS の基礎から設計パターン、 プロダクトマネジメント まで、幅広い知識が得られるラインナップです。 これらの書籍を通して、モバイル アプリ開発 における技術力を高めるだけでなく、プロダクト開発全体の視野を広げることができるのではないかと思います。 モバイル開発者として成長を続けたい方は、ぜひ一度手に取って、キャリアの指針として参考にしてみてください。
アバター
はじめに こんにちは。楽楽電子保存のバックエンド開発チーム兼オフショア開発のリーダーを務めています、small-chestnutです。 今回は、私が担当しているグローバル開発におけるチームビルディングの経験をシェアしたいと思います。 この記事では、弊社の子会社である ラク ス ベトナム (以下、RV)との協働を通じて経験したチームビルディングの遷移や、各年度ごとに取り組んだ施策、課題解決のプロセスを振り返ります。グローバル開発やチームビルディングに悩んでいる方々にとって、参考になれば幸いです。 はじめに サービス紹介 チーム紹介 2022年度:立ち上げ期(形成期) オフショアチームの立ち上げ 施策と課題 2023年度:RVの本格開発参入(混乱期) サービス成長とチーム体制の変化 成果と混乱 2024年度:RVの統一と安定期 チームの成熟と新たなステップ 今後の展望 まとめ:グローバル開発で築く強いチームビルディングの5つのポイント 1.基本的な認識合わせの明文化と資料化 2.タスクやドキュメントの形式化 3.同じ資料・チケット管理システムの使用 4. KPIの設定による品質改善 5.チーム開発を意識したソフトウェア設計の改善 さいごに サービス紹介 「楽楽電子保存」は、 電子帳簿保存法 に準拠した帳票保存サービスで、特に受取側企業向けに利用されています。紙の帳簿や書類をデジタル化し、CMでおなじみの「楽楽明細」と連携することで、効率的な管理が可能です。2022年1月にリリースされ、今年で3年目を迎えます。法改正の影響もあり、ユーザー数は着実に増加しており、現在は機能拡充のフェーズに入っています。 楽楽電子保存 チーム紹介 「楽楽電子保存」は、 PMF (プロダクトマーケットフィット)が見え始め、開発の規模も拡大している段階です。日本チームによる対応が一段落したタイミングで、RV主導での開発に移行しました。これは、楽楽電子保存が新規プロジェクトであり、他の10年以上運用している複雑なサービスに比べて、RV中心での開発が行いやすいという背景があります。 また、 ベトナム では日本よりもIT人材の採用しやすく、組織をスケールしやすいという利点もあります。 2022年度:立ち上げ期(形成期) オフショアチームの立ち上げ 2022年は、RVとの初期連携を強化した時期でした。RVは開発4名、テスター2名の体制で、日本チームは7名でサポート。ブリッジSE(以下BrSE)と共に作業依頼や連携を進めながら、RVを徐々に育成していく計画を立てました。 RVは「期日までに作り切る」という意識は強い一方で、 ソースコード の品質(可読性・保守性)を高める意識が弱いという課題があります。 2022年度 立上げ期体制 施策と課題 比較的簡単なタスクから徐々に難易度を上げていきましたが、RVと日本チームの間で品質に対する意識の違いがありました。また、日本側のレビュー観点やドキュメントの形式化が不十分だったため、RVの成果物に対するレビュー指摘が多くなりました。さらに、RVと日本チーム間のレビュー連携がうまく進まず、課題に直面しました。 加えて、既存コードの一部が ドメイン 駆動設計(DDD)の貧血モデルとなっており、品質維持が難しい場面もありました。 2023年度:RVの本格開発参入(混乱期) サービス成長とチーム体制の変化 2023年、RVは10名体制に拡大し、本格的な開発フェーズに入りました。一方、日本チームは他サービス対応の優先度が上がり、メンバーが4名に縮小。日本チームはRVの成果物レビューに多くの時間を割くことになり、リソースが逼迫する状況になりました。 2023年度 RV本格参入期体制 さらに、サービス利用が急増し、2023年9月時点で月160万リク エス トだったものが、2024年3月には1,400万リク エス トにまで急増。問い合わせ対応も日本チームが担っていたため、レビューや開発に加え、対応業務が増え、チーム全体に大きな負担がかかりました。 成果と混乱 RVが本格的に開発に参入する中、日本チームではレビュー作業に多くの時間が割かれることに不満が高まりました。特に、自分たちも開発に携わりたいというメンバーの声があり、レビュー作業が増える現状にフラストレーションが溜まっていました。レビューによって開発リソースが圧迫されていたことも問題でしたが、私自身がリーダーとして、日本チームとの認識共有や方針の説明が不足していたことも一因でした。 当初、RVの品質向上を優先し、その後に日本チームが再び実装に戻る方針を考えていましたが、この計画を十分に共有できず、不満が広がってしまいました。日本チームとRVの双方に対して、明確に計画やビジョンを共有するべきだったと感じています。 そこで、まず KPI(レビュー指摘数、開発量に対するレビュー時間など) を設定し、それを基にRVの品質を向上させる施策を進めました。また、日本チームが感じる課題がRV側では同じように捉えられていない場合もあり、 定量 的な情報を用いて認識のズレを埋める努力をしました。 さらに、RVでテスト仕様書やフォーマットの整備を進める一方、日本チームには新技術採用の見通しや関連実装の機会が増えることを共有し、チーム間の連携強化を図りました。結果として、改善の兆しが見え始めましたが、依然として課題は残っている状況でした。 2024年度:RVの統一と安定期 チームの成熟と新たなステップ 現在、RVは開発10名、テスター3名体制で、日本チームと共にほぼすべての機能実装を担当しています。レビューの連携も次第に改善され、日本と ベトナム 間での出張を通じて認識を合わせ、同じチケット管理システムを活用により、タスクの透明性を確保されてきました。言語の違いはあるものの、成果物の形式化やドキュメントベースでの進行管理が効果を発揮し始めています。 また、品質向上のため、KPIに基づいた改善活動を計画的に進めており、着実な成果が見られています。さらに、 ドメイン 設計の見直し(アグリゲイト デザインパターン の導入検討など)を進めており、RVの設計・実装を明確化することで、日本チームとRVが並行して開発を進めやすい環境の整備を検討しています。 今後の展望 今後、チーム トポロジー のプ ラク ティスを活用し、RVをストリームアラインドチーム、日本チームをイネイブリングチームとして役割を明確にしていきたいと考えています。 これにより、RVは機能実装を主導し、日本チームは技術支援や リファクタリング 、ソフトウェア改善に集中できる体制を構築します。両チームが効率的に連携し、モチベーションを高めながらプロジェクトを進めていく予定です。 今後のチーム認識 まとめ:グローバル開発で築く強いチームビルディングの5つのポイント 1.基本的な認識合わせの明文化と資料化 問題点や組織としてあるべき状態を文書化し、全員が共通の理解を持つ。 2.タスクやドキュメントの形式化 作業の流れや文書をフォーマット化し、属人化を防ぐ。 3.同じ資料・チケット管理システムの使用 言語が異なる場合でも、できる限り同じ資料を共有し、透明性と一貫性を確保。 4. KPIの設定による品質改善 定量 情報に基づいてチームの改善を進め、品質向上を目指す。 5.チーム開発を意識したソフトウェア設計の改善 良い設計手法を採用し、人員増加による効率向上を実現して ベトナム の豊富なIT人材を活かす。 さいごに 最後までお読みいただき、ありがとうございます! ラク ス ベトナム (RV)との協働を通じて経験したチームビルディングの遷移とそのポイントをご紹介しましたが、いかがでしたでしょうか。この記事を読んで「興味を持った」「もっと知りたい」と感じられた方は、ぜひ当社の採用サイトや主催イベントの情報をご覧ください!
アバター
はじめに ラク スでは、「PdM(プロダクトマネージャー)」をテーマにした対談イベントを積極的に開催しております。 本記事では、その目的や、各回の概要・内容、今後の開催テーマをご紹介します。 イベントでのリアルな取り組み紹介を通じて、各社の開発戦略やPdM組織の役割、さらにはプロダクトを通じた顧客課題解決への想いを知る一助になれば幸いです。 ※明日開催のイベント情報もあります!是非最後までご覧ください! はじめに 開催背景と目的 各回の内容 【PdM Meetup】プロダクトマネジメントの最適解とは?~BtoB SaaS 3社合同イベント 【ログラス×ラクス】PdM Meetup ~製品・組織フェーズによって異なるPdMの役割や考え方〜 【弁護士ドットコム × ラクス】PdM Meetup〜ビジョンを成功に導く効果的なPdM組織とは? 【日経 × ラクス】PdM Meetup〜 toC/toBの違いから学ぶプロダクトマネジメント実践 ARR300億超え! ラクスPMが語るPM組織と仕事【PM Career】 番外編:RakusTechConference2024での発表もご紹介 今後開催予定のイベント 9/18開催!【オープンロジ×ラクス】バーティカル/ホリゾンタルの違いから学ぶプロダクトマネジメントのアプローチ 最後に 開催背景と目的 「顧客志向」を大事にし続け、マルチプロダクトで成長を続けてきた ラク ス。 ラク スのPdM組織「製品管理課」は「ビジネス、エンジニアリングの架け橋となり、カスタマーサクセスに導く、売れる製品を実現する」をミッションとし、 顧客課題の解決につながる製品づくりのため積極的に活動中です。 本記事でご紹介するイベントの目的は、PdM組織の役割や取り組み、課題をリアルに知っていただくことです。 より具体的には、組織の目的や役割、製品解像度の上げ方や ステークホルダー との関わり方、マネジメントの方法、 PdMのあるべき人物像などを参加者の皆さんと一緒に考え、少しでもお伝えできればと考えています。 また、各社で プロダクトマネジメント を取り巻く組織のあり方は大きく異なります。 対談させていただくことで、それぞれのPdMの役割や考え方が浮き彫りになり、参加者皆が大きな学びを得られると考えています。 各回の内容 【PdM Meetup】 プロダクトマネジメント の最適解とは?~BtoB SaaS 3社合同イベント rakus.connpass.com <概要> BtoB SaaS サービスを展開する3社(Chatwork株式会社、株式会社LegalOn Technologies、株式会社 ラク ス)で、 プロダクトマネジメント のあり方について下記 トーク テーマでディスカッションしました。 PdMの責任と役割定義について PdMの責任と役割は、企業・製品・組織フェーズによって異なります。本セッションでは、PdMの責任と役割に焦点を当て、プロダクト開発を取り巻く ステークホルダー との連携方法やその苦労、今後の方向性について話し合います。 PdMによる顧客志向な組織作り PdMはどのようにお客様と向き合っているのか、そしてお客様の声や課題をどのようにプロダクト開発に活かし、またプロダクト開発組織をどう顧客志向に導いているのかについてディスカッションします。 PdM業務での仕組み化について 製品フェーズが進んでくると、1プロダクトで複数のPdMが関わります。そういった状  況下で、 プロダクトマネジメント の質を落とさずお客様に選ばれる製品づくりを継続的に遂行する必要があり、再現性は重要なキーワードとなります。PdM業務の中で「仕組み化」をキーワードにどういった取り組みをしているかについて議論をします。 <登壇者紹介> ・松下 三四郎 様 | Chatwork株式会社 コミュニケーションプラットフォーム本部 プロダクトマネジメント 部 Product Strategist 大阪府 出身、神奈川県 三浦郡 葉山町 在住。ソフトウェアエンジニアのバックグラウンドを持ちながら、 ディー・エヌ・エー 、スマートニュース、ヤフー、プレイドで、本部長、事業責任者などを務め、主に プロダクトマネジメント 領域を担当。 ToC 、 ToB 問わず、多くのプロダクトのグロース戦略に関わり、描き、成長に導く実績を持つ。2023年6月Chatworkにジョイン。趣味はDJ25年目。ヨーロッパツアー、大型フェス出演、書籍出版など。 ・泉 真悟様|株式会社LegalOn Technologies プロダクトマネジメント グループ マネージングディレクター 1999年4月株式会社 PFU に入社。文書管理、AI OCR 、 電子帳簿保存法 対応ソリューションをはじめとするドキュメント関連ソフトウェアの企画に従事。 株式会社Cogent Labsの マーケティング & プロダクトマネジメント のシニアプロダクトマネージャー、 執行役員 を経て、2023年4月に株式会社LegalOn Technologiesに入社。 現在、 プロダクトマネジメント グループにて、LegalForceキャビネのプロダクト マーケティング マネージャー等を担当。 ・稲垣 剛之|株式会社 ラク ス 楽楽精算開発部 PdMチーム マネージャー 大学卒業後、独立系 SIer 企業に入社。約10年間、WEB系 システム開発 ・運用のPG、SE、PMを経験。その後、ファッション ECサイト の立ち上げ直後から約9年間、開発責任者として参画。最終的には企画・デザイン・開発といったプロダクト開発全般の責任者を担当。 ラク スに入社後は楽楽精算のPdM及びプロダクトサポート、QAといった、開発の中でもプロダクトを全体視点で見る組織のマネージャーを経て、現在は プロダクトマネジメント 領域に特化した組織のマネージャー ※以降各回とも、当社からはPdM組織マネージャーが登壇しております。 <当社セッションのポイント(一部)> ・PdMはなぜやるのか、スコープ、ゴールに集中し、製品戦略上の優先度提示を行う ・顧客課題の解像度向上には、CSを通じたお客様の声の収集や、アンケート、 ヒアリ ングを実施。王道の方法をしっかりやることに注力。 ・CS・営業によるお客様の声の収集内容をフォーマット化し、解像度向上に努めている。 【ログラス× ラク ス】PdM Meetup ~製品・組織フェーズによって異なるPdMの役割や考え方〜 loglass-tech.connpass.com <概要> 製品や組織のフェーズが異なるBtoB SaaS サービスでは、製品やPdMを取り巻く組織の組み方はどう異なるのか下記 トーク テーマで対談しました。 ミッション達成に近づけるプロダクト開発とは 製品や組織フェーズによってPdMに求められるミッションも違います。ミッション達成のために求められる素養や考え方は何かをディスカッションします。 PdMの育成と採用の実例 PdMの育成と採用は会社ごとに異なります。各社が自社の状況を踏まえた上で重要視している考え方、捨てている考え方とともにその実例を紹介します。 <登壇者紹介> 斉藤 知明様|株式会社ログラス 執行役員 VPoP 東京大学 在学時にAI研究に従事、動画像を対象としたDeepLearningの研究でICME2016に論文が採択される。在学中に英単語アプリmikanを運営する株式会社mikanを協同創業しCTOに従事。その後Fringe81株式会社(現Unipos株式会社)に入社、ピアボーナスサービスUniposを立ち上げ子会社化、代表に就任。2023年5月、株式会社ログラスに入社。 執行役員 VPoPとして従事。「すべての挑戦が報われる社会に」を個人ミッションとする。 <当社セッションのポイント(一部)> ・「製品をグロースさせることで、企業の成長に貢献すること」への強い共感が大前提。 ・製品フェーズの変化に対応するための、思考力、行動力、変化への適応力が大事。 ・オンボーディングでは顧客理解、製品理解、 ステークホルダー との関係性理解に注力。 【弁護士ドットコム × ラク ス】PdM Meetup〜ビジョンを成功に導く効果的なPdM組織とは? rakus.connpass.com <概要> 効率的なPdM組織はどのようにあるべきか、下記の トーク テーマで対談しました。 PdMの責任と役割定義について PdMの責任と役割は、企業・製品・組織フェーズによって異なります。本セッションでは、PdMの責任と役割に焦点を当て、プロダクト開発を取り巻く ステークホルダー との連携方法やその苦労、今後の方向性について話し合います。 プロダクト 開発プロセス の工夫とこだわり 機能ごとにMVPとして何を作り、何を作らないか、開発効率を高めるためにどのような工夫や取り組みを行ってきたか、理想のプロセスとはどのようなものか、各社の取り組みを比較・紹介しつつ、ディスカッションします。 PdMの育成と採用の実例 PdMの育成は会社ごとに異なります。各社が自社の状況を踏まえた上で重要視している考え方、捨てている考え方とともにその実例を紹介します。 <登壇者紹介> 松井 聡様|弁護士ドットコム株式会社 クラウド サイン事業本部 プロダクトマネジメント グループ 大学院修了後、スタートアップ企業でイベント管理システムのプロジェクトマネージャーとしてキャリア開始。 マーケティング 系 SaaS のQAエンジニア、バックエンド開発エンジニアを経験後、プロダクトマネージャーとして製品企画を担当。 フィンテック 系企業のプロダクトマネージャーを経て、弁護士ドットコムに参画。 B2B 領域における プロダクトマネジメント と開発の広範囲な知識と経験が強み。 <当社セッションのポイント(一部)> ・PdM組織の発足経緯 ・プロダクト4階層で見るPdMとPMMの役割分担 ・ ディスカバリー に集中するための仕組み化と、迅速な情報共有に徹底的にこだわる 【日経 × ラク ス】PdM Meetup〜 toC / toB の違いから学ぶ プロダクトマネジメント 実践 rakus.connpass.com <概要> BtoCサービス『日経電子版』を展開する 日本経済新聞社 に登壇いただき、BtoBサービスの プロダクトマネジメント との違いや共通点を探りました。組織の位置づけや優先順位、KPIについて、下記 トーク テーマで対談を行いました。 ・PdMの開発組織での立ち位置や役割について PdMの立ち位置や役割は、開発組織の規模や構造によって異なります。本セッションでは、PdMがどのように開発チームと連携し、効果的なプロダクト開発を推進しているかを探ります。 具体的には、PdMが開発チーム内で果たすべき役割、コミュニケーション方法、また各組織における成功事例と課題についてディスカッションします。 ・プロダクト開発の優先順位やKPIについて プロダクト開発において、優先順位の設定とKPIの管理は重要な役割を果たします。本セッションでは、PdMがどのようにしてプロダクトの優先順位を決定し、KPIを設定・管理しているのかに焦点を当てます。 toB / toC サービス観点での違いにも注目です。 <登壇者> 鈴木 陽介様|株式会社 日本経済新聞社 デジタル編成ユニット Product Manager/Engineering Manager 大学卒業後、新卒で 日本経済新聞社 に入社。ウェブ向けの編集者、新聞記者などを経て、日経電子版の企画開発に従事。米国駐在を経て、2022年から日経電子版のProduct Managementを担当。 <当社セッションのポイント(一部)> ・PdMとPMMの役割分担 ・優先度決定のロジック ・ユーザー価値を測るノーススターメトリックの採用 ARR300億超え! ラク スPMが語るPM組織と仕事【PM Career】 pmclub.connpass.com <概要> 日本最大級のプロダクト開発コミュニティ PM Clubの「PM Career CrossTalk vol.7」に登壇させていただきました。 ラク スのプロダクト、PdM組織の概要や役割のほか、 ラク スのPdMならではの楽しさや難しさ、人物像についてもご紹介しました。 <登壇者> PdM組織マネージャーの稲垣のほか、楽楽精算PdMの紀井と、楽楽明細PdMの柴が登壇いたしました。 <当社セッションのポイント(一部)> ・ベストオブブリード型開発で、プロダクトの顧客課題に焦点を絞って解決できる組織構造 ・PdMは ディスカバリー の領域に集中。仕組み化にも注力 ・製品満足度指標にはノーススターメトリックを設定し運用。 番外編:RakusTechConference2024での発表もご紹介 当社PdM組織の活動内容を、当社主催のRakus TechConference2024で発表いたしました。 上記各イベントでのセッションの雰囲気も感じていただけるかと思いますので、是非ご覧ください。 speakerdeck.com 今後開催予定のイベント 9/18開催!【オープンロジ× ラク ス】 バーティカル / ホリゾン タルの違いから学ぶ プロダクトマネジメント のアプローチ rakus.connpass.com <概要> 「物流版 AWS 」をコンセプトに物流プラットフォームを展開する「オープンロジ」様に登壇いただきディスカッションを行います。 いわゆる バーティカル SaaS (業界/業種特化型)と ホリゾン タル SaaS (業種不問、汎用型)というターゲットの違いによって プロダクトマネジメント の在り方が大きく異なるのではないか、というテーマで開催いたします。 製品やお客様との向き合い方について ターゲットの異なる2社がどのように製品やお客様と向き合っているか?具体的にはユーザーとの接点・ ヒアリ ング・課題感の違いなどを軸にディスカッションします。 今どのような課題にチャレンジしているのか(今後求めるPdM像) 2社のPdMが現在どのような課題に取り組んでいるのかをざっくばらんに語り合います。本 トーク から2社が求めるPdM像も見えてくるのではないでしょうか。ターゲット・市場の違いによるPdMのあるべき姿なども注目です。 <登壇者> 高橋 祐哉様| 株式会社オープンロジ プロダクトマネージャー/UIUXデザイナー HR系プロダクトのPM/カスタマーサクセス/開発を経験し、DevOps組織のマネジメントを数年担当した後、業務アプリの ユーザビリティ をなんとかしたいとデザイナーに転身。現在はプロダクト戦略/UX/組織作りに注力してプロダクト開発を行っています。 開催は明日となります!ご都合のつく方は是非お越しください。 最後に 今後も各社様と共催形式で、プロダクトの成長を支える開発戦略や プロダクトマネジメント 、技術マネジメント領域の発信に取り組んでいきますので、是非ご参加ください! また、当社とイベントを共催頂ける会社様もご連絡お待ちしております!!是非 X でお気軽にメッセージ頂けますと幸いです。 https://x.com/DevRakus
アバター
はじめに 事前準備 トリガーを使用する方法 補足:トリガーと関数のみ消す方法 まとめ はじめに こんにちは! エンジニア2年目のTKDSです! PostgreSQL でのテーブル変更検知方法について調べました。 今回はトリガーを使用する方法について説明します。 事前準備 DBの準備(compose. yaml ) services : db : image : postgres:16.4-bullseye container_name : db environment : POSTGRES_USER : postgres POSTGRES_DB : postgres POSTGRES_PASSWORD : postgres ports : - "127.0.0.1:5432:5432" volumes : - db_data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck : test : [ "CMD-SHELL" , "pg_isready -U postgres -d postgres" ] interval : 30s timeout : 10s retries : 5 start_period : 10s volumes : db_data : 初期化 SQL (init. sql ) CREATE TABLE users ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT NOT NULL , email TEXT NOT NULL , created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); INSERT INTO users (name, email) VALUES ( ' user1 ' , ' user1@example.com ' ), ( ' user2 ' , ' user2@example.com ' ), ( ' user3 ' , ' user3@example.com ' ); トリガーを使用する方法 PostgreSQL の機能であるトリガーを使用すると、特定のイベントが発生した時に指定した関数を実行することができます。 トリガーについての詳細は ドキュメント をみてください。 下記の例では、トリガーを使用して、テーブルの操作をしたときのログを記録します。 create_table. sql CREATE TABLE audit_log ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, operation TEXT, -- 操作の種類(INSERT、UPDATE、DELETE) old_data JSON, -- 変更前のデータ(UPDATEやDELETE時) new_data JSON, -- 変更後のデータ(INSERTやUPDATE時) query TEXT, -- 実行されたクエリ changed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -- 変更時刻 ); create_trigger. sql CREATE OR REPLACE FUNCTION log_table_changes() RETURNS TRIGGER AS $$ BEGIN IF TG_OP = ' INSERT ' THEN -- INSERT操作の新しいデータを記録 INSERT INTO audit_log (operation, new_data, query) VALUES ( ' INSERT ' , row_to_json(NEW), current_query()); ELSIF TG_OP = ' UPDATE ' THEN -- UPDATE操作の変更前と変更後のデータを記録 INSERT INTO audit_log (operation, old_data, new_data, query) VALUES ( ' UPDATE ' , row_to_json(OLD), row_to_json(NEW), current_query()); ELSIF TG_OP = ' DELETE ' THEN -- DELETE操作の削除されたデータを記録 INSERT INTO audit_log (operation, old_data, query) VALUES ( ' DELETE ' , row_to_json(OLD), current_query()); END IF ; RETURN NEW; END ; $$ LANGUAGE plpgsql; CREATE TRIGGER audit_trigger AFTER INSERT OR UPDATE OR DELETE ON users FOR EACH ROW EXECUTE FUNCTION log_table_changes(); まず CREATE OR REPLACE FUNCTION log_table_changes() に続く SQL でトリガー起動時に実行する関数を定義します。 TG_OP は実行された操作を表す特殊な変数です。 NEW はINSERT/UPDATE操作によって更新された行を保持する変数です。 OLD はUPDATE/DELETE操作によって更新される前の行を保持する変数です。 これらについては詳しくは トリガプロシージャ のページに載ってます。 current_query() は、現在実行中の SQL クエリを文字列として返す PostgreSQL の関数です。 詳細は システム情報関数 に載ってます。 この関数では操作を判別し、データを記録してます。 3つが分かれているのは、 OLD がUPDATE/DELETEしか対応しておらず、 NEW がINSERTとUPDATEしか対応してないのと、操作種別を固定値でいれるためです。 次にトリガー作成部分について説明します。 CREATE TRIGGER audit_trigger に続く部分です。 一行目でトリガー名、2行目で実行タイミングと対象操作、3行目でトリガーの起動単位(行)と実行される関数を指定してます。 下記に実行手順と結果を示します。 準備 # コンテナ起動 docker compose up # 記録用テーブル作成 cat ./sql/create_table.sql | docker exec -i db psql -U postgres -d postgres # トリガー作成 cat ./sql/create_trigger.sql | docker exec -i db psql -U postgres -d postgres 試す コンテナに接続 docker exec -it db psql -U postgres -d postgres 動作確認用 SQL を実行 postgres=# INSERT INTO users (name, email) VALUES ('auditlog1', 'aaa@example.com'); INSERT 0 1 postgres=# INSERT INTO users (name, email) VALUES ('auditlog2', 'sss@example.com'); INSERT 0 1 postgres=# SELECT * FROM users; id | name | email | created_at ----+-----------+-------------------+------------------------------- 1 | user1 | user1@example.com | 2024-09-10 14:34:03.56919+00 2 | user2 | user2@example.com | 2024-09-10 14:34:03.56919+00 3 | user3 | user3@example.com | 2024-09-10 14:34:03.56919+00 4 | auditlog1 | aaa@example.com | 2024-09-10 14:35:49.888486+00 5 | auditlog2 | sss@example.com | 2024-09-10 14:36:02.663281+00 (5 rows) postgres=# UPDATE users SET email = 'update@example.com' WHERE name = 'auditlog2'; UPDATE 1 postgres=# DELETE FROM users WHERE name = 'auditlog1'; DELETE 1 postgres=# SELECT * FROM users; id | name | email | created_at ----+-----------+--------------------+------------------------------- 1 | user1 | user1@example.com | 2024-09-10 14:34:03.56919+00 2 | user2 | user2@example.com | 2024-09-10 14:34:03.56919+00 3 | user3 | user3@example.com | 2024-09-10 14:34:03.56919+00 5 | auditlog2 | update@example.com | 2024-09-10 14:36:02.663281+00 (4 rows) postgres=# SELECT * FROM audit_log ORDER BY changed_at DESC; 結果は以下の通りです。 操作が記録されているのが確認できました。 補足:トリガーと関数のみ消す方法 以下のコマンドで消せます。 DROP TRIGGER IF EXISTS audit_trigger ON users; DROP FUNCTION IF EXISTS log_table_changes(); DROP TRIGGER IF EXISTS トリガー名 ON テーブル名; DROP FUNCTION IF EXISTS 関数名; 簡単に投入・削除ができるのでテスト時など記録がほしいときだけいれて、終わったら消すこともできますね、便利です。 まとめ 今回は PostgreSQL のデータ変更検知方法について調べました。 トリガーを使用する方法はDBへの負荷などデメリットもあるみたいですが、簡単で導入しやすそうです。 活用例として、テスト時にステートストアのレコードの内容が期待通りに遷移してるか確認する、ORMを通して実際に実行された SQL を記録するなどに使えそうです。 ログで検知する方法やWALを使用する方法もあるみたいなのでいずれ調べてみたいです。 ここまで読んでいただきありがとうございました!
アバター
はじめまして。私は楽楽精算の機能開発チームのマネージャーを務めている高波です。 今回のブログでは、楽楽精算の開発チームの組織構成、これまでの取り組み、そして今後の展望についてお話しします。 チームの紹介 開発組織構成 チームのミッション チーム体制と担当業務 取り組み事例 二重計上リスクを防ぐ機能開発 申請の差し戻し負荷を軽減する機能開発 今後の展望 業務効率を向上させるUI/UXの改善 利用者増に対応したパフォーマンスの向上 経費精算業務へのAI活用 チームの紹介 開発組織構成 楽楽精算の開発は、3つの課に分かれて行っています。 内部構造の刷新(技術負債の解消)とオフショア開発を担当する開発1課、機能開発を担当する開発2課、そしてモバイルアプリを担当するモバイル開発課です。 今回は、私が担当する開発2課について紹介します。 チームのミッション 顧客に求められる機能を開発・提供することで顧客の経費精算業務を楽にする 開発2課のミッションは、顧客の経費精算業務を「楽」にするため、最も効果的な解決策を製品機能として設計・開発し提供することです。 ラク スのミッション「ITサービスで企業の成長を継続的に支援します」を体現するために、私たちは楽楽精算を通じてお客様の業務を効率化し、企業の成長をサポートしています。 チーム体制と担当業務 現在、開発2課には課長の私を含め12名のエンジニアが所属しています。 チームはベテランから新卒まで多様なメンバーで構成され、3つのサブチームに分かれて開発を行っています。 主な業務内容は以下の通りです。 法制度や顧客業務の効率化に基づく機能開発 インシデント発生時のプログラム改修 経費精算業務におけるAI活用の機能検証 機能開発においては、まずPMMとPdMがお客様の課題を調査し、その中で最も高い価値を提供できるものを決定します。 この結果に基づいて開発リストとPRDが作成されます。 開発2課は、そのPRDをもとに具体的な機能要件やUX設計を行い、製造を担当します。 要求仕様からどれだけお客様にとって価値ある機能を提案できるかが、私たちの腕の見せ所です。 楽楽精算は2009年7月にサービスを開始し、今年で15年目を迎えます。 システムがカバーする業務は多岐にわたり、複雑です。 これらの機能を深く理解することが、顧客の課題に対して最適な解決策を提示するために不可欠です。 また、お客様の業務プロセスやシステムをどのように利用しているかを理解することで、真のニーズに応える価値ある機能を提供できると考えています。 このため、チーム内で製品機能や顧客業務に関する勉強会を開催したり、開発案件に取り組む際にPdM主催の要求に至った顧客課題の理解を深めるための説明会に参加したりと、技術力と顧客視点を強化する取り組みを行っています。 取り組み事例 楽楽精算は、 インボイス 制度や 電子帳簿保存法 対応に伴い、システムが法制度に対応することで 経理 部門の業務効率を高める機能を開発してきました。 現在は、より多くの顧客に選ばれ、選ばれ続けるための機能開発に注力しています。 最近の取り組み事例として、以下の2つを紹介します。 二重計上リスクを防ぐ機能開発 電子帳簿保存法 の普及に伴い、同一の領収書や請求書が二重に使用されるリスクが増加しました。 これにより、 経理 担当者の確認業務が増加するという課題が発生しています。 楽楽精算では、この課題を解決するため以下の機能を実装しました。 領収書や請求書の登録・申請・承認時に二重申請を防止する機能 承認済みの二重申請を検知する機能 これにより、申請者が誤って同じ領収書や請求書を登録してしまうケースを警告し、 経理 担当者の確認業務を軽減することができます。 申請の差し戻し負荷を軽減する機能開発 インボイス 制度の導入に伴い、事業者登録番号の管理が必要になりました。 しかし、システム利用者が制度を十分に理解していないため、未入力や誤入力が発生し、申請の差し戻しが増えるという課題が発生しています。 これを解決するため、楽楽精算では以下の機能を実装しました。 領収書や請求書の登録時に事業者登録番号が未入力の場合の警告機能 登録された事業者登録番号を承認者が修正する機能 この機能により、申請に不備があった場合の差し戻しを軽減し、経費精算の申請から承認完了までのリードタイムを短縮することが可能です。 今後の展望 楽楽精算は法制度に関する機能開発を経て、今後もお客様の課題を継続的に解決するために以下の取り組みを続けていきます。 業務効率を向上させるUI/UXの改善 経年による操作性の低下を改善し、操作手順を整理することで、より直感的に操作できるUI/UXを実現します。 これにより、経費精算業務を一層効率的に行えるようにします。 利用者増に対応したパフォーマンスの向上 社員数や組織規模、取引量・データ量が多いお客様にも安心してご利用いただけるよう、システムのパフォーマンスを向上させていきます。 経費精算業務へのAI活用 AIの普及に伴い、バックオフィス業務での活用への期待が高まっています。 楽楽精算では、AIを活用してお客様に価値を提供できる機能を開発・提供していきます。 これらを達成するために、開発組織のスケールと開発リードタイムの短縮を両立させる必要があります。 お客様に最も価値ある機能をタ イムリ ーに提供できるよう、エンジニア一人ひとりの成長と 開発プロセス の強化を目指していきます。 ◆ 関連記事のご紹介 career-recruit.rakus.co.jp career-recruit.rakus.co.jp career-recruit.rakus.co.jp
アバター