<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matsuda</title>
    <description>The latest articles on DEV Community by Matsuda (@mazyu36).</description>
    <link>https://dev.to/mazyu36</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2384030%2Fd7082734-6770-4132-8750-cbf42cda3115.jpeg</url>
      <title>DEV Community: Matsuda</title>
      <link>https://dev.to/mazyu36</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mazyu36"/>
    <language>en</language>
    <item>
      <title>Adding Cognito Authentication to Self-Hosted Langfuse with AWS CDK</title>
      <dc:creator>Matsuda</dc:creator>
      <pubDate>Sat, 15 Feb 2025 13:04:48 +0000</pubDate>
      <link>https://dev.to/aws-builders/adding-cognito-authentication-to-self-hosted-langfuse-with-aws-cdk-4gfe</link>
      <guid>https://dev.to/aws-builders/adding-cognito-authentication-to-self-hosted-langfuse-with-aws-cdk-4gfe</guid>
      <description>&lt;p&gt;Langfuse provides built-in authentication using ID/password by default, but it also allows using other IdPs (NextAuth.js is used).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://langfuse.com/self-hosting/authentication-and-sso" rel="noopener noreferrer"&gt;https://langfuse.com/self-hosting/authentication-and-sso&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although the default authentication method in Langfuse is ID/password, there are cases where integrating an external IdP is desirable for enhanced security.&lt;/p&gt;

&lt;p&gt;In this post, I have added authentication using Amazon Cognito (hereafter referred to as Cognito) to &lt;a href="https://dev.to/aws-builders/self-hosting-langfuse-v3-on-aws-using-cdk-508a"&gt;Langfuse with AWS CDK&lt;/a&gt;, which was created in the previous post.&lt;/p&gt;

&lt;p&gt;After configuration, you will be able to log in with Cognito as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtqszlll4fg4947q6506.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtqszlll4fg4947q6506.png" alt="Image description" width="800" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, it is possible to disable the default authentication mechanism and enforce authentication via Cognito.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3znddfyvqfm1ttfpufe.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy3znddfyvqfm1ttfpufe.jpeg" alt="Image description" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post summarizes the key points of this setup.&lt;/p&gt;

&lt;p&gt;The GitHub repository is available below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk" rel="noopener noreferrer"&gt;https://github.com/mazyu36/langfuse-with-aws-cdk&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use an External IdP?
&lt;/h2&gt;

&lt;p&gt;One of the main motivations for using an external IdP is to enhance security.&lt;/p&gt;

&lt;p&gt;When using Langfuse's built-in authentication, users sign up via the following screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgq5jb44qlzwulhid2p4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgq5jb44qlzwulhid2p4c.png" alt="Image description" width="800" height="744"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During sign-up, users enter their email addresses, but there is no additional verification step such as sending a verification code via email. As a result, accounts are created immediately, and users are redirected to the Langfuse dashboard.&lt;/p&gt;

&lt;p&gt;This means that as long as someone has access, they can sign up and gain access right away, raising concerns about the misuse of API keys.&lt;/p&gt;

&lt;p&gt;Another drawback is the limited configurability of authentication settings.&lt;/p&gt;

&lt;p&gt;By using an external IdP, you can enforce email verification, restrict sign-up to pre-created users only, and flexibly customize authentication according to your requirements.&lt;/p&gt;

&lt;p&gt;This is particularly useful when the application is widely available but you want to limit the users who can issue API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Cognito
&lt;/h2&gt;

