কম্পিউটার টিউটোরিয়াল

সার্ভারলেস আর্কিটেকচার: AWS Lambda, Upstash Redis এবং Go দিয়ে স্কেলেবল, খরচ-প্রভাবশালী অ্যাপ তৈরি

ভূমিকা

সার্ভারলেস কম্পিউট প্ল্যাটফর্মগুলো দারুণ, কিন্তু সার্ভারলেস ডাটাবেস ছাড়া এগুলো খুব সীমাবদ্ধ।

আমার আগামী কোর্স "The Elements of CI/CD"-র প্ল্যাটফর্ম তৈরি করার সময়, আমি নির্দিষ্ট কাজের জন্য AWS Lambda সার্ভার হিসেবে ব্যবহার করার সিদ্ধান্ত নিয়েছিলাম। তখন আমার প্রয়োজন ছিল একটি সার্ভারলেস ডাটাবেস। আমার প্রয়োজনীয়তাগুলো ছিল:

  1. পে-অ্যাজ-ইউ-গো প্রাইসিং। আমি প্রতি ঘন্টা বা নোডের জন্য টাকা দিতে চাই না, বরং ব্যবহার (রিকোয়েস্ট, স্টোরেজ ইত্যাদি) অনুযায়ী দিতে চাই। শুরুতে এটি খুব সস্তা হওয়া উচিত এবং ব্যবহার বাড়ার সাথে সাথে খরচটি आनুপাতিকভাবে বৃদ্ধি পাবে।
  2. কম লেটেন্সি। কেউ ধীর রেসপন্স পছন্দ করে না, তাই AWS রিজন (যেমন eu-west-1) থেকে ডাটাবেস কোয়েরি করা দ্রুত হতে হবে।
  3. চমৎকার ডেভেলপার এক্সপেরিয়েন্স (DevX)। অন্য কোনো নিচ DSL শিখতে না হলে বা ওয়েবসাইটের সাথে ঘন্টা কাটাতে না হলে ডাটাবেসের জন্য একটি সুন্দর ইন্টারফেস থাকা ভালো।

Upstash Redis উপরের সব প্রয়োজনীয়তা পূরণ করে এবং এটি খুব ভালোভাবে করে।

  • পে-অ্যাজ-ইউ-গো?
    • ✅ শুরুতে খুব সস্তা, স্কেলেও একইভাবে!
  • কম লেটেন্সি?
    • ✅ AWS Lambda থেকে কোয়েরি করলে <1ms লেটেন্সি!
  • চমৎকার DevX?
    • ✅ এটি স্ট্যান্ডার্ড Redis। তাই হ্যাঁ।

এই আর্টিকেলে আমরা দেখব কিভাবে AWS Lambda থেকে Upstash Redis ব্যবহার করতে হয়, এটি আমাদের প্রয়োজনের জন্য যথেষ্ট দ্রুত কিনা নিশ্চিত করতে হয়, এবং একই সাথে কোডটি কীভাবে মেইনটেইনযোগ্য রাখতে হয় যাতে আমরা লোকালি টেস্ট করতে পারি বা অন্য প্ল্যাটফর্মে ডিপ্লয় করতে পারি।

আমরা কী ইমপ্লিমেন্ট করব?

সरलতার জন্য, আমরা শুধু ৩টি API এন্ডপয়েন্ট ইমপ্লিমেন্ট করব:

  1. GET|POST /login এন্ডপয়েন্টটি GET-এ কোয়ারী প্যারামিটার হিসেবে userId নেয়, অথবা POST রিকোয়েস্টে ফর্ম ভ্যালু হিসেবে নেয়। এই এন্ডপয়েন্টটি একটি সেশন আইডি জেনারেট করে, এটি Redis-এ সংরক্ষণ করে, এবং পরবর্তী ভিজিটের জন্য একটি কুকি সেট করে। GET শুধু টেস্ট করার জন্য সহজ করে দেয়।🙃
  2. GET /lessons/completed এন্ডপয়েন্টটি লগইন করা ইউজারদের জন্য (অর্থাৎ যাদের কুকিতে সেশন আইডি আছে) এবং ইউজার যে সব লেসন কমপ্লিট করেছে এবং কখন করে সেটা JSON রেসপন্স হিসেবে রিটার্ন করে।
  3. POST /lessons/{lessonSlug}/mark-complete এন্ডপয়েন্টটিও লগইন করা ইউজারদের জন্য এবং lessonSlug দ্বারা নির্দিষ্ট লেসনটি বর্তমান সময়ের সাথে কমপ্লিট হিসেবে মার্ক করে।

