Amazon Web Services ブログ

アプリケーション開発者のための PostgreSQL アーキテクチャに関する検討: パート 1

アプリケーション層は多くのクラウドアーキテクチャで世界中がアクセスする部分ですが、使用しているデータベースに合わせてアプリケーションを最適化する方法を検討することはほとんどないようです。リレーショナルデータベースエンジンを使用するときは、スキーマの設計だけでなく、アプリケーションが管理可能で、スケーラブルで、パフォーマンスが高いことを保証するために、データベースがストレージシステムに対してデータを読み書きする方法を理解することが重要です。シリーズのパート 1 となるこの投稿では、PostgreSQL の主要な用語について説明し、次に、Amazon Aurora PostgreSQL 互換エディションまたは Amazon Relational Database Service (Amazon RDS) for PostgreSQL を使用する場合の自動コミット処理、自動バキューム処理、およびトランザクション中のアイドル状態についてもう少し詳しく説明します。

PostgreSQL パラメータの変更: どこで、いつ、なぜ

パラメータは、データベースと PostgreSQL でその要素プロパティを定義するために使用されます。PostgreSQL では、新しいデータベースを作成するときにデフォルトのパラメータが設定されており、多くのシステムでは、通常はデフォルトパラメータのパフォーマンスが良く、チューニングは必要ありません。システムが成長し、規模が拡大し、負荷が高まるにつれて、最適なパフォーマンスを得るために一部のパラメータを調整する必要があります。

セルフマネージドデータベースと AWS マネージドデータベースのどちらを使用しているかによって、異なるパラメータ値を変更する必要があります。セルフマネージドデータベースの場合、パラメータの変更は postgresql.conf ファイルで行われます。AWS マネージドデータベースでは、postgresql.conf ファイルへのアクセスは制限されているため、AWS マネジメントコンソールAWS コマンドラインインターフェイス (AWS CLI)、SDK、または AWS CloudFormation を介してのみ、基礎となるデータベースまたはクラスターパラメータグループに対して変更を行うことができます。

postgresql.conf (セルフマネージド)

セルフマネージドデータベースでは、PostgreSQL クラスター全体にパラメータを設定する場合、このファイル (PostgreSQL データディレクトリ内) に変更を加えます。詳細については、PostgreSQL コミュニティドキュメントの パラメータ設定 を参照してください。

RDS DB パラメータグループ (AWS マネージド)

Amazon RDS のクラスターレベルとデータベースレベルのパラメータグループには、インスタンスクラスとサイズに応じたデフォルト設定があります。パフォーマンスを向上させるために他の値に変更が必要な場合は、コンソールAWS CLI、SDK、または AWS CloudFormation を使用して新しいパラメータグループを作成できます。詳細については、DB パラメータグループの作成 を参照してください。

セッションレベル (セルフマネージドまたは AWS マネージド)

PostgreSQL のパラメータの多くは、セッションレベル (1つ以上のトランザクションで構成される) で変更できます。これらのパラメータは、そのワークロードの中で実行されたクエリにのみ適用され、データベース全体には適用されません。これらのパラメータは、SET コマンドを使用して変更できます。詳細については、PostgreSQL コミュニティドキュメントの SET を参照してください。

PostgreSQL のコンセプト

このセクションでは、PostgreSQL データベースの運用に不可欠な PostgreSQL のコアコンセプトについて説明します。

トランザクション

PostgreSQL では、トランザクションは単一の操作として実行される一連の SQL ステートメントです。このトランザクションモデルは、トランザクション内の全てのステートメントがデータベースに正常にコミットされるか、ステートメントが失敗した (もしくはエラーが発生した) 場合にロールバックされることを保証します。 PostgreSQL は ACID に準拠しています。ACID とは、データベース操作において原子性、一貫性、独立性、永続性が常に維持されることを保証する一連のデータベース特性です。

  • Atomicity (原子性) – データベースの原子性により、トランザクションが完了するまでオープントランザクションは他のトランザクションから見えなくなり、その後、すべての変更が単一のユニットとして同時に表示されます。
  • Consistency (一貫性) – 一貫性により、データにコミットされた変更があった場合、その変更が数日前、数時間前、または数秒前にコミットされたかどうかにかかわらず、新しいトランザクションでその変更を確認できます。また、サーバーがクラッシュした後でも、データをエラーなく回復できます。
  • Isolation (独立性) – PostgreSQL は、他の同時実行中のトランザクションへのデータ変更の可視性を制御するためのさまざまな分離レベルを提供します。デフォルトでは、read commited の分離レベルが使用されます。これにより、トランザクションは、他のトランザクションによってコミットされた後にのみ変更を確認できます。
  • Durability (永続性) – 永続性は、コミットされたすべての変更をデータベースが追跡することを保証します。そのため、異常なキャンセルが発生した場合、データベースは元の状態にロールバックするか、トランザクションログを再生して中断したところから続行できます。

