LayerX エンジニアブログ

LayerX の エンジニアブログです。

Amazon ECS と AWS Fargate で動作する「LayerX インボイス」のコスト最適化手法

f:id:shun_tak:20210719111823p:plain

こんにちは!LayerXの高際 (@shun_tak) です!過去にはOCR関連の記事を書きました!

LayerX インボイスのアプリケーション・サーバーはAmazon ECS と AWS Fargateで動作しています。今回の記事ではそのコスト最適化手法について解説したいと思います。よろしくお願いします!

tech.layerx.co.jp

サマリーとしては、LayerX インボイスではオーソドックスに費用の削減タスク数の最適化という2つの方法でコスト最適化を行っています。

  1. Savings PlansとFargate Spotを活用した費用の削減
  2. Auto ScalingとScheduled scalingによるタスク数の最適化

早速次節から解説していきます!

Savings Plansを活用した費用の削減

Fargateは使った分だけ料金が請求されます(vCPU数×メモリサイズ×起動時間)。この料金体系はクラウドの最も優れた特性の一つで、事業がうまくいくかどうか分からなかったり、その負荷の量が読めないときには非常にありがたいです。

しかし、一定の顧客を獲得することに成功し事業が安定してきたならば、1年または3年契約の1時間あたりの利用金額をコミットするSavings Plansを活用することで、さらなる費用の削減を実現できます。

東京リージョンでの削減額は1年全額前払いで22%削減、3年全額前払いで47%削減です(2021年7月18日時点)。また、削減額は減りますが毎月払いも可能です。すごい!参考:Savings Plans 料金

1時間あたりの利用金額での契約なので、定常的に利用する分の利用料に合わせて契約するのが良いようです。LayerX インボイスでは多くの企業が休日となる土日など、タスク数が少ない時間の利用料に合わせて契約しています。

どのくらい契約すればいいのか分からない場合、AWSのマネジメントコンソール内のCost Explorerを見るとちょうどいい購入金額を教えてくれるので、そちらも参考に決定しましょう。

f:id:shun_tak:20210719112203p:plain

(画像はAWS Cost Explorerより引用)

Fargate Spotを活用した費用の削減

LayerX インボイスでは後述するAuto Scalingによるタスク数の最適化を行っていますが、Fargate Spotを活用することでさらなる費用の削減を実現できます。

EC2のスポットインスタンスを活用したことある方ならピンと来たと思いますが、Fargate SpotはAWSの余剰キャパシティを活用してタスクを実行することで、最大70%の割引を受けられます。参考:AWS Fargate 料金

Fargate Spotは余剰キャパシティで稼働しているため、AWSにキャパシティが必要になるとデフォルトで30秒、最大で2分前に警告が送信され、実行中のタスクにSIGTERMシグナルが送信され、アプリケーションが終了します。参考:Fargate Spot 終了通知の処理

LayerX インボイスのアプリケーション・サーバーではREST APIに go-swagger を、ジョブワーカーに go machinery (v1) を利用して実装していますが、それぞれSIGTERMシグナル受信時のGraceful Shutdownに対応しているので、Fargate Spotの終了が通知されても安全にアプリケーションを終了できます。

ただし、すべてのタスクが終了されるとさすがに困るので、一部のタスクは通常のFargateで、残りはFargate Spotで稼働するような構成にしています。

このような設定はECSサービスのキャパシティプロバイダ戦略設定によって調整できます。

以下はECSサービス設定の画面ですが、例えばこの画面のような設定をすることで、5タスクは通常のFargateで起動し、残り全てがFargate Spotで起動するような構成を実現できます。

f:id:shun_tak:20210719112556p:plain

ベースやウェイトの詳しい意味はAmazon ECSのドキュメントをご参照ください。

Auto ScalingとScheduled scalingによるタスク数の最適化

Auto Scalingは多くのサービスで利用されていると思うので詳細は省略しますが、サービスの負荷に応じてタスク数を自動的に増減することで負荷分散をコントロールするとともに、コスト最適化を実現するものです。参考:ECSサービスのAuto Scaling

LayerX インボイスでは、より自動的にタスク数が調整されるターゲット追跡スケーリングポリシーによるAuto Scalingを行っています。

加えて、予測可能な負荷の増減に対応するためのScheduled scalingという仕組みも併用しています。LayerX インボイスでは平日の日中にアクセスが増加することが分かっており、Scheduled scalingにより最小タスク数を増加させるようにしています。

最小タスク数を増やすとコストも増えますが、お客様にご不便をおかけしないよう可用性を高めるために実施しています。これまでコスト削減ではなくコスト最適化という言葉を使ってきたのにはこういう理由もあるのです。

まとめ

本記事では、Savings PlansとFargate Spotを活用した費用の削減、Auto ScalingとScheduled scalingによるタスク数の最適化を組み合わせたコスト最適化手法について解説しました。

コストをうまくコントロールすることは、利益率を高め、ひいてはサービス再投資による顧客の満足につながるものです。基本的な取り組みではありますが、こういったことこそ大切にしていかねばなと思います。

基本に忠実に、製品を磨き、顧客に向き合う。そんな仲間を募集しています。

herp.careers

SQLは口ほどにものを言うーRedashによるクエリ共有、監視、データ連携によるチーム効率化ー

f:id:naomasabit:20210704162817p:plain

こんにちは。DX事業部の花村(@naomasabit)です。先日の投稿でユーザーの利用状況確認のためにAWSのQuickSightを利用していると書きましたが、並行して分析ツールのRedashも利用しています。Redashの良い点としてクエリベースでの分析、監視アクション、スプレッドシートとのデータ連携が存在します。

SaaSチームの運営において、これらを活用したユースケースについて伝えていきます。

アドホックな分析クエリの共有によるコミュニケーション効率化

Redashでは、まずクエリベースでアドホックな分析クエリの共有が可能です。アドホックにデータ状況を見たいケースなどで、SQLを書いて分析が可能です。