নোট: নিচের কোডে কিছু জিনিস অনুপস্থিত, তাই এটি প্রোডাকশন-রেডি কপি-পেস্টযোগ্য কোড নয়। উদাহরণস্বরূপ, আপডেট করার আগে দেওয়া lessonSlugটি আছে কিনা চেক করা উচিত। লগইন এন্ডপয়েন্টটিও পাসওয়ার্ড এক্সেপ্ট করতে হবে এবং সেশন আইডি তৈরি করার আগে প্রপার সল্টেড/হ্যাশড ভেরিফিকেশন করা উচিত।

১. সেটআপ

  • নিচে বিস্তারিত করা পুরো কোড আমার aws-playground রিপোজিটরিতে রয়েছে যদি আপনি দেখতে চান সব কিছু কীভাবে একসাথে ফিট হয়।

নিচে দেখবেন, আমরা দুটি এন্ট্রিপয়েন্ট তৈরি করছি, অর্থাৎ দুটি এক্সিকিউটেবল কমান্ড। একটি হবে নরমাল লোকাল সার্ভারের জন্য, আরেকটি AWS Lambda-র জন্য। এইভাবে, আমরা আমাদের পুরো লজিক লোকালি টেস্ট করতে পারব, এবং চাইলে স্ট্যান্ডার্ড ইউনিট/ইন্টিগ্রেশন টেস্টও করতে পারব।

এদের মধ্যে একমাত্র পার্থক্য নিচের ১.২ এবং ১.৩ সেকশনে দেখানো হয়েছে।

১.১ ওয়ার্কস্পেস

কোডে নেমার আগে, চলুন Go-এর জন্য আমাদের ওয়ার্কিং ডিরেক্টরি সেট আপ করি।

  1. Go ডাউনলোড করুন।
  2. Upstash Redis অ্যাকাউন্ট এবং ডাটাবেস তৈরি করুন আপনার পছন্দের রিজনে। আদর্শভাবে এটি একই AWS রিজন হওয়া উচিত যেখানে আপনি আপনার Lambda ডিপ্লয় করবেন। এই আর্টিকেলে আমি eu-west-1 (ইউরোপ, আয়ারল্যান্ড) ব্যবহার করব।

সার্ভারলেস আর্কিটেকচার: AWS Lambda, Upstash Redis এবং Go দিয়ে স্কেলেবল, খরচ-প্রভাবশালী অ্যাপ তৈরি

উপরের কাজগুলো সম্পন্ন হলে, এখন আমরা আমাদের ওয়ার্কস্পেস তৈরি করতে পারি। এই আর্টিকেলের বাকি অংশে ধরে নিন আমাদের কোড ~/dev/aws-lambda-upstash-redis-এ আছে।

mkdir -p ~/dev/aws-lambda-upstash-redis
cd ~/dev/aws-lambda-upstash-redis

এরপর, একটি Go প্যাকেজ তৈরি করুন:

go mod init com.upstash/example/aws-lambda-upstash-redis

১.২ লোকাল সার্ভার এন্ট্রিপয়েন্ট

  • নিচের কোডটি ~/dev/aws-lambda-upstash-redis/cmd/server/main.go-এ পেস্ট করুন।
package main
import (
    "log"
    "net/http"
    "os"
    "com.upstash/example/aws-lambda-upstash-redis/core"
)
func main() {
    mux := core.NewMux()
    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = "5000"
    }
    if err := http.ListenAndServe(":"+port, mux); err != nil {
        log.Fatal(err)
    }
}

১.৩ AWS Lambda এন্ট্রিপয়েন্ট

  • নিচের কোডটি ~/dev/aws-lambda-upstash-redis/cmd/lambda/main.go-এ পেস্ট করুন।
