0
0

Prisma + zod を使用したLambdaのファイルサイズ肥大を抑えるために Lambda Layersを利用したメモ

Posted at

概要

前回に作成したファイルだと、バンドル時にハンドラファイルにスキーマファイルを含めてしまい、サイズが肥大化する問題があった。

今回、Lambda Layersを使用し、スキーマファイルを外だしできるようにする。
/opt/nodejs/ にレイヤーのパスが通るため、そこにスキーマファイルを入れるように設定した。

ソースコード

ソースの変更

Lambdaの定義ファイル

tsconfigを修正し、ソースを直接読みにいっていた部分を、デプロイ後のパスである/opt/nodejs/以下のフォルダを見に行くようにする

backend/tsconfig.json
    "paths": {
      "@kartagraph-backend/*": ["./src/*"],
      "@kartagraph-types/*": ["../types/src/*"],
-      "@kartagraph-prisma-zod": ["../prisma/generated/zod/index.ts"],
+      "/opt/nodejs/*": ["../prisma/generated/zod/*"],
    }
backend/src/domain/scenario/scenarioRepository.ts
import { execQuery } from '@kartagraph-backend/utils/repository';
- import { scenario_listSchema } from '@kartagraph-prisma-zod';
+ import { scenario_listSchema } from '/opt/nodejs/index';
import { ScenarioListItem } from '@kartagraph-types/index';