f:id:naomasabit:20210702143207p:plain

書いたSQL文はURLベースでチーム内に共有できます。LayerXではBizメンバーがSQLを叩くことが日常となっているため、クエリを書いてささっと共有してコミュニケーションをしています。

f:id:naomasabit:20210704162434p:plain
Bizメンバーがクエリを書いてチーム内に共有する図

SQLがなければ、「調査したところX%のユーザーがYのアクションを行い〜」という文章で説明しなければいけませんが、SQLという共通言語を持つことでこのように「ほしい情報出せたろう(残りはクエリとデータセットを見て個々人それぞれコメントしよう)」という言葉だけでコミュニケーションが完了します。

エンジニア、カスタマーサクセス、セールスと多くのロールが混じり合うSaaS組織において、コミュニケーションの効率化に非常に役立ちます。SQLは口ほどにものを言います。

監視アクション設定によりデータ不整合にすぐ気づける体制整備

f:id:naomasabit:20210702110715p:plain

Redashはチェッククエリに基づいてアラートを設定することができ、設定した閾値条件になると指定した場所へ通知します。画像はアラート設定画面で、分析のためのクエリを別途設定し、その結果に基づいてアラートの閾値条件を設定します。通知先は様々設定できますが、LayerX インボイスではSlackを通知先に設定しています。

f:id:naomasabit:20210702135838p:plain

監視対象の通知が動くとSlackには画像のようなアラートが飛んできます。修正を施してアラート条件から脱すると、回復の通知も飛んできます。このような仕組みによりデータ不整合の検知と対応を素早くエンジニアができるようにしています。データ不整合は新たなデータ不整合を呼びがちなため、素早い監視が重要になる領域です。

システムエラーの監視にはDataDogを用いていますが、エラーの結果や意図していないオペレーションなどでデータ不整合が起きることが存在します。業務用SaaSであるLayerX インボイスでは、データ不整合が起きてはいけない箇所にチェッククエリを投げ、不整合があった場合はslackに適宜通知を投げるようにしています。データ不整合はそもそも起きないのがベストですが、あったとしても早め早めに気づいて対応し、素早く修正して傷口を最小限に防ぎ、改善していくサイクルを起こすことが肝要です。

複数チームからのデータソース連携によるヘルススコアダッシュボード作成

カスタマーサクセスにおいて、ユーザーがサービスをうまく活用してくれているかを測る「ヘルススコア」も監視しています。LayerX インボイスにおいてヘルススコアのインプットはサービス活用状況を集約したDBに限らず、成約前にセールスが得たユーザー企業の基礎情報なども含みます。

そのため、サービス活用状況のDBとセールスからの情報を合わせてヘルススコアを記録する必要があります。多くのサービスではS3などストレージとの連携には対応していますが、セールスチームが関連情報をCSVにしてアップロードしてS3に上げて連携…というのは非常に手間です。セールスチームにとって扱いやすいデータ形式のインプットを用意してあげる必要があります。

f:id:naomasabit:20210702132746p:plain Supported Databases, APIs and Integrations

redashは多くのIntegrationが用意されており、特にGoogle Spreadsheeetsとの連携は非常に便利です。Redashではスプレッドシートと連携することで、DBと同様のデータソースとして扱うことができます。セールスチームが慣れ親しんだスプレッドシートにデータを入れることで、情報が即時反映されます。

f:id:naomasabit:20210702134144p:plain

セールスチームから、成約した新規ユーザーをカスタマーサクセスチームに連携するとき、ユーザーの基礎情報をスプレッドシートに記入します。カスタマーサクセス業務においては、その基礎情報と分析用DBを突き合わせてヘルススコアを確認し、対策をカスタマーサクセスチームで練ってアクションに落とし込むことができます。

最後に - Redashと他の分析ダッシュボードツールの併用について

Redashを用いたデータの監視、複数のインテグレーションによるモニタリング効率化について書きました。Redashはリアルタイムのデータ監視や、データ連携が得意な一方、ドリルダウン分析などはRedashの得意な分野ではありません。簡単なダッシュボードを並べることは可能ですが、全体傾向から個別ユーザーの状況へ、複数のダッシュボードを組み合わせて深掘りするような分析においては、QuickSightやTableau、Lookerといったツールの方が適しています。いずれも予算帯との相談ではありますが、LayerX インボイスでは素早い分析や監視のためのRedashと、深掘りするための分析ダッシュボードツールも併用する運用にしています。

分析ツールと一言でいっても特性が様々に異なり、インテグレーションのサポートも様々です。そのような特性を見ながら、分析といってもケースを分けて最適なツールをそれぞれ選定していくことが重要なのだと思います。

Terraform import のススメ 〜開発効率化編〜

こんにちは、LayerX で主にインフラを担当している高江です。
今回は、一見地味ではありますが実はとても役に立つ機能である Terraform import についてお話したいと思います。 

Terraform import とは

公式サイトでは次のように説明されています。

Terraform is able to import existing infrastructure. This allows you take resources you've created by some other means and bring it under Terraform management.

要するに、AWS 等のサービスプロバイダー上に既に存在する、Terraform 管理されていないリソースの情報を取得して Terraform 管理下に置く(tfstate ファイルに import する)機能です。
また、同じく公式サイトには

This is a great way to slowly transition infrastructure to Terraform,

ともあります。
このことから、まだ IaC (Infrastructure as Code) を実践していない環境において、既存のリソースを運用しながら徐々に Terraform による IaC を進めていくときなどに有用な機能であることが分かります。

ただ、import の利用シーンはそれだけではなく、開発を効率化する際にも力を発揮します。
ここでは、普段の開発において私がどのように import を活用しているかをご紹介します。

Terraform import の使い方

その前に、まずは import の使い方について簡単に説明します。
コマンドの形式は以下になります。 (公式サイト参照)

% terraform import [options] ADDRESS TO