package main
import (
    "com.upstash/example/aws-lambda-upstash-redis/core"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/awslabs/aws-lambda-go-api-proxy/httpadapter"
)
func main() {
    mux := core.NewMux()
    lambda.Start(httpadapter.NewV2(mux).ProxyWithContext)
}

১.৪ কোর লজিক

আমাদের মূল কোর লজিক core প্যাকেজে যাবে যাতে উপরের দুটি এন্ট্রিপয়েন্ট শেয়ার করতে পারে।

  • নিচের কোডটি ~/dev/aws-lambda-upstash-redis/core/lib.go-এ পেস্ট করুন।
package core
import (
    "github.com/go-chi/chi/v5"
)
func NewMux() *chi.Mux {
    r := chi.NewRouter()
    return r
}

১.৫ বিল্ডিং / কম্পাইলিং

আমি সাধারণত একটি ছোট makefile লিখি যাতে বারবার বড় কমান্ড টাইপ করতে না হয়। নিচের কন্টেন্ট ~/dev/aws-lambda-upstash-redis/makefile-এ কপি করুন:

default: build
clean: rm -rf build/
build: build-lambda build-server
build-lambda: clean
    GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o build/handler cmd/lambda/main.go
    cd build/ && zip handler.zip ./handler
build-server: clean
    CGO_ENABLED=0 go build -o build/server cmd/server/main.go

এখনকার জন্য ডিটেইলস নিয়ে চিন্তা না করুন। এটি আমাদের দরকার পड़े:

  • make build-server: লোকালি সার্ভার চালানোর জন্য বাইনারি বিল্ড করে (এক্সিকিউটেবল ./build/server)।
  • make build-lambda: AWS Lambda-এ চালানোর জন্য বাইনারি বিল্ড করে (এক্সিকিউটেবল ./build/handler এবং ./build/handler.zip)।
  • make বা make build beide করে।

CGO_ENABLED=0 অপশনটি নিশ্চিত করে যে আমাদের এক্সিকিউটেবল বাইনারিগুলো সেলফ-কন্টেইন্ড (স্ট্যাটিক্যালি কম্পাইল্ড)। GOOS=linux GOARCH=amd64 অপশনগুলো ক্রস-কম্পাইল করার জন্য এবং AWS Lambda-এর লিনাক্স এনভায়রনমেন্টের সাথে মেলাতে দরকার হয় যদি আপনি লোকালি Mac বা Windows ব্যবহার করেন।

এরপর, go mod tidy চালান যেন সব ডিপেন্ডেন্সি ফেচ হয়। প্রতিবার Go ডিপেন্ডেন্সি যোগ বা বাদ দেওয়ার পর এটি চালাতে ভুলবেন না।

শেষে, একবার make চালান যেন সব কিছু বিল্ড হয় এবং ডিপ কোডে যাওয়ার আগে ওয়ার্কস্পেস সেটআপ ঠিক আছে কিনা নিশ্চিত হয়।

২. API ইমপ্লিমেন্টেশন

এই সেকশনে আমরা সবসময় ~/dev/aws-lambda-upstash-redis/core/lib.go ফাইলটিতে কাজ করব।

নিচের কিছু লাইন আগে আলোচনা করা API এন্ডপয়েন্টগুলো ডিফাইন করে, অՄেজিং go-chi লাইব্রেরি ব্যবহার করে।

import (
    //...
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)
func NewMux() *chi.Mux {
    r := chi.NewRouter()
    r.Use(middleware.RequestID)
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Get("/login", login)
    r.Post("/login", login)
    r.Group(func(r chi.Router) {
        r.Use(UsersWithSessionOnly)
        r.Get("/lessons/completed", listLessonsCompleted)
        r.Post("/lessons/{lessonSlug}/mark-complete", markLessonComplete)
    })
    return r
}

উপরের স্নিপেটে, r.Group(...) একটি শেয়ার্ড লেয়ার তৈরি করে যেখানে আমরা কোনো রুটের জন্য কমন মিডলওয়ার অ্যাপ্লাই করতে পারি। এই কেসে, আমরা আমাদের নিজস্ব মিডলওয়ার UsersWithSessionOnly অ্যাড করেছি, যা (যেমন পরে দেখব) নিশ্চিত করে যে রিকোয়েস্টে সক্রিয় সেশন আইডি সহ কুকি আছে।