export const getScenarioList = async (): Promise<ScenarioListItem> => {

スキーマファイルを個別でバンドルする

prisma/package.json
{
  "name": "@kartagraph/prisma-zod",
  "scripts": {
+    "bundle": "esbuild --bundle generated/zod/index.ts --outfile=dist/index.js --target=node20 --format=cjs",
    "generate": "prisma generate",
    "pull": "prisma db pull"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@prisma/client": "^5.10.2",
    "zod": "^3.22.4"
  },
  "devDependencies": {
+    "esbuild": "^0.20.1",
    "prisma": "^5.10.2",
    "zod-prisma-types": "^3.1.6"
  }
}

バンドルしたファイルを含めたLayerを作成

infrastracture/lib/process/setup.ts
#!/usr/bin/env node
import * as childProcess from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path';

const nodeModulesPath = './bundle-node_modules';
export const NODE_LAMBDA_LAYER_DIR = path.resolve(process.cwd(), nodeModulesPath);
+ const zodSchemaPath = '../prisma/dist';
+ const commonModulesPath = './bundle-common';
+ export const COMMON_LAMBDA_LAYER_DIR = path.resolve(process.cwd(), commonModulesPath);

const NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME = `nodejs`;
const runtimeDirName = path.resolve(process.cwd(), `${nodeModulesPath}/${NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME}`);
const distFilePath = (file: string) => path.resolve(process.cwd(), `${nodeModulesPath}/${NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME}/${file}`);
const srcFilePath = (file: string) => path.resolve(`${process.cwd()}/../backend/${file}`);

export const bundleNpm = () => {
  createNodeModules();
+   copyCommonModules();
};

const createNodeModules = () => {
  copyPackageJson();

  childProcess.execSync(`npm install --omit=dev`, {
    cwd: getModulesInstallDirName(),
    env: { ...process.env },
  });
};

const copyPackageJson = () => {
  fs.mkdirsSync(getModulesInstallDirName());
  ['package.json'].map((file) => fs.copyFileSync(srcFilePath(file), distFilePath(file)));
};

const getModulesInstallDirName = (): string => {
  return runtimeDirName;
};
+ const copyCommonModules = () => {
+   const dist = path.resolve(process.cwd(), `${commonModulesPath}/${NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME}`);
+   const src = path.resolve(`${process.cwd()}/${zodSchemaPath}`);
+   fs.mkdirsSync(`${commonModulesPath}`);
+   fs.copySync(src, dist);
+ };
infrastracture/bin/lambda-layer-version-deploy.ts
// 省略

bundleNpm();

new LambdaLayersStack(app, `${processEnv.PROJECT_ID}-LambdaLayersStack`, {
  ssmKey: `${processEnv.SSM_PARAM_KEY_LAYER_VERSIONS_ARN}-${processEnv.PROJECT_ID}`,
+  commonSsmKey: `${processEnv.SSM_PARAM_KEY_LAYER_VERSIONS_ARN}-${processEnv.PROJECT_ID}-common`,
  env,
});

infrastracture/lib/lambda-layer-stack.ts
// 省略

interface LambdaLayersStackProps extends StackProps {
  ssmKey: string;
+  commonSsmKey: string;
}

export class LambdaLayersStack extends Stack {
  constructor(scope: Construct, id: string, props: LambdaLayersStackProps) {
    super(scope, id, props);
    const nodeModulesLayer = new lambda.LayerVersion(this, 'NodeModulesLayer', {
      code: lambda.AssetCode.fromAsset(NODE_LAMBDA_LAYER_DIR),
      compatibleRuntimes: [RUNTIME_VERSION],
    });
    // 省略

+    const commonModulesLayers = new lambda.LayerVersion(this, 'CommonModulesLayer', {
+      code: lambda.AssetCode.fromAsset(COMMON_LAMBDA_LAYER_DIR),
+      compatibleRuntimes: [RUNTIME_VERSION],
+    });
+    const commonArnParameter = new StringParameter(this, 'ssm-layer-version-common', {
+      parameterName: props.commonSsmKey,
+      stringValue: commonModulesLayers.layerVersionArn,
+      description: 'layer version arn for common lambda',
+    });

    Aspects.of(this).add(new Tag('Stack', id));
  }
}
infrastracture/package.json
{
  // 省略
  "scripts": {
    // 省略
+    "predeploy-layer": "cd ../prisma && npm run bundle",
    "deploy-layer": "cdk -a \"npx ts-node --prefer-ts-exts bin/lambda-layer-version-deploy.ts\" deploy --all",
    "deploy-rest": "cdk -a \"npx ts-node --prefer-ts-exts bin/rest.ts\" deploy --all"
  },
  // 省略
}

スキーマファイル入りのレイヤーを使用するようにする

infrastracture/lib/rest-stack.ts
// 省略

const bundling = {
  externalModules: [
                     '@neondatabase/serverless'
                   , '@aws-sdk/client-s3'
+                   , '/opt/nodejs/*'
                   ],
};

interface Props extends core.StackProps {
  projectId: string;
  ssmLambdaLayerKey: string;
+  ssmLambdaCommonLayerKey: string;
  ssmAPIGWUrlKey: string;
  apiVersion: string;
  neonEndpoint: string;
  cloundFrontDomain: string;
  mediaBucketName: string;
}

// 省略
export class KartaGraphRESTAPIStack extends core.Stack {
  private apiRoot: apigateway.Resource;
  private resourceMap = new Map<string, apigateway.Resource>();

  constructor(scope: Construct, id: string, props: Props) {
    super(scope, id, props);
    // 省略

    const defaultLambdaProps = this.createLambdaProps({
      ssmKeyForLambdaLayerArn: props.ssmLambdaLayerKey,
+      ssmKeyForLambdaCommonLayerArn: props.ssmLambdaCommonLayerKey,
      environment: { ...commonEnv },
      timeoutSec: 5, // 外部エンドポイントを経由してJSONを処理するため3秒では足りない
    });

    // 省略
  }

  // 省略

  private createLambdaProps(props: {
    ssmKeyForLambdaLayerArn: string;
+    ssmKeyForLambdaCommonLayerArn: string;
    environment?: Record<string, string>;
    initialPolicy?: iam.PolicyStatement[];
    timeoutSec?: number;
  }) {
    const lambdaLayerArn = StringParameter.valueForStringParameter(this, props.ssmKeyForLambdaLayerArn);
+    const commonLambdaLayerArn = StringParameter.valueForStringParameter(this, props.ssmKeyForLambdaCommonLayerArn);

    const layers = [
      LayerVersion.fromLayerVersionArn(this, 'node_modules-layer', lambdaLayerArn),
+      LayerVersion.fromLayerVersionArn(this, 'common-layer', commonLambdaLayerArn),
    ];

    // 同じStack上でLayerVersionを作っていない場合、cdk synthで sam local 実行用のoutputを作るときにレイヤーを使うとエラーになる。
    const layerSettings = !!process.env['CDK_SYNTH'] ? {} : { bundling, layers };

    return {
      runtime: RUNTIME_VERSION,
      ...layerSettings,
      environment: props.environment,
      initialPolicy: props.initialPolicy,
      timeout: props.timeoutSec ? core.Duration.seconds(props.timeoutSec) : undefined,
    };
  }
  // 省略
}

参考

cdkv2を使って別Stackで作ったlambda layerをlambdaに適用したメモ
aws-cdk v2でlambda layers を扱ったメモ
dbマイグレーションの型付けのファイル容量を減らす
Deploying Prisma with AWS Lambda Layers in CDK

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0