ADDRESS は Terraform ファイル(.tf)内で定義した、対象リソースの type と name です。 TO は対象とするリソースによって違いがあり、ID だったり ARN だったりします。

また、import を実行するためには、事前に対象のリソースを定義した Terraform ファイルを作成しておく必要があります。 例えば、sample-bucket という名前の S3 bucket を import する場合の手順は以下のとおりです。

1.Terraform ファイルを作成

resource "aws_s3_bucket" "sample_bucket" {
    bucket = "sample-bucket"
}

2.Terraform import を実行

% terraform import aws_s3_bucket.sample_bucket sample-bucket

これによって、import した sample-bucket が tfstate に追加され、Terraform 管理下に置かれることになります。
S3 bucket を import する場合は上記のように bucket 名を指定します。対象リソースによって何を指定するかは変わってくるので、詳細は公式サイトの各リソースのページを参照ください。ページ最下部に import する場合の説明が記載されています。S3 bucket の場合はコチラになります。

Terraform import を使って開発を効率化する

ここからが本題です。Terraform を使ってインフラを構築するにあたり、「この場合どう書けばいいんだっけ?」というケースが出てくると思います。特に、初めて Terraform で構築しようとするリソースであったり、Terraform を使った開発経験自体があまりない場合には、正しいリソース定義の書き方について迷うことも多いのではないでしょうか。

このような場合に、公式サイトのドキュメントを見たりググったりしながら手探りで作るのではなく、import を利用することで開発を効率化することができます。
ここでは、AWS CodePipeline と AWS CodeBuild を使ったビルドパイプラインを構築する例をもとに、具体的な開発の流れについて説明します。

パイプラインの概要

構築するパイプラインを図1に示します。

f:id:shnjtk:20210625102609p:plain
図1 ビルドパイプライン アーキテクチャ

  • CodePipeline でパイプライン全体の構成を管理
  • CodeStar connections で GitHub リポジトリと接続
  • 取得したコードを CodeBuild に渡すために S3 bucket をアーティファクトストアとして利用
  • CodeBuild でビルド実行
  • IAM で各種権限管理

開発の流れ

では開発に移っていきましょう。全体の流れとしては以下のようになります。

  1. AWS 管理画面からマニュアルでリソースを構築
  2. terraform import で tfstate を更新
  3. tfstate の内容を見ながら Terraform ファイルを編集
  4. terraform plan で差分を確認しながら Terraform ファイルを編集

1. AWS 管理画面からマニュアルでリソースを構築

AWS 管理画面にログインし、ボタンポチポチでパイプラインを構築していきます。このとき、マニュアルで作るものは仮として、後から Terraform で作り直す場合はそれと分かるようにリソース名の prefix や suffix に temp のようなものを付与することをおすすめします。今回は、リソース名を terraform-import-sample としました。

f:id:shnjtk:20210625112144p:plain
図2 AWS 管理画面からのパイプライン構築の様子

2. terraform import で tfstate を更新

次に、以下の Terraform ファイルを作成して import を実行します。

  • main.tf
terraform {
  required_version = "~> 1.0"

  required_providers {
    aws = {
      version = "~> 3.47"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_codepipeline" "import_sample" {}
  • コマンド
% terraform init

# AWS CodePipeline の import はリソース名を指定する
% terraform import aws_codepipeline.import_sample terraform-import-sample

そうすると、次のような tfstate ファイルが作成されます。これが、Terraform ファイルでリソースを定義していく上でのヒントになります。

{
  "version": 4,
  "terraform_version": "1.0.1",
  "serial": 1,
  "lineage": "00c448d1-bda9-2c60-68e8-0be02331c6d9",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_codepipeline",
      "name": "import_sample",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "arn": "arn:aws:codepipeline:ap-northeast-1:(snip):terraform-import-sample",
            "artifact_store": [
              {
                "encryption_key": [],
                "location": "codepipeline-ap-northeast-1-(snip)",
                "region": "",
                "type": "S3"
              }
            ],
            "id": "terraform-import-sample",
            "name": "terraform-import-sample",
            "role_arn": "arn:aws:iam::(snip):role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-samp",
            "stage": [
              {
                "action": [
                  {
                    "category": "Source",
                    "configuration": {
                      "BranchName": "main",
                      "ConnectionArn": "arn:aws:codestar-connections:ap-northeast-1:(snip):connection/(snip)",
                      "FullRepositoryId": "(snip)",
                      "OutputArtifactFormat": "CODE_ZIP"
                    },
                    "input_artifacts": [],
                    "name": "Source",
                    "namespace": "SourceVariables",
                    "output_artifacts": [
                      "SourceArtifact"
                    ],
                    "owner": "AWS",
                    "provider": "CodeStarSourceConnection",
                    "region": "ap-northeast-1",
                    "role_arn": "",
                    "run_order": 1,
                    "version": "1"
                  }
                ],
                "name": "Source"
              },
              {
                "action": [
                  {
                    "category": "Build",
                    "configuration": {
                      "ProjectName": "terraform-import-sample"
                    },
                    "input_artifacts": [
                      "SourceArtifact"
                    ],
                    "name": "Build",
                    "namespace": "BuildVariables",
                    "output_artifacts": [
                      "BuildArtifact"
                    ],
                    "owner": "AWS",
                    "provider": "CodeBuild",
                    "region": "ap-northeast-1",
                    "role_arn": "",
                    "run_order": 1,
                    "version": "1"
                  }
                ],
                "name": "Build"
              }
            ],
            "tags": {},
            "tags_all": {}
          },
          "sensitive_attributes": [],
          "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ=="
        }
      ]
    }
  ]
}

3. tfstate の内容を見ながら Terraform ファイルを編集

main.tf を編集していきます。tfstate ファイルや公式サイトのCodePipeline ドキュメントを参照しながら、必要なパラメータを設定します。

  • main.tf (terraformproviderは省略)