২.১ UsersWithSessionOnly মিডলওয়ার

এই মিডলওয়ারে আমরা নিচের কাজগুলো ইমপ্লিমেন্ট করতে চাই:

  1. সেশন আইডি ধারণকারী কুকিটি এক্সট্র্যাক্ট করা, না থাকলে ফেইল করা।
  2. সেশন আইডি ব্যবহার করে Redis থেকে ইউজার ডিটেইল ফেচ করা, সেশন আইডি এক্টিভ না থাকলে ফেইল করা।
  3. ইউজার আইডি রিকোয়েস্টের context.Context-এ স্টোর করা যাতে ডাউনস্ট্রিম মিডলওয়ার বা হ্যান্ডলারদের কাছে উপলব্ধ থাকে।

প্রথমে, সর্বত্র ব্যবহৃত কিছু বয়লারপ্লেট কোড লাগবে ইমপোর্ট এবং ডেফিনিশনের জন্য।

import (
    //...
    "log"
    "os"
    "strings"
    "github.com/go-redis/redis/v8"
)
type contextKey struct {
    name string
}
const (
    COOKIE_AUTH_NAME = "xxx_session_id"
)
var (
    CTX_USER_ID = &contextKey{"LoggedInUserId"}
    redisDb = NewClient()
)
func NewClient() *redis.Client {
    redisUrl := strings.TrimSpace(os.Getenv("UPSTASH_REDIS_URL"))
    if redisUrl == "" {
        log.Fatalln("Required env UPSTASH_REDIS_URL not set!")
    }
    opt, _ := redis.ParseURL(redisUrl)
    redisDb := redis.NewClient(opt)
    return redisDb
}

এখন অথেন্টিকেশন মিডলওয়ারের মূল লজিক।

// UsersWithSessionOnly মিডলওয়ার শুধু লগইন করা ইউজারদের এক্সেস সীমাবদ্ধ করে।
// ভ্যালিডেশন পাস করলে, কন্টেক্সটে ইউজার আইডি থাকবে (CTX_USER_ID)।
func UsersWithSessionOnly(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        c, err := r.Cookie(COOKIE_AUTH_NAME)
        if err != nil {
            render.Status(r, http.StatusForbidden)
            render.JSON(w, r, struct{}{})
            return
        }
        ctx := r.Context()
        userId, err := redisDb.Get(ctx, "session:"+c.Value).Result()
        if err == redis.Nil {
            // সেশন না পেলে ইউজার ফরবিডেন!
            render.Status(r, http.StatusForbidden)
            render.JSON(w, r, struct{}{})
            return
        } else if err != nil {
            // Redis কোয়েরি করতে সমস্যা!
            render.Status(r, http.StatusInternalServerError)
            render.JSON(w, r, struct{ Message string }{Message: "We could not validate the provided session ID"})
            return
        }
        // ডাউনস্ট্রিম মিডলওয়ার এবং হ্যান্ডলারদের জন্য সেট করা।
        next.ServeHTTP(w, r.WithContext(context.WithValue(ctx, CTX_USER_ID, userId)))
    })
}

২.২ markLessonComplete(...)

এটি একটি সরল অপারেশন যেখানে আমরা Redis-এ স্টোর করতে চাই যে lessonSlug পাথ প্যারামিটার দ্বারা নির্দিষ্ট লেসনটি রিকোয়েস্টের বর্তমান সময়ে কমপ্লিট হয়েছে।

Redis-এ আমরা প্রতিটি ইউজারের জন্য একটি ম্যাপ রাখতে চাই যেখানে প্রতিটি কী-ভ্যালু পেয়ার হবে লেসন কী হিসেবে এবং কমপ্লিশন ডেট ভ্যালু হিসেবে। তাই আমরা Redis-এর HSET কমান্ড ব্যবহার করব। আমরা প্রতিটি লেসনের জন্য আলাদা কীও স্টোর করতে পারতাম কিন্তু এটি পরে একসাথে ইউজারের সব লেসন ফেচ করার জন্য সহজ করে দেয়।