トランザクションの詳細については、トランザクション を参照してください。ACID コンプライアンスの詳細については、PostgreSQL コミュニティドキュメントの用語集 を参照してください。

ロッキング

PostgreSQLは、複数のトランザクション間の競合を防ぐために、ロッキングメカニズムを使用してデータへの同時アクセスを管理します。PostgreSQL には次の 2 種類のロックがあります。

  • 共有ロック – これにより、複数のトランザクションが特定のデータオブジェクトを同時に読み取ることができます。
  • 排他ロック – ロックが解除されるまで、他のトランザクションがデータオブジェクトにアクセスできないようにします。

PostgreSQLはロックプロトコルを利用しており、ロックは特定の順序で取得および解除されます。これにより、データベースをデッドロックから保護できます。デッドロックとは、2 つ以上のトランザクションが互いにリソースアクセスのロックの解放を待っている間にブロックされる状況です。 PostgreSQL は行レベルのロックもサポートしています。これにより、テーブル全体ではなく個々の行をロックできるため、同時アクセスをきめ細かく制御できます。最後に、 PostgreSQL はロックエスカレーションを実装しています。これにより、 1 つのオブジェクトに対する多数のロックを、 1 つの高レベルのロックに置き換えて、全体的なロックオーバーヘッドを削減できます。
ロックの詳細については、 明示的ロック を参照してください。

VACUUM

PostgreSQL は、データの行をタプルと呼ばれる構造に格納します。タプルが論理的に更新または削除されても、データベースには目に見えないバージョンが残っています。削除または更新コマンドと同時に実行されているトランザクションが、トランザクションが開始された時点からのデータベースのスナップショットで終了できるように、タプルが保持されます。 PostgreSQL は VACUUM プロセスを使用して、古い不可視タプルのバージョンで使用されていたスペースを解放し、ストレージを再利用します。更新および削除された行はデッドタプルとしてマークされ、後で VACUUM プロセスによってクリーンアップされます。異なるトランザクションが同じタプルで同時に処理される可能性があるため、これらはすぐにはクリーンアップされません。マルチバージョン同時実行制御 (MVCC) によって精度が保証されます。たとえば、元のバージョンをすぐに削除すると、同時に実行されているトランザクションは正確にロールバックされません。VACUUM を実行するだけでもデッドタプルは削除されますが、VACUUM コマンドには他にも理解しておかなければならないバリエーションがあります。これについてはこのセクションで説明します。

VACUUM ANALYZE

このコマンドは、デッドタプルを削除し、そのテーブルの内容に関するデータベース統計を収集します (これらの統計は pg_statistic システムカタログに保存されます) 。このデータは PostgreSQL クエリオプティマイザによって使用され、クエリ実行時に最も効率的なクエリ実行プランを決定するのに役立ちます。

VACUUM FREEZE

このオプションは、デッドタプルをクリーンアップするだけでなく、VACUUM が古い行をフリーズしてすべてのユーザーに表示するか、削除するかを決定するために使用するカットオフ期間 (トランザクション XID 単位) を指定して、タプルを積極的にフリーズします。これにより、VACUUM プロセス中に古いアクティブなタプルのデータが失われたり、トランザクションのラップアラウンドによる破壊を防ぐことができます。トランザクションは固有の XID で追跡され、固定番号であるため使い果たされる可能性があります。適切に監視しないと、運用データベースの XID 番号が登録可能な上限に達し、過去の XID 番号が現在になって再利用できるというラップアラウンドが発生する可能性があります。このラップアラウンド破損により、データの整合性を保護するためにデータベースがシャットダウンする可能性があります。

VACUUM FULL