&lt;p&gt;After creating a Cognito user pool and an app client, authentication can be enabled by setting the following environment variables in Langfuse Web (refer to the &lt;a href="https://langfuse.com/self-hosting/authentication-and-sso#aws-cognito" rel="noopener noreferrer"&gt;Langfuse documentation&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AUTH_COGNITO_CLIENT_ID&lt;/code&gt;: ID of the user pool client&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTH_COGNITO_CLIENT_SECRET&lt;/code&gt;: Secret of the user pool client&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTH_COGNITO_ISSUER&lt;/code&gt;: Set this to &lt;code&gt;https://cognito-idp.{region}.amazonaws.com/{PoolId}&lt;/code&gt;. This value is also mentioned in the &lt;a href="https://next-auth.js.org/providers/cognito" rel="noopener noreferrer"&gt;NextAuth.js documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTH_DISABLE_USERNAME_PASSWORD&lt;/code&gt;: Setting this to &lt;code&gt;true&lt;/code&gt; disables Langfuse’s built-in authentication. This is optional.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTH_COGNITO_ALLOW_ACCOUNT_LINKING&lt;/code&gt;: Setting this to &lt;code&gt;true&lt;/code&gt; merges accounts if the same email address exists in Langfuse's built-in authentication. This is useful when migrating existing users to an external IdP. This setting is also optional.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, you must configure Cognito's app client with a callback URL set to &lt;code&gt;https://{Langfuse domain}/api/auth/callback/cognito&lt;/code&gt;. Without this, authentication will not function correctly.&lt;/p&gt;

&lt;p&gt;One important point to note is that Cognito requires callback URLs (except for localhost) to use &lt;code&gt;https&lt;/code&gt;. Therefore, when implementing Cognito authentication in a self-hosted environment, HTTPS is mandatory.&lt;/p&gt;

&lt;p&gt;Furthermore, you need to configure Cognito’s managed login (hosted UI) and domain settings.&lt;/p&gt;

&lt;p&gt;The details of the CDK implementation will be covered later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Below is the overall architecture. Compared to the previous post, a Cognito UserPool and an associated ACM certificate have been added.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fay6jq7wwunwsgcb8r36j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fay6jq7wwunwsgcb8r36j.png" alt="Image description" width="791" height="931"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this setup, a custom domain is used for Cognito’s authentication page, which internally creates a CloudFront distribution. Since ACM certificates must be issued in Virginia (&lt;code&gt;us-east-1&lt;/code&gt;), deploying Langfuse in a different region results in a cross-region setup.&lt;/p&gt;

&lt;p&gt;To handle this in CDK, the implementation is split into two stacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A stack specifically for Cognito’s custom domain ACM certificate (&lt;code&gt;us-east-1&lt;/code&gt; fixed)&lt;/li&gt;
&lt;li&gt;A stack for all other resources (region can be any)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ref: &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CDK Implementation
&lt;/h2&gt;

&lt;p&gt;The key implementation points are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a stack for &lt;code&gt;us-east-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create Cognito’s UserPool, UserPool Client, and Domain&lt;/li&gt;
&lt;li&gt;Configure Langfuse Web environment variables with Cognito information&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Creating a Stack for &lt;code&gt;us-east-1&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A new stack is added to create the ACM certificate for Cognito’s custom domain. Since ACM certificates must be issued in &lt;code&gt;us-east-1&lt;/code&gt;, the stack's region is fixed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;crossRegionReference&lt;/code&gt; is enabled to allow references across regions. This is a CDK-specific mechanism that utilizes SSM parameters. For more details, refer to the &lt;a href="https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/core/adr/cross-region-stack-references.md" rel="noopener noreferrer"&gt;CDK documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Stack for creating ACM certificate&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enableCognitoAuth&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;usEast1Stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UsEast1Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`UsEast1Stack-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CDK_DEFAULT_ACCOUNT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Fixed to us-east-1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;crossRegionReferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Enable cross-region references&lt;/span&gt;
    &lt;span class="na"&gt;domainConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domainConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LangfuseWithAwsCdkStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`LangfuseWithAwsCdkStack-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CDK_DEFAULT_ACCOUNT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CDK_DEFAULT_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Region for deploying Langfuse&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;hostName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domainConfig&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;hostName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domainConfig&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;crossRegionReferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Enable cross-region references&lt;/span&gt;
  &lt;span class="na"&gt;disableEmailPasswordAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disableEmailPasswordAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;certificateForCognito&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usEast1Stack&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;certificateForCognito&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Pass ACM certificate&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Creating Cognito UserPool, UserPool Client, and Domain
&lt;/h3&gt;

&lt;p&gt;We will now define the resources related to Cognito. The implementation can be found in &lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/lib/constructs/auth/cognito-auth.ts" rel="noopener noreferrer"&gt;lib/constructs/auth/cognito-auth.ts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, let's create a user pool. There are no particular points to highlight here. Customize the authentication methods as needed based on authentication requirements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cognito&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UserPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserPool&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;accountRecovery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cognito&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AccountRecovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EMAIL_ONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signInAliases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;standardAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;mutable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;selfSignUpEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userVerification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;emailSubject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Langfuse - Verify your new account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's create an app client. It is mandatory to specify the Callback URL and generate a client secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolclient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CognitoClient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;authFlows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;userPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userSrp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;oAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;flows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;authorizationCodeGrant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cognito&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OAuthScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cognito&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OAuthScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EMAIL&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;callbackUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cdnLoadBalancer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/auth/callback/cognito`&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;// Specify the Callback URL&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;generateSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Generate the client secret&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, enable managed login for the Cognito authentication screen and set up a custom domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Configure managed login&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cognito&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnManagedLoginBranding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ManagedLoginBranding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userPoolId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolclient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolClientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;useCognitoProvidedValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Set up a custom domain for Cognito&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDomain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CognitoDomain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;customDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;domainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`auth.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hostName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hostedZone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zoneName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;certificateForCognito&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;managedLoginVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cognito&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ManagedLoginVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEWER_MANAGED_LOGIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// **Wait for the A record of Langfuse to be generated**&lt;/span&gt;
&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cdnLoadBalancer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;albARecord&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Generate a custom domain record&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;route53&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ARecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CognitoARecord&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hostedZone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;recordName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`auth.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hostName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route53&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RecordTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAlias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UserPoolDomainTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One crucial point to be aware of is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// **Wait for the A record of Langfuse to be generated**&lt;/span&gt;
&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cdnLoadBalancer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;albARecord&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Due to the specifications of Cognito's custom domains, a valid A record must exist for the parent domain. In this CDK implementation, the Cognito custom domain is designed to prepend &lt;code&gt;auth.&lt;/code&gt; to the domain assigned to Langfuse.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Langfuse domain: &lt;code&gt;langfuse.example.com&lt;/code&gt; (The domain can be configured in &lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/bin/app-config.ts#L35" rel="noopener noreferrer"&gt;bin/app-config.ts&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Cognito custom domain: &lt;code&gt;auth.langfuse.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a valid A record does not exist for the parent Langfuse domain when setting up the Cognito custom domain, the deployment will fail with an error.&lt;/p&gt;

&lt;p&gt;Therefore, in the CDK implementation, we define a dependency to ensure that the A record for the Langfuse domain (specifically, the domain assigned to the ALB) is generated before setting up the Cognito custom domain.&lt;/p&gt;

&lt;p&gt;This requirement is documented in the following AWS documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A web domain that you own. Its parent domain must have a valid DNS A record. You can assign any value to this record. The parent may be the root of the domain, or a child domain that is one step up in the domain hierarchy. For example, if your custom domain is auth.xyz.example.com, Amazon Cognito must be able to resolve xyz.example.com to an IP address.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html#cognito-user-pools-add-custom-domain-prereq" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html#cognito-user-pools-add-custom-domain-prereq&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Configuring Cognito-related Information in Langfuse Web Environment Variables
&lt;/h3&gt;

&lt;p&gt;Finally, configure the Cognito-related information as environment variables in Langfuse Web.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;AUTH_COGNITO_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cognitoAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolclient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolClientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;AUTH_COGNITO_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cognitoAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolclient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolClientSecret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsafeUnwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="nx"&gt;AUTH_COGNITO_ISSUER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://cognito-idp.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cognitoAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userPoolId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, authentication via Cognito is now enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;p&gt;To enable Cognito authentication in Langfuse with AWS CDK, set &lt;code&gt;enableCognitoAuth&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in &lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/bin/app-config.ts#L32" rel="noopener noreferrer"&gt;bin/app-config.ts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/bin/app-config.ts#L65" rel="noopener noreferrer"&gt;sample configuration&lt;/a&gt; in the repository, it is enabled for &lt;code&gt;prod&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;prod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// omit&lt;/span&gt;
    &lt;span class="nl"&gt;disableEmailPasswordAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Set to true to disable Langfuse's built-in authentication&lt;/span&gt;
    &lt;span class="nx"&gt;enableCognitoAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Enable Cognito authentication&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying these settings, deploy to the target environment. Since enabling Cognito authentication results in two separate stacks, the &lt;code&gt;--all&lt;/code&gt; flag is required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cdk deploy &lt;span class="nt"&gt;--context&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the deployment is complete, access the Langfuse URL. You should see a Cognito login menu. Click on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyg1chua2powal934due.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyg1chua2powal934due.jpeg" alt="Image description" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this implementation, users are redirected to Cognito's managed login authentication screen. It may take some time for the custom domain to propagate, so if you encounter an error, try again after a while. If authentication still fails, there may be a configuration issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F335evactzg0ouvsduph5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F335evactzg0ouvsduph5.png" alt="Image description" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, follow the user pool's settings to complete the signup process. In this implementation, after entering signup details, a verification code is sent to the specified email address, so complete the verification process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqptg42a0adqu01614hx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqptg42a0adqu01614hx2.png" alt="Image description" width="800" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once signup is complete, users will be redirected to the Langfuse initial screen. From here on, authentication via Cognito will be available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujgw16zbrwsie98e1nck.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujgw16zbrwsie98e1nck.jpeg" alt="Image description" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Introducing an external IdP can be a valuable way to enhance the security of a self-hosted Langfuse environment. &lt;br&gt;
Give it a try!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Self-Hosting Langfuse v3 on AWS Using CDK</title>
      <dc:creator>Matsuda</dc:creator>
      <pubDate>Thu, 06 Feb 2025 14:11:11 +0000</pubDate>
      <link>https://dev.to/aws-builders/self-hosting-langfuse-v3-on-aws-using-cdk-508a</link>
      <guid>https://dev.to/aws-builders/self-hosting-langfuse-v3-on-aws-using-cdk-508a</guid>
      <description>&lt;p&gt;I created a CDK project to self-host the observability tool (OSS) Langfuse v3 on AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk" rel="noopener noreferrer"&gt;https://github.com/mazyu36/langfuse-with-aws-cdk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, I'll share the usage method, architecture, and troubleshooting know-how.&lt;/p&gt;

&lt;p&gt;The following official documents are helpful for self-hosting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/self-hosting#deployment-options" rel="noopener noreferrer"&gt;Self-host Langfuse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/self-hosting/infrastructure/containers" rel="noopener noreferrer"&gt;Infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Architecture
&lt;/h1&gt;

&lt;p&gt;I've built Langfuse v3 on AWS. Essentially, it's a simple configuration that replaces Langfuse's architecture with managed services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5ln1qeotfbzcicclous.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5ln1qeotfbzcicclous.png" alt="Image description" width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Langfuse Application (Web, Worker)
&lt;/h2&gt;

&lt;p&gt;I've deployed the Langfuse container image to ECS on Fargate.&lt;/p&gt;

&lt;p&gt;Service-to-service communication with ClickHouse, mentioned later, uses ECS Service Connect. Since Web and Worker act as clients (the requesting side) in service-to-service communication, Service Connect only needs to be defined with client settings.&lt;/p&gt;

&lt;p&gt;If you want to reduce costs and just need basic communication, Service Discovery should also work fine.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://langfuse.com/self-hosting/infrastructure/containers#recommended-sizing" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, for production environments, the following is recommended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All containers should have at least 2 CPUs and 4GB RAM&lt;/li&gt;
&lt;li&gt;Langfuse Web should have 2 instances for redundancy&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;For production environments, we recommend to use at least 2 CPUs and 4 GB of RAM for all containers. You should have at least two instances of the Langfuse Web container for high availability. For auto-scaling, we recommend to add instances once the CPU utilization exceeds 50% on either container.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the CDK implementation, you can configure whether to use Fargate Spot in &lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/lib/stack-config.ts" rel="noopener noreferrer"&gt;lib/stack-config.ts&lt;/a&gt;. In development environments, you can use Fargate Spot to reduce costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  ClickHouse - OLAP
&lt;/h2&gt;

&lt;p&gt;ClickHouse is also running directly as a container image on ECS on Fargate. EFS is mounted for data persistence.&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL - OLTP
&lt;/h2&gt;

&lt;p&gt;Using Aurora Serverless v2.&lt;/p&gt;

&lt;p&gt;In the CDK implementation, &lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/lib/stack-config.ts" rel="noopener noreferrer"&gt;lib/stack-config.ts&lt;/a&gt; allows enabling Zero Capacity for cost reduction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/database/introducing-scaling-to-0-capacity-with-amazon-aurora-serverless-v2" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/database/introducing-scaling-to-0-capacity-with-amazon-aurora-serverless-v2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When enabled, the database stops after a certain period without connections. When attempting to connect during this state, it takes about 15 seconds to restart, so retries are necessary after a certain time. This is intended for development environments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2-auto-pause.html#auto-pause-whynot" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2-auto-pause.html#auto-pause-whynot&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  S3 - Blob Storage
&lt;/h2&gt;

&lt;p&gt;Storage for storing events and traces. This uses S3 directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache/Queue
&lt;/h2&gt;

&lt;p&gt;Redis/Valkey is needed for caching and asynchronous communication between Web and Worker. In the CDK project, ElastiCache is used, with Valkey as the engine for cost benefits.&lt;/p&gt;

&lt;p&gt;There are several points to note about ElastiCache, which I'll explain in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use REDIS_STRING when using in-transit encryption
&lt;/h3&gt;

&lt;p&gt;ElastiCache allows encryption settings for communication (TLS). In CDK, setting &lt;code&gt;transitEncryptionMode&lt;/code&gt; to &lt;code&gt;required&lt;/code&gt; allows only TLS communication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;elasticache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnReplicationGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// omit&lt;/span&gt;
      &lt;span class="na"&gt;transitEncryptionEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;transitEncryptionMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Allow only TLS communication. 'preferred' allows both TLS and non-TLS.&lt;/span&gt;
      &lt;span class="c1"&gt;// omit&lt;/span&gt;
      &lt;span class="na"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsafeUnwrap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When setting connection information for Langfuse Web/Server in environment variables, there are two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;REDIS_CONNECTION_STRING&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;REDIS_HOST&lt;/code&gt;, &lt;code&gt;REDIS_PORT&lt;/code&gt;, &lt;code&gt;REDIS_AUTH&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://langfuse.com/self-hosting/infrastructure/cache#configuration" rel="noopener noreferrer"&gt;https://langfuse.com/self-hosting/infrastructure/cache#configuration&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When only TLS communication is allowed, currently, method 1 (&lt;code&gt;REDIS_CONNECTION_STRING&lt;/code&gt;) must be used. Method 2 will not connect and won't output an error (I got stuck on this).&lt;/p&gt;

&lt;p&gt;Here's the English translation:&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;(Updated February 15, 2025)&lt;/strong&gt;&lt;br&gt;
As of Langfuse v3.28.0 and later, TLS communication settings can now be configured using "2. Set &lt;code&gt;REDIS_HOST&lt;/code&gt;, &lt;code&gt;REDIS_PORT&lt;/code&gt;, &lt;code&gt;REDIS_AUTH&lt;/code&gt;".&lt;/p&gt;

&lt;p&gt;This can be enabled by setting the &lt;code&gt;REDIS_TLS_ENABLED&lt;/code&gt; environment variable to &lt;code&gt;true&lt;/code&gt; in the Langfuse Web/Worker environment. Since this eliminates the need to generate a &lt;code&gt;REDIS_CONNECTION_STRING&lt;/code&gt; value, method 2 is generally preferred.&lt;/p&gt;

&lt;p&gt;I have also updated the CDK implementation to use method 2. For specific implementation details, please refer to the environment variable configuration section below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/lib/constructs/services/common-environment.ts#L57" rel="noopener noreferrer"&gt;https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/lib/constructs/services/common-environment.ts#L57&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For versions prior to v3.28.0, you will need to use the &lt;code&gt;REDIS_CONNECTION_STRING&lt;/code&gt; configuration as described in the following sections.&lt;/p&gt;



&lt;p&gt;For TLS-only communication, set &lt;code&gt;REDIS_CONNECTION_STRING&lt;/code&gt; with &lt;code&gt;rediss://...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is due to the implementation in Langfuse's &lt;a href="https://github.com/langfuse/langfuse/blob/89896c70eed8973afaabcd3f673019062ec4dfa9/packages/shared/src/server/redis/redis.ts#L23" rel="noopener noreferrer"&gt;packages/shared/src/server/redis/redis.ts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Langfuse uses &lt;code&gt;ioredis&lt;/code&gt;, and the instance creation is defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_CONNECTION_STRING&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_CONNECTION_STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;defaultRedisOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;additionalOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_HOST&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_PORT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REDIS_AUTH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
           &lt;span class="c1"&gt;// No TLS configuration&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;defaultRedisOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;additionalOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using &lt;code&gt;REDIS_HOST&lt;/code&gt;, individual host settings are configured, but the &lt;code&gt;tls&lt;/code&gt; property is necessary for TLS communication (reference: &lt;a href="https://github.com/redis/ioredis?tab=readme-ov-file#tls-options" rel="noopener noreferrer"&gt;ioredis documentation&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;redis.my-service.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="c1"&gt;// TLS configuration is necessary&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this option is currently not provided by Langfuse. Therefore, using &lt;code&gt;REDIS_HOST&lt;/code&gt; etc. makes TLS communication impossible.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;code&gt;ioredis&lt;/code&gt; allows TLS connection by defining connection information starting with &lt;code&gt;rediss&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rediss://redis.my-service.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Specify TLS with rediss&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Langfuse, using &lt;code&gt;REDIS_CONNECTION_STRING&lt;/code&gt; enables TLS communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set noeviction
&lt;/h3&gt;

&lt;p&gt;The parameter &lt;code&gt;maxmemory-policy&lt;/code&gt; must be set to &lt;code&gt;noeviction&lt;/code&gt; (no eviction setting).&lt;br&gt;
This ensures that queue jobs are not removed from the cache.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You must set the parameter maxmemory-policy to noeviction to ensure that the queue jobs are not evicted from the cache.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;[&lt;a href="https://langfuse.com/self-hosting/infrastructure/cache#deployment-options:embed:cite" rel="noopener noreferrer"&gt;https://langfuse.com/self-hosting/infrastructure/cache#deployment-options:embed:cite&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;To prevent jobs from accumulating infinitely, retry and job retention on failure are considered (example in &lt;a href="https://github.com/langfuse/langfuse/blob/89896c70eed8973afaabcd3f673019062ec4dfa9/packages/shared/src/server/redis/ingestionQueue.ts#L27" rel="noopener noreferrer"&gt;IngestionQueue&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;IngestionQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newRedis&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TQueueJobTypes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;QueueName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IngestionQueue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;QueueName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IngestionQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newRedis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;defaultJobOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;removeOnComplete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Remove job after success&lt;/span&gt;
              &lt;span class="na"&gt;removeOnFail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Maximum number of failed job retentions&lt;/span&gt;
              &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Number of retries&lt;/span&gt;
              &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// Retry with exponential backoff&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;noeviction&lt;/code&gt; can be set in the parameter group in CDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="cm"&gt;/**
     * We must set the parameter `maxmemory-policy` to `noeviction` to ensure that the queue jobs are not evicted from the cache.
     * @see https://langfuse.com/self-hosting/infrastructure/cache#deployment-options
     */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parameterGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;elasticache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnParameterGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RedisParameterGroup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;cacheParameterGroupFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;valkey8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Custom parameter group for Langfuse ElastiCache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;maxmemory-policy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noeviction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// here&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cluster mode and ElastiCache Serverless are not supported
&lt;/h3&gt;

&lt;p&gt;At the time of writing, Langfuse Web/Worker does not support Redis/Valkey cluster mode. Therefore, ElastiCache cluster mode cannot be used.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Langfuse handles failovers between read-replicas, but does not support Redis cluster mode for now, i.e. there is no sharding support. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://langfuse.com/self-hosting/infrastructure/cache#managed-redisvalkey-by-cloud-providers" rel="noopener noreferrer"&gt;https://langfuse.com/self-hosting/infrastructure/cache#managed-redisvalkey-by-cloud-providers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means ElastiCache Serverless also cannot be used currently. AWS documentation also states that ElastiCache Serverless runs in cluster mode.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ElastiCache Serverless runs Valkey, Memcached, or Redis OSS in cluster mode and is only compatible with clients that support TLS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/WhatIs.corecomponents.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/WhatIs.corecomponents.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try to use ElastiCache Serverless, you'll frequently encounter CROSSSLOT errors:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;2025-02-04T09:09:03.525Z warn Redis connection error: CROSSSLOT Keys in request don't hash to the same slot&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The cause is that keys are being operated on that hash to different slots.&lt;/p&gt;

&lt;p&gt;In cluster mode, keys are calculated and stored in hash slots. When operating multiple keys, they must be in the same hash slot. A detailed explanation can be found in this article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/inspector/resolved-crossslot-keys-error-in-redis-cluster-mode-enabled-3kec"&gt;https://dev.to/inspector/resolved-crossslot-keys-error-in-redis-cluster-mode-enabled-3kec&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the current implementation does not consider cluster mode, these errors occur. This cannot be resolved through infrastructure settings.&lt;/p&gt;

&lt;h1&gt;
  
  
  Usage
&lt;/h1&gt;

&lt;p&gt;Please refer to the README for details. Below is a simple deployment method and the flow for verifying the operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDK Configuration and Deployment
&lt;/h2&gt;

&lt;p&gt;The CDK implementation allows parameters to be defined for each environment (dev/stg/prod), and the deployment target environment is specified as a context. The general deployment method is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clone the repository and install the necessary libraries with &lt;code&gt;npm ci&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the parameters for the corresponding environment in &lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/bin/app-config.ts" rel="noopener noreferrer"&gt;bin/app-config&lt;/a&gt; and &lt;a href="https://github.com/mazyu36/langfuse-with-aws-cdk/blob/main/lib/stack-config.ts" rel="noopener noreferrer"&gt;lib/stack-config&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Deploy with &lt;code&gt;npx cdk deploy --context env=ENV_NAME&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deployment takes about 20 minutes. The URL is output in the Outputs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; ✅  LangfuseWithAwsCdkStack-dev

✨  Deployment &lt;span class="nb"&gt;time&lt;/span&gt;: 1238.58s

Outputs:

&lt;span class="c"&gt;# omit&lt;/span&gt;

LangfuseWithAwsCdkStack-dev.LangfuseURL &lt;span class="o"&gt;=&lt;/span&gt; https://langfuse.example.com

&lt;span class="c"&gt;# omit&lt;/span&gt;

✨  Total &lt;span class="nb"&gt;time&lt;/span&gt;: 1247.3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initial Setup
&lt;/h2&gt;

&lt;p&gt;After opening the URL, first sign up by entering your email address and other details, then click &lt;code&gt;Sign up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjdm2p40bxzu4qipx1or.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxjdm2p40bxzu4qipx1or.png" alt="Image description" width="607" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If Aurora Serverless v2 Zero scaling is enabled and the database is in an idle state, you may encounter a DB error like the following (the error message is hard to read due to the color...). At this point, the DB will start to wake up from idle, so wait for a while (about 15-30 seconds) and then click &lt;code&gt;Sign up&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59jb5nwpa5ic2xp0durc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59jb5nwpa5ic2xp0durc.png" alt="Image description" width="561" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, create an &lt;code&gt;Organization&lt;/code&gt; by clicking &lt;code&gt;New Organization&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmdfidsx0onh0aj6v3q7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmdfidsx0onh0aj6v3q7.png" alt="Image description" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the Organization name and click &lt;code&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqss9ltkk6xelhfkark89.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqss9ltkk6xelhfkark89.png" alt="Image description" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to add members, configure them here. I will not set this up here, so click &lt;code&gt;next&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnzmhyjd236fl5nphi7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnzmhyjd236fl5nphi7r.png" alt="Image description" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, set the Project name. After setting, click &lt;code&gt;create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdie15gbgfzqa69ct105.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdie15gbgfzqa69ct105.png" alt="Image description" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, select &lt;code&gt;API Keys&lt;/code&gt; and click &lt;code&gt;Create new API Keys&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8s6v7h1ra9at7acf510i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8s6v7h1ra9at7acf510i.png" alt="Image description" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the Secret key and Public key are issued, make a note of them. This completes the initial setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0qmcqs96uq5dd5oc83t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0qmcqs96uq5dd5oc83t.png" alt="Image description" width="521" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Operation Verification
&lt;/h2&gt;

&lt;p&gt;Perform operation verification using the issued API keys. Here, we use &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, set the API keys and hostname as environment variables in an appropriate environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LANGFUSE_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_SECRET_KEY"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LANGFUSE_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_PUBLIC_KEY"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LANGFUSE_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_LANGFUSE_URL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, execute the ingestion (trace ingestion) API with &lt;code&gt;curl&lt;/code&gt;. Note that the content is dummy, so there is almost no actual data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LANGFUSE_HOST&lt;/span&gt;&lt;span class="s2"&gt;/api/public/ingestion"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LANGFUSE_PUBLIC_KEY&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$LANGFUSE_SECRET_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "batch": [
      {
        "type": "trace-create",
        "id": "'&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'",
        "timestamp": "'&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s2"&gt;"%Y-%m-%dT%H:%M:%S.000Z"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'",
        "metadata": null,
        "body": {
          "id": "'&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'",
          "name": "test",
          "timestamp": "'&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +&lt;span class="s2"&gt;"%Y-%m-%dT%H:%M:%S.000Z"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s1"&gt;'",
          "public": false
        }
      }
    ],
    "metadata": null
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a 201 response is output, it is working correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"successes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"523EFC5E-8BAC-485D-9CC3-C049B5F64FA4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check the traces in the browser under &lt;code&gt;Tracing&lt;/code&gt; -&amp;gt; &lt;code&gt;Traces&lt;/code&gt;.&lt;br&gt;
If the executed data is included, the operation verification is complete 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffk5ifzrhjh3n06avkmdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffk5ifzrhjh3n06avkmdi.png" alt="Image description" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Troubleshooting Approach
&lt;/h1&gt;

&lt;p&gt;I will summarize the actual troubleshooting steps I took during the construction.&lt;/p&gt;

&lt;p&gt;When I was building, the Langfuse application started, but I struggled with issues such as ingestion (trace ingestion) and trace confirmation on the screen.&lt;/p&gt;

&lt;p&gt;There is documentation available for troubleshooting during self-hosting.&lt;br&gt;
&lt;a href="https://langfuse.com/self-hosting/troubleshooting" rel="noopener noreferrer"&gt;https://langfuse.com/self-hosting/troubleshooting&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Especially if ingestion is not working, the &lt;a href="https://langfuse.com/self-hosting/troubleshooting#missing-events-after-post-apipublicingestion" rel="noopener noreferrer"&gt;Missing Events After POST /api/public/ingestion&lt;/a&gt; section lists points to check.&lt;/p&gt;

&lt;p&gt;Ingestion is done by implementing a program using the SDK or executing the API with the curl command mentioned earlier to ingest traces into Langfuse. If you cannot confirm it in the browser, the process is failing somewhere.&lt;/p&gt;

&lt;p&gt;The general flow of ingestion is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When received via the web, upload the event to the bucket and set a job in the cache queue.&lt;/li&gt;
&lt;li&gt;The worker picks up the job from the cache queue, reads the event from the bucket, and finally stores it in ClickHouse.&lt;/li&gt;
&lt;li&gt;When accessed from the browser, the web retrieves the data from ClickHouse and displays it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;※You can get a feel for this by looking at &lt;a href="https://github.com/langfuse/langfuse/blob/main/packages/shared/src/server/ingestion/processEventBatch.ts#L78" rel="noopener noreferrer"&gt;packages/shared/src/server/ingestion/processEventBatch.ts&lt;/a&gt; and &lt;a href="https://github.com/langfuse/langfuse/blob/main/worker/src/queues/ingestionQueue.ts" rel="noopener noreferrer"&gt;worker/src/queues/ingestionQueue.ts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Based on the above process flow and documentation, I performed troubleshooting as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check the logs of the Web and Worker.&lt;/li&gt;
&lt;li&gt;Check the bucket (S3).&lt;/li&gt;
&lt;li&gt;Check the cache (ElastiCache).&lt;/li&gt;
&lt;li&gt;Check ClickHouse.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I will describe what I mainly did in order.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Check the Logs of the Web and Worker
&lt;/h2&gt;

&lt;p&gt;First, check the logs of the Web and Worker as the logs of Langfuse itself. If there is an error due to infrastructure layer configuration issues, the cause can be identified here.&lt;/p&gt;

&lt;p&gt;However, at the time of writing, there were not many logs output, and the cause was often difficult to identify due to the asynchronous nature of the queue, etc.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Check the Bucket (S3)
&lt;/h2&gt;

&lt;p&gt;When ingestion is performed, the event JSON is stored in S3. If the JSON does not exist in S3, it is likely that the connection between the Web and S3 is not working properly. Check the NW and permission settings.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Check the Cache (ElastiCache)
&lt;/h2&gt;

&lt;p&gt;If the JSON is stored in S3, next check if the job is being stored and processed in ElastiCache.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;valkey-cli&lt;/code&gt; for connection (if using Redis, &lt;code&gt;redis-cli&lt;/code&gt; is better). The setup method is summarized in the following documentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/connect-tls.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/connect-tls.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After setting up, connect to the cache with the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;valkey-cli &lt;span class="nt"&gt;-h&lt;/span&gt; Primary or Configuration Endpoint &lt;span class="nt"&gt;--tls&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s1"&gt;'your-password'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 6379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once connected, tail the logs with &lt;code&gt;MONITOR&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;MONITOR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;OK&lt;/code&gt; appears after executing the command and no logs are displayed, it is likely that the Web/Worker cannot connect to ElastiCache. Recheck the connection settings. If connected, some logs will be displayed.&lt;/p&gt;

&lt;p&gt;Also, check the &lt;code&gt;ingestion-queue&lt;/code&gt; as follows. If the Web and ElastiCache are connected and jobs are being added to the queue, something like the following should be displayed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;KEYS bull:ingestion-queue:&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Result&lt;/span&gt;
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:2"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:active"&lt;/span&gt;
3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:3"&lt;/span&gt;
4&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:stalled"&lt;/span&gt;
5&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:3:lock"&lt;/span&gt;
6&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:meta"&lt;/span&gt;
7&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:id"&lt;/span&gt;
8&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:events"&lt;/span&gt;
9&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:1"&lt;/span&gt;
10&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:1:lock"&lt;/span&gt;
11&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:2:lock"&lt;/span&gt;
12&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"bull:ingestion-queue:stalled-check"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I was troubleshooting, the above was the case, but the traces were not displayed in the browser, so I dug deeper.&lt;/p&gt;

&lt;p&gt;First, check if there are any failed jobs with the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ZRANGE bull:ingestion-queue:failed 0 &lt;span class="nt"&gt;-1&lt;/span&gt; WITHSCORES

&lt;span class="c"&gt;# Result&lt;/span&gt;
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"2"&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"1738464474985"&lt;/span&gt;
3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
4&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"1738464474986"&lt;/span&gt;
5&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;
6&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"1738464474996"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jobs with IDs 1~3 have failed. Let's look at the details of job ID 2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;HGETALL bull:ingestion-queue:2

&lt;span class="c"&gt;# Result&lt;/span&gt;
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt;

&lt;span class="c"&gt;# ... omit&lt;/span&gt;

17&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"stacktrace"&lt;/span&gt;
18&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: upstream request timeout&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n    at parseError (/app/node_modules/.pnpm/@clickhouse+client-common@1.4.0/node_modules/@clickhouse/client-common/dist/error/parse_error.js:37:39)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n    at ClientRequest.onResponse 

# ...omit
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, the error cause is &lt;code&gt;upstream request timeout&lt;/code&gt;, and it is happening in the Clickhouse related part.&lt;br&gt;
Therefore, it is expected that a timeout occurred when the Worker tried to request ClickHouse while processing the job.&lt;/p&gt;

&lt;p&gt;In the issue I encountered, the cause was ultimately a missing security group setting. The inbound rule of the Fargate Service's security group for ClickHouse did not allow connections from the Worker's Fargate Service, so communication was not possible.&lt;/p&gt;

&lt;p&gt;In this case, the Worker's logs did not output any errors, so the error content could not be identified without investigating ElastiCache.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Check ClickHouse
&lt;/h2&gt;

&lt;p&gt;If everything seems fine on ElastiCache, check if data is stored in ClickHouse. First, connect to ClickHouse with ECS Exec.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecs execute-command &lt;span class="nt"&gt;--cluster&lt;/span&gt; cluster-name &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--task&lt;/span&gt; task-id &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--container&lt;/span&gt; container-name &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--interactive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--command&lt;/span&gt; &lt;span class="s2"&gt;"/bin/sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start the client with &lt;code&gt;clickhouse-client&lt;/code&gt;. The following indicates a successful start.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# clickhouse-client&lt;/span&gt;
ClickHouse client version 25.1.2.3 &lt;span class="o"&gt;(&lt;/span&gt;official build&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Connecting to localhost:9000 as user clickhouse.
Connected to ClickHouse server version 25.1.2.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, check if data is stored with SQL, just like with RDB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;USE default&lt;span class="p"&gt;;&lt;/span&gt;
SHOW TABLES&lt;span class="p"&gt;;&lt;/span&gt;
SELECT &lt;span class="k"&gt;*&lt;/span&gt; FROM traces ORDER BY created_at DESC LIMIT 10&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the results of checking the contents, you can suspect the following. However, in such cases, errors are likely to be output in the Web or Worker logs, so the issue might be identified at the &lt;code&gt;1. Check the Logs of the Web and Worker&lt;/code&gt; stage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No data in ClickHouse: It is likely that the job processing results could not be stored, so the communication between the Worker and ClickHouse is suspicious.&lt;/li&gt;
&lt;li&gt;Data exists in ClickHouse: If data exists but is not displayed in the browser, the communication between the Web and ClickHouse is suspicious.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;If you find any issues or have suggestions for additions to the CDK project, please let me know!&lt;br&gt;
I plan to work on CloudFront's VPC Origin support at some point in the near future.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>ElastiCache Serverless: L2 CDK Construct Released in open-constructs</title>
      <dc:creator>Matsuda</dc:creator>
      <pubDate>Fri, 17 Jan 2025 02:19:31 +0000</pubDate>
      <link>https://dev.to/aws-builders/elasticache-serverless-l2-cdk-construct-released-in-open-constructs-fpe</link>
      <guid>https://dev.to/aws-builders/elasticache-serverless-l2-cdk-construct-released-in-open-constructs-fpe</guid>
      <description>&lt;p&gt;I contributed an L2 Construct for ElastiCache Serverless to open-constructs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/open-constructs/aws-cdk-library" rel="noopener noreferrer"&gt;GitHub Repository: open-constructs/aws-cdk-library&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created this construct because ElastiCache Serverless seems likely to see increased demand, with features like Valkey support allowing usage from around $6 per month and Valkey 8.0 enabling faster scaling. It was also something I personally needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/database/get-started-with-amazon-elasticache-for-valkey/" rel="noopener noreferrer"&gt;Get Started with Amazon ElastiCache for Valkey&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/database/amazon-elasticache-version-8-0-for-valkey-brings-faster-scaling-and-improved-memory-efficiency/" rel="noopener noreferrer"&gt;Amazon ElastiCache version 8.0 for Valkey brings faster scaling and improved memory efficiency&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, I'll introduce how to use it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Installing open-constructs
&lt;/h1&gt;

&lt;p&gt;After creating your CDK project, simply run &lt;code&gt;npm install&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx cdk init &lt;span class="nt"&gt;--language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;typescript
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @open-constructs/aws-cdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Usage
&lt;/h1&gt;

&lt;p&gt;Here's the general flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;User&lt;/code&gt; for RBAC&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;UserGroup&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;ServerlessCache&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Configure IAM policies (only if using IAM authentication)&lt;/li&gt;
&lt;li&gt;Other useful features (&lt;code&gt;connections&lt;/code&gt;, &lt;code&gt;metrics&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that RBAC is only supported for &lt;code&gt;Valkey&lt;/code&gt; and &lt;code&gt;Redis&lt;/code&gt;. Therefore, steps 1 and 2 are not necessary when using &lt;code&gt;Memcached&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html" rel="noopener noreferrer"&gt;ElastiCache RBAC Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following examples assume using &lt;code&gt;Valkey&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Creating RBAC Users
&lt;/h2&gt;

&lt;p&gt;First, create users for cache access control. There are three types: IAM authentication, password authentication, and no password authentication.&lt;/p&gt;

&lt;p&gt;For IAM authentication users, use the &lt;code&gt;IamUser&lt;/code&gt; class. With IAM authentication, the user ID and username must match, so the Construct only allows setting the ID (the username is automatically set to the same value as the user ID).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;accessString&lt;/code&gt; indicates permissions following Redis syntax. In this example, it allows all operations on all keys. Please refer to the documentation for details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#Access-string" rel="noopener noreferrer"&gt;Access String Documentation&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IamUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-iam-user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accessString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on ~* +@all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For password authentication users, use the &lt;code&gt;PasswordUser&lt;/code&gt; class. You can set up to two passwords, and the user ID and username can be set separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PasswordUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-user-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-user-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accessString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on ~* +@all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;passwords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecretValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsafePlainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;strongPassword123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecretValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unsafePlainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;backupPassword456&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For users without password authentication, use the &lt;code&gt;NoPasswordRequiredUser&lt;/code&gt; class. Note that this provides weaker security and should be used carefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NoPasswordRequiredUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-user-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-user-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accessString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on ~* +@all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Important Note: The default User
&lt;/h3&gt;

&lt;p&gt;This is perhaps the most complex aspect to understand.&lt;/p&gt;

&lt;p&gt;ElastiCache requires that a user with the &lt;strong&gt;username&lt;/strong&gt; &lt;code&gt;default&lt;/code&gt; must be included in the &lt;code&gt;UserGroup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/Clusters.RBAC.html#rbac-using" rel="noopener noreferrer"&gt;RBAC Usage Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A user with the &lt;strong&gt;user ID&lt;/strong&gt; &lt;code&gt;default&lt;/code&gt; is created by AWS by default (no password authentication, full permissions). This user cannot be edited or deleted.&lt;/p&gt;

&lt;p&gt;Additionally, usernames must be unique within a region.&lt;/p&gt;

&lt;p&gt;Therefore, you need to provide a user with the &lt;strong&gt;username&lt;/strong&gt; &lt;code&gt;default&lt;/code&gt; and include it in the &lt;code&gt;UserGroup&lt;/code&gt; using one of these methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the AWS-created default user (user ID and username are both &lt;code&gt;default&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a new default user (user ID is &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;default&lt;/code&gt;, but username is &lt;code&gt;default&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use the former in your Construct, import it using the import method. For the latter, create a new default user with the username set to &lt;code&gt;default&lt;/code&gt;. The latter approach is suitable if you want to customize the default user's permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import the AWS-created default user&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NoPasswordRequiredUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromUserAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DefaultUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Both user ID and username must be 'default'&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new default user&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newDefaultUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NoPasswordRequiredUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NewDefaultUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// User ID must not be 'default'&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Username must be 'default'&lt;/span&gt;
  &lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Creating UserGroup
&lt;/h2&gt;

&lt;p&gt;Next, use the &lt;code&gt;UserGroup&lt;/code&gt; class to create a logical group of users that will be associated with the cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newDefaultUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anotherUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UserGroup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Register users. Must include a default user (username 'default')&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;newDefaultUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// You can also add users using the addUser method&lt;/span&gt;
&lt;span class="nx"&gt;userGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;anotherUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Creating ServerlessCache
&lt;/h2&gt;

&lt;p&gt;Finally, use the &lt;code&gt;ServerlessCache&lt;/code&gt; class to create the cache and associate the user group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vpc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserGroup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ServerlessCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ServerlessCache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VALKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;majorEngineVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MajorVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VER_8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;serverlessCacheName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-serverless-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Associate the user group&lt;/span&gt;
  &lt;span class="nx"&gt;userGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also configure CMK and snapshots. See the &lt;code&gt;README&lt;/code&gt; for all available properties.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/open-constructs/aws-cdk-library/blob/main/src/aws-elasticache/README.md" rel="noopener noreferrer"&gt;open-constructs ElastiCache README&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that at the time of writing, the &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-serverlesscache.html#cfn-elasticache-serverlesscache-cacheusagelimits" rel="noopener noreferrer"&gt;CacheUsageLimits&lt;/a&gt; property is not supported due to limitations in the &lt;code&gt;aws-cdk-lib&lt;/code&gt; version used by open-constructs. I plan to add this property when it becomes available.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Configuring IAM Policies
&lt;/h2&gt;

&lt;p&gt;If using IAM authentication users, you need to configure policies to allow connections:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/auth-iam.html" rel="noopener noreferrer"&gt;IAM Authentication Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Construct provides a &lt;code&gt;grantConnect&lt;/code&gt; method for easy permission setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IamUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerlessCache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Grant "elasticache:Connect" permission&lt;/span&gt;
&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Other Useful Features
&lt;/h2&gt;

&lt;p&gt;You can easily configure security groups using &lt;code&gt;connections&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerlessCache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Allow connection from EC2 to cache on default port 6379&lt;/span&gt;
&lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allowDefaultPortFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Construct also makes it easy to create metrics and alarms. Dedicated methods are provided for &lt;code&gt;BytesUsedForCache&lt;/code&gt; and &lt;code&gt;ElastiCacheProcessingUnits&lt;/code&gt; metrics mentioned in the official documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerlessCache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Get total bytes used for cache data (5-minute average)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytesUsedForCache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metricBytesUsedForCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Get total ElastiCacheProcessingUnits (ECPUs) consumed (5-minute average)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elastiCacheProcessingUnits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metricElastiCacheProcessingUnits&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Create an alarm for ECPUs metric&lt;/span&gt;
&lt;span class="nx"&gt;elastiCacheProcessingUnits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAlarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ElastiCacheProcessingUnitsAlarm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;evaluationPeriods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also define custom metrics using the &lt;code&gt;metric&lt;/code&gt; method. For available serverless cache metrics, refer to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/serverless-metrics-events-redis.html" rel="noopener noreferrer"&gt;Serverless metrics and events for Valkey and Redis OSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/serverless-metrics-events.memcached.html" rel="noopener noreferrer"&gt;Serverless metrics and events for Memcached&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerlessCache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Define CacheHits metric - total number of successful read operations (5-minute average)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheHits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;serverlessCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CacheHits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;statistic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Thanks to the thorough review by the reviewers, I believe this has become a user-friendly Construct. Please give it a try!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
    </item>
  </channel>
</rss>