func markLessonComplete(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    lessonSlug := chi.URLParam(r, "lessonSlug")
    userId := r.Context().Value(CTX_USER_ID).(string)
    timeNow := time.Now().Format(time.RFC3339)
    err := redisDb.HSet(ctx, "lessons:"+userId, lessonSlug, timeNow).Err()
    if err != nil {
        render.Status(r, http.StatusInternalServerError)
        render.JSON(w, r, struct{ Message string }{Message: "We could not save your progression..."})
        return
    }
    render.JSON(w, r, struct {
        LessonSlug string
        LastCompleted string
    }{
        lessonSlug, timeNow,
    })
}

২.৩ listLessonsCompleted(...)

পূর্বের সেকশনের মতোই, এখানেও আমরা পুরো লেসন কমপ্লিশন ম্যাপটি রিটার্ন করতে চাই JSON রেসপন্স হিসেবে। এখানে আমরা HGETALL কমান্ড ব্যবহার করব।

func listLessonsCompleted(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    userId := r.Context().Value(CTX_USER_ID).(string)
    lessons, err := redisDb.HGetAll(ctx, "lessons:"+userId).Result()
    if err == redis.Nil {
        lessons = map[string]string{}
    } else if err != nil {
        render.Status(r, http.StatusInternalServerError)
        render.JSON(w, r, struct{ Message string }{Message: "We could not load your lessons..."})
        return
    }
    render.JSON(w, r, struct {
        Lessons map[string]string
    }{
        lessons,
    })
}

২.৪ login(...)

শেষে, লগইন এন্ডপয়েন্ট। একবার আবার বলছি, নিচের কোড প্রোডাকশনে কপি করবেন না, কারণ এটি কোনো ভ্যালিডেশন করে না। এই আর্টিকেলের জন্য আমরা শুধু এতে আগ্রহী যে এটি কীভাবে Redis কোয়েরি করে এবং সেশন আইডির জন্য কুকি কীভাবে সেট করে।

সেশন আইডি ksuid লাইব্রেরি দ্বারা জেনারেট হয়, যার নরমাল UUID-এর চেয়ে কিছু সুবিধা আছে, এবং আমরা এটি শুধু ১ ঘন্টা এক্টিভ मानি। Redis-এর SET কমান্ডের টাইম-টু-লিভ ফাংশনালিটি ব্যবহার করে ১ ঘন্টা পর অটোমেটিকভাবে ডাটাবেস থেকে রিমুভ করতে পারি।

import (
    // ...
    "github.com/segmentio/ksuid"
)
func login(w http.ResponseWriter, r *http.Request) {
    // ক্রেডেনশিয়াল চেক করুন এবং রেডিস সেশন আপডেট করুন এবং Set-Cookie রিটার্ন করুন
    // সতর্কতা: প্রোডাকশনে আপনাকে অবশ্যই ক্রেডেনশিয়ালের জন্য আসল ভ্যালিডেশন করতে হবে!
    // ...
    // এখনlás আমরা সবসময় সঠিক ধরে নিই এবং অটোমেটিক্যালি একটি সেশন টোকেন তৈরি করি
    // এটিকে Redis-এ সংরক্ষণ করে, এবং কুকি হিসেবে সেট করি।
    userId := strings.TrimSpace(r.FormValue("userId"))
    if userId == "" {
        render.Status(r, http.StatusBadRequest)
        render.JSON(w, r, struct{ Message string }{Message: "Missing required userId"})
        return
    }
    sessionId := ksuid.New()
    redisDb.Set(r.Context(), "session:"+sessionId.String(), userId, time.Hour*1)
    http.SetCookie(w, &http.Cookie{
        Name: COOKIE_AUTH_NAME,
        Value: sessionId.String(),
        Path: "/",
        MaxAge: int((time.Hour * 1).Seconds()),
        // প্রোডাকশনে (https) এটি true হওয়া উচিত, কিন্তু লোকালি (http) false।
        Secure: false,
    })
    http.Redirect(w, r, "/lessons/completed", http.StatusTemporaryRedirect)
}

৩. ডেমো - লোকালি