このオプションは、内容を完全に書き換えることにより、テーブル (またはデータベース) からデッドタプルを削除します。データを物理的に再配置してより徹底的なクリーンアップを行うことで、削除および更新されたタプルからディスク容量を再利用します。その結果、ストレージがより圧縮され、クエリパフォーマンスが向上します。このオプションは、テーブルに対して排他ロックを作成し、操作が完了するまで他のすべてのアクセスを防止するため、通常の運用環境では使用しないでください。

PostgreSQL のタイムアウト関連のパラメータ

ベンチマーク中にワークロードがどのように動作するかを見積もることはできますが、本番環境のワークロードが予期しない動作をすることがあるため、タイムアウト設定は必要です。タイムアウト設定を適切に設定することは、ワークロードの実行中の異常からクラスターを保護するためのセーフガードとして機能します。これらの設定は、データベースのライフサイクルを通じて調整可能であり、また、調整する必要があります。グッドプラクティスはコネクションとリクエストのタイムアウトを設定することです。RDS for PostgreSQL データベースには便利なデフォルト設定がありますが、異なる設定でパフォーマンスが向上すると判断した場合は、本番環境に適用する前に、設定を 1 つずつ変更してテストしてください。

PostgreSQL データベースタイムアウト設定は、ステートメント、ユーザ、またはデータベースレベルで設定できます。アプリケーション開発者は、これらのタイムアウトパラメータと、それらがタイムアウトエラーを防ぐためにどのように機能するかを理解しておくと役に立ちます。アプリケーションのパフォーマンスとユーザーエクスペリエンスがタイムアウトエラーによって悪影響を受けないように調整できるタイムアウトに関するパラメーターは次のとおりです。

  • statement_timeout – クエリ内のステートメントがタイムアウトするまでのミリ秒数。デフォルトはタイムアウトなしです。
  • idle_in_transaction_session_timeout – このパラメータは、指定された期間を超えてアイドル状態が続いているオープントランザクションのあるセッションをすべて閉じます。これにより、そのセッションで保持されていたロックがすべて解放され、コネクションスロットを再利用できます。また、このトランザクションでのみ表示されるタプルをバキュームされ、肥大化を抑えることができます。デフォルト値は (0) で無効です。
  • idle_session_timeout – PostgreSQL バージョン 14 以降では、idle_session_timeout パラメーターを使用できます。アイドル状態であるが、指定された時間を超えてオープントランザクションの外にあったセッションはすべて閉じられます。デフォルト値は (0ms) で無効です。バージョン 13 以前では、idle_in_transaction_session_timeout パラメーターが使用されていましたが、開いているセッションのすべてのトランザクションが停止させるものでした。
  • client_connection_check_interval – PostgreSQL バージョン 14 以降では、client_connection_check_interval パラメーターを使用できます。このパラメータを使用すると、クエリ実行時にクライアントコネクションをオプションでチェックする間隔を設定できます。このチェックにより、カーネルからコネクションが閉じられたと報告された場合に、長時間実行されるクエリをより早く終了させることができます。デフォルト値は (0ms) で無効です。バージョン 13 以前では、サーバーはクエリが完了するまでコネクションの切断を検出しなかったため、コネクションが予期せず終了した場合、結果をクライアントに送り返すことができませんでした。

データベースの動作に関するPostgreSQLの機能とそのベストプラクティス

PostgreSQL は、実証済みのデータ整合性、信頼性、拡張性を備えた強力なオブジェクトリレーショナルデータベースシステムです。高性能で革新的なデータベースソリューションを提供できる強力なアーキテクチャを備えています。 PostgreSQL には、アプリケーション開発者がフリーのオープンソースの拡張可能な環境で運用可能なフォールトトレラントアプリケーションを構築するのに役立つ多くの機能があります。開発者は、データベースを再コンパイルしなくても、カスタム関数を構築し、さまざまなプログラミング言語のコードを使用できます。このセクションでは、データベースの動作に関するいくつかの機能とベストプラクティスについて説明します。

AUTOCOMMIT