resource "aws_codepipeline" "import_sample" {
  name     = "terraform-import-sample"
  role_arn = "arn:aws:iam::(snip):role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-samp"

  artifact_store {
    type     = "S3"
    location = "codepipeline-ap-northeast-1-(snip)"
  }

  stage {
    name = "Source"
    action {
      category         = "Source"
      name             = "Source"
      namespace        = "SourceVariables"
      owner            = "AWS"
      provider         = "CodeStarSourceConnection"
      version          = "1"
      output_artifacts = ["SourceArtifact"]
      configuration = {
        ConnectionArn        = "arn:aws:codestar-connections:ap-northeast-1:(snip):connection/(snip)"
        FullRepositoryId     = "(snip)"
        BranchName           = "main"
        OutputArtifactFormat = "CODE_ZIP"
      }
    }
  }

  stage {
    name = "Build"
    action {
      category         = "Build"
      name             = "Build"
      namespace        = "BuildVariables"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = "1"
      run_order        = 1
      input_artifacts  = ["SourceArtifact"]
      output_artifacts = ["BuildArtifact"]
      configuration = {
        ProjectName : "terraform-import-sample"
      }
    }
  }
}

4. terraform plan で差分を確認しながら Terraform ファイルを編集

terraform plan を実行してみましょう。

% terraform plan
aws_codepipeline.import_sample: Refreshing state... [id=terraform-import-sample]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

変更なしと表示されましたね。これで、マニュアルで作ったものと main.tf ファイルで定義したものが同一になることが確認できました。

CodePipeline 以外のリソースについても同様の作業を行います。例えば、今の main.tf では IAM role が ARN ハードコードになっているので、これを Terraform のリソースとして定義します。 そのため、次はこの IAM role を import します。

  • main.tf (IAM role 定義部分のみ抜粋)
resource "aws_iam_role" "import_sample_codepipeline" {}
  • コマンド
# IAM role の import はリソース名を指定する
% terraform import aws_iam_role.import_sample_codepipeline AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-samp

そうすると、tfstate に IAM role が追加されます。(該当箇所のみ抜粋)

    {
      "mode": "managed",
      "type": "aws_iam_role",
      "name": "import_sample_codepipeline",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "arn": "arn:aws:iam::(snip):role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-samp",
            "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"codepipeline.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}",
            "create_date": "2021-06-25T01:22:35Z",
            "description": "",
            "force_detach_policies": false,
            "id": "AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-samp",
            "inline_policy": [
              {
                "name": "",
                "policy": ""
              }
            ],
            "managed_policy_arns": [
              "arn:aws:iam::(snip):policy/service-role/AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-sample"
            ],
            "max_session_duration": 3600,
            "name": "AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-samp",
            "name_prefix": null,
            "path": "/service-role/",
            "permissions_boundary": null,
            "tags": {},
            "tags_all": {},
            "unique_id": "AROAZYMBT63SJTFIQE7EF"
          },
          "sensitive_attributes": [],
          "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjAifQ=="
        }
      ]
    }

これを参考に、CodePipeline の時と同じようにリソースを定義します。(該当箇所のみ抜粋)

resource "aws_codepipeline" "import_sample" {
    role_arn = aws_iam_role.import_sample_codepipeline.arn
}

resource "aws_iam_role" "import_sample_codepipeline" {
  name = "AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-samp"
  path = "/service-role/"

  assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"codepipeline.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}

terraform plan を実行してみましょう。

% terraform plan
aws_iam_role.import_sample_codepipeline: Refreshing state... [id=AWSCodePipelineServiceRole-ap-northeast-1-terraform-import-samp]
aws_codepipeline.import_sample: Refreshing state... [id=terraform-import-sample]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

今回も変更なしと表示されましたね。上手くいきました。

このように、import した tfstate ファイルを見ながら必要なパラメータを設定していくことで Terraform によるインフラ開発を効率化することができます。一通りリソース定義が終わったら、マニュアルで作ったリソースや import した tfstate はそのままにして運用するもよし、一旦削除して各種リソース名等をプロジェクトの命名規則等にあわせて作り直すもよしです。

まとめ

以上、Terraform import を利用したインフラ開発の効率化についてご紹介しました。いかがでしたでしょうか?
Terraform import は既存リソースを Terraform 管理下に置く場合に使うものという意識があるかもしれませんが、新規にリソースを構築する際にもとても有用なので、ぜひご活用いただければと思います。 余談ですが、答えを見ながら必要な項目を埋めていくので若干カンニングしてる感があります。

おまけ

ちなみに、上記の IAM role リソース定義で、 assume_role_policy は string でベタ書きされていますが、明らかにメンテナンス性が低いので、Data Source を使って定義した方がよいです。

こんな感じです。(関連箇所のみ抜粋)

data "aws_iam_policy_document" "import_sample_codepipeline_assume_role_policy" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["codepipeline.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "import_sample_codepipeline" {
  assume_role_policy = data.aws_iam_policy_document.import_sample_codepipeline_assume_role_policy.json
}

あるいは、Terraform の IAM role の 公式ドキュメントにもあるように、 jsonencode を使ってもいいですね。
一つのリソースでしか使わないのであれば jsonencode でインラインに、複数のリソースで使うのであれば Data Source として再利用できるように、それぞれ使い分ければいいかなと思います。

それでは。

MySQL Generated Columns を活用したユニークキー制約

DX事業部の @yyoshiki41(中川佳希)です。 現在は、LayerX インボイス という経理業務を行う方を対象ユーザーにした SaaS をメインで開発しています。

今回は、MySQL での Generated Column の活用についての紹介です。

Generated Column とは?

カラム定義時にロジックを組んでおくことで、演算や条件分岐ロジックの結果を値として、仮想的に参照可能にするもしくは記憶領域に格納することが出来る機能です。

dev.mysql.com

ドキュメントには以下のような直角三角形の斜辺 sidec を格納するスキーマの例が紹介されています。

CREATE TABLE triangle (
  sidea DOUBLE,
  sideb DOUBLE,
  sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb))
);
INSERT INTO triangle (sidea, sideb) VALUES(1,1),(3,4),(6,8);