উফ, খুব কোড লিখলাম।😅

চলুন কুইক ডেমো করি যেন সব কিছু ঠিকমতো কাজ করছে কিনা দেখি।

  • প্রথমে, UPSTASH_REDIS_URL এনভায়রনমেন্ট ভেরিয়েবলটি সেকশন ১-এ তৈরি করা ডাটাবেসের URL-এ সেট করুন। এটি আপনার ডাটাবেস পেজের details ট্যাবে পাবেন (সেকশন ১.১ দেখুন)।
export UPSTASH_REDIS_URL=""
  • এরপর, লোকাল সার্ভার বিল্ড করে চালান:
make build-server && ./build/server

ব্রাউজার টেস্টিং

এখন ব্রাউজারে https://localhost:5000/lessons/completed ভিজিট করে টেস্ট করি।

সার্ভারলেস আর্কিটেকচার: AWS Lambda, Upstash Redis এবং Go দিয়ে স্কেলেবল, খরচ-প্রভাবশালী অ্যাপ তৈরি

আমরা পেলাম 403 - Forbidden, তাই লগইন করি, https://localhost:5000/login?userId=lambros ভিজিট করে।

সার্ভারলেস আর্কিটেকচার: AWS Lambda, Upstash Redis এবং Go দিয়ে স্কেলেবল, খরচ-প্রভাবশালী অ্যাপ তৈরি

এখন লগইন হয়েছে, এবং অটোমেটিক্যালি /lessons/completed-এ রিডাইরেক্ট হলাম, কিন্তু খালি। তাহলে একটি লেসন কমপ্লিট হিসেবে মার্ক করি। ব্রাউজারের ডেভটুলসের console ট্যাবে নিচের কোড চালান:

await ( await fetch("https://localhost:5000/lessons/123/mark-complete", {
    method: "POST",
    credentials: "same-origin",
}) ).json();
// আউটপুট হবে কিছু এইরকম:
// {LessonSlug: '123', LastCompleted: '2022-10-12T02:01:14+03:00'}

এখন https://localhost:5000/lessons/completed ভিজিট করলে লেসনটি মার্ক করা দেখাবে:

{
  "Lessons": {
    "123": "2022-10-12T02:01:14+03:00"
  }
}

এত সিম্পল! সব ঠিক কাজ করছে।

Upstash-এর নবিন লঞ্চ করা অনলাইন Data Browser-এ লক্ষ্য করলে ডাটাবেসে এক্সপেক্টেড ডাটা আছে তা প্রুফ হয়।

সার্ভারলেস আর্কিটেকচার: AWS Lambda, Upstash Redis এবং Go দিয়ে স্কেলেবল, খরচ-প্রভাবশালী অ্যাপ তৈরি

৪. AWS Lambda

AWS Lambda-এ টেস্ট এবং ডিপ্লয় করার জন্য আমরা sam CLI ব্যবহার করব।

  • প্রথমে, SAM CLI সেট আপ করুন এবং নিশ্চিত করুন আপনার ইউজার/রোলের সঠিক পারমিশন আছে।

  • sam CLI-র জন্য একটি CloudFormation টেমপ্লেট লাগে, তাই নিচের কন্টেন্ট aws-iac/sam-template.yml-এ কপি করুন:

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Defines all the AWS resources we need for our Upstash Redis API.
Resources:
  # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html
  GoUpstashRedis:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ../build/handler.zip
      Handler: handler
      Runtime: go1.x
      MemorySize: 512
      FunctionUrlConfig:
        AuthType: NONE
        Cors:
          AllowCredentials: false
          AllowMethods: ["*"]
          AllowOrigins: ["*"]
Outputs:
  GoUpstashRedisApi:
    Description: "Endpoint URL"
    Value: !GetAtt GoUpstashRedisUrl.FunctionUrl
  GoUpstashRedis:
    Description: "Lambda Function ARN"
    Value: !GetAtt GoUpstashRedis.Arn
  GoUpstashRedisIamRole:
    Description: "Implicit IAM Role created for GoUpstashRedis"
    Value: !GetAtt GoUpstashRedisRole.Arn
  • AWS Lambda-র জন্য হ্যান্ডলার বান্ডেল বিল্ড করুন:
make build-lambda
  • makefile-এ নিচের কন্টেন্ট যোগ করুন যেন কোড চেঞ্জ করার পর সহজে ডিপ্লয় করা যায়:
sam-deploy: build-lambda
    sam deploy -t aws-iac/sam-template.yml --stack-name "UpstashRedisGoArticleStackDemo" --region eu-west-1 --resolve-s3 --no-confirm-changeset --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND
  • নির্দিষ্ট রিজনে ডিপ্লয় করুন (পূর্বের কমান্ড দেখুন)।
make sam-deploy
  • আউটপুটে নিচের মতো আউটপুট দেখতে পাবেন:
CloudFormation outputs from deployed stack
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Key GoUpstashRedis Description Lambda Function ARN Value arn:aws:lambda:eu-west-1::function:UpstashRedisGoArticleStackDem-GoUpstashRedis-baB8dQPkTfg0
Key GoUpstashRedisIamRole Description Implicit IAM Role created for GoUpstashRedis Value arn:aws:iam:::role/UpstashRedisGoArticleStac-GoUpstashRedisRole-16UWC7HR6KII8
Key GoUpstashRedisApi Description Endpoint URL Value https://6pmmwqmg5vec3bcsldabckaf5i0nlgje.lambda-url.eu-west-1.on.aws/
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - UpstashRedisGoArticleStackDemo in eu-west-1
  • ডিপ্লอยড AWS Lambda-এর URL আউটপুটে দেখাবে, এই কেসে https://6pmmwqmg5vec3bcsldabckaf5i0nlgje.lambda-url.eu-west-1.on.aws/। তাই ব্রাউজারে আগের ডেমো স্টেপগুলো localhost-র পরিবর্তে এই ডোমেন দিয়ে রিপিট করতে পারেন।
    • বিকল্পভাবে, CloudFormation কনসোলে UpstashRedisGoArticleStackDemo স্ট্যাকে আউটপুটসে URL পাওয়া যাবে।
    • নোট: আপনার AWS Lambda কনফিগারেশনে UPSTASH_REDIS_URL এনভায়রনমেন্ট ভেরিয়েবল সেট করতে ভুলবেন না, অন্যথায় এটি ক্র্যাশ করবে। AWS Lambda কনসোলে যান, আপনার নতুন ডিপ্লোয়েড Lambda-তে ক্লিক করুন, Configuration ট্যাবে ক্লিক করুন, বাম দিকের মেনু থেকে Environment variables-এ ক্লিক করুন। Key হিসেবে UPSTASH_REDIS_URL টাইপ করুন, এবং Value হিসেবে আপনার Upstash Redis URL দিন। Save ক্লিক করুন, এবং আপনার Lambda প্রস্তুত।

৪.১ SAM লোকাল টেস্ট

সঠিক পাথ/কুকি/কোয়ারী প্যারামিটার সহ একটি sample-event.json দিয়ে আমরা আমাদের Lambda লোকালি টেস্ট করতে পারি। такого JSON উদাহরণ aws-lambda-upstash-redis-article/sample-event.json-এ পাওয়া যাবে।

  • তब একটি ভ্যালিড JSON ইভেন্ট ফাইল পাওয়া গেলে, নিচের কমান্ড চালিয়ে সার্ভার লজিক আহ্বান করুন যেন AWS Lambda-এ যেভাবে চলে সেভাবে চলে:
sam local invoke -t aws-iac/sam-template.yml -e sample-event.json

৪.২ Upstash Redis URL-এর সিকিউরিটি

এই আর্টিকেলে, সادگیের জন্য আমরা Upstash Redis URL-টি (পাসওয়ার্ডসহ) এনভায়রনমেন্ট ভেরিয়েবল দিয়ে দিয়েছি। আমরা এটিকে SAM CloudFormation টেমপ্লেটে হার্ডকোড করতে চাই না যা আমাদের কোডের সাথে ভার্সন্ড হয়, তাই আমাদের ম্যানুয়ালি AWS Lambda কনসোল থেকে কনফিগার করতে হয়েছিল।

এটার একটি ভালো উপায় আছে যাতে প্রতিবার Lambda কনফিগারেশন মডিফাই করতে না হয় এবং কাউকে কনসোল এক্সেস থাকলে Redis ক্রেডেনশিয়াল/URL প্লেইন টেক্সটে দেখতে না পায়।

আমরা AWS Systems Manager Parameter Store এবং সংশ্লিষ্ট CloudFormation রিসোর্স AWS::SSM::Parameter ব্যবহার করতে পারি URL রাখতে (একবার সেট করলে ডিপ্লয়মেন্টের দরমিয়ান রিটেইন থাকে), এবং আমাদের Lambda কোড পরিবর্তন করে রানটাইমে প্যারামিটারটির ভ্যালু ফেচ করতে পারি। আমরা sam-template.yml-এ এটিকে অটোমেটিক্যালি এনভ ভেরিয়েবল হিসেবে ইনজেক্ট করতেও পারি, যদিও এটি কনসোলে প্লেইন টেক্সটে থাকবে।

কোড পরিবর্তন করে SSM Parameter Store থেকে ফেচ করা সহজ কারণ আমাদের এন্ট্রিপয়েন্ট আলাদা, তাই আমরা প্যারামিটারটি শুধু AWS Lambda-র ভিতর থেকে (~/dev/aws-lambda-upstash-redis/cmd/lambda/main.go) ফেচ করে NewClient() ফাংশনে পাস করতে পারি যা Redis ক্লায়েন্ট তৈরি করে।

৫. কত দ্রুত?

প্রথম কোল্ড স্টার্ট ইনভোকেশন (প্রায় 100-120 ms) বাদ দিলে, তারপর প্রতিটি ইনভোকেশন লাইটনিং ফাস্ট এবং সবসময় 4 ms-র নিচে।

নিচে উপরের ইমপ্লিমেন্টেশন অনুযায়ী /login?userId=lambros এন্ডপয়েন্টের একটি হট ইনভোকেশনের উদাহরণ দেওয়া হলো:

সার্ভারলেস আর্কিটেকচার: AWS Lambda, Upstash Redis এবং Go দিয়ে স্কেলেবল, খরচ-প্রভাবশালী অ্যাপ তৈরি

দেখুন টোটাল রিকোয়েস্ট ড্যুরেশন ছিল 2.06 ms। হ্যাঁ, এটাই দুই মিলিসেকেন্ড - একটি সেশন আইডি জেনারেট করা, রিমোট Upstash Redis-এ লিখা, এবং রিডাইরেক্ট রেসপন্স রিটার্ন করা।

Log output সেকশনটি ध्यान দিয়ে দেখলে, আমাদের কোডের পক্ষে রিকোয়েস্টটি 789.435μs সময় নিলো। অর্থাৎ আমাদের লজিক 1 ms-র নিচে কমপ্লিট হলো (প্রায় 0.790 ms)। রিমোট ডাটাবেস ব্যবহার করে এত দ্রুত!🤯

উপসংহার

Upstash Redis কত ভালো পারফর্ম করে সেটা देखकर আমি বিস্মিত। বিশেষ করে যেহেতু AWS Lambda-এর মতো প্ল্যাটফর্মের জন্য উপযুক্ত সার্ভারলেস ডাটাবেসে এমন পারফরম্যান্স খুব কঠিন।

Redis API খুব কনভেনিয়েন্ট, Upstash Redis-র টপ-নাচ প্রাইসিং মডেল এবং অসম চমৎকার ডেভেলপার এক্সপেরিয়েন্স, এবং এটি দ্রুত। এই কম্বিনেশন আমার খুব ভালো লাগে!

AWS Lambda + Upstash Redis + Go = 🚀❤️

  1. অ্যান্ড্রয়েডে কাস্টম নোটিফিকেশন লেআউট ও টেক্সট কালার তৈরি করা

  2. C++-এ হ্যাপি উইমেনস ডে প্রোগ্রাম লেখা

  3. অ্যান্ড্রয়েড TextView-এ isEmpty() মেথড কীভাবে ব্যবহার করবেন?

  4. SAP .NET Connector 3.0 Visual Studio 2008/2010-এ কাজ না করলে সমাধান