PostgreSQLはACIDに準拠しているため、トランザクションを明示的にコミットする必要があります。これに役立つ機能の 1 つが、トランザクションをデータベースに自動的に保存する AUTOCOMMIT です。AUTOCOMMIT では、各ステートメントがトランザクション内で実行され、各ステートメントが自動的にコミットされます。デフォルト値は ON です。つまり、実行するために BEGIN または COMMIT コマンドを特別に発行する必要はありません。トランザクションは BEGIN で始まり、COMMIT コマンドで終わります。コミットはユーザーの変更を保存します。AUTOCOMMITOFF に設定されている場合、BEGIN コマンドは不要ですが、変更がデータベースに反映されるようにするには、ステートメントの最後に明示的に COMMIT コマンドを記述する必要があります。

AUTOCOMMIT のデフォルト設定はほとんどの環境で役に立ち、変更する必要はありません。たとえば、\COPY コマンドを使用して行を一括ロードする場合、AUTOCOMMIT を無効にする必要はありません。100 行の AUTOCOMMIT の一括挿入 (たとえば、INSERTVALUES (...), (...), (...), (...)のほうが、100 行の INSERT ステートメント (BEGIN; INSERTINSERTINSERT… ) からなる単一の COMMIT よりもパフォーマンスが向上します。これは、個々の BEGIN コマンドと COMMIT コマンドが大量のディスクアクティビティと CPU を消費するためです。ただし、特定の状況下では、設定をオフにした方が作業しやすい場合があります。1 回の挿入に失敗すると、すべての行がロールバックされます。これは、ビジネスニーズによっては問題となる可能性のある不要な部分的データロードを回避するためです。

この機能は、WHERE 句なしで DELETE ステートメントを誤って実行してしまったなどの間違いからすばやく回復できるため、アプリケーション開発者にとって有益な場合があります。AUTOCOMMIT をオフのままにしておきたい場合は、ワークロードの特定の側面に合わせてセッションレベルでこの設定を変更するのが最善です。AUTOCOMMIT をオフにしてステートメントを発行し、COMMIT コマンドを指定しなかった場合、PostgreSQL はこの投稿で前述したように COMMIT が指定されるまでロックを保持するため、次のステートメントはロック状態になります。

AUTOCOMMIT を使用する際のベストプラクティスを次に示します。

  • AUTOCOMMIT はグローバルにオンのままにしておき、ビジネス上の理由がある場合にのみ無効にします。次の点に注意してください。
    • AUTOCOMMIT をオンにすると、クエリはグループ化されません。
    • AUTOCOMMIT は暗黙的に発行されるため、どのクエリがコミットまたはロールバックされるかが不確実になることはありません。
    • PostgreSQL のオートコミットには暗黙的な BEGINCOMMIT があり、しばしばトランザクションブロックと呼ばれ、COMMIT コマンドは不要です。
    • AUTOCOMMIT をオンにすると、すべての SQL ステートメントが自動的にコミットされることが保証され、AUTOCOMMIToff でない限りロールバックはできません。
  • AUTOCOMMIT を無効にする場合は、セッションレベルでのみ無効にしてください。次の点に注意してください。
    • 無効にすると、データベースは常にトランザクションモードになり、COMMIT または ROLLBACK コマンドで明示的に終了する必要があります。
    • AUTOCOMMIT がオフの場合、間違いがあった時に、ロールバックを実行するのは簡単で、すべてが元に戻されます。これにより、変更がデータベースに保持されていないため、間違いから迅速かつ簡単に回復できます。

AUTOVACUUM

VACUUM はデッドタプルをクリーンアップする手動プロセスですが、AUTOVACUUMは削除されたタプルや更新された古いタプルの削除を自動化する定期的なバックグラウンドユーティリティデーモンです。AUTOVACUUM はデフォルトで有効になっており、データベースに多数の UPDATE コマンドと DELETE コマンドが発行されている場合は、そのパラメータをテストして調整する必要があります。PostgreSQL は MVCC モデルを使用しており、同時読み取り要求を完了できるように古い行バージョンを保持します。これらのステートメントの間、行は削除されず、古いバージョンはトランザクションが完了するまで保持されます。COMMIT コマンドが指定されていない場合、トランザクションは技術的にはまだ実行中であり、その行がまだ必要である可能性があるため、AUTOVACUUM はこれらの行を削除できません。オートコミットを無効にすることで発生する問題の例としては、データベースのロックや AUTOVACUUM メンテナンスの障害があります。

AUTOVACUUM を無効にすると、デッドタプルが削除されず、テーブルが肥大化します。データベースの肥大化はテーブルとインデックスの全体的なディスク使用量の増加につながり、クエリの実行時間が増加し、クエリを実行するためにテーブルやインデックスからデッドタプルやアクティブな可視行が読み取られることになるため、これは望ましくありません。AUTOVACUUM が自動的にデッドタプルを削除する代わりに、VACUUM FULL コマンドを明示的に呼び出して物理的に削除しなければならない場合があります。

AUTOVACUUM のベストプラクティスは次のとおりです。

  • タプルレベルの統計用の pgstattuple 拡張機能で、肥大化を評価できる情報を定期的に取得して、AUTOVACUUM が実行されていることを確認します。次の点に注意してください。
    • 肥大化によってディスク消費量が増加し、パフォーマンスが低下します。その結果、管理されず、定期的に削除されていないと、関連するデータを取得するクエリは、 2 倍 (またはそれ以上) かかります。AUTOVACUUM は、適切に設定されていれば、時間の経過に伴うデータベースの肥大化を最小限に抑えるのに役立ちます。
    • autovacuum_naptime パラメーターを適切に調整して、AUTOVACUUUM が十分な頻度で実行されるようにします。これにより、データベースの肥大化を防ぐことができます。
    • 書き込みの多いワークロードの裏で実行される、長時間実行されるクエリは避けてください。AUTOVACUUM はテーブルに弱いロックをかけるため、サーバーで実行されているワークロードを完全に把握するようにしてください。INSERTUPDATEDELETE などの通常のデータベース操作は続行できますが、インデックスやテーブルの切り詰めには影響します。
  • テーブルの使用状況とアクセスパターンに基づいて AUTOVACUUM 設定を調整します。次の点に注意してください。
    • DELETE ステートメントや INSERT ステートメントが多いテーブルで、前述の VACUUM パラメータに基づいてテーブル の AUTOVACUUM のしきい値をテストして設定します。
    • データベースへの影響を小さくするために、ビジーでない時間帯に AUTOVACUUUM をいつどのように実行するかについての最善の戦略を計画してテストしてください。また、ロックによって本番システムのワークロードが中断されないように、ビジー時に実行する方法も決定してください。
    • AUTOVACUUM を頻繁に使われないように注意してください。バキューム処理の間、ビジー状態になって回復するのを待つか、 VACUUM FULL を使わなければならないようなリスクがシステムに発生するかもしれません。

トランザクション中のアイドル状態

コネクション状態の変化はエラーのトラブルシューティングの出発点になる可能性があるため、 PostgreSQL コネクションの監視は重要なタスクです。 PostgreSQL には、トランザクションまたはステートメントの4つの主な状態があります。

  • active – オープンでクエリを実行しているコネクション
  • idle – アイドル状態でクエリを実行していないが、メモリや CPU などのサーバーリソースを消費し、パフォーマンスの低下の一般的な原因となっているコネクション
  • idle_in_transaction – バックエンドがトランザクション中であるが、アイドル状態で現在入力を待っているコネクション
  • idle_in_transaction (aborted) – idle_in_transaction に似ていますが、トランザクション内のステートメントが原因でエラーが起こった状態

トランザクション処理中は、ロックを保持したり、他のクエリをブロックしたり、AUTOVACUUM や VACUUM のパフォーマンスを妨げてテーブルが肥大化したりする可能性があります。特定が難しい場合が多く、パフォーマンスの問題の原因となっている重要な状態が idle_in_transaction です。データベースが BEGIN コマンドを発行し、1 つまたは複数のテーブルをロックし、ユーザー入力を待っているが、何らかの理由で COMMIT または ROLLBACK コマンドを発行していない場合、クエリは idle_in_transaction になります。トランザクション中のアイドル状態のコネクションはハングアップし、この状態がずっと続く可能性があります。PostgreSQL はなぜ待っているのかわからず、トランザクションスレッドを消費している間はトランザクションを自動的に停止しないからです。プロセスが待機しているのはビジネス上の理由がある可能性があるため、この状態は自動的には解決されません。たとえば、ドキュメントが読まれるまでに時間がかかったり、電子メールの送受信に何営業日も待たされたりします。idle_in_transaction をなくすには、データベースロックを理解してそのロックが発行された理由を判断する必要があります。また、アプリケーションがロックに遭遇したときの処理方法を知っておく必要があります。

idle_in_transaction は簡単に再現できます。まず、テーブルを作成してデータを追加します。次に、BEGIN と入力してステートメントを開始します。ステートメントの開始後、コミットやロールバックで終了せずに別の列を追加してテーブルを変更します。このアクションにより、2 番目のステートメントが最初のステートメントを終了せずに開始されたため、トランザクションロックでアイドル状態になります。
psql セッションで idle_in_transaction を再現するには、次のコードを実行します。

CREATE
 TABLE mydbtable ( 
id int GENERATED BY DEFAULT AS IDENTITY, 
username varchar (50), 
password varchar (50));
BEGIN;
alter table mytable add column last_update timestamp;

別の psql タブを開き、次のコードを実行します。

SELECT `*` `FROM` mydbtable`;`

テーブルにロックがかかっているので何も起こりません。ロックを解除するには、最初のセッションに戻り、コミットまたはロールバックを実行します。

COMMIT`;`

コマンドが実行されると、2 番目のセッションはただちに終了します。ロックは、コミットまたはロールバックが行われるまで常に保持されます。

idle_in_transaction セッションを管理するためのベストプラクティスを次に示します。

  • pg_stat_activity テーブルにクエリを実行して、現在 idle_in_transaction になっているクエリを見つけます。このテーブルとその使用方法の詳細については、 pg_stat_activity を参照してください。
  • idle_in_transaction を回避するために、トランザクションをより小さくて扱いやすい部分に分割してください。次の点に注意してください。
    • セッションごとまたはデータベースごとの設定で、指定した時間より長く実行されないようにクエリを準備します。
    • タイムアウトログを定期的にチェックして、実行時間の長いトランザクションを検出します。
    • 長時間実行されているトランザクションをキャンセルできるように、idle_in_transaction_session_timeout パラメータを設定することを検討してください。デフォルトは 0 で、タイムアウトがないことを意味します。
    • pg_stat_activity を使用して、実行時間の長いクエリと、クエリがその状態であった時間をチェックします。
  • 長時間実行されるストアドプロシージャまたは関数をデータベース層からアプリケーション層に移動します。次の点に注意してください。
    • エラーは、アプリケーションにコーディングされたエラー処理ロジックによって処理できます。
    • クエリ結果を処理する前にトランザクションを終了するようにアプリケーションをコーディングします。
    • 不要なエラーを避けるため、アプリケーション層とデータベース層で AUTOCOMMIT がオンになっていることを確認してください。
    • idle_in_transaction トランザクションの pg_stat_activity パラメータを使用してテーブルを監視し、idle_in_transaction セッションが VACUUM やその他のクエリによるテーブルへのアクセスを妨げていないことを確認してください。これにより、すべてのオープントランザクションとその状態が一覧表示されます。

まとめ

この投稿では、PostgreSQL の主要な機能と、 PostgreSQL エンジンの具体的な機能と、アプリケーションアーキテクチャの指針となるベストプラクティスを詳しく説明しました。Amazon RDS for PostgreSQL と Aurora PostgreSQL を使ったアプリケーションを設計する時にデータベース設計とパラメータ設定を検討することは、ダウンタイムを削減できると同時に、データベースパフォーマンスのコストと不便な中断を回避できます。この投稿はこれらのトピックに関する網羅的なリソースではありませんが、アプリケーション開発者向けの追加の PostgreSQL アーキテクチャとチューニングの考慮事項について説明するフォローアップ投稿の入門書となることを意図しています。

コメント欄でコメントやフィードバックをお待ちしています。

この記事のトルコ語翻訳版はこちらからご覧ください。

この記事の翻訳はソリューションアーキテクトの鈴木 大樹が担当しました。原文はこちらです。


著者について

Peter Celentano は、アマゾン ウェブ サービスのスペシャリストソリューションアーキテクトで、マネージド PostgreSQL を専門としています。彼は AWS のお客様と協力して、スケーラブルで、安全で、パフォーマンスが高く、堅牢なデータベースアーキテクチャをクラウド上で設計しています。


Tracy Jenkins は、アマゾンウェブサービスのデータベーススペシャリストソリューションアーキテクトです。彼女はデータベースを扱い、信頼性、コスト、セキュリティに関する推奨事項を提示しながら、パフォーマンスが高く、可用性が高く、スケーラブルなソリューションを顧客が設計できるよう支援することを楽しんでいます。