AS 以降にカラムの値計算を行う式が定義されています。

mysql> SELECT * FROM triangle;
+-------+-------+--------------------+
| sidea | sideb | sidec              |
+-------+-------+--------------------+
|     1 |     1 | 1.4142135623730951 |
|     3 |     4 |                  5 |
|     6 |     8 |                 10 |
+-------+-------+--------------------+

ユースケース

よく紹介されている例としては、以下のようなものがあります。

  1. 複雑な条件結果を先にカラムに定義しておき、クエリ条件を簡略化させる
  2. STORED を指定して記憶領域に書き込みを行っておき、クエリ参照時には計算コストをかけない
  3. JSON 型のようなインデックスキーをつけれないカラムに対して、インデックスキー用のカラムを生成する

JSON との組み合わせの例としては、以下のようなものです。

CREATE TABLE `tests` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `json` json DEFAULT NULL,
  `version` int GENERATED ALWAYS AS (json_extract(`json`,'$.version')) VIRTUAL,
  PRIMARY KEY (`id`),
  KEY `version` (`version`)
);
INSERT INTO tests(`json`) VALUES ('{"version": "1", "definitions": "foo"}');

json のキーとして version を持つデータに対して、インデックスキーカラムを設定できました。

mysql> SELECT * FROM tests;
+----+--------------------------------------+---------+
| id | json                                 | version |
+----+--------------------------------------+---------+
|  1 | {"version": 1, "definitions": "foo"} |       1 |
+----+--------------------------------------+---------+

アプリケーションでのデータ整合性

アプリケーションが読み書きするデータの整合性は、DB側でも外部キー、ユニークキーやCHECK制約などを用いて担保したいものです。

今回紹介したいのは、ユニークキーとして Generated Columns を活用する例です。

例1. 状態やステータスで有効なユニークキーを表現する

レコードの物理削除を行いたくない場合に、deleted_at カラムを用いることがあるかと思います。 名称 name カラムに対してユニークキーを設定したいとします。

まずは、namedeleted_at(nullableなカラム)の複合ユニークキーを設定した良くない例は下記です。

CREATE TABLE `bad_examples` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `deleted_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_1` (`name`,`deleted_at`)
);

NULLとなっているカラム値に対しユニークキーは有効でないため、重複名が登録できてしまいます

mysql> INSERT INTO `bad_examples`(name) VALUES("foo"), ("foo");
mysql> SELECT * FROM `bad_examples`;
+----+------+------------+
| id | name | deleted_at |
+----+------+------------+
|  1 | foo  | NULL       |
|  2 | foo  | NULL       |
+----+------+------------+
2 rows in set (0.00 sec)

このようなケースでは、deleted_at が NULL であるかをフラグとして持つ Generated Columns を定義し、ユニークキーとして使うと有効です。

CREATE TABLE `good_examples` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `deleted_at` timestamp NULL DEFAULT NULL,
  `is_active` tinyint(1) AS ((case when isnull(`deleted_at`) then 1 else NULL end)) VIRTUAL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_1` (`name`,`is_active`)
);
mysql> INSERT INTO `good_examples`(`name`,`deleted_at`) \
    -> VALUES("foo", NULL), ("foo", NOW() - INTERVAL 1 SECOND), ("foo", NOW());
mysql> SELECT * FROM `good_examples`;
+----+------+---------------------+-----------+
| id | name | deleted_at          | is_active |
+----+------+---------------------+-----------+
|  1 | foo  | NULL                |         1 |
|  2 | foo  | 2021-06-20 23:49:03 |      NULL |
|  3 | foo  | 2021-06-20 23:49:04 |      NULL |
+----+------+---------------------+-----------+

例2. ユニークキーの条件がレコードにより異なる

更にこみいったビジネスロジックにおけるデータ整合性を担保する場合を考えてみます。

条件

  1. 各店舗の商品を持つテーブル
  2. 全店舗で商品名 name は必ず持つが、商品コード code は店舗により未設定の場合がある
  3. 商品コード code をもつ場合、code がユニークキーとなる
  4. 商品コード code をもたない場合、商品名 name がユニークキーとなる

これをスキーマとして持つテーブルは以下のように定義できます。

CREATE TABLE `shop_goods` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `shop_id` int unsigned NOT NULL,
  `name` varchar(255) NOT NULL,
  `code` varchar(255) NULL DEFAULT NULL,
  `is_null_code` tinyint(1) GENERATED ALWAYS AS ((case when isnull(`code`) then 1 else NULL end)) VIRTUAL,
    PRIMARY KEY (`id`),
  UNIQUE KEY `uq_1` (`shop_id`, `name`, `is_null_code`),
  UNIQUE KEY `uq_2` (`shop_id`, `code`)
  );

商品コードを設定していない店舗1で、重複した商品名は以下のようにユニークキー制約でエラーになります。

mysql> INSERT INTO `shop_goods`(`shop_id`, `name`, `code`) \
    -> VALUES(1, "商品A", NULL), (1, "商品A", NULL);
ERROR 1062 (23000): Duplicate entry '1-商品A-1' for key 'uq_1'

商品コードを設定している店舗2では、重複した商品名も登録可能になります!

mysql> INSERT INTO `shop_goods`(`shop_id`,`name`, `code`) \
    -> VALUES(1, "商品A", "コードA-1"), (1, "商品A", "コードA-2");
mysql> SELECT * from `shop_goods`;
+----+---------+---------+--------------+--------------+
| id | shop_id | name    | code         | is_null_code |
+----+---------+---------+--------------+--------------+
|  1 |       1 | 商品A    | コードA-1      |         NULL |
|  2 |       1 | 商品A    | コードA-2      |         NULL |
+----+---------+---------+--------------+--------------+

おわりに

Generated Columns の活用例をいくつか紹介してみました。 INSERT/UPDATE 時にはカラムにセットする値をアプリケーション側から無視する必要があったり(もしくは DEFAULT を使う)、考えることはあります。
しかし、有効なケースも多々あると思いますので参考になりましたら幸いです!

herp.careers

デザイナーからみた、眠れる「銭」をActivateする会社

こんにちは!LayerXでデザイナーをしているsaikoです。
LayerXから三井物産デジタル・アセットマネジメント株式会社(以下、MDM)に出向し、プロダクトや会社に関わるデザイン全般の業務に携わっています。

今年4月に入社してから早2ヶ月。
むしろ「まだ2ヶ月しか経ってないの?」と思うくらいあっという間だった日々を振り返り、入社エントリーと併せて「デザイナーからみたMDM」について現場の雰囲気が伝わるよう書いてみたいと思います。
エンジニアっぽい内容はほぼ出てこないですが、「LayerXのエンジニアがどうMDM事業に関わりを持っているか」が見える内容になっています。

Be Animal!

まずは、私が入社に至った経緯を簡単に。
2018年、Webデザインの制作会社で働き詰めだった日々に終止符を打ち、Gunosyに入社しました。
ニュースアプリ「NewsPass」のデザインを担当し、チーム内での動き方も見極められてきたある日、全社の朝会で「LayerX設立」の話を聞きました。
話を聞いた直後は「デザインで力になれることはすぐには無さそうだけど、素晴らしい理想と志だなぁ」という印象で、その後は、各チームごとの朝会の時にオフィスのどこかから聞こえてくる「Be Animal!」という掛け声?の印象が強かったのを覚えています。

その後、デザイナーとしてより厳しい環境に身を投じる為にNewsPicksに入社。
立ち上げたばかりの新規事業にjoinし、1日の中でinputとoutputを高速で回し続ける目まぐるしい日々を過ごしていた頃、Gunosy時代に大変お世話になったデザイナーの森さんがLayerXに入社されたことを知りました。
当初は、どんな理由があってデザイナーが必要なのかが分からなかったのですが、2021年に入って「LayerX インボイス」のリリースを知り「これか!」と。しかも、耳を疑うほどの爆速開発&リリースを行なっており、驚きのあまり言葉が出ないくらいでした。

爆速開発の様子を知りたい方はこちら☟ tech.layerx.co.jp

それから2ヶ月ほど過ぎた頃、不意に森さんから「デザイナーを採用することになったから、saikoさんどう?」と連絡をいただきました。
森さんとはまた一緒に働きたいと思っていたこともありますが、元々関心のあった金融領域のDXを積極的に推し進めようとする会社の方針や組織全体のスピード感、福島さん・石黒さんの日々の発信からLayerXの魅力をじわじわ感じ始めていたので、二つ返事で「行きます」とお伝えし、2021年4月に入社するに至りました。

今では私も一緒になって「Be Animal!」と叫んでいますが、Gunosyに居た頃は考えもしなかったことなので、本当に人生は面白いなと感じています。

眠れる「銭」を、Activateせよ。

「投資・資産運用」と「デザイン」と聞いて、ふと思い浮かぶことはありますか?
すぐ思い浮かんだ方も思い浮かばなかった方もいらっしゃると思いますが、私は後者でした。

LayerXの面接を進む中で、入社後に担当する事業はMDMと教えていただきましたが、不動産投資については「正直不動産(漫画)」で知った程度の知識量でした。
ですが、入社初日に丸野さん(peroyuki_)から受けたオンボーディングで、「アセマネ業務の恐ろしいほどの非効率さ」と「資産運用をしている割合が日本は圧倒的に少ない事実」を知り、「なんて働きかけ甲斐のある状況だろう!」と正直ワクワクしました(正直不動産だけに)

丸野さんのユーモアに富んだMDM事業に関する記事はこちら☟ tech.layerx.co.jp

投資家の方々が「投資してみたい」と感じる商品を提供する為にも、デザインや情報設計の観点から社内外にアプローチを行いたい、それができる!やるぞ!と決めたのもこの時でした。

眠れる「銭」を、Activateせよ。
は、MDMのミッションなのですが、ゆくゆくは個人の資産運用状況をも変えうる一手にするという強い意志を感じる、素晴らしい言葉だなと個人的に思っています。

MDMの魅力

ここからは、joinしてから2ヶ月経った私が感じている「MDMの魅力」を3つに絞ってお伝えします。

① どんな状況も楽しめる雰囲気がある

MDMでやろうしていることは「答えがない」ので、如何に「投資家への提供価値」と「運用効率」を高められるかを、日々活発な意見交換と議論を行いながら常に考え続けています。
部署ごとに対応しているプロジェクトがスムーズに進捗することもあれば、丁寧に少しずつ進捗させることに努めなければならない状況になることもあります。
ただ、どんな状況下においても社内の雰囲気は前向きで、「コッカラッス!」を合言葉に互いに鼓舞し合っています。
基本笑い声が絶えないので、これは大きな魅力なのではと最近感じています!

② 互いに信頼し助け合える

端的にいうと、「心理的安全性」と「透明性」を強く感じます。
自分の意見を言いやすく、できないことはできる方に頼ることができる環境です。
これはまさしく、MDMの行動指針「細部に宿る誠意、揺るぎない透明性。」に則っているじゃないか!とお世辞なく感じます。

f:id:s1k_0u0:20210617194221p:plain
3つの行動指針のうちの一つです

あと、開発メンバーで行うスプリント毎の振り返りでは、KPTの時にメンバーを褒める内容が毎回出ます。互いに自然と褒め合える環境って素敵だなぁと参加しながらいつも感じています。

f:id:s1k_0u0:20210617193756p:plain
自粛明けに行った振り返りの様子

③ なかなか出会わないバックグラウンドを持つメンバーがいる

LayerXからの出向メンバーも過去になかなか面白い経験をしているメンバーが多いですが、会社全体で見ても掘りがいのあるメンバー揃いです。
会社の立ち上げがMDMで4社目の社長をはじめ、不動産アセマネ、コンプライアンス、投資家営業等の各プロフェッショナルが集まっており、皆がそれぞれ持つ過去の経験を生かして、プロ意識を持って日々邁進しています。

最近MDM側でも新卒・インターンの採用募集を公開したのですが、過去にインターンから採用に至った方の経験歴がなかなかのインパクトで、SNS界隈で少し話題になったと聞きました。
(話を盛ってないから尚更すごい...参考まで☟)
即戦力のプロフェッショナル新卒・インターン若干名募集! - 三井物産デジタル・アセットマネジメント株式会社

振り返ってみて

こうして振り返ると、今私がワクワクを感じながら金融業界のDXに挑戦できる機会を得られているのは、声をかけてもらった時にすぐさま動いたこと、つまり「行動できたこと」が大きいと感じています。
気になったらまず行動できるフットワークの軽さは、チャンスを逃さない人になる為にも重要なことかも知れません。

MDMでは現在、共に働くメンバーを募集中です。ぜひ一緒にワクワクしましょう!! herp.careers

【イベントレポート】LayerXプロダクトさわらNight #1 を企画・実施しました

LayerXの石黒(@takaya_i)です。LayerXでは採用や組織づくり、広報の面から開発チームをサポートしています。

今回は、6月16日にオンラインで実施した【LayerXプロダクトさわらNight #1 】のオンラインイベントをレポートします。(一番下に次回の申込みフォームがあるのでぜひ!) f:id:t_1496:20210611153809p:plain

『toCのプロダクトに比べて、toB/SaaSのプロダクトって、触る機会を持ちにくい』

ソフトウェアエンジニアを始めとした、開発に携わるメンバーの方とカジュアル面談の回数を重ねていく中で、こういった声を多数いただきました。「なんとかその声に応えたい」こういった思いから、今回のイベントを企画するに至りました。

forms.gle

イベント概要

今回は、LayerX インボイスのワークフロー機能について「参加者向けにアカウントを払い出して触っていただきワイワイする会」をオンラインで実施します。

今回は、「LayerX インボイス」開発メンバーからのプロダクトの概要説明と4つのグループに分かれてプロダクトを実際に触っていただく場を作り、またフィードバックをいただく時間を設けました。

f:id:t_1496:20210611154518p:plain
当日のアジェンダ

この記事では、当日の様子と運営チーム及び参加者からのフィードバックをお届けします。

事前準備

各種環境設定
  • 「LayerX インボイス/ワークフロー」のデモ環境設定(テナント作成)
  • 参加者用アカウントの作成
  • デモ用のSlack環境構築
  • 「LayerX インボイス/ワークフロー」の経路及び権限設定
当日のご案内
  • 抽選による参加者決定
  • チーム分け案内(属性が近い方をグルーピング)
  • 参加時の注意事項(お互い顔が見えます、録画NG、Slackセットアップ依頼等)

さわらNightでやったこと

詳細は割愛しますが、細かな設定まで実施いただきました!(ご興味を持たれた方はぜひ次回以降ご参加ください)

f:id:t_1496:20210614182530p:plain
設定画面まで具体的に操作いただきました

感想戦

参加者

ーUIとか迷うことがなかった、説明無しでできるのはすごい。あと、Slackでできるのがデフォルトで入ってくるのは有り難いと思った。
ーAPIが欲しいんですよ!笑
ー会自体も楽しかったです。請求書OCRからとれるのが「早っ、早いな〜」という感想でした。
ーSlackの連携が直感的で操作に迷いがなかった。全体的に機能がミニマムに収まっているように見えて、開発者として好感が持てました。
ー「金額変えたら経路を変える」みたいなのがリアルタイムにできてよかった。ロールプレイのやり方は改善できるところがありそう!
ー「十分ありなんじゃない?」と思った。あと自由時間が10分だと足りないと思った!笑

運営チーム

プロダクトの説明をすることはありますが、実際に見て体験して頂く機会ってこれまであまりなかったので、今回”さわらNight”を実施できてよかったです! 百聞は一見にしかずと言いますが、参加いただきました皆さんの反応を見て、ただ説明するよりも圧倒的に多くのものを伝えられたと感じました。 ー ソフトウェアエンジニア 高際 @shun_tak

エンジニア目線での質問・フィードバックは今後のサービス開発に活かせるものばかりでした。 また異なる職種の皆様であれば、違うフィードバックを頂けるのかなと想像しています。 今後も開催していきますので、ぜひご参加ください。 ー PM 生田@ikutyy

最後に

以上、イベントの様子についてお届けしました。

第2回は以下よりエントリーください!

forms.gle

一緒に働く仲間を募集しています!

herp.careers

【イベントレポート】DX Tech Talk #4 「ソフトウェアの力で巨大な課題解決に挑む」_Autify × LayerX

LayerXの石黒(@takaya_i)です。LayerXでは採用や組織づくり、広報の面から開発チームをサポートしています。

今回は、AutifyとLayerXの共催で5月25日にオンラインで実施した【DX Tech Talk #4_ソフトウェアの力で巨大な課題解決に挑む】のオンラインイベントをレポートします。開発組織の課題感や、向き合う課題について興味をお持ちいただくきっかけになれば嬉しいです。

layerx.connpass.com

イベント概要

DX Tech Talk とは

「もう一つのDX」と呼ばれる、DX(=Developer eXperience)を題材に、開発組織やTechnologyのトークが大好きなゲストをお迎えし、Developer eXperienceを中心にトークするイベントがDX Tech Talkです。

今回は「技術の力で世界中の人々の創造性を高める」をミッションに掲げるAutifyとLayerXの共催で、下記2つのトークセッションに分けてそれぞれ対談を行いました。(2021年5月25日 オンライン開催)

Talk Session 1 :開発スピードを出すためにこれまで各社が「捨ててきたこと」
Talk Session 2 :今のフェーズだから「シニアなエンジニアに来てほしい」その理由

この記事では、当日の様子を各セッションのハイライトと共にお届けします。

f:id:t_1496:20210603200702p:plain
パネラーの様子。笑顔の絶えないイベントになりました

当日の資料

speakerdeck.com

Talk Session 1:開発スピードを出すためにこれまで各社が「捨ててきたこと」

Q.開発スピードの「源泉」について教えてください

f:id:t_1496:20210603201609p:plain

守屋: Autifyでは、開発スピードを上げるといった時に、まずは雑務を減らしていくこと、つまり自動化を大切にしています。例えば、デプロイの手間を減らしたり、リリースノートなども自動で分類して出るようにして、開発に集中できるよう「気の散る作業」を減らしています。

梶原:その自動化は誰が進めているんですか?

守屋:できる範囲で、全員がやります。どの分野においても、触ったことがある、または触ってみたい、という人がいて、それぞれが自動化に取り組みます。自動化(の達成)には馬力が必要ですが、積み重なっていくものなので、将来的に報われるなと思っています。そういった背景から、社内では「自動化は大事」という共通意識がありますね。

梶原:LayerXの開発文化にはDDD(Demo Driven Development)という言葉があります。開発スピードとは、つまり「お客様に対して価値を提供するスピード」なので、作る前に『その機能は本当に必要なのか?』の議論があります。本当にお客様が欲している機能だけを作っていく、いらない機能を作らない、というのが開発スピードにつながるのではと思います。

Q.開発スピードを出すために「取捨選択」したことはありますか?

梶原:LayerXではエンジニアブログを書いていて、私はその中でQAへの取り組みについて書いたのですが、まずインセプションデッキを作り、一番最初に「やること」と「やらないこと」を決めました。過度なドキュメンテーションをしない、過度な品質管理をしない、など。価値を提供できるスピードを優先して、(経理のプロダクトなので)もちろんちゃんとはしているんですが、ちゃんとしすぎない、というのを意識しています。

f:id:t_1496:20210604082146p:plain
「やること」と「やらないこと」

例えば、簡単な機能は仕様書などを作らずにエンジニアがその場で考えて作って、デモでOKであれば、そのまま実装するといったような感じです。最終的にE2Eのテストシナリオには書くけれども、開発の段階でエンジニアにドキュメンテーションをさせない、というところは意識していますね。

守屋:初期のAutifyはSPAで作られていたのですが、一度Railsのアプリに戻す、ということがありました。SPAだと、例えばAPIを作って、UIを作って、両方一緒に出すというように、エンジニア間でのいってこい(お互いの完了を待つタイミング)が発生しますよね。でも、実はその両方を作らなくてもいい管理画面の方が機能が多いじゃないか、ということが発生しまして、『SPA、やめない?』という意思決定になりました。 SPAの方が新しい技術なので、SPAを使いたいというエンジニアの気持ちがありつつも、お客様に価値が届くまでのスピードに対し、SPAをやめた方が早いなら、その方がいい、という結論になりました。

Talk Session 2:今のフェーズだから「シニアなエンジニアに来てほしい」その理由(CTO対談)

Q.シニアなエンジニアの定義について教えてください

松本:まず、LayerXでは12ヶ月〜18ヶ月後を見据えた将来の組織図を作っていて、そこから逆算して行こう、「組織図はソフトウェアに従う」という言葉を常に意識しよう、という話をしています。例えばSaaS事業は、現在プロダクトが増えていく時期です。認証認可系とか、OCR系とか。それぞれのチームの中に必要なロールがあって、それはPOだったり、PMだったり、EM、テックリード、デザイナーなど。そのロールごとのトップをシニアと定義しています。

直近だと、組織とプロダクトを設計できる人が必要という意味でPEM(Product Engineering Manager)の募集を開始しました。テックリードとか、PdMとか、EMの領域を全部カバーできる人をシニアと呼んでいて、それぞれの専門性にフォーカスして、周りを引っ張っていける人を想定しています。

松浦:Autifyの組織には、フロントエンドエンジニアとバックエンドエンジニアしかいないんです。その理由は、ビジネスゴールを考えたときに、「フロントエンドだけできる人」ではなくて「フロントエンドに軸足を置きつつ、バックエンドも回せる人」がスタートアップに向いているエンジニアだと思っているからです。(それを前提として、)会社のゴールを最短で実現できる人がシニアなエンジニアだと思いますね。要するに、軸足は持ちつつも、フルスタック的に動ける人です。技術面も重要ですが、エンジニアリングチーム外(ビジネスチームなど)とも連携して進めていける人を想定しています。

松本:円滑にコミュニケーションができるというのが大事ですよね。あと付け加えるとしたら、信頼できる意思決定が可能で、リーダーシップがある、ということですかね。

今後数年で作りたいエンジニア組織はどんな組織ですか?

松浦:いろんなエンジニアリング組織があることを前提としつつ、大きく分けると「狭く深い技術を持つエンジニアを集めた、(いわゆる)特化型の組織」と「ビジネス航路を見据えて、(どちらかといえば)浅く広い技術を持つエンジニアを集めた組織」があると思います。Autifyは、お客様のバーニングニーズを解決する方向へ、常にスピード感を持って進んでいくために、お客様の抱える問題を幅広く見て、いろんな技術を使って解決するような、浅く広いスキルを持ったエンジニア組織にしたいなと考えています。チーム間をエンジニアが異動する、というのも見据えていますね。フットワーク軽く、なんでもできる組織にしたいなと思っています。

松本:LayerXには「すべての経済活動をデジタル化する」というミッションがあります。いろんなドメイン、新規事業が出てくる中で、それぞれがベストプラクティスを共有しながら、独立して動ける組織を目指しています。

最後に

以上、イベントの様子についてお届けしました。 今後のイベントについては随時LayerXのconnpassページにUP予定です!

一緒に働く仲間を募集しています!

herp